diff --git a/static/css/tokens.css b/static/css/00-tokens.css similarity index 89% rename from static/css/tokens.css rename to static/css/00-tokens.css index 2a23629ad..3c9e49d3a 100644 --- a/static/css/tokens.css +++ b/static/css/00-tokens.css @@ -1,13 +1,18 @@ -/* Fragment 1/9 of the former static/style.css. +/* 00-tokens - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ /* ============================================ */ /* Odysseus UI — Consolidated Stylesheet */ diff --git a/static/css/style-part-01.css b/static/css/01-agent-chat.css similarity index 77% rename from static/css/style-part-01.css rename to static/css/01-agent-chat.css index 7e4250bcb..9e70c4f2c 100644 --- a/static/css/style-part-01.css +++ b/static/css/01-agent-chat.css @@ -1,13 +1,18 @@ -/* Fragment 2/9 of the former static/style.css. +/* 01-agent-chat - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ #cookbook-modal .modal-content { height: 94vh; max-height: 94vh; } .pdf-export-overlay .modal-content { max-height: 86vh; } @@ -6010,3 +6015,1806 @@ body.bg-pattern-ascii-fireflies { background-color: var(--red); border-radius: 50%; } + .loading-dot:nth-child(1) { + animation: loading-bounce 1.4s infinite ease-in-out both; + } + .loading-dot:nth-child(2) { + animation: loading-bounce 1.4s infinite ease-in-out both; + animation-delay: -0.32s; + } + .loading-dot:nth-child(3) { + animation: loading-bounce 1.4s infinite ease-in-out both; + animation-delay: -0.64s; + } + .session-loading-state { + min-height: 100%; + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 12px; + color: var(--fg); + font-size: 12px; + pointer-events: none; + position: relative; + padding: 16px 14px 24px; + box-sizing: border-box; + overflow: hidden; + } + .session-loading-state::before { content: none; } + .session-skeleton-bubble { + width: min(78%, 680px); + border: 1px solid color-mix(in srgb, var(--border) 74%, transparent); + border-radius: 10px; + background: color-mix(in srgb, var(--panel, var(--bg)) 72%, transparent); + padding: 10px 12px 12px; + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.14); + } + .session-skeleton-bubble.is-user { + align-self: flex-end; + width: min(64%, 520px); + } + .session-skeleton-bubble.is-ai { + align-self: flex-start; + } + .session-skeleton-line { + position: relative; + overflow: hidden; + background: color-mix(in srgb, var(--fg) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); + border-radius: 999px; + } + .session-skeleton-line { + height: 10px; + margin-top: 8px; + opacity: 0.62; + } + .session-skeleton-line::after { + content: ''; + position: absolute; + inset: -1px; + transform: translateX(-120%); + background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--fg) 14%, transparent), transparent); + animation: session-skeleton-shimmer 1.25s ease-in-out infinite; + } + .session-skeleton-bubble:nth-child(2) .session-skeleton-line::after { animation-delay: 0.12s; } + .session-skeleton-bubble:nth-child(3) .session-skeleton-line::after { animation-delay: 0.24s; } + .session-skeleton-bubble:nth-child(4) .session-skeleton-line::after { animation-delay: 0.36s; } + @keyframes session-skeleton-shimmer { + 100% { transform: translateX(120%); } + } + @media (max-width: 768px) { + .session-loading-state { padding: 12px 10px 18px; gap: 10px; } + .session-skeleton-bubble { width: 86%; padding: 9px 10px 11px; border-radius: 9px; } + .session-skeleton-bubble.is-user { width: 72%; } + .session-skeleton-line { height: 9px; margin-top: 7px; } + } + @keyframes loading-bounce { + 0%, 80%, 100% { + transform: scale(0); + } + 40% { + transform: scale(1); + } + } + /* Modal styling */ + .modal { + position:fixed; + top:0; left:0; width:100%; height:100%; + background:none; + display:flex; align-items:center; justify-content:center; + z-index:250; + backdrop-filter:none; + pointer-events:none; + } + /* Cookbook always sits above Gallery so the "Serve a model in + Cookbook…" flow (and any other "open Cookbook from inside another + modal") is visible no matter which modal was opened first. */ + #cookbook-modal { z-index: 260; } + .modal.hidden { display:none; } + + /* Tool windows open centered in the CHAT AREA (the space right of the + sidebar + icon rail), rather than the full viewport. + We narrow the overlay to the chat area so its flex-centering lands the + window there; fullscreen / docked states use position:fixed so they + escape this narrowed overlay and still fill the screen. The + --sidebar-w / --icon-rail-w vars track collapse state live, so when a + window (e.g. Cookbook) hides the sidebar this naturally re-centers. + Desktop only — on mobile these are full-screen sheets. */ + @media (min-width: 769px) { + #calendar-modal, + #gallery-modal, + #tasks-modal, + #memory-modal, + #doclib-modal, + #compare-model-overlay, + #research-overlay, + #theme-modal, + #settings-modal, + #email-lib-modal { + left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)); + width: calc(100% - (var(--icon-rail-w, 48px) + var(--sidebar-w, 0px))); + box-sizing: border-box; + /* Slide in sync with the sidebar's 0.25s collapse/expand so the + centered window glides instead of jumping when the nav toggles. */ + transition: left 0.25s ease, width 0.25s ease; + } + } + .modal-content { + background:var(--panel); + border:1px solid var(--border); + width:min(520px, 92vw); max-height:85vh; padding:10px; + box-sizing:border-box; font-size:14px; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + letter-spacing: -0.015em; + display:flex; flex-direction:column; + position:relative; + overflow-y:auto; + border-radius:10px; + box-shadow:0 8px 32px rgba(0,0,0,0.45); + pointer-events:auto; + animation: modal-enter 0.25s ease-out both; + } + .memory-modal-content, + .tasks-modal-content, + .preset-modal-content, + #cookbook-modal .modal-content, + #theme-popup, + .doclib-modal-content, + .gallery-modal-content { + container-type: inline-size; + } + .modal-header { + display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; + cursor:grab; user-select:none; + /* Pin the header (with its close button) to the top of the + scrollable modal-content so users can always dismiss the modal + even after scrolling far down — especially important on mobile + where the modal can be taller than the viewport. */ + position: sticky; + top: 0; + z-index: 5; + /* Inherit the modal-content's background so the header matches the + panel body. Many tool modals set their content to var(--bg) inline + while this header was hard-coded to var(--panel) — making the title + strip a different shade in every theme. `inherit` tracks whatever + the content uses (var(--bg) or the default var(--panel)) and stays + opaque, so the sticky header still masks scrolled content. */ + background-color: inherit; + } + .modal-header:active { cursor:grabbing; } + /* Edge/corner window resize (windowResize.js). While a resize is in + progress, suppress text selection and force the active resize cursor + across the whole document so it does not flicker as the pointer passes + over child elements mid-drag. */ + body.window-resizing-active { user-select:none !important; } + body.window-resizing-active * { cursor:inherit !important; } + /* Suppress only TRANSITIONS while resizing so the edge tracks the cursor + crisply. We deliberately do NOT toggle `animation` here: toggling + animation off→on re-triggers the modal open-animation (a scale-in) on + mouseup, which both mis-measures the final size and visibly "pops" the + window. windowResize.js instead kills the one-shot open animation inline + once, in begin(). */ + .window-resizing { + transition:none !important; + } + /* Cookbook opts out of the sticky header on mobile; the title bar scrolls + away with the content instead of following. Background is inherited from + the shared .modal-header rule so frosted/glass themes stay consistent. */ + #cookbook-modal .modal-header { + position: static; + top: auto; + } + .modal-header h4 { + margin:0; + /* Push every header control (opacity slider, minimize, close) into one + group on the right — works whether or not optional controls like the + opacity slider are present, so the minimize button never floats to + the centre when a sibling is hidden. */ + margin-right:auto; + font-size:1rem; + font-weight:600; + letter-spacing:-0.03em; + color:var(--red); + } + /* Headers with an optional Peek control already have the title's + auto-margin grouping all right-side controls. A second auto-margin on + the injected minimize button splits the group and makes it look + different from the other modal headers. */ + .modal-header .theme-opacity-wrap + .modal-minimize-btn { + margin-left: 0 !important; + } + .close-btn, + .modal-close { + background:transparent; + color:color-mix(in srgb, var(--fg) 58%, transparent); + border:1px solid transparent; + font-size:0; + width:24px; + height:24px; + padding:0; + display:inline-flex; + align-items:center; + justify-content:center; + line-height:1; + text-indent:0; + cursor:pointer; + border-radius:999px; + line-height:1; + flex-shrink:0; + position:relative; + } + .close-btn > svg, + .modal-close > svg { + display:none; + } + .close-btn::before, + .modal-close::before { + content:""; + width:10px; + height:10px; + display:block; + background:currentColor; + -webkit-mask: + url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") + center / contain no-repeat; + mask: + url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") + center / contain no-repeat; + } + .close-btn:hover, + .modal-close:hover { + background:color-mix(in srgb, var(--fg) 10%, transparent); + border-color:color-mix(in srgb, var(--fg) 12%, transparent); + color:var(--fg); + } + /* Minimize button — sits beside the close button on every modal */ + .minimize-btn { + background:transparent; + color:color-mix(in srgb, var(--fg) 58%, transparent); + border:1px solid transparent; + font-size:0; + font-weight:700; + width:24px; + height:24px; + padding:0; + display:inline-flex; + align-items:center; + justify-content:center; + line-height:1; + cursor:pointer; + border-radius:999px; + flex-shrink:0; + margin-left:auto; /* push to the right so it docks next to .close-btn */ + margin-right:4px; + } + .minimize-btn::before { + content:""; + width:9px; + height:2px; + border-radius:999px; + background:currentColor; + display:block; + transform:translateY(2px); + } + .modal-minimize-btn.minimize-btn::before { + content:none; + display:none; + } + .minimize-btn:hover { + background:color-mix(in srgb, var(--fg) 10%, transparent); + border-color:color-mix(in srgb, var(--fg) 12%, transparent); + color:var(--fg); + } + @media (max-width: 768px) { + .minimize-btn { display: none !important; } + #modal-dock { display: none !important; } + } + /* Minimized modals are hidden but stay in the DOM with their state intact */ + .modal.minimized { display:none !important; } + /* Bottom dock for minimized modals */ + #modal-dock { + position:fixed; + bottom:var(--composer-clearance, 0px); + left:0; + right:0; + display:flex; + flex-wrap:wrap; + gap:4px; + padding:4px 8px; + z-index:240; + pointer-events:none; + justify-content:flex-start; + } + #modal-dock:empty { display:none; } + .modal-dock-item { + background:var(--panel); + border:1px solid var(--border); + border-bottom:none; + border-radius:6px 6px 0 0; + padding:4px 4px 4px 10px; + display:inline-flex; + align-items:center; + gap:6px; + cursor:pointer; + pointer-events:auto; + font-size:12px; + color:var(--fg); + max-width:220px; + box-shadow:0 -2px 8px rgba(0,0,0,0.25); + transition:background 0.15s; + } + .modal-dock-item:hover { background:var(--bg); } + .modal-dock-label { + white-space:nowrap; + overflow:hidden; + text-overflow:ellipsis; + max-width:180px; + } + .modal-dock-close { + background:transparent; + border:none; + color:var(--fg); + cursor:pointer; + font-size:14px; + padding:0 4px; + line-height:1; + opacity:0.6; + } + .modal-dock-close:hover { color:var(--red); opacity:1; } + .modal-body { flex:1; overflow-y:auto; } + .modal-body button { margin-top:6px; } + /* Styled confirm dialog — keeps backdrop */ + #styled-confirm-overlay { + background:rgba(0,0,0,0.5); + backdrop-filter:blur(4px); + pointer-events:auto !important; + z-index: 99999 !important; + position: fixed !important; + top: 0 !important; + left: 0 !important; + width: 100% !important; + height: 100% !important; + top: 0; left: 0; width: 100%; height: 100%; + } + #styled-confirm-overlay .modal-content { + position: relative; + z-index: 10001; + } + .email-attachment-warning-modal { + z-index: 99999 !important; + } + .email-attachment-warning-modal .modal-content { + position: relative; + z-index: 100001; + } + .styled-confirm-box { + width:360px; max-width:90vw; + max-height:none; /* override modal-content's 85vh */ + padding:14px 18px; + } + .styled-confirm-box .modal-header { margin-bottom:4px; } + .styled-confirm-box .modal-body p { + margin:8px 0 12px; color:var(--fg); font-size:0.92rem; line-height:1.45; + white-space:pre-line; + } + .styled-confirm-box .modal-footer { + display:flex; justify-content:flex-end; gap:8px; padding-top:6px; + border-top:1px solid var(--border); margin-top:4px; + } + @media (max-width:768px) { + .styled-confirm-box { + width: 85vw; + padding: 12px 16px; + font-size: 0.88rem; + border-radius: 12px; + } + .styled-confirm-box .modal-header h4 { font-size: 0.9rem; } + .styled-confirm-box .modal-body p { font-size: 0.85rem; margin: 6px 0 10px; } + .styled-confirm-box .modal-footer { gap: 10px; } + .styled-confirm-box .confirm-btn { + flex: 1; + /* More bottom than top padding nudges the label up ~2px so it isn't + sitting low in the taller mobile buttons. */ + padding: 8px 12px 12px; + font-size: 0.85rem; + border-radius: 8px; + text-align: center; + } + } + .confirm-btn { + /* Asymmetric padding nudges the label UP ~2px from where it was (more + bottom than top padding), so the confirm-dialog text isn't sitting low. */ + padding:3px 16px 5px; border-radius:4px; font-size:0.85rem; + cursor:pointer; border:1px solid var(--border); + font-family:inherit; + } + @media (max-width: 820px) { + /* Mobile: flip the asymmetry to shift the text 1 px UP from + centre instead (the bigger touch targets on mobile read better + with the label sitting slightly high). */ + .confirm-btn { padding:2px 16px 4px; } + } + .confirm-btn-secondary { background:var(--bg); color:var(--fg); } + .confirm-btn-secondary:hover { background:var(--border); } + #styled-confirm-cancel .styled-confirm-cancel-label { + position:relative; + top:2px; + } + .confirm-btn-primary { background:var(--accent-primary, var(--red, #4a9eff)); color:#fff; border-color:transparent; } + .confirm-btn-primary:hover { filter:brightness(1.15); } + .confirm-btn-danger { background:var(--color-danger); color:#fff; border-color:transparent; } + .confirm-btn-danger:hover { background:var(--color-error); } + .cookbook-gguf-delete-box { + width:560px; + max-width:92vw; + } + .cookbook-gguf-delete-list { + display:flex; + flex-direction:column; + gap:6px; + max-height:42vh; + overflow:auto; + padding:2px 2px 4px; + } + .cookbook-gguf-delete-row { + display:grid; + grid-template-columns:18px minmax(0,1fr); + gap:7px 8px; + align-items:start; + padding:7px 8px; + border:1px solid var(--border); + border-radius:7px; + background:color-mix(in srgb, var(--panel, var(--bg)) 92%, var(--fg) 8%); + cursor:pointer; + } + .cookbook-gguf-delete-row:hover { + border-color:color-mix(in srgb, var(--accent-primary, var(--fg)) 45%, var(--border)); + } + .cookbook-gguf-delete-cb { + -webkit-appearance:none; + appearance:none; + width:8px !important; + height:8px !important; + min-width:8px; + min-height:8px; + padding:0; + margin:4px 0 0; + border:1px solid var(--border); + border-radius:50%; + background:transparent; + box-sizing:content-box; + cursor:pointer; + transition:background 0.15s, border-color 0.15s, transform 0.12s; + } + .cookbook-gguf-delete-cb:hover { + border-color:var(--accent, var(--red)); + transform:scale(1.12); + } + .cookbook-gguf-delete-cb:checked { + background:var(--accent, var(--red)); + border-color:var(--accent, var(--red)); + } + .cookbook-gguf-delete-main, + .cookbook-gguf-delete-path { + min-width:0; + overflow:hidden; + text-overflow:ellipsis; + white-space:nowrap; + } + .cookbook-gguf-delete-main { + font-size:0.86rem; + color:var(--fg); + } + .cookbook-gguf-delete-path { + grid-column:2; + margin-top:-2px; + font-size:0.74rem; + opacity:0.58; + } + /* Styled prompt — text-input dialog (used in place of window.prompt) */ + #styled-prompt-overlay { + background:rgba(0,0,0,0.5); + backdrop-filter:blur(4px); + pointer-events:auto !important; + z-index: 99999 !important; + position: fixed !important; + top: 0 !important; + left: 0 !important; + width: 100% !important; + height: 100% !important; + } + #styled-prompt-overlay .modal-content { + position: relative; + z-index: 10001; + } + .styled-prompt-box { width: min(400px, 92vw); max-width: 100%; box-sizing: border-box; } + .styled-prompt-box .modal-body { padding-top: 4px; } + .styled-prompt-input { + width:100%; + box-sizing:border-box; + margin-top:8px; + padding:9px 12px; + border:1px solid var(--border); + border-radius:6px; + background:var(--bg); + color:var(--fg); + font:inherit; + font-size:0.95rem; + outline:none; + transition: border-color 0.15s, box-shadow 0.15s; + } + .styled-prompt-input:focus { + border-color: var(--accent-primary, var(--red, #4a9eff)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-primary, var(--red, #4a9eff)) 25%, transparent); + } + @media (max-width:768px) { + .styled-prompt-box { width: 85vw; } + .styled-prompt-input { font-size: 0.9rem; padding: 10px 12px; } + } + /* Scroll navigation buttons */ + .scroll-nav-btn { + position:fixed; + background:var(--panel); + color: var(--accent); + border:none; + border-radius:10px; + width:38px; height:38px; + padding:0; + display:flex; align-items:center; justify-content:center; + font-size:14px; + line-height:1; + font-family:inherit; + cursor:pointer; + opacity:0; + pointer-events:none; + transition: opacity .2s, transform .3s cubic-bezier(0.25, 1, 0.5, 1), background .15s; + z-index:100; + transform: translateY(0); + } + .scroll-nav-btn::before { + content: ''; + position: absolute; + inset: 0; + border-radius: 10px; + padding: 1px; + background: linear-gradient(to bottom, var(--border), color-mix(in srgb, var(--border) 30%, transparent)); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + pointer-events: none; + } + .scroll-nav-btn.agent-working::after, + body.chat-agent-working #scroll-bottom-btn.show::after { + content: ''; + position: absolute; + inset: 0; + border-radius: 10px; + padding: 1px; + background: conic-gradient( + from 0deg, + transparent 0deg, + transparent 245deg, + color-mix(in srgb, var(--accent, var(--red)) 78%, transparent) 315deg, + transparent 360deg + ); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + opacity: 0.65; + pointer-events: none; + animation: scrollAgentWorkingRing 1.05s linear infinite; + } + @keyframes scrollAgentWorkingRing { + to { transform: rotate(360deg); } + } + #scroll-bottom-btn.show { opacity:1; pointer-events:auto; } + .scroll-nav-caret { + display: inline-block; + font-family: Arial, Helvetica, sans-serif; + font-size: 16px; + line-height: 1; + transform: translateY(-1px); + pointer-events: none; + } + @media (hover: hover) and (pointer: fine) { + #scroll-bottom-btn.show:hover { + border-color: color-mix(in srgb, var(--fg) 25%, transparent); + } + } + #scroll-bottom-btn.slide-out { + transform: translateY(20px); + opacity: 0 !important; + pointer-events: none; + } + /* Focus outline for accessibility */ + :focus-visible { + outline: 2px solid var(--red); + outline-offset: 2px; + } + /* Hamburger menu button */ + .hamburger { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + background: none; + border: 1px solid transparent; + border-radius: 6px; + padding: 0; + cursor: pointer; + transition: background 0.15s, border-color 0.15s; + } + .hamburger:hover { + background: color-mix(in srgb, var(--fg) 7%, transparent); + border-color: var(--border); + } + .hamburger span { + display: block; + width: 16px; + height: 2px; + background: var(--fg); + border-radius: 1px; + transition: transform 0.2s, opacity 0.2s; + } + .hamburger span + span { margin-top: 3px; } + /* Agent indicator */ + #agent-indicator { + position: fixed; + top: 20px; + right: 20px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + padding: 6px 12px; + border-radius: 6px; + font-size: 12px; + display: none; + z-index: 100; + cursor: pointer; + transition: all 0.2s ease; + } + #agent-indicator.active { + display: block; + border-color: var(--color-agent-active); + box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); + } + #agent-indicator:hover { + border-color: var(--color-agent-active); + background: var(--panel); + } + #research-toggle-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + } + +/* ── Drag & Drop ── */ + +/* ---------- Color palette (dark / light) ---------- */ + + /* Drag and drop styling */ + .section[dnd-active="true"] { + background: color-mix(in srgb, var(--red) 10%, transparent) !important; + border-color: var(--red) !important; + } + + .section[dnd-over="true"] { + background: color-mix(in srgb, var(--red) 20%, transparent) !important; + border-color: var(--red) !important; + transform: scale(1.02); + } + + .drag-handle { + cursor: grab; + opacity: 0.5; + padding: 0 6px; + user-select: none; + } + + .drag-handle:hover { + opacity: 0.8; + } + + .drag-handle:active { + cursor: grabbing; + } + +/* ── UI Controls (Radio, Presets, Toolbar, Settings) ── */ + + + /* Custom radio button styling */ + .radio-option { + display: flex; + align-items: center; + gap: 10px; + padding: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + cursor: pointer; + transition: all 0.2s ease; + } + + .radio-option:hover { + background: color-mix(in srgb, var(--fg) 9%, transparent); + border-color: var(--fg); + } + + .radio-option input[type="radio"] { + appearance: none; + -webkit-appearance: none; + width: 18px; + height: 18px; + border: 2px solid var(--border); + border-radius: 50%; + outline: none; + margin: 0; + background: var(--bg); + transition: all 0.2s ease; + position: relative; + flex-shrink: 0; + } + + .radio-option input[type="radio"]:checked { + border-color: var(--red); + background: var(--red); + } + + .radio-option input[type="radio"]:focus { + box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 30%, transparent); + } + + .radio-label { + color: var(--fg); + font-size: 14px; + user-select: none; + } + + /* Preset buttons */ + .preset-btn { + height: 27.2px; /* 15% smaller than 32px */ + padding: 0 8.5px; /* 15% smaller than 10px */ + margin-left: 4px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + color: var(--fg); + font-family: 'Fira Code', monospace; + font-size: 10.2px; /* 15% smaller than 12px */ + cursor: pointer; + transition: all 0.2s ease; + } + + .preset-btn:hover { + background: var(--panel); + border-color: var(--fg); + } + + .preset-btn.active { + background: var(--panel); + border-color: var(--fg); + box-shadow: 0 0 0 1px var(--fg), 0 0 8px color-mix(in srgb, var(--fg) 16%, transparent); + font-weight: 600; + } + + /* All preset buttons use the same blue color */ + .preset-btn { + border-color: var(--red); /* Blue color */ + } + + .preset-btn.active { + border-color: var(--red); /* Blue color when active */ + box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); + } + + /* Custom preset modal: the base .preset-modal-content sets overflow:hidden + (for desktop rounded-corner clipping), which on the mobile sheet clipped + the footer (Start/Cancel) off the bottom with no way to reach it. Let the + whole sheet scroll — same as every other mobile modal (.modal-content is + overflow-y:auto on mobile) — so the footer is always reachable. No flex + changes, so the body can't collapse. */ + #custom-preset-modal .preset-modal-content { + overflow-y: auto !important; + } + #custom-preset-modal .modal-body label { + font-size: 13px; + font-weight: 500; + color: var(--fg); + margin-top: 8px; + margin-bottom: 4px; + display: block; + } + + #custom-preset-modal .modal-body input, + #custom-preset-modal .modal-body textarea { + width: 100%; + margin-bottom: 8px; + box-sizing: border-box; + } + #custom-preset-modal .modal-body textarea, + #custom-preset-modal .modal-body input[type="text"] { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + padding: 8px 10px; + font-size: 13px; + font-family: inherit; + transition: border-color 0.15s; + } + #custom-preset-modal .modal-body textarea { + resize: vertical; + } + #custom-preset-modal .modal-body textarea:focus, + #custom-preset-modal .modal-body input[type="text"]:focus { + outline: none; + border-color: var(--red); + } + + #custom-preset-modal .modal-footer { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 12px; + padding-top: 10px; + border-top: 1px solid var(--border); + } + + #custom-preset-modal .modal-footer button { + padding: 7px 14px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + border: 1px solid var(--border); + background: none; + color: var(--fg); + cursor: pointer; + transition: all 0.15s; + } + #custom-preset-modal .modal-footer button:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + } + + #custom-preset-modal .modal-footer button#save-custom-preset { + /* The theme's accent is stored in --red (theme.js sets --red = accentHex) + and --accent is undefined, so the canonical accent expression is + var(--accent, var(--red)). The old bare var(--accent) resolved to nothing + which, with color:var(--bg), made the button invisible. */ + background: var(--accent, var(--red)); + color: var(--bg); + border-color: var(--accent, var(--red)); + } + #custom-preset-modal .modal-footer button#save-custom-preset:hover { + opacity: 0.85; + } + /* Toolbar visibility tab */ + .toolbar-hint { + font-size: 12px; + color: color-mix(in srgb, var(--fg) 55%, transparent); + margin-bottom: 12px; + } + /* ── Appearance visibility toggles ── */ + .vis-toggles { + display: flex; + flex-direction: column; + } + .vis-row { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + padding: 5px 6px; + border-radius: 4px; + transition: background 0.12s; + } + .vis-row:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); + } + .vis-row input[type="checkbox"] { + display: none; + } + .vis-icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + color: color-mix(in srgb, var(--fg) 40%, transparent); + } + .vis-icon-text { + font-size: 10px; + font-weight: 700; + font-family: inherit; + letter-spacing: -0.5px; + } + .vis-label { + flex: 1; + font-size: 12px; + color: var(--fg); + user-select: none; + } + .vis-switch { + position: relative; + width: 30px; + height: 16px; + background: color-mix(in srgb, var(--fg) 15%, transparent); + border-radius: 8px; + transition: background 0.2s; + flex-shrink: 0; + } + .vis-switch::after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 12px; + height: 12px; + background: var(--panel); + border-radius: 50%; + transition: transform 0.2s; + box-shadow: 0 1px 2px rgba(0,0,0,0.25); + } + .vis-row input:checked + .vis-switch { + background: var(--red); + } + .vis-row input:checked + .vis-switch::after { + transform: translateX(14px); + } + .vis-row input:checked ~ .vis-icon, + .vis-row:has(input:checked) .vis-icon { + color: var(--fg); + } + .persona-display-name-row .vis-label { + position: relative; + top: -4px; + } + .persona-display-name-row .vis-switch { + position: relative; + left: 4px; + } + .md-attach-paperclip-icon { + position: relative; + top: 1px; + } + /* Compare model selector — match the calendar modal's clean header + (no border underline). */ + #compare-model-overlay .modal-header h4 { + pointer-events: none; + } + /* Compare model selector: keep manually-resized/tiny windows contained. + Picker dropdowns are appended to document.body, so the card itself can + clip and scroll without cropping the dropdown list. */ + #compare-model-overlay .modal-content { + display: flex; + flex-direction: column; + max-height: min(720px, calc(100dvh - 48px)); + overflow: hidden; + min-height: 180px; + } + #compare-model-overlay .modal-body { + overflow: auto; + flex: 1 1 auto; + min-height: 0; + } + .vis-hint { + font-size: 10px; + color: color-mix(in srgb, var(--fg) 30%, transparent); + font-weight: 400; + margin-left: 2px; + } + /* Settings toggle — admin-only lock indicator */ + .ui-vis-lock { + display: none; + } + /* (legacy toolbar-toggle styles removed — now using .vis-* classes) */ + + /* Demo highlight pulse */ + .odysseus-highlight { + outline: 2px solid var(--accent, var(--red)) !important; + outline-offset: 1px; + box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + animation: ody-pulse 1.5s ease-in-out infinite; + z-index: 100; + position: relative; + } + @keyframes ody-pulse { + 0%, 100% { outline-color: var(--accent, var(--red)); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); } + 50% { outline-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); } + } + /* Floating breathing halo. Rendered as a body-level div positioned over + the target, so we don't fight the target's own outline / box-shadow / + overflow chain. JS keeps it in sync with the target's bounding rect. */ + .tour-halo { + position: fixed; + pointer-events: none; + border: 3px solid var(--accent, var(--red)); + border-radius: 10px; + z-index: 10000; + animation: ody-breathe 1.4s ease-in-out infinite; + opacity: 0; + transform: scale(0.94); + transition: opacity 0.35s ease-out, transform 0.35s ease-out; + } + .tour-halo.tour-fade-in { + opacity: 1; + transform: scale(1); + } + @keyframes ody-breathe { + 0%, 100% { + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent), + 0 0 22px 4px color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + border-color: var(--accent, var(--red)); + } + 50% { + box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 30%, transparent), + 0 0 40px 14px color-mix(in srgb, var(--accent, var(--red)) 70%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 80%, transparent); + } + } + /* While the tour is active, lift overflow:hidden on common clipping + ancestors so the halo around a highlighted child isn't cropped. */ + body.tour-active .sidebar, + body.tour-active .sidebar-inner, + body.tour-active .chat-input-bar, + body.tour-active .chat-input-top, + body.tour-active .chat-input-wrap, + body.tour-active .chat-input-right, + body.tour-active .mode-toggle, + body.tour-active .model-picker-wrap { + overflow: visible !important; + } + + /* ── Secret tour hint (drag-to-snap on first modal open) ── */ + .tour-hint { + position: fixed; + z-index: 10002; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 10px; + padding: 12px 14px 10px; + width: 240px; + font-size: 0.78rem; + line-height: 1.5; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.32); + opacity: 0; + transform: translateY(-4px); + transition: opacity 0.28s ease-out, transform 0.28s ease-out; + pointer-events: auto; + } + .tour-hint.tour-hint-in { opacity: 1; transform: translateY(0); } + .tour-hint.tour-hint-out { opacity: 0; transform: translateY(-4px); } + .tour-hint-visual { + display: flex; + justify-content: center; + margin-bottom: 8px; + color: var(--accent, var(--red)); + } + .tour-hint-visual svg { display: block; } + .tour-hint-text { margin-bottom: 10px; opacity: 0.92; } + .tour-hint-text b { color: var(--accent, var(--red)); font-weight: 600; } + .tour-hint-dismiss { + display: block; + margin: 0 0 0 auto; + background: none; + border: 1px solid var(--border); + color: var(--fg); + border-radius: 6px; + padding: 3px 12px; + cursor: pointer; + font-family: inherit; + font-size: 0.72rem; + opacity: 0.85; + transition: opacity 0.15s, background 0.15s; + } + .tour-hint-dismiss:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, transparent); } + + /* SVG dance: cursor approaches title bar, drags right, modal snaps to a + right-half zone, holds, returns. 3.2s loop. */ + .th-cursor { animation: th-cursor 3.2s ease-in-out infinite; transform-origin: 0 0; } + .th-modal-group { animation: th-modal-slide 3.2s ease-in-out infinite; transform-origin: 39px 31px; } + .th-zone { animation: th-zone-flash 3.2s ease-in-out infinite; } + @keyframes th-cursor { + 0%, 5% { transform: translate(35px, 32px); } + 18% { transform: translate(35px, 22px); } + 48% { transform: translate(80px, 22px); } + 72% { transform: translate(80px, 22px); } + 90%, 100% { transform: translate(35px, 32px); } + } + @keyframes th-modal-slide { + 0%, 18% { transform: translate(0, 0) scale(1, 1); } + 48% { transform: translate(45px, 0) scale(1, 1); } + 58% { transform: translate(30px, -19px) scale(1.4, 2.4); } + 72% { transform: translate(30px, -19px) scale(1.4, 2.4); } + 90%, 100% { transform: translate(0, 0) scale(1, 1); } + } + @keyframes th-zone-flash { + 0%, 38% { opacity: 0; } + 48% { opacity: 0.22; } + 58%, 72% { opacity: 0; } + 100% { opacity: 0; } + } + @media (prefers-reduced-motion: reduce) { + .th-cursor, .th-modal-group, .th-zone { animation: none !important; } + } + .odysseus-hl-label { + position: absolute; + top: -22px; + left: 8px; + background: var(--red); + color: var(--bg); + font-size: 0.7rem; + padding: 2px 8px; + border-radius: 4px; + white-space: nowrap; + z-index: 101; + pointer-events: none; + } + + /* Generated images inside chat bubbles */ + .msg.generated-image-wrap .body { text-align: center; } + .generated-image { + max-width: 100%; + max-height: 512px; + border-radius: 8px; + cursor: pointer; + transition: transform 0.2s; + display: inline-block; + margin: 0 auto; + } + .generated-image:hover { transform: scale(1.02); } + .generated-image-caption { + font-size: 0.8rem; + opacity: 0.5; + margin-top: 6px; + font-style: italic; + text-align: center; + } + + /* Setup wizard */ + .setup-wizard { padding: 16px; max-width: 500px; } + .setup-wizard .setup-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 12px; color: var(--fg); } + .setup-wizard .setup-label { font-size: 0.85rem; color: var(--fg); opacity: 0.7; margin-bottom: 8px; } + .setup-wizard .setup-presets { display: flex; flex-wrap: wrap; gap: 8px; } + .setup-wizard .setup-preset-btn { + padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; + background: var(--panel); color: var(--fg); cursor: pointer; + font-size: 0.85rem; transition: all 0.2s; + } + .setup-wizard .setup-preset-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } + .setup-wizard .setup-input { + display: block; width: 100%; padding: 8px 12px; margin-bottom: 8px; + background: var(--panel); border: 1px solid var(--border); + border-radius: 6px; color: var(--fg); font-size: 0.85rem; box-sizing: border-box; + } + .setup-wizard .setup-input:focus { border-color: var(--red); outline: none; } + .setup-wizard .setup-connect-btn { + padding: 8px 20px; background: var(--red); color: var(--bg); + border: none; border-radius: 6px; cursor: pointer; font-weight: 600; margin-top: 4px; + } + .setup-wizard .setup-connect-btn:hover { opacity: 0.9; } + .setup-wizard .setup-status { font-size: 0.8rem; margin-top: 8px; color: var(--fg); opacity: 0.7; } + .setup-wizard .setup-model-list { display: flex; flex-wrap: wrap; gap: 8px; } + .setup-wizard .setup-model-btn { + padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; + background: var(--panel); color: var(--fg); cursor: pointer; + font-size: 0.85rem; transition: all 0.2s; + } + .setup-wizard .setup-model-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } + .setup-wizard .setup-step.hidden { display: none; } + + /* Dropdown menu styles */ + .dropdown { + position: absolute; + top: calc(100% + 6px); + right: 0; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 10px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); + z-index: 1000; + display: none; + min-width: 220px; + padding: 6px; + backdrop-filter: blur(12px); + } + + .dropdown.show { + display: block; + animation: dropdown-in 0.15s ease-out; + } + @keyframes dropdown-in { + from { opacity:0; transform:translateY(-6px) scale(0.97); } + to { opacity:1; transform:translateY(0) scale(1); } + } + + .dropdown-item { + padding: 8px 10px; + border-radius: 6px; + cursor: pointer; + transition: background 0.12s ease; + display: flex; + align-items: center; + gap: 10px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); + } + + .dropdown-item:last-child { + border-bottom: none; + } + + .dropdown-item .menu-icon { + width: 28px; + height: 28px; + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + flex-shrink: 0; + } + + .dropdown-item .menu-text h4 { + margin: 0; + font-size: 12.5px; + font-weight: 500; + color: var(--fg); + line-height: 1.3; + } + + .dropdown-item .menu-text p { + margin: 0; + font-size: 10.5px; + color: var(--color-subheader); + line-height: 1.3; + } + + .dropdown-item:hover { + background: color-mix(in srgb, var(--red) 10%, transparent); + } + .dropdown-item:hover .menu-icon { + background: color-mix(in srgb, var(--red) 15%, transparent); + } + + /* Compact dropdown items (session/model context menus) */ + .dropdown-item-compact { + cursor: pointer; + padding: 6px 8px; + font-size: 11px; + border-radius: 8px; + display: flex; + align-items: center; + gap: 10px; + color: var(--fg); + transition: background 0.1s; + } + .dropdown-item-compact:hover { + background: color-mix(in srgb, var(--accent) 10%, transparent); + } + .dropdown-item-compact .dropdown-icon { + width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + opacity: 0.5; + } + .dropdown-item-compact .dropdown-icon svg { + width: 14px; + height: 14px; + } + .dropdown-item-compact .dropdown-shortcut { + margin-left: auto; + font-size: 9.5px; + opacity: 0.35; + font-weight: 400; + } + /* Keyboard shortcut hints (⌘+Alt+D etc.) are meaningless on touch — + hide them in the per-chat actions menu on mobile. */ + @media (max-width: 768px) { + .session-dropdown .dropdown-shortcut { display: none; } + } + .dropdown-item-danger { + color: var(--red) !important; + } + .dropdown-item-danger .dropdown-icon { + opacity: 0.7; + } + + /* Inline rename input for sessions */ + .session-rename-input { + width: 100%; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--accent, var(--accent-primary)); + border-radius: 4px; + padding: 2px 6px; + font-family: inherit; + font-size: 12px; + line-height: 1.3; + outline: none; + } + + /* Session dropdown container */ + .session-dropdown-menu { + position: fixed; + z-index: 1000; + display: none; + min-width: auto; + width: max-content; + padding: 4px; + animation: dropdown-in 0.15s ease-out; + } + + /* Folder move submenu */ + .session-folder-submenu { + position: fixed; + z-index: 1001; + display: none; + min-width: auto; + width: max-content; + padding: 4px; + } + + .dropdown-divider { + height: 1px; + background: var(--border); + margin: 4px 8px; + } + + /* Search toggle styles */ + .search-toggle { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 0; + border-bottom: 1px solid var(--border); + margin-bottom: 8px; + } + + .search-provider-label { + font-size: 14px; + color: var(--fg); + } + +/* ── Voice, Search, Themes, Comparison, Censor, Print ── */ + + /* Voice recording styles */ + #mic-btn { + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; + transition: all 0.2s ease; + } + + #mic-btn:hover { + background: color-mix(in srgb, var(--fg) 6%, transparent); + border-color: var(--fg); + } + + #mic-btn.recording { + background: var(--color-recording); + border-color: var(--color-recording); + animation: pulse 1.5s infinite; + } + + @keyframes pulse { + 0% { opacity: 1; } + 50% { opacity: 0.7; } + 100% { opacity: 1; } + } + + #recording-indicator { + position: fixed; + top: 10px; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(10px); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + margin: 10px; + z-index: 1000; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + } + + #recording-indicator.hidden { + display: none !important; + } + + .recording-content { + display: flex; + align-items: center; + gap: 12px; + color: white; + } + + .recording-icon { + color: var(--color-recording); + font-size: 20px; + animation: pulse 1.5s infinite; + } + + .recording-text { + font-size: 16px; + font-weight: 500; + } + + #stop-recording { + background: var(--color-recording); + color: white; + border: none; + border-radius: 6px; + padding: 6px 12px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s ease; + } + + #stop-recording:hover { + background: var(--color-recording-hover); + } + + /* Error state for recording */ + #recording-indicator.error { + background: rgba(173, 26, 26, 0.9); + } + + .recording-error { + color: var(--color-recording); + font-size: 14px; + margin-top: 4px; + } + + @media (max-width: 768px) { + #recording-indicator { + margin: 8px; + padding: 10px; + } + + .recording-text { + font-size: 14px; + } + + #stop-recording { + padding: 6px 10px; + font-size: 12px; + } + } +/* SYNTAX HIGHLIGHTING — uses theme vars from style.css, no hardcoded overrides */ +.hljs { color: var(--hl-fg, #9cdef2); background: none !important; } +pre { background: var(--code-bg, var(--hl-bg, #282c34)) !important; } + + /* ---------- Search overlay (Ctrl+K command palette) ---------- */ + .search-overlay { + position: fixed; + top: 0; left: 0; width: 100%; height: 100%; + background: rgba(0, 0, 0, 0.6); + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: var(--search-overlay-top, 15vh); + z-index: 300; + backdrop-filter: blur(6px); + } + .search-overlay.hidden { display: none; } + .search-popup { + width: 520px; + max-width: 90vw; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 12px; + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5), 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); + overflow: hidden; + display: flex; + flex-direction: column; + max-height: 60vh; + } + .search-popup input#search-input { + width: 100%; + background: transparent; + border: none; + border-bottom: 1px solid var(--border); + outline: none; + color: var(--fg); + font-size: 16px; + font-family: 'Fira Code', monospace; + padding: 14px 16px; + box-sizing: border-box; + } + .search-popup input#search-input::placeholder { + color: color-mix(in srgb, var(--fg) 30%, transparent); + } + .search-results { + overflow-y: auto; + flex: 1; + padding: 4px; + } + .search-results:empty { + display: none; + } + .search-group-header { + font-size: 10px; + font-weight: 600; + color: var(--color-subheader); + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 10px 12px 4px; + } + .search-result-item { + display: flex; + align-items: baseline; + gap: 8px; + padding: 8px 12px; + border-radius: 6px; + cursor: pointer; + transition: background 0.1s; + } + .search-result-item:hover, + .search-result-item.selected { + background: color-mix(in srgb, var(--red) 10%, transparent); + } + .search-result-role { + font-size: 10px; + font-weight: 600; + color: var(--color-subheader); + flex-shrink: 0; + min-width: 24px; + } + .search-result-snippet { + flex: 1; + font-size: 12px; + color: var(--fg); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .search-result-time { + font-size: 10px; + color: color-mix(in srgb, var(--fg) 35%, transparent); + flex-shrink: 0; + white-space: nowrap; + } + mark.search-highlight { + /* Was orange-on-orange (0.35 alpha bg + orange text) — too low-contrast + to read. Solid accent fill with bg-colored text reads clearly. */ + background: var(--accent, #e8a830); + color: var(--bg, #1a1a1a); + border-radius: 2px; + padding: 0 2px; + font-weight: 600; + } + .search-empty { + text-align: center; + color: color-mix(in srgb, var(--fg) 35%, transparent); + padding: 24px; + font-size: 13px; + } + + /* ---------- Theme popup (uses .modal > .modal-content frame) ---------- */ + #theme-modal { z-index: 260; } + #theme-popup .theme-popup-sub { color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 11px; margin-bottom: 10px; } + /* `max-height` instead of a fixed height so the popup shrinks to fit its + content (avoids the leftover whitespace after the time-based switching + card was removed). The inner .theme-tab-panel keeps overflow:auto, so + any taller content still scrolls inside. */ + #theme-popup { overflow-y: hidden; max-height: min(85vh, 600px); } + #theme-popup .modal-header { flex-shrink: 0; } + #theme-popup .admin-tabs { margin: 0 -10px 8px; padding: 0 10px; flex-shrink: 0; } + .theme-tab-panel { overflow-y: auto; min-height: 0; flex: 1; padding-bottom: 10px; } + + .theme-grid { + display: grid; grid-template-columns: repeat(auto-fill, minmax(66px, 1fr)); + gap: 6px; margin-bottom: 12px; + } + .theme-swatch { + border: 2px solid var(--border); border-radius: 8px; cursor: pointer; + padding: 5px; text-align: center; font-size: 0.65rem; color: var(--fg); + transition: border-color 0.15s, transform 0.15s; + } + .theme-swatch:hover { transform: scale(1.06); } + .theme-swatch.active { border-color: var(--red); box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 33%, transparent); } + .theme-swatch-colors { + display: flex; justify-content: center; margin-bottom: 3px; + } + .theme-swatch-colors span { + width: 15px; height: 15px; border-radius: 50%; + margin-left: -5px; + border: 1.5px solid color-mix(in srgb, var(--fg) 12%, transparent); + } + .theme-swatch-colors span:first-child { margin-left: 0; } + .theme-custom-label { font-size: 11px; font-weight: 600; color: color-mix(in srgb, var(--fg) 50%, transparent); text-transform: uppercase; letter-spacing: 0.06em; margin: 10px 0 6px; } + .theme-custom { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; } + .color-row { display: flex; align-items: center; gap: 4px; position: relative; } + .color-row label { font-size: 13px; font-weight: 500; color: var(--fg); opacity: 0.7; flex: 1; } + .color-row input[type="color"], + .color-row input.cp-swatch-input { + width: 24px; height: 24px; border: 1px solid var(--border); border-radius: 50%; + background: none; cursor: pointer; padding: 0; flex-shrink: 0; + overflow: hidden; + -webkit-appearance: none; + appearance: none; + } + .color-row input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } + .color-row input[type="color"]::-webkit-color-swatch { border: none; border-radius: 50%; } + .color-row input[type="color"]::-moz-color-swatch { border: none; border-radius: 50%; } + #theme-bg-effect-color { position: relative; top: 2px; } + .color-row input.cp-swatch-input { + color: transparent; + text-shadow: none; + caret-color: transparent; + font-size: 0; + user-select: none; + } + .color-row input.cp-swatch-input::selection { background: transparent; } + .color-row input.cp-swatch-input:focus { outline: 1px solid var(--red); outline-offset: 1px; } + .color-reset-btn { + width: 24px; height: 24px; border: none; background: none; cursor: pointer; + color: var(--fg); opacity: 0; font-size: 1.15rem; padding: 0; line-height: 1; + transition: opacity 0.15s, color 0.15s; flex-shrink: 0; pointer-events: none; + position: absolute; right: 6px; top: 50%; transform: translateY(-50%); + } + .color-row input[type="color"], + .color-row input.cp-swatch-input { margin-left: auto; } + /* Keep the harmony hex readout on the left, with the swatch dot anchored + to the right like the other color controls. */ + #theme-harmony-card #harmony-accent-hex { + order: -1; + margin-right: auto; + } + .color-row:has(.color-reset-btn.changed) input[type="color"], + .color-row:has(.color-reset-btn.changed) input.cp-swatch-input { margin-right: 28px; } + .color-reset-btn.changed { opacity: 0.4; pointer-events: auto; } + .color-reset-btn.changed:hover { opacity: 1; color: var(--red); } + .theme-custom-divider { + grid-column: 1 / -1; font-size: 11px; color: var(--fg); opacity: 0.5; + text-transform: uppercase; letter-spacing: 0.04em; margin: 6px 0 2px; + border-top: 1px solid var(--border); padding-top: 6px; + } + .theme-swatch[data-custom] { position: relative; overflow: visible; } + /* Accent-coloured circular X — always visible (mobile-friendly), sits + INSIDE the swatch's top-right corner so it never crops into a + neighbouring swatch. */ + .theme-delete-btn { + position: absolute; + top: -2px; + right: -2px; + width: 20px; + height: 20px; + padding: 0; + border: none; + border-radius: 50%; + background: var(--accent, var(--red, #d92534)); + color: #fff; + cursor: pointer; + z-index: 2; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); + transition: transform 0.12s, background 0.12s; + } + .theme-delete-btn:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 80%, white); + transform: scale(1.12); + } + .theme-delete-btn:active { transform: scale(0.95); } + .theme-delete-btn svg { + display: block; + /* Optical nudge — the X reads off-center inside the circle without it. */ + position: relative; + left: 1px; + top: -1px; + } + .theme-save-row { + display: flex; gap: 6px; margin-top: 8px; + } + .theme-save-row input { + flex: 1; padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; + background: var(--bg); color: var(--fg); font-size: 12px; font-family: inherit; + } + .theme-save-row input:focus { outline: none; border-color: var(--red); } + .theme-save-row input::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } + .theme-save-row button { + padding: 6px 12px; border: 1px solid var(--red); border-radius: 6px; + background: transparent; color: var(--red); cursor: pointer; + font-size: 12px; font-family: inherit; white-space: nowrap; transition: all 0.15s; + } + .theme-save-row button:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } + .theme-save-error { + font-size: 11px; color: var(--red); margin-top: 2px; display: none; + } + /* Import/Export */ + .theme-io-row { display: flex; gap: 6px; margin-top: 6px; } + .theme-io-btn { + flex: 1; padding: 5px 10px; border: 1px solid var(--border); border-radius: 6px; + background: transparent; color: var(--fg); cursor: pointer; + font-size: 12px; opacity: 0.7; transition: all 0.15s; font-family: inherit; + } + .theme-io-btn:hover { opacity: 1; border-color: var(--fg); background: color-mix(in srgb, var(--fg) 5%, transparent); } + + #memory-import-btn svg, + #memory-export-btn svg { + color: var(--accent, var(--red)); + } + .theme-import-area { + width: 100%; margin-top: 6px; padding: 6px 8px; + border: 1px solid var(--border); border-radius: 6px; + background: var(--bg); color: var(--fg); font-size: 0.7rem; + font-family: inherit; resize: vertical; min-height: 48px; + } + .theme-import-area:focus { outline: none; border-color: var(--red); } + .theme-import-area.hidden { display: none; } + .theme-import-actions { display: flex; gap: 6px; margin-top: 4px; } + .theme-import-actions.hidden { display: none; } + /* Font & Density */ + .theme-fd-row { display: flex; gap: 8px; margin-bottom: 8px; } + .theme-fd-group { flex: 1; display: flex; flex-direction: column; gap: 3px; } + .theme-fd-label { font-size: 12px; font-weight: 500; color: var(--fg); opacity: 0.6; } + .theme-fd-select { + padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; + background: var(--bg); color: var(--fg); font-size: 12px; + font-family: inherit; cursor: pointer; + } + .theme-fd-select:focus { outline: none; border-color: var(--red); } + @media (max-width: 768px) { + /* The switch is too cramped beside the Font and Density selects on + narrow screens; give Frosted a dedicated row instead. */ + .theme-fd-row:first-of-type { + flex-wrap: wrap; + } + .theme-fd-row:first-of-type #theme-frosted-group { + flex: 1 1 100%; + flex-direction: row; + align-items: center; + justify-content: space-between; + min-width: 0; + } + } + .theme-fd-range { + width: 100%; + max-width: 100%; + box-sizing: border-box; + margin: 0; + padding: 0; + height: 24px; + background: transparent; + cursor: pointer; + -webkit-appearance: none; + appearance: none; + accent-color: var(--red); + } + .theme-fd-range::-webkit-slider-runnable-track { + height: 4px; background: var(--border); border-radius: 2px; + } + .theme-fd-range::-moz-range-track { + height: 4px; background: var(--border); border-radius: 2px; + } + .theme-fd-range::-webkit-slider-thumb { + -webkit-appearance: none; appearance: none; + width: 14px; height: 14px; border-radius: 50%; + background: var(--red); border: none; margin-top: -5px; cursor: pointer; + } + .theme-fd-range::-moz-range-thumb { + width: 14px; height: 14px; border-radius: 50%; + background: var(--red); border: none; cursor: pointer; + } + .theme-fd-range:focus { outline: none; } + /* Color Harmony Generator */ + .theme-harmony-row { display: flex; gap: 8px; align-items: flex-end; } + .harmony-generate-btn { + padding: 5px 14px; border: 1px solid var(--red); border-radius: 6px; + background: transparent; color: var(--red); cursor: pointer; + font-size: 12px; white-space: nowrap; transition: all 0.15s; + font-family: inherit; width: 100%; + } diff --git a/static/css/02-compare.css b/static/css/02-compare.css new file mode 100644 index 000000000..df2e1e444 --- /dev/null +++ b/static/css/02-compare.css @@ -0,0 +1,1805 @@ +/* 02-compare - 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. + */ + .harmony-generate-btn:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } + .harmony-preview { + display: flex; height: 20px; border-radius: 6px; overflow: hidden; + margin-top: 8px; border: 1px solid var(--border); + } + .harmony-preview:empty { display: none; border: none; } + .harmony-preview span { flex: 1; } + #theme-reset-btn { + margin-top: 6px; width: 100%; padding: 6px; border: 1px solid var(--border); + border-radius: 6px; background: var(--bg); color: var(--fg); cursor: pointer; + font-size: 12px; font-family: inherit; opacity: 0.7; transition: opacity 0.15s; + } + #theme-reset-btn:hover { opacity: 1; } + .theme-adv-toggle { + margin-top: 10px; margin-bottom: 10px; + padding: 6px 8px; cursor: pointer; + font-size: 11px; color: var(--red); opacity: 0.8; + border: 1px solid var(--border); border-radius: 6px; + transition: opacity 0.15s, background 0.15s; + user-select: none; + } + .theme-adv-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 4%, transparent); } + .theme-adv-toggle .theme-adv-arrow { + display: inline-block; transition: transform 0.2s; font-size: 10px; + margin-right: 4px; + } + .theme-adv-toggle.open .theme-adv-arrow { transform: rotate(90deg); } + .theme-adv-section { margin-top: 8px; margin-bottom: 10px; } + .theme-adv-section.hidden { display: none; } + .theme-adv-group { margin-bottom: 8px; } + .theme-adv-group-label { + font-size: 10px; color: var(--fg); opacity: 0.5; + text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; + } + .theme-adv-clear-btn { + margin-top: 6px; width: 100%; padding: 5px; border: 1px solid var(--border); + border-radius: 6px; background: transparent; color: var(--fg); opacity: 0.5; + cursor: pointer; font-size: 12px; font-family: inherit; transition: opacity 0.15s; + } + .theme-adv-clear-btn:hover { opacity: 1; } + + /* Customization is an editor, so keep its sections visually related while + preserving the existing controls and their order. */ + #theme-tab-customize { + display: flex; + flex-direction: column; + gap: 10px; + } + #theme-tab-customize > .admin-card, + #theme-tab-customize > .theme-adv-toggle, + #theme-tab-customize > .theme-adv-section { + margin-top: 0; + margin-bottom: 0; + } + #theme-tab-customize > .admin-card { + padding: 12px; + } + #theme-tab-customize > .admin-card > h2 { + display: flex; + align-items: center; + min-height: 20px; + margin: 0 0 10px; + padding-bottom: 7px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 65%, transparent); + font-size: 13px; + } + #theme-tab-customize .theme-custom { + gap: 6px 14px; + } + #theme-tab-customize .color-row { + min-height: 30px; + padding: 3px 6px; + box-sizing: border-box; + border: 1px solid color-mix(in srgb, var(--border) 55%, transparent); + background: color-mix(in srgb, var(--fg) 2%, transparent); + } + #theme-tab-customize .color-row label { + font-size: 12px; + } + #theme-tab-customize .theme-adv-toggle { + flex-shrink: 0; + margin: 0; + background: color-mix(in srgb, var(--fg) 2%, transparent); + } + #theme-tab-customize .theme-adv-section { + padding: 10px 12px 2px; + border: 1px solid color-mix(in srgb, var(--border) 75%, transparent); + border-radius: 7px; + background: color-mix(in srgb, var(--panel) 55%, transparent); + } + #theme-tab-customize .theme-adv-group { + margin: 0; + padding: 0 0 9px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 48%, transparent); + } + #theme-tab-customize .theme-adv-group + .theme-adv-group { + padding-top: 9px; + } + #theme-tab-customize .theme-adv-group:last-of-type { + border-bottom: 0; + } + #theme-tab-customize .theme-adv-group-label { + margin-bottom: 6px; + color: var(--red); + opacity: 0.8; + font-weight: 600; + } + #theme-tab-customize .theme-fd-row, + #theme-tab-customize .theme-harmony-row { + gap: 12px; + margin-bottom: 10px; + } + #theme-tab-customize .theme-fd-row:last-child, + #theme-tab-customize .theme-harmony-row:last-child { + margin-bottom: 0; + } + #theme-tab-customize .theme-fd-group { + min-width: 0; + } + #theme-tab-customize .theme-fd-select { + min-height: 30px; + } + #theme-tab-customize #theme-harmony-card .theme-fd-label { + font-size: 11px; + } + #theme-tab-customize .theme-save-row, + #theme-tab-customize .theme-io-row { + gap: 8px; + } + #theme-tab-customize #theme-reset-btn { + margin-top: 8px; + } + @media (max-width: 560px) { + #theme-tab-customize .theme-custom { + grid-template-columns: 1fr; + } + #theme-tab-customize .theme-fd-row, + #theme-tab-customize .theme-harmony-row { + flex-direction: column; + align-items: stretch; + } + #theme-tab-customize .theme-fd-group, + #theme-tab-customize .theme-harmony-row .theme-fd-group { + width: 100%; + } + } + + + /* Mobile: bottom sheet modals — slide up from bottom */ + @media (max-width: 768px) { + .modal { + align-items: flex-end; + background: rgba(0,0,0,0.4); + pointer-events: auto; + /* Anchor the bottom sheet to the DYNAMIC viewport. The base overlay is + position:fixed; height:100% = the layout viewport, whose bottom sits + UNDER Firefox Android's bottom URL bar — so flex-end parked the sheet + (and its footer / Start button) beneath the bar. dvh excludes the bar + so the footer lands above it. Extra safe-area pad for iOS home bar. */ + height: 100dvh; + padding-bottom: env(safe-area-inset-bottom, 0px); + } + /* Confirm dialog stays centered, not a bottom sheet */ + #styled-confirm-overlay { + align-items: center; + } + .styled-confirm-box { + border-radius: 12px !important; + border: 1px solid var(--border) !important; + animation: modal-enter 0.25s ease-out both !important; + max-height: none !important; + } + .styled-confirm-box.modal-closing { + animation: modal-exit 0.18s ease-in both !important; + } + .styled-confirm-box::before { + display: none !important; + } + .styled-confirm-box .close-btn { + display: none !important; + } + #theme-popup { + width: 100% !important; + height: 65vh !important; + max-height: 65vh !important; + top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important; + position: fixed !important; + border-radius: 14px 14px 0 0; + border: none; + border-top: 1px solid var(--border); + padding: 6px 12px 12px; + animation: sheet-enter 0.2s ease-out forwards; + overflow-y: hidden !important; + } + #theme-popup .theme-tab-panel { + touch-action: pan-y; + overscroll-behavior: contain; + -webkit-overflow-scrolling: touch; + } + #theme-popup.sheet-ready { + animation: none; + } + #theme-popup.modal-closing { + animation: sheet-exit 0.15s ease-in both !important; + } + .modal-content, + .memory-modal-content, + .settings-modal-content, + #compare-model-overlay .modal-content { + width: 100% !important; + /* 85dvh leaves comfortable headroom above the sheet so the user can + still see the chat behind it and the drag handle has breathing + room. Was 65vh (too short, content clipped) → 90vh (too tall, top + hugged the status bar). */ + max-height: 85dvh !important; + max-height: 85vh !important; /* fallback for browsers without dvh */ + height: auto !important; + border-radius: 14px 14px 0 0; + border: none; + border-top: 1px solid var(--border); + padding-top: 6px !important; + /* Clip children to the rounded top corners — otherwise the sticky + modal-header's var(--panel) background paints a darker + rectangle past the radius and the corners look square again. */ + overflow: hidden; + animation: sheet-enter 0.2s ease-out forwards; + } + /* Keep the tall tool drawers on a shared mobile height ceiling. The + final mobile drawer rule below makes this content-sized so shorter + tools remain compact while tall ones leave a visible top gap. */ + #cookbook-modal .modal-content, + #tasks-modal .modal-content, + #calendar-modal .modal-content, + #memory-modal .memory-modal-content, + #settings-modal .settings-modal-content, + #email-lib-modal .modal-content, + #doclib-modal .doclib-modal-content { + max-height: 90vh !important; + max-height: 90dvh !important; + height: auto !important; + /* Reserve the iOS home-indicator / bottom-bar strip so an expanded + skill card's footer (Delete / Edit / Run) isn't hidden under it. + dvh already excludes the URL bar; this handles the safe area. */ + padding-bottom: env(safe-area-inset-bottom, 0px) !important; + } + /* Grab handle pill on the non-standard content classes too. Memory's + desktop rule defines a radial-glow ::before later in the file — + these body-prefixed selectors win the specificity battle on mobile. */ + body .memory-modal-content::before, + body .settings-modal-content::before { + content: ''; + display: block; + position: static; + inset: auto; + width: 36px; height: 4px; + background: var(--fg); + opacity: 0.25; + border-radius: 2px; + margin: 0 auto 4px; + flex-shrink: 0; + padding: 0; + border-top: 10px solid transparent; + border-bottom: 6px solid transparent; + background-clip: padding-box; + animation: none; + } + .memory-modal-content .close-btn, + .memory-modal-content .modal-close, + .settings-modal-content .close-btn, + .settings-modal-content .modal-close { + display: none !important; + } + .memory-modal-content, + .settings-modal-content { + touch-action: pan-y; + overscroll-behavior: contain; + } + /* Library modals use the same bottom-sheet sizing as the other tools. + The shared mobile drawer rule below provides the top breathing room. */ + #cookbook-modal .modal-content, + #calendar-modal .modal-content, + #email-lib-modal .modal-content, + #doclib-modal .modal-content, + #gallery-modal .modal-content, + #tasks-modal .modal-content { + /* vh-first as fallback for very old browsers, then dvh wins so + the modal shrinks/grows with the mobile URL bar. Wrong order + previously made the expanded chat preview extend below the + visible viewport on Chrome/Safari mobile (URL bar covered the + action buttons row). */ + max-height: 90vh !important; + max-height: 90dvh !important; + height: auto !important; + } + /* Anchor those modals to the top so the full height is usable */ + #cookbook-modal, + #calendar-modal, + #email-lib-modal, + #doclib-modal, + #gallery-modal, + #tasks-modal { + padding-top: 0 !important; + align-items: stretch !important; + } + /* Deep Research already gets the swipe grab-handle pill from the + shared `.modal-content::before` rule (the pane carries that class). + The previous header-level pill was redundant and rendered as a + SECOND pill stacked above the real one — removed. */ + /* The inner body must flex to fill the new full height, and the + tasks list inside it must scroll independently — otherwise the + list crops at whatever pre-mobile height the desktop layout had. */ + #tasks-modal .modal-content { + display: flex !important; + flex-direction: column !important; + } + #tasks-modal .modal-body { + flex: 1 1 auto !important; + min-height: 0 !important; + } + /* Memory/Skills: the body lacks flex:1, so on the fixed-height mobile + sheet (overflow:hidden) it grew past the viewport and clipped the + bottom of the skills list + its row action buttons with no way to + scroll there. Bound the body so the inner list scrolls internally. */ + #memory-modal .memory-modal-content { + display: flex !important; + flex-direction: column !important; + } + /* flex-basis MUST be 0 (not auto) — matches the working #doclib-modal + .modal-body. With basis:auto, Firefox (incl. mobile) sizes the body + to its content (~half height) instead of filling, while Chromium + fills it regardless — which is exactly why the skill expand worked + on desktop/Chromium but stuck at ~50% on Firefox mobile. */ + #memory-modal .memory-modal-body { + flex: 1 1 0 !important; + min-height: 0 !important; + overflow: hidden !important; + } + /* Same basis:0 fix down the rest of the skills chain so every layer + fills instead of sizing to content under Firefox. */ + #memory-modal .memory-tab-panel[data-memory-panel="skills"] { + flex: 1 1 0 !important; + min-height: 0 !important; + } + #memory-modal .memory-tab-panel[data-memory-panel="skills"] > .admin-card { + flex: 1 1 0 !important; + min-height: 0 !important; + } + /* The skills modal carries an extra toolbar row (search/sort/select + + bulk bar) the doc/email libraries don't, so the shared 82dvh preview + min-height overflowed here and pushed the expanded footer (Delete / + Edit / Run) off the bottom of the screen. Let flexbox size it from + the resolved card height instead, so the footer always pins inside + the visible area. */ + #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview { + min-height: 0 !important; + } + /* Once enter animation finishes, clear it so inline transform works for dragging */ + .modal-content.sheet-ready { + animation: none; + } + .modal-content.modal-closing { + animation: sheet-exit 0.15s ease-in both !important; + } + /* Grab handle — large touch target, visible pill */ + #theme-popup::before, + .modal-content::before { + content: ''; + display: block; + width: 36px; height: 4px; + background: var(--fg); opacity: 0.25; + border-radius: 2px; + margin: 0 auto 4px; + flex-shrink: 0; + padding: 0; + /* Expand touch target without changing visual size */ + border-top: 10px solid transparent; + border-bottom: 6px solid transparent; + background-clip: padding-box; + } + /* Hide close X on mobile — swipe down to dismiss */ + .modal-content .close-btn, + .modal-content .modal-close, + #theme-popup .close-btn { + display: none !important; + } + /* Hide the auto-injected minimize (_) button on mobile — the dock + chip already represents the minimized state, and the swipe-down + gesture is the canonical minimize action. */ + .modal-minimize-btn, + .minimize-btn, + [data-minimize] { + display: none !important; + } + /* Lock modals to vertical touch only, prevent horizontal dragging */ + .modal-content { + touch-action: pan-y; + overscroll-behavior: contain; + } + .modal-content .cookbook-body, + .modal-content .modal-body { + touch-action: pan-y; + overscroll-behavior: contain; + } + .modal-header { + cursor: default; + touch-action: none; + } + @keyframes sheet-enter { + from { transform: translateY(100%); } + to { transform: translateY(0); } + } + @keyframes sheet-exit { + from { transform: translateY(0); } + to { transform: translateY(100%); } + } + } + + /* ── Model A/B Comparison ── */ + + /* -- Extracted inline-style classes -- */ + .cmp-header-action-btn { + background: none; border: 1px solid var(--border); color: var(--fg); + cursor: pointer; padding: 3px 10px; font-size: 11px; font-weight: 600; + opacity: 0.7; transition: all 0.15s; line-height: 1; border-radius: 4px; + display: inline-flex; align-items: center; font-family: inherit; + } + .cmp-form-control { + padding: 8px; background: var(--bg); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; font-size: 0.85em; + } + .cmp-btn-secondary { + background: transparent; color: var(--fg); border: 1px solid var(--border); + border-radius: 6px; cursor: pointer; + } + .cmp-btn-primary { + background: var(--fg); color: var(--bg); border: none; + border-radius: 6px; cursor: pointer; font-weight: 600; + transition: filter 0.12s, background 0.12s, color 0.12s; + } + /* Override global button:hover (which switches bg to var(--panel) = + very dark) — keep the bright primary look and just brighten slightly. */ + .cmp-btn-primary:hover:not(:disabled) { + background: var(--fg); color: var(--bg); + filter: brightness(1.1); + } + .cmp-btn-primary:active:not(:disabled) { filter: brightness(0.95); } + .cmp-btn-secondary:hover:not(:disabled) { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); color: var(--fg); + } + .cmp-model-row { + display: flex; align-items: center; gap: 8px; margin-bottom: 8px; + flex-wrap: wrap; + padding: 2px 0; + transition: margin-left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.15s, border-color 0.15s; + } + .cmp-model-row-duplicate { + margin-left: -4px; + margin-right: -4px; + padding: 4px; + border-radius: 6px; + background: color-mix(in srgb, var(--color-error) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--color-error) 20%, transparent); + } + .cmp-model-row-swap-target { + margin-left: -4px; + margin-right: -4px; + padding: 4px; + border-radius: 6px; + background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); + } + .cmp-model-row-swap-target .cmp-row-label { + color: var(--accent, var(--red)); + opacity: 1; + } + .cmp-row-label { + color: var(--fg); font-size: 0.82em; font-weight: 600; min-width: 20px; + opacity: 0.4; text-align: center; flex-shrink: 0; + } + .cmp-model-meta { + order: 3; + flex: 1 0 calc(100% - 28px); + margin-left: 28px; + margin-top: -4px; + display: flex; + align-items: center; + gap: 5px; + min-width: 0; + font-size: 10px; + line-height: 1.2; + } + .cmp-model-meta-chip, + .cmp-model-meta-warning, + .cmp-model-meta-muted { + display: inline-flex; + align-items: center; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + border-radius: 999px; + padding: 1px 6px; + letter-spacing: 0; + } + .cmp-model-meta-chip { + color: color-mix(in srgb, var(--fg) 60%, transparent); + background: color-mix(in srgb, var(--fg) 5%, transparent); + border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + } + .cmp-model-meta-warning { + color: var(--color-error); + background: color-mix(in srgb, var(--color-error) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--color-error) 24%, transparent); + font-weight: 600; + } + .cmp-model-meta-muted { + color: color-mix(in srgb, var(--fg) 36%, transparent); + } + .cmp-rm-btn { + background: none; border: none; color: var(--accent, var(--red)); cursor: pointer; + min-width: 20px; font-size: 16px; font-weight: 600; opacity: 0.3; + transition: all 0.15s; padding: 0; line-height: 1; text-align: center; + position: relative; top: -2px; + order: 2; + margin-left: auto; + align-self: center; + } + .cmp-prov-select { + flex: 0 0 auto; width: 120px; font-size: 0.8em; + } + /* Eval-prompts picker — only shown during compare; absolute top-right + inside .chat-input-top, matching .model-picker-wrap's slot. */ + .chat-input-top > .cmp-eval-wrap { + position: relative; + grid-column: 2; + grid-row: 1; + top: -2px; right: 0; + z-index: 2; + } + .cmp-eval-btn { + display: inline-flex; align-items: center; gap: 4px; + padding: 4px 8px; + background: transparent; + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font-size: 11px; font-weight: 500; + font-family: inherit; + cursor: pointer; opacity: 0.75; + transition: opacity 0.15s, border-color 0.15s; + } + .cmp-eval-btn:hover { opacity: 1; border-color: var(--fg); } + .cmp-eval-clear { + display: inline-flex; align-items: center; gap: 4px; margin-left: 4px; + border: 0; background: transparent; color: var(--fg); opacity: .55; + padding: 3px 5px; font: inherit; font-size: 10px; cursor: pointer; + } + .cmp-eval-clear:hover { opacity: 1; } + .cmp-eval-caret { opacity: 0.7; transform: rotate(180deg); } + .cmp-eval-menu { + position: absolute; bottom: calc(100% + 4px); right: 0; + min-width: 220px; max-width: 280px; + max-height: 360px; overflow-y: auto; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 6px; + box-shadow: 0 -4px 16px rgba(0,0,0,0.3); + padding: 4px; + z-index: 1000; + } + .cmp-eval-menu.hidden { display: none; } + .cmp-eval-group-label { + font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px; + opacity: 0.45; font-weight: 600; + padding: 6px 8px 2px; + } + .cmp-eval-item { + display: block; width: 100%; + text-align: left; + padding: 5px 8px; + background: none; border: none; + color: var(--fg); font-size: 11px; + font-family: inherit; + border-radius: 4px; + cursor: pointer; + } + .cmp-eval-item:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + } + .cmp-eval-empty { + padding: 10px; text-align: center; + font-size: 11px; opacity: 0.5; + } + /* Tick on items that ship a known expected answer */ + .cmp-eval-item-tick { + float: right; + margin-left: 6px; + font-size: 10px; + color: var(--color-success, #4caf50); + opacity: 0.8; + } + /* Expected-answer panel — floats as its own little window above the + chat-input-bar. Distinct surface, padded, drop-shadow, slightly + lifted so it reads as a separate UI element, not part of the input. */ + .chat-input-bar:has(.cmp-eval-expected) { position: relative; } + .cmp-eval-expected { + position: absolute; + bottom: calc(100% + 8px); + right: 0; + display: inline-flex; align-items: center; gap: 8px; + padding: 8px 12px; + font-size: 11px; + background: var(--panel); + border: 1px solid color-mix(in srgb, var(--color-success, #4caf50) 50%, transparent); + border-radius: 8px; + box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2); + color: var(--fg); + width: fit-content; + z-index: 5; + pointer-events: auto; + } + .cmp-eval-expected.hidden { display: none; } + .cmp-eval-expected-label { + opacity: 0.6; + text-transform: uppercase; + font-size: 9px; + letter-spacing: 0.5px; + font-weight: 600; + } + .cmp-eval-expected-value { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 11px; + } + .cmp-eval-expected-close { + background: none; border: none; color: var(--fg); + font-size: 14px; line-height: 1; padding: 0 0 0 4px; + opacity: 0.5; cursor: pointer; font-family: inherit; + } + .cmp-eval-expected-close:hover { opacity: 1; } + /* Auto-grade badge — stamped on a pane after stream completes when an + eval prompt with a known expected answer was used. */ + .pane-grade-badge { + display: inline-flex; align-items: center; justify-content: center; + width: 18px; height: 18px; + margin: 0 4px; + font-size: 12px; font-weight: 700; + border-radius: 50%; + border: 1px solid currentColor; + flex-shrink: 0; + } + .pane-grade-badge.pass { + color: var(--color-success, #4caf50); + background: color-mix(in srgb, var(--color-success, #4caf50) 12%, transparent); + } + .pane-grade-badge.fail { + color: var(--color-error, #e55); + background: color-mix(in srgb, var(--color-error, #e55) 12%, transparent); + } + + /* Compare probe overlay */ + .compare-probe-overlay { + position: fixed; inset: 0; z-index: 300; + background: rgba(0,0,0,0.5); display: flex; + align-items: center; justify-content: center; + padding: 12px; + box-sizing: border-box; + } + .compare-probe-card { + width: min(460px, calc(100% - 16px)); + max-height: min(620px, calc(100dvh - 24px)); + box-sizing: border-box; + background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px; + display: flex; flex-direction: column; align-items: stretch; gap: 10px; + min-width: 280px; box-shadow: 0 8px 28px rgba(0,0,0,0.28); + overflow: hidden; + font-family: inherit; + } + .compare-probe-title { + font-size: 13px; font-weight: 400; color: var(--fg); + } + .compare-probe-title-label { font-weight: 600; } + .compare-probe-title-status { font-weight: 400; } + .compare-probe-list { + display: grid; grid-template-columns: 1fr 1fr; gap: 6px; min-width: 0; width: 100%; + overflow-y: auto; + } + .compare-probe-row { + display: flex; align-items: center; gap: 6px; padding: 6px 10px; + border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent); + font-size: 12px; transition: background 0.2s; overflow: hidden; + } + .compare-probe-row.fail { + background: color-mix(in srgb, var(--color-error, #f44) 8%, transparent); + } + .compare-probe-spinner { + width: 24px; text-align: center; font-size: 10px; flex-shrink: 0; + letter-spacing: -1px; opacity: 0.6; + } + .compare-probe-spinner.ok { color: var(--color-success); animation: none; } + .compare-probe-spinner.fail { color: var(--color-error, #f44); animation: none; } + .compare-probe-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .compare-probe-status { font-size: 11px; opacity: 0.5; flex-shrink: 0; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .compare-probe-status.ok { color: var(--color-success); opacity: 1; } + .compare-probe-status.fail { color: var(--color-error, #f44); opacity: 1; } + .compare-probe-action-btn { + display: inline-flex; align-items: center; gap: 5px; + min-height: 25px; padding: 3px 8px; background: transparent; color: var(--fg); border: 1px solid var(--border); + border-radius: 5px; cursor: pointer; font-size: 10px; font-family: inherit; + opacity: 0.78; transition: opacity 0.15s, border-color 0.15s, background 0.15s; white-space: nowrap; flex-shrink: 0; + } + .compare-probe-action-btn svg, + .compare-probe-footer-btn svg { width: 12px; height: 12px; flex: 0 0 auto; color: var(--accent, var(--red)); } + .compare-probe-action-btn:hover { opacity: 1; border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--fg) 5%, transparent); } + .compare-probe-feedback { display: flex; flex-direction: column; gap: 5px; } + .compare-probe-feedback:empty { display: none; } + .compare-probe-detail { + display: flex; align-items: center; gap: 6px; min-width: 0; + padding: 6px 8px; font-size: 10px; + background: color-mix(in srgb, var(--color-error, #f44) 6%, transparent); + border-left: 2px solid color-mix(in srgb, var(--color-error, #f44) 72%, transparent); + border-radius: 4px; + } + .compare-probe-detail-icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 14px; + width: 14px; + height: 14px; + color: var(--accent, var(--red)); + font-family: inherit; + font-size: 11px; + font-weight: 700; + line-height: 1; + } + .compare-probe-detail-message { flex: 1 1 auto; min-width: 0; line-height: 1.35; color: color-mix(in srgb, var(--fg) 72%, transparent); overflow-wrap: anywhere; } + .compare-probe-footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; margin-top: 2px; } + .compare-probe-footer-btn { + display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 5px 10px; + font-family: inherit; font-size: 11px; font-weight: 600; line-height: 1; + } + .compare-probe-start-anyway, + .compare-probe-start-anyway:hover:not(:disabled) { + background: var(--accent, var(--red)); + border: 1px solid var(--accent, var(--red)); + color: var(--bg); + } + .compare-probe-start-anyway svg { color: currentColor; } + .compare-probe-start-anyway span { font-weight: 700; } + @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } + @keyframes pane-shake { + 0%, 100% { transform: translateX(0); } + 15% { transform: translateX(-3px) rotate(-0.5deg); } + 30% { transform: translateX(3px) rotate(0.5deg); } + 45% { transform: translateX(-2px); } + 60% { transform: translateX(2px); } + 75% { transform: translateX(-1px); } + } + + .chat-container.compare-active { + display: flex; + flex-direction: column; + padding: 0; + overflow: hidden; + animation: compare-enter 0.3s ease-out; + } + @keyframes compare-enter { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } + } + .chat-container.compare-active .chat-input-bar { + margin-bottom: 0; + } + .chat-container.compare-active #model-picker-wrap { + display: none !important; + } + .compare-grid { + display: grid; + gap: 4px; + flex: 1 1 0; + min-height: 0; + overflow: hidden; + position: relative; + } + .compare-mobile-tabs { display: none; } + .compare-grid[data-cols="2"] { grid-template-columns: 1fr 1fr; } + .compare-grid[data-cols="3"] { grid-template-columns: 1fr 1fr 1fr; } + .compare-grid[data-cols="4"] { grid-template-columns: repeat(4, 1fr); } + .compare-grid[data-cols="5"] { grid-template-columns: repeat(4, 1fr); } + .compare-grid[data-cols="6"] { grid-template-columns: repeat(4, 1fr); } + .compare-grid[data-cols="7"] { grid-template-columns: repeat(4, 1fr); } + .compare-grid[data-cols="8"] { grid-template-columns: repeat(4, 1fr); } + .compare-grid { grid-auto-rows: 1fr; } + .compare-shuffle-notice { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) scale(0.94); + z-index: 30; + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 14px; + border: 1px solid color-mix(in srgb, var(--red) 28%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--panel, var(--bg)) 92%, transparent); + color: var(--fg); + box-shadow: 0 10px 30px color-mix(in srgb, #000 24%, transparent); + font-size: 12px; + font-weight: 700; + line-height: 1; + opacity: 0; + pointer-events: none; + transition: opacity 0.14s ease, transform 0.14s ease; + } + .compare-shuffle-notice.show { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } + .compare-shuffle-notice-icon { + display: inline-flex; + color: var(--red); + animation: compare-shuffle-dice 0.56s ease both; + } + .compare-shuffle-notice-icon svg { + display: block; + width: 18px; + height: 18px; + } + @keyframes compare-shuffle-dice { + 0% { transform: rotate(-18deg) scale(0.9); } + 55% { transform: rotate(380deg) scale(1.08); } + 100% { transform: rotate(360deg) scale(1); } + } + /* Sequential waterfall layout — stacked rows, staggered left, flush right */ + .compare-grid.sequential-layout { + display: flex !important; + flex-direction: column !important; + grid-template-columns: none !important; + gap: 4px; + overflow-y: auto; + } + .compare-grid.sequential-layout .compare-pane { + flex-shrink: 0; + min-height: 200px; + transition: margin-left 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease; + } + /* Herringbone diagonal cut on left side of sequential pane headers */ + .compare-grid.sequential-layout .compare-pane .pane-header { + clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%); + padding-left: 26px; + } + .compare-pane { + display: flex; + flex-direction: column; + border: 1px solid var(--border); + border-radius: 6px; + overflow: hidden; + min-height: 0; + min-width: 0; + position: relative; + transition: border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; + } + .compare-pane::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + z-index: 2; + pointer-events: none; + background: transparent; + transform-origin: left center; + } + .compare-pane.is-streaming { + border-color: color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + } + .compare-pane.is-streaming::before { + background: linear-gradient(90deg, transparent 0%, var(--accent, var(--red)) 35%, var(--red) 50%, var(--accent, var(--red)) 65%, transparent 100%); + animation: compare-pane-progress 1.25s linear infinite; + } + .compare-pane.is-awaiting-input { + border-color: color-mix(in srgb, var(--red) 42%, var(--border)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--red) 12%, transparent); + } + .compare-pane.is-awaiting-input::before { + background: var(--red); + animation: none; + } + .compare-pane.is-done { + border-color: color-mix(in srgb, var(--green, #50fa7b) 20%, var(--border)); + } + .compare-pane.is-failed { + border-color: color-mix(in srgb, var(--color-error) 42%, var(--border)); + } + .compare-pane.is-failed::before { + background: var(--color-error); + } + @keyframes compare-pane-progress { + from { transform: translateX(-100%); } + to { transform: translateX(100%); } + } + .compare-pane .pane-header { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 4px; + padding: 4px 10px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border-bottom: 1px solid var(--border); + font-size: 0.82em; + font-weight: 600; + color: var(--fg); + transition: background 0.4s; + flex-shrink: 0; + overflow: visible; + min-width: 0; + flex-wrap: nowrap; + } + .pane-header-row { display: flex; align-items: center; width: 100%; min-width: 0; } + .pane-header-primary { min-height: 24px; } + .pane-header-secondary { min-height: 20px; } + .pane-primary-actions, .pane-stats { display: flex; align-items: center; gap: 4px; min-width: 0; } + .pane-primary-actions { margin-left: auto; flex: 0 0 auto; } + .pane-stats { flex: 1 1 auto; overflow: hidden; } + .compare-header-bar button:hover, + .compare-header-bar button:focus-visible { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)) !important; + color: var(--fg) !important; + opacity: 1 !important; + outline: none; + } + .compare-header-bar button:active { + background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent) !important; + transform: scale(0.96); + } + .compare-more-wrap { position: relative; display: inline-flex; } + .compare-more-btn { + display: inline-flex; align-items: center; justify-content: center; + width: 28px; height: 24px; padding: 0 !important; + border: 1px solid transparent; border-radius: 5px; + background: none; color: var(--fg); cursor: pointer; + opacity: 0.5; transition: opacity .15s, background .15s; + } + .compare-header-bar .compare-close-btn { + width: 24px; height: 24px; min-width: 24px; padding: 0; + border-radius: 999px; font-size: 0; display: inline-flex; + align-items: center; justify-content: center; + } + .compare-header-bar .compare-close-btn:hover, + .compare-header-bar .compare-close-btn:focus-visible { + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; + border-color: transparent !important; + color: var(--fg) !important; + opacity: 1 !important; + outline: none; + } + .compare-header-bar .compare-more-btn:hover, + .compare-header-bar .compare-more-btn:focus-visible, + .compare-header-bar .compare-more-btn[aria-expanded="true"] { + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; + border-color: transparent !important; + color: var(--fg) !important; + opacity: 1 !important; + outline: none; + } + .compare-more-menu { + position: absolute; top: calc(100% + 5px); right: 0; z-index: 10002; + display: none; flex-direction: column; gap: 3px; min-width: 132px; + padding: 5px; border: 1px solid var(--border); border-radius: 8px; + background: var(--panel, var(--bg)); box-shadow: 0 8px 24px rgba(0,0,0,.3); + } + .compare-more-menu.is-open { display: flex; } + .compare-more-menu > .compare-export-wrap, + .compare-more-menu > button { + width: 100%; display: flex; align-items: center; + } + .compare-more-menu #compare-export-btn, + .compare-more-menu #compare-shuffle-btn, + .compare-more-menu #compare-check-btn, + .compare-more-menu #compare-add-menu-btn { + width: 100%; min-height: 32px; height: 32px; + box-sizing: border-box; justify-content: flex-start; gap: 7px; + padding: 7px 9px !important; + border: 1px solid transparent !important; border-radius: 6px; + background: transparent !important; color: var(--fg) !important; + font-size: 11px; font-weight: 500; opacity: .72; + transition: background .12s ease, opacity .12s ease, color .12s ease; + } + .compare-more-menu #compare-export-btn:hover, + .compare-more-menu #compare-shuffle-btn:hover, + .compare-more-menu #compare-check-btn:hover, + .compare-more-menu #compare-add-menu-btn:hover, + .compare-more-menu #compare-export-btn:focus-visible, + .compare-more-menu #compare-shuffle-btn:focus-visible, + .compare-more-menu #compare-check-btn:focus-visible, + .compare-more-menu #compare-add-menu-btn:focus-visible { + opacity: 1; background: color-mix(in srgb, var(--red) 10%, transparent) !important; + border-color: transparent !important; color: var(--fg) !important; outline: none; + } + .compare-more-menu #compare-export-btn svg, + .compare-more-menu #compare-shuffle-btn svg, + .compare-more-menu #compare-check-btn svg, + .compare-more-menu #compare-add-menu-btn svg { flex: 0 0 auto; opacity: .65; } + .compare-more-menu #compare-export-btn span, + .compare-more-menu #compare-shuffle-btn span, + .compare-more-menu #compare-check-btn span, + .compare-more-menu #compare-add-menu-btn span { + font-size: 11px !important; margin-left: 0 !important; + color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; + } + .compare-more-menu #compare-export-btn, + .compare-more-menu #compare-shuffle-btn, + .compare-more-menu #compare-check-btn, + .compare-more-menu #compare-add-menu-btn { + color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; + } + .compare-more-menu #compare-export-btn svg, + .compare-more-menu #compare-shuffle-btn svg, + .compare-more-menu #compare-check-btn svg, + .compare-more-menu #compare-add-menu-btn svg { color: var(--fg) !important; opacity: .65 !important; } + .compare-export-item:hover, + .compare-export-item:focus-visible { + background: color-mix(in srgb, var(--red) 10%, transparent) !important; + border-color: transparent !important; outline: none; + } + .compare-export-item { display: flex; align-items: center; gap: 8px; } + .compare-export-item > svg { flex: 0 0 auto; opacity: .65; } + .compare-export-menu .compare-export-item, + .compare-export-menu .compare-export-item > span { + color: var(--fg) !important; + opacity: 1 !important; + } + .compare-add-flap { + position: absolute; z-index: 20; right: -8px; top: 50%; + width: 34px; min-width: 34px; height: 30px; box-sizing: border-box; + padding: 0 !important; + display: inline-flex; flex-direction: row; align-items: center; justify-content: center; + gap: 0; border: 1px solid var(--border); + border-radius: 0 7px 7px 0; background: var(--panel, var(--bg)); + color: var(--fg); cursor: pointer; opacity: .7; + box-shadow: 3px 2px 9px color-mix(in srgb, var(--fg) 12%, transparent); + transform: translate(0, -50%); transition: width .18s ease, opacity .15s, background .15s, box-shadow .15s; + } + .compare-add-flap:hover, .compare-add-flap:focus-visible { + width: 70px; opacity: 1; background: color-mix(in srgb, var(--fg) 8%, var(--panel)); + box-shadow: 4px 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); + outline: none; + } + .compare-add-flap span { + max-width: 0; overflow: hidden; white-space: nowrap; margin-left: 0 !important; + font-size: 10px; opacity: 0; transition: max-width .18s ease, margin-left .18s ease, opacity .12s; + } + .compare-add-flap:hover span, .compare-add-flap:focus-visible span { + max-width: 32px; margin-left: 3px !important; opacity: 1; + } + .compare-add-flap svg { flex-shrink: 0; position: relative; left: -3px; } + @media (max-width: 768px) { + .compare-add-flap { + top: 8px; right: 8px; width: auto; min-width: 32px; height: 32px; + padding: 5px 8px !important; flex-direction: row; transform: none; + border-right: 1px solid var(--border); border-radius: 6px; + } + .compare-add-flap span { opacity: 1; } + } + .pane-actions { + display: flex; gap: 4px; align-items: center; margin-left: auto; flex-shrink: 0; + } + .compare-pane-footer { + font-size: 0.72em; opacity: 0.4; padding: 4px 10px; + border-top: 1px solid color-mix(in srgb, var(--border) 40%, transparent); + text-align: center; flex-shrink: 0; + } + /* Per-pane vote button. Sits at the bottom of each compare pane so + the action lives right under the response it judges. */ + .pane-vote-footer { + padding: 6px 8px; + border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + background: color-mix(in srgb, var(--fg) 3%, transparent); + flex-shrink: 0; + } + .pane-vote-btn { + width: 100%; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px 10px; + font-family: inherit; + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, opacity 0.15s; + } + .pane-vote-btn:hover:not(:disabled) { + background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); + border-color: var(--accent, var(--fg)); + } + .pane-vote-btn:disabled { cursor: not-allowed; } + .pane-vote-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-width: 0; + } + .pane-action-btn { + background: none; border: none; color: var(--fg); cursor: pointer; + opacity: 0.3; padding: 2px; border-radius: 4px; + display: flex; align-items: center; transition: all 0.15s; + } + .pane-action-btn:hover { opacity: 0.8; background: color-mix(in srgb, var(--fg) 6%, transparent); } + /* Pane title as clickable model-swap button */ + .pane-title-btn { + background: none; border: none; cursor: pointer; + font-size: 10px; font-weight: 400; font-family: inherit; + color: var(--fg); padding: 0; + text-align: left; display: flex; align-items: center; gap: 4px; + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + transition: opacity 0.15s; + min-width: 0; flex: 1 1 0; + } + .pane-title-btn:hover { opacity: 0.7; } + .pane-title-caret { font-size: 0.6em; opacity: 0.35; flex-shrink: 0; position: relative; top: 2px; } + .pane-title-btn:hover .pane-title-caret { opacity: 0.7; } + .pane-mode-badge { + display: inline-flex; + align-items: center; + gap: 4px; + flex: 0 0 auto; + height: 18px; + padding: 0 6px; + border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + border-radius: 999px; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + color: color-mix(in srgb, var(--fg) 76%, transparent); + font-size: 9px; + font-weight: 600; + line-height: 1; + letter-spacing: 0; + white-space: nowrap; + } + .pane-mode-dot { + width: 5px; + height: 5px; + border-radius: 50%; + background: currentColor; + opacity: 0.75; + flex: 0 0 auto; + } + .pane-mode-detail { + opacity: 0.52; + font-weight: 500; + } + .pane-mode-badge.pane-mode-icon-only { + width: 20px; + padding: 0; + justify-content: center; + } + .pane-mode-settings { + opacity: 0.65; + cursor: pointer; + } + .pane-settings-btn { width: 20px; height: 20px; justify-content: center; } + .pane-settings-btn[aria-expanded="true"] { opacity: 1; background: color-mix(in srgb, var(--fg) 9%, transparent); } + .response-metrics[data-action="settings"] { cursor: pointer; } + .response-thinking-state { color: color-mix(in srgb, var(--fg) 55%, transparent); } + .pane-mode-settings:hover, + .pane-mode-settings:focus-visible, + .pane-mode-settings[aria-expanded="true"] { + opacity: 1; + color: var(--fg); + background: color-mix(in srgb, var(--fg) 9%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--fg)) 45%, var(--border)); + outline: none; + } + .pane-mode-icon, + .pane-mode-icon svg { + display: block; + width: 12px; + height: 12px; + } + .pane-mode-agent { + color: color-mix(in srgb, var(--fg) 76%, transparent); + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + border-color: color-mix(in srgb, var(--border) 70%, transparent); + } + .pane-mode-search { + color: color-mix(in srgb, var(--accent, var(--red)) 82%, var(--fg)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, var(--border)); + } + .pane-mode-research { + color: color-mix(in srgb, #36c48f 82%, var(--fg)); + background: color-mix(in srgb, #36c48f 10%, var(--bg)); + border-color: color-mix(in srgb, #36c48f 28%, var(--border)); + } + .compare-check-label { + position: relative; + top: -2px; + font-size: 11px; + margin-left: 3px; + } + .compare-check-icon { + position: relative; + top: 1px; + } + #compare-check-btn > .spinner-whirlpool { + position: relative; + top: -2px; + transform: none; + } + .compare-pane .pane-close-btn { opacity: 0.3; } + .compare-pane .pane-close-btn:hover { opacity: 1; color: var(--color-error); } + /* Model swap dropdown under pane title */ + .pane-model-dropdown { + position: absolute; + z-index: 1000; + min-width: 220px; + max-height: 300px; + overflow-y: auto; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 4px 16px rgba(0,0,0,0.3); + padding: 4px; + } + .pane-model-item { + display: block; width: 100%; + box-sizing: border-box; + padding: 8px 10px; font-size: 11px; line-height: 1.3; + text-align: left; background: none; border: none; border-radius: 6px; + color: var(--fg); cursor: pointer; transition: background 0.12s ease; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + .pane-model-item:hover, + .pane-model-item:focus-visible { background: color-mix(in srgb, var(--red) 10%, transparent); outline: none; } + .pane-model-item.current { color: var(--red); font-weight: 600; } + .pane-timer { + font-size: 10px; font-weight: 400; opacity: 0.45; font-variant-numeric: tabular-nums; + white-space: nowrap; padding-right: 4px; + } + .pane-summary { + display: inline-flex; + align-items: center; + position: relative; + top: 1px; + min-height: 16px; + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding: 0 5px; + border-radius: 999px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 9px; + font-weight: 500; + line-height: 1; + font-variant-numeric: tabular-nums; + cursor: pointer; + transition: max-width .15s ease, white-space .15s ease; + } + .pane-summary:empty { display: none; } + .pane-stats-popup { + position: fixed; z-index: 10040; width: 210px; padding: 6px; + background: var(--panel, var(--bg)); color: var(--fg); + border: 1px solid var(--border); border-radius: 7px; + box-shadow: 0 8px 24px rgba(0,0,0,.3); font-size: 11px; + } + .pane-stats-popup-title { padding: 4px 6px 7px; font-weight: 600; border-bottom: 1px solid var(--border); } + .pane-stats-popup-row { display: flex; justify-content: space-between; gap: 12px; padding: 6px; } + .pane-stats-popup-row span { color: color-mix(in srgb, var(--fg) 55%, transparent); } + .pane-stats-popup-row strong { font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; } + /* Metrics stay on the lower row even in dense comparisons. */ + .compare-grid[data-cols="4"] .pane-timer, + .compare-grid[data-cols="5"] .pane-timer, + .compare-grid[data-cols="6"] .pane-timer { + width: auto; order: initial; margin-top: 0; padding-bottom: 0; padding-left: 0; + } + .pane-finish-badge { + font-weight: 600; color: var(--green, #50fa7b); + position: relative; + top: 2px; + } + .compare-pane.winner .pane-header { + background: color-mix(in srgb, var(--green, #50fa7b) 12%, transparent); + border-bottom-color: color-mix(in srgb, var(--green, #50fa7b) 30%, var(--border)); + } + .compare-pane.winner .pane-title { + color: var(--green, #50fa7b); + } + .compare-pane.loser .pane-header { + opacity: 0.5; + } + .confetti-piece { + position: fixed; + pointer-events: none; + z-index: 1000000; + } + .compare-pane.expanded { grid-column: 1 / -1; } + .compare-pane .chat-history { + flex: 1 1 0; + min-height: 0; + overflow-y: auto !important; + overflow-x: hidden; + padding: 8px; + display: flex; + flex-direction: column; + } + .compare-pane .chat-history .msg { + flex-shrink: 0; + } + .compare-gen-image { + max-width: 100%; + max-height: 100%; + object-fit: contain; + border-radius: 4px; + cursor: pointer; + } + .compare-section { + margin-bottom: 14px; + } + .compare-section:last-child { + margin-bottom: 0; + } + .compare-section-label { + font-size: 9px; + text-transform: uppercase; + letter-spacing: 0.6px; + opacity: 0.5; + margin-bottom: 5px; + font-weight: 600; + } + /* Active-type/mode readout next to "Type:"/"Mode:" — only needed on mobile + (where the tab/toggle text labels are hidden); hidden on desktop. */ + .compare-type-current, + .compare-mode-current { display: none; } + /* Contextual one-liner under the mode toggles describing what you just + changed — empty until the first toggle, then a subtle hint. */ + .compare-mode-hint { + font-size: 11px; + opacity: 0.55; + margin-top: 6px; + min-height: 0; + line-height: 1.3; + } + .compare-mode-hint:empty { display: none; } + .compare-type-tabs .compare-mode-tab > svg, + .compare-type-current > svg { + color: var(--accent, var(--red)); + } + + .compare-mode-tabs { + display: flex; + gap: 4px; + flex-wrap: wrap; + min-width: 0; + } + /* Type tabs match Mode toggles 1:1 (same flex column layout, same metrics) */ + .compare-mode-tab { + display: flex; flex-direction: column; align-items: center; justify-content: center; + width: 56px; height: 38px; flex: 1 1 0; + padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; + cursor: pointer; user-select: none; transition: all 0.15s; + background: none; color: var(--fg); opacity: 0.35; + flex-shrink: 0; gap: 0; + font-family: inherit; + } + .compare-mode-tab > svg { + width: 13px; height: 13px; flex: 0 0 13px; + } + .compare-mode-tab:hover { + opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); + } + .compare-mode-tab.active { + opacity: 1; border-color: var(--fg); + background: color-mix(in srgb, var(--fg) 10%, transparent); + } + .compare-sources-box { + display: flex; align-items: center; gap: 6px; + padding: 6px 10px; margin-bottom: 8px; + border-radius: 6px; font-size: 0.78em; + background: color-mix(in srgb, var(--red) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); + color: color-mix(in srgb, var(--fg) 70%, transparent); + cursor: default; + } + .compare-sources-box .sources-label { font-weight: 600; } + /* Compact tool blocks inside compare panes */ + .compare-pane .agent-thread-node { + margin: 4px 0; font-size: 0.85em; + } + .compare-pane .agent-thread-cmd { + max-height: 80px; overflow-y: auto; + } + .compare-pane .agent-tool-output pre { + max-height: 120px; overflow-y: auto; + } + .compare-vote-bar { + display: flex; justify-content: center; gap: 8px; + padding: 8px; border-top: 1px solid var(--border); + flex-shrink: 0; flex-wrap: wrap; + } + .compare-vote-bar.hidden { display: none; } + .compare-vote-btn { + padding: 6px 13px; border: 1px solid var(--border); border-radius: 6px; + background: var(--panel); color: var(--fg); cursor: pointer; font-size: 0.8em; + transition: all 0.15s; white-space: nowrap; + } + .compare-score-label { + position: relative; + top: -2px; + display: inline-block; + } + .compare-vote-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } + .compare-vote-tie { opacity: 0.7; } + .compare-rematch-btn { + display: flex; align-items: center; gap: 6px; margin-left: 8px; + border-color: color-mix(in srgb, var(--accent, var(--red)) 60%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--fg); opacity: 1; font-weight: 600; + } + .compare-rematch-btn:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent); } + /* Preview button accent in pane header */ + .pane-preview-btn.active { color: var(--red); } + /* Full-pane iframe for HTML preview */ + .compare-pane-iframe { + flex: 1; + width: 100%; + border: none; + border-radius: 0 0 8px 8px; + background: #fff; + } + /* ---- Add-pane "+" button in pane header (last pane only) ---- */ + .pane-add-btn { display: none !important; font-size: 16px; font-weight: 600; } + .compare-pane:last-child .pane-add-btn { display: flex !important; } + /* Dropdown for adding a pane */ + .add-pane-dropdown { + position: absolute; + right: 0; + z-index: 100; + background: var(--panel, var(--bg)); + border: 1px solid var(--border); + border-radius: 10px; + max-height: 300px; + overflow-y: auto; + min-width: 220px; + box-shadow: 0 8px 24px rgba(0,0,0,0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); + padding: 6px; + backdrop-filter: blur(12px); + animation: dropdown-in 0.15s ease-out; + } + .add-pane-search { + width: 100%; + padding: 6px 8px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + color: var(--fg); + font-size: 0.85em; + box-sizing: border-box; + margin-bottom: 4px; + position: sticky; + top: 0; + z-index: 1; + } + /* Compare toggle buttons — icon + label stacked */ + .compare-toggle-label { + display: block; + font-size: 9px; + line-height: 1; + margin-top: 2px; + font-weight: 500; + } + .compare-blind-toggle, + .compare-save-toggle, + .compare-dice-toggle, + .compare-parallel-toggle, + .compare-reset-toggle { + display: flex; flex-direction: column; align-items: center; justify-content: center; + width: 56px; height: auto; flex: 1 1 0; + padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; + cursor: pointer; user-select: none; transition: all 0.15s; + background: none; color: var(--fg); opacity: 0.35; + flex-shrink: 0; gap: 0; + } + .compare-blind-toggle:hover, + .compare-save-toggle:hover, + .compare-dice-toggle:hover, + .compare-parallel-toggle:hover, + .compare-reset-toggle:hover { + opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); + } + .compare-blind-toggle.active { + opacity: 1; color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + } + .compare-blind-toggle.active > svg { + color: var(--accent, var(--red)); + } + .compare-save-toggle.active { + opacity: 1; color: var(--color-save-green); border-color: var(--color-save-green); + background: color-mix(in srgb, var(--red) 10%, transparent); + } + .compare-dice-toggle.active { + opacity: 1; color: var(--red); border-color: var(--red); + background: color-mix(in srgb, var(--red) 10%, transparent); + } + .compare-parallel-toggle { + opacity: 1; color: #e0a050; border-color: #e0a050; + background: rgba(224, 160, 80, 0.1); + } + .compare-parallel-toggle.active { + color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + } + @media (max-width: 520px) { + .compare-toggle-label { display: none; } + /* Tab text labels are hidden on mobile, so spell out the active type next + to "Type:" with its icon. */ + .compare-type-current { + display: inline-flex; + align-items: center; + gap: 4px; + margin-left: 5px; + font-weight: 600; + vertical-align: -3px; + } + .compare-type-current svg { width: 14px; height: 14px; } + /* Mode list — comma-separated, each name already coloured inline. */ + .compare-mode-current { + display: inline; + margin-left: 5px; + font-weight: 600; + } + .compare-blind-toggle, + .compare-save-toggle, + .compare-dice-toggle, + .compare-parallel-toggle, + .compare-reset-toggle { + width: 32px; height: 32px; min-width: 32px; padding: 0; + } + /* The Group tab's seq/parallel toggle is a single full-width button (not + a row of compact icons like the Compare header), so keep its label and + make it a comfortable touch target with the text beside the icon. */ + #group-mode-btn { + flex-direction: row !important; + width: auto !important; + height: auto !important; + min-height: 44px; + padding: 8px 14px !important; + gap: 8px !important; + font-size: 13px; + } + #group-mode-btn .compare-toggle-label { + display: inline !important; + font-size: 13px; + margin-top: 0; + } + #group-mode-btn svg { width: 18px; height: 18px; } + /* Compare header: hide labels + close button, show icons only */ + #compare-shuffle-btn span { + display: none; + } + #compare-shuffle-btn, + #compare-check-btn, + #compare-add-btn { + padding: 3px 6px; + } + /* Save space so the header buttons fit on one row: tighter padding + + smaller labels (icons kept full size). (Score now lives in the vote bar.) */ + .compare-header-bar button { padding: 3px 5px !important; } + .compare-header-bar #compare-check-btn > span, + .compare-header-bar #compare-add-btn > span { + font-size: 10px; margin-left: 2px; + } + /* Compare mobile: keep the close X visible — it's the only way out + now that the hamburger is hidden during compare mode. */ + .compare-header-bar { + padding: 14px 8px 10px 8px !important; + min-height: 44px; + } + .compare-pane .pane-header { + align-items: stretch; + row-gap: 3px; + } + .compare-pane .pane-title-btn { + flex: 1 1 auto; + width: auto; + padding-right: 2px; + } + /* Compare is already visibly in chat mode; the Chat speech-bubble + badge adds a redundant second row on narrow screens. */ + .compare-pane .pane-mode-chat { display: none; } + .compare-pane .pane-timer, + .compare-pane .pane-summary, + .compare-pane .pane-finish-badge, + .compare-pane .pane-mode-badge, + .compare-pane .pane-actions { + order: 1; + align-self: center; + } + .compare-pane .pane-finish-badge { top: 0; } + .compare-pane .pane-summary { + max-width: 110px; + } + .compare-pane .pane-mode-detail { + display: none; + } + .compare-grid[data-cols="4"] .compare-pane .pane-timer, + .compare-grid[data-cols="5"] .compare-pane .pane-timer, + .compare-grid[data-cols="6"] .compare-pane .pane-timer { + width: auto; + order: 1; + margin-top: 0; + padding-bottom: 0; + padding-left: 0; + } + .compare-pane .pane-actions { + margin-left: auto; + } + /* Mode tabs: icons only, centered */ + .compare-mode-tab span { display: none; } + .compare-mode-tabs { justify-content: center; } + /* Header action buttons: hide text labels on Export / Shuffle / + Model on mobile so the close X fits on the right. Score keeps + its "Score" label because its icon (4-square grid) reads too + similar to Shuffle's icon without text. */ + .compare-header-bar #compare-export-btn > span, + .compare-header-bar #compare-shuffle-btn > span { + display: none; + } + /* Override the desktop override: on mobile the model picker overlay + MUST cap its height to the viewport so the dropdown doesn't run + past the bottom of the screen. */ + #compare-model-overlay .modal-content { + max-height: 85dvh !important; + max-height: 85vh !important; + overflow: hidden !important; + } + #compare-model-overlay .modal-body { + overflow: auto !important; + flex: 1 1 auto !important; + min-height: 0 !important; + } + } + /* Hide number input spinners */ + input[type="number"]::-webkit-inner-spin-button, + input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; margin: 0; + } + + /* ── Sensitive info censor ── */ + .censored-item { + filter: blur(5px); + cursor: pointer; + transition: filter 0.2s; + border-radius: 2px; + padding: 0 2px; + background: rgba(255,100,100,0.08); + user-select: none; + } + .censored-item:hover { filter: blur(3px); background: rgba(255,100,100,0.15); } + .censored-item.revealed { + filter: none; + background: rgba(100,255,100,0.08); + user-select: auto; + cursor: text; + } + + #settings-menu-list .list-item.active { + background: color-mix(in srgb, var(--accent) 10%, transparent); + border-color: var(--accent); + } + + /* ── Print / PDF Export ── */ + @media print { + body { background: #fff !important; color: #000 !important; } + #sidebar, .sidebar, #icon-rail, .hamburger-btn, #sidebar-backdrop, .chat-input-bar, .input-bar-wrapper, + #welcome-screen, .chat-top-bar, .chat-meta-overlay, .msg-footer, + .modal, .toast, .overflow-wrapper, .mode-toggle, .incognito-btn, + button, .dropdown, .session-dropdown, + .agent-tool-spinner, .agent-thread-node.running { display: none !important; } + main.chat-container { width: 100% !important; margin: 0 !important; padding: 0 !important; max-height: none !important; overflow: visible !important; } + #chat-history { max-height: none !important; overflow: visible !important; height: auto !important; padding: 0 !important; } + .msg { break-inside: avoid; page-break-inside: avoid; border: none !important; box-shadow: none !important; } + .msg-ai { background: #f5f5f5 !important; color: #000 !important; } + .msg-user { background: #e8e8e8 !important; color: #000 !important; } + .msg .role { color: #333 !important; font-weight: bold; } + .msg .body { color: #000 !important; } + pre, code { background: #f0f0f0 !important; color: #000 !important; border: 1px solid #ccc !important; } + details { display: block !important; } + details[open] summary ~ * { display: block !important; } + details > summary { list-style: none; } + details > summary::before { content: "" !important; } + #chat-history::before { content: attr(data-print-title); display: block; font-size: 1.3em; font-weight: bold; margin-bottom: 1em; color: #000; } + a { color: #000 !important; text-decoration: underline; } + } + + +/* ── Components (from style.css) ── */ + +/* Self-hosted Fira Code font */ +@font-face { font-family: 'Fira Code'; font-weight: 300; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Light.woff2') format('woff2'); } +@font-face { font-family: 'Fira Code'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Regular.woff2') format('woff2'); } +@font-face { font-family: 'Fira Code'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-SemiBold.woff2') format('woff2'); } + +/* Scrollbar styling */ + +/* Code block styling */ +pre, code, .hljs { + font-size: 11px; + line-height: 1.5; +} + +/* WebKit (Chrome, Edge, Safari) */ +/* Utility class for red text */ +.red-text { + color: var(--red); +} + +/* Internal chat links (search results, session references) */ +a.chat-link { + color: var(--hl-function); + text-decoration: none; + border-bottom: 1px dotted var(--hl-function); + cursor: pointer; +} +a.chat-link:hover { + opacity: 0.8; + border-bottom-style: solid; +} + +a.chat-link[href^="#email-"] { + display: inline; + padding: 1px 5px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, transparent); + border-radius: 5px; + background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); + color: var(--fg); + font-weight: 600; + border-bottom-style: solid; +} +a.chat-link[href^="#email-"]:hover { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); +} + +a.chat-link[href^="#notes-more-"], +a.chat-link[href^="#skills-more-"], +a.chat-link[href^="#memories-more-"], +a.chat-link[href^="#events-more-"], +a.chat-link[href^="#sessions-more-"] { + color: var(--muted); + border-bottom: 1px dotted color-mix(in srgb, var(--muted) 55%, transparent); + font-size: 11px; + font-weight: 500; +} +a.chat-link[href^="#notes-more-"]:hover, +a.chat-link[href^="#skills-more-"]:hover, +a.chat-link[href^="#memories-more-"]:hover, +a.chat-link[href^="#events-more-"]:hover, +a.chat-link[href^="#sessions-more-"]:hover { + color: var(--fg); + opacity: 1; + border-bottom-style: solid; +} +.more-list-expanded { + display: block; + max-height: 0; + overflow: hidden; + opacity: 0; + color: inherit; + font: inherit; + line-height: inherit; + transition: max-height 220ms ease, opacity 160ms ease; +} +.more-list-expanded.is-open { + opacity: 1; +} diff --git a/static/css/03-agent-chat.css b/static/css/03-agent-chat.css new file mode 100644 index 000000000..ed4f7e58b --- /dev/null +++ b/static/css/03-agent-chat.css @@ -0,0 +1,2846 @@ +/* 03-agent-chat - 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. + */ +.more-list-expanded-content { + color: inherit; + font: inherit; + line-height: inherit; +} + +/* Session items */ +.session-item { position: relative; } +.text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.session-menu-btn { padding: 0 2px !important; min-width: 20px; height: 20px; display: inline-flex !important; align-items: center; justify-content: center; background: none !important; border-color: transparent !important; } +.session-menu-btn:hover { background: none !important; border-color: transparent !important; } +@media (max-width: 768px) { + .session-menu-btn { display: none !important; } + .item-drag-handle { display: none !important; } +} +.session-menu-btn svg { transition: transform 0.2s ease; } + +/* First-time swipe hint */ +.swipe-hint { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + font-size: 0.7rem; + color: var(--color-error, #f44); + opacity: 0.8; + transition: opacity 0.5s ease; + pointer-events: none; + display: flex; + align-items: center; + gap: 4px; + z-index: 2; +} +.swipe-hint-arrow { + animation: swipe-nudge 1s ease-in-out infinite; +} +@keyframes swipe-nudge { + 0%, 100% { transform: translateX(0); } + 50% { transform: translateX(-6px); } +} + +/* Utility classes */ +.muted { opacity: 0.5; } +.muted-sm { opacity: 0.35; font-size: 0.8em; } +.accent-link { color: var(--accent-primary, var(--color-accent)); cursor: pointer; font-size: 0.85em; } +.models-empty-state { text-align: center; padding: 12px 8px; line-height: 1.6; } + +/* Provider logo inside favorite dot */ +.provider-logo { + border: none !important; + background: none !important; + width: 14px !important; height: 14px !important; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.15s; +} +.provider-logo svg { width: 14px; height: 14px; display: block; } +.provider-logo:hover { opacity: 1 !important; transform: scale(1.2); } + +/* Hide session menu button until hover — use width:0 so it doesn't steal space from text */ +.list-item .hamburger { opacity: 0; width: 0; min-width: 0; overflow: hidden; padding: 0 !important; transition: opacity 0.15s, width 0.15s, padding 0.15s; flex-shrink: 0; display: flex; align-items: center; justify-content: center; } +.list-item:hover .hamburger { opacity: 1; width: 24px; min-width: 24px; padding: 0 4px !important; } +@media (max-width: 768px) { + .list-item .hamburger { opacity: 0.5; width: 28px; min-width: 28px; padding: 0 4px !important; } + .list-item .hamburger:active { opacity: 1; } +} + +/* Hamburger menu button styling (overrides default button appearance) */ +button.hamburger { + background: none; + border: none; + padding: 0; + cursor: pointer; +} + +/* ============================================ */ +/* HEADER SIZING FOR CHAT MESSAGES */ +/* ============================================ */ +/* Markdown in chat messages — colorful, scannable */ +.msg h1, .msg h2, .msg h3, .msg h4, .msg h5, .msg h6 { + margin: 0.6em 0 0.3em 0; + line-height: 1.3; + border-bottom: none; + padding-bottom: 0; +} + +.msg h1 { + font-size: 1.15em; + font-weight: 700; + color: var(--hl-keyword, #c678dd); +} + +.msg h2 { + font-size: 1.1em; + font-weight: 600; + color: var(--hl-function, #5b8def); +} + +.msg h3 { + font-size: 1.05em; + font-weight: 600; + color: var(--hl-string, #98c379); +} + +.msg h4 { + font-size: 1.02em; + font-weight: 600; + color: var(--hl-builtin, #e5c07b); +} + +.msg h5 { + font-size: 1em; + font-weight: 600; + color: var(--hl-variable, #61afef); +} + +.msg h6 { + font-size: 0.95em; + font-weight: 600; + color: var(--hl-number, #d19a66); +} + +/* Bold text — subtle accent color */ +.msg strong, .msg b { + color: var(--hl-builtin, #e5c07b); + font-weight: 600; +} + +/* Italic — softer highlight */ +.msg em, .msg i { + color: var(--hl-params, #abb2bf); + font-style: italic; +} + +/* Bold + italic */ +.msg strong em, .msg em strong, +.msg b i, .msg i b, +.msg b em, .msg em b, +.msg strong i, .msg i strong { + color: var(--hl-keyword, #c678dd); +} + +/* Strikethrough */ +.msg del { + color: color-mix(in srgb, var(--fg) 45%, transparent); + text-decoration: line-through; +} + +/* Inline code */ +.msg code:not(pre code) { + color: var(--hl-string, #98c379); + background: color-mix(in srgb, var(--fg) 6%, transparent); + padding: 1px 5px; + border-radius: 4px; + font-size: 0.9em; +} + +/* Blockquotes */ +.msg blockquote { + border-left: 3px solid var(--hl-function, #5b8def); + padding: 4px 12px; + margin: 0.5em 0; + color: color-mix(in srgb, var(--fg) 75%, transparent); + background: var(--bg); + border-radius: 0 6px 6px 0; +} +.msg blockquote p { margin: 0.3em 0; } + +/* Horizontal rules */ +.msg hr { + border: none; + height: 1px; + background: linear-gradient(90deg, transparent, var(--border), transparent); + margin: 0.8em 0; +} + +/* Lists */ +.msg ul, .msg ol { + margin: 0.3em 0 0.3em 1.2em; + padding: 0; +} +.msg li { + margin: 0.15em 0; +} +.msg li::marker { + color: var(--hl-function, #5b8def); +} + +/* Links */ +.msg a { + color: var(--hl-function, #5b8def); + text-decoration: none; + border-bottom: 1px solid rgba(91, 141, 239, 0.3); + transition: border-color 0.15s; +} +.msg a:hover { + border-bottom-color: var(--hl-function, #5b8def); +} +.msg a.is-loading { + opacity: 0.65; + border-bottom-color: var(--accent, #bd93f9); + cursor: progress; +} + +/* Tables */ +.msg table { + border-collapse: collapse; + margin: 0.5em 0; + font-size: 0.9em; + display: block; + width: max-content; + min-width: 100%; + max-width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + table-layout: auto; +} +.msg th { + background: color-mix(in srgb, var(--fg) 7%, transparent); + color: var(--hl-keyword, #c678dd); + font-weight: 600; + padding: 6px 12px; + border: 1px solid var(--border); + text-align: left; + min-width: 9ch; + word-break: normal; + overflow-wrap: break-word; +} +.msg td { + padding: 5px 12px; + border: 1px solid var(--border); + min-width: 9ch; + word-break: normal; + overflow-wrap: break-word; +} + +/* Agent UI Styling */ +.agent-controls { + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border: 1px solid #dee2e6; + border-radius: 8px; + padding: 12px; + margin-bottom: 12px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.agent-toggle label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-weight: 500; + margin-bottom: 8px; +} + +#workflow-selector { + margin-top: 8px; +} + +#workflow-type { + width: 100%; + padding: 6px 12px; + border: 1px solid #ced4da; + border-radius: 4px; + background: white; + font-size: 14px; +} + +.agent-progress { + background: #ffebee; + border: 1px solid #ef9a9a; + border-radius: 6px; + padding: 12px; + margin: 8px 0; + text-align: center; +} + +.agent-working { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-style: italic; + color: var(--red); +} + +.loading-dots::after { + content: '...'; + animation: dots 1.5s infinite; +} + +@keyframes dots { + 0%, 20% { opacity: 0; } + 40% { opacity: 0.5; } + 60%, 100% { opacity: 1; } +} + +.workflow-info { + background: #f8f9fa; + border: 1px solid var(--red); + border-radius: 6px; + padding: 8px 12px; + margin: 4px 0; + font-size: 0.9em; + color: var(--red); + text-align: center; +} + +/* Scrollbar uses --red from :root (set at top of file) */ + +/* Loading spinner */ +@keyframes spin { + to { transform: rotate(360deg); } +} +.spinner { + width: 24px; + height: 24px; + margin: 8px auto; + border: 3px solid var(--border); + border-top-color: var(--red); + border-radius: 50%; + animation: spin 0.9s linear infinite; +} + +/* Inline spinner for buttons */ +.btn-spinner { + display: inline-block; + width: 12px; + height: 12px; + border: 2px solid transparent; + border-top-color: currentColor; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-right: 6px; +} + +/* Loading indicator for messages */ +.loading-indicator { + display: flex; + align-items: center; + justify-content: center; + padding: 10px; +} +.loading-dots { + display: flex; + gap: 4px; +} +.loading-dot { + width: 6px; + height: 6px; + background-color: var(--fg); + border-radius: 50%; + opacity: 0.6; +} +.loading-dot:nth-child(1) { + animation: loading-bounce 1.4s infinite ease-in-out both; +} +.loading-dot:nth-child(2) { + animation: loading-bounce 1.4s infinite ease-in-out both; + animation-delay: -0.32s; +} +.loading-dot:nth-child(3) { + animation: loading-bounce 1.4s infinite ease-in-out both; + animation-delay: -0.64s; +} +@keyframes loading-bounce { + 0%, 80%, 100% { + transform: scale(0); + } + 40% { + transform: scale(1); + } +} + +/* Hamburger menu button */ +.hamburger { + display: inline-flex; + flex-direction: column; + justify-content: space-between; + width: 24px; + height: 18px !important; + background: none; + border: none; + padding: 0; + cursor: pointer; +} +.hamburger span { + display: block; + width: 100%; + height: 3px; + background: var(--fg); + border-radius: 2px; +} + +/* Agent indicator */ +#agent-indicator { + position: fixed; + top: 20px; + right: 20px; + background: var(--sidebar-bg, var(--panel)); + color: var(--fg); + border: 1px solid var(--border); + padding: 6px 12px; + border-radius: 6px; + font-size: 12px; + display: none; + z-index: 100; + cursor: pointer; + transition: all 0.2s ease; +} +#agent-indicator.active { + display: block; + border-color: var(--color-agent-active); + box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); +} +#agent-indicator:hover { + border-color: var(--color-agent-active); + background: var(--panel); +} + +/* Preset buttons */ +.preset-btn { + height: 27.2px; + padding: 0 8.5px; + margin-left: 4px; + border: 1px solid var(--red); + border-radius: 4px; + background: var(--sidebar-bg, var(--panel)); + color: var(--fg); + font-family: inherit; + font-size: 10.2px; + cursor: pointer; + transition: all 0.2s ease; +} + +.preset-btn:hover { + background: var(--panel); + border-color: var(--fg); +} + +.preset-btn.active { + background: var(--panel); + border-color: var(--red); + box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); + font-weight: 600; +} + +/* Custom preset modal — inherits from .modal base class */ + +/* Unified chat input area */ +.chat-input-area { + display: flex; + flex-direction: column; + gap: 8px; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + margin-top: 12px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.chat-controls-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; +} + +.chat-controls-left { + display: flex; + align-items: center; + gap: 8px; +} + +.chat-controls-right { + display: flex; + align-items: center; + gap: 8px; +} + +.control-group { + display: flex; + align-items: center; + gap: 4px; +} + +.control-label { + font-size: 11px; + color: var(--fg); + opacity: 0.8; +} + +.toggle-switch { + position: relative; + display: inline-block; + width: 30px; + height: 16px; +} + +.toggle-switch input { + opacity: 0; + width: 0; + height: 0; +} + +.toggle-slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: color-mix(in srgb, var(--fg) 15%, transparent); + border-radius: 8px; + transition: background 0.08s; +} + +.toggle-slider:before { + position: absolute; + content: ""; + height: 12px; + width: 12px; + left: 4px; + top: 2px; + background-color: var(--panel); + border-radius: 50%; + transition: transform 0.08s; + box-shadow: 0 1px 2px rgba(0,0,0,0.25); +} + +.toggle-switch input:checked + .toggle-slider { + background-color: var(--toggle-active, var(--red)); +} + +.toggle-switch input:checked + .toggle-slider:before { + transform: translateX(14px); +} + +.preset-buttons-row { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.chat-input-form { + display: flex; + gap: 8px; + align-items: flex-end; +} + +#message { + flex: 1; + min-height: 34px; + max-height: 120px; + resize: none; + font-size: 13px !important; + overflow-y: auto !important; + line-height: 1.4 !important; + font-family: inherit !important; +} + +.action-button { + width: 34px; + height: 34px; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + margin: 0; + background: none; + border: 1px solid var(--border); + border-radius: 4px; + cursor: pointer; + color: var(--fg); + transition: all 0.2s ease; +} + +.action-button:hover { + background: color-mix(in srgb, var(--fg) 6%, transparent); + border-color: var(--fg); +} + +.action-button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.action-button.recording { + background: var(--color-recording); + border-color: var(--color-recording); + animation: pulse 1.5s infinite; +} + +@keyframes pulse { + 0% { opacity: 1; } + 50% { opacity: 0.7; } + 100% { opacity: 1; } +} + +#stop-icon { + display: none; + width: 14px; + height: 14px; + background: var(--color-recording); + border-radius: 2px; +} + +/* Attachment strip — centered + max-width to match the chat-input-bar below, + otherwise the chip floats flush-left while the input is centered (visible on + desktop where the chat area is wider than 800px). */ +.attach-strip { + display: flex; + gap: 6px; + flex-wrap: wrap; + padding: 2px 8px; + max-width: 800px; + width: 100%; + margin-left: auto; + margin-right: auto; + box-sizing: border-box; +} +.attach-strip:empty { display: none; } + +/* Upload-in-progress feedback: the message bubble shows immediately, so while + the files are still uploading we put a whirlpool ON each attachment chip and + dim the chip's content — making it obvious that file is being sent, not stuck. */ +.attach-strip.attach-uploading .thumb { + position: relative; + pointer-events: none; +} +.attach-strip.attach-uploading .thumb > :not(.thumb-upload-spinner) { + opacity: 0.4; +} +.thumb-upload-spinner { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + z-index: 3; +} + +.thumb { + border: 1px solid var(--border); + background: color-mix(in srgb, var(--fg) 11%, transparent); + padding: 3px 6px; + font-size: 12px; + display: flex; + gap: 6px; + align-items: center; + border-radius: 4px; + transition: all 0.2s ease; + max-width: 180px; +} +.thumb-img { + max-width: 60px; + max-height: 40px; + border-radius: 3px; + object-fit: cover; +} +.attach-image-preview { + margin: 4px 0; +} +.attach-image-preview img { + box-shadow: 0 1px 4px rgba(0,0,0,0.2); + /* Same border as the chat bubbles. */ + border: 1px solid var(--bubble-border, var(--border)); +} +/* Image chips: image fills the chip, X overlays as a corner accent badge. + Same on desktop and mobile — doc/text chips keep the beside-X layout. */ +.thumb.thumb-image { + position: relative; + padding: 0; +} +.thumb.thumb-image .thumb-img { + max-height: 56px; + display: block; +} +@media (max-width: 768px) { + .thumb.thumb-image .thumb-img { + cursor: pointer; + touch-action: manipulation; + } +} +.thumb.thumb-image button { + position: absolute; + /* Sit on the top-right corner edge as an accent badge. */ + top: -7px; + right: -7px; + width: 24px; + height: 24px; + min-width: 0; + padding: 0; + border: 2px solid var(--bg); + border-radius: 50%; + background: var(--accent-primary, var(--red)); + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + line-height: 1; + z-index: 3; + transition: transform 0.12s ease, filter 0.12s ease, box-shadow 0.12s ease; +} +.thumb.thumb-image button:hover { + transform: scale(1.12); + filter: brightness(1.12); + box-shadow: 0 2px 8px rgba(0,0,0,0.25); +} +.thumb.thumb-image button:active { + transform: scale(0.96); +} + +.attach-crop-overlay { + position: fixed; + inset: 0; + z-index: var(--attach-crop-z, 100001); + background: rgba(0, 0, 0, 0.72); + display: flex; + align-items: center; + justify-content: center; + padding: 14px; + box-sizing: border-box; +} +.attach-crop-panel { + width: min(94vw, 520px); + max-height: min(86vh, 720px); + background: var(--panel, #111); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 18px 50px rgba(0,0,0,0.45); + overflow: hidden; + display: flex; + flex-direction: column; +} +.attach-crop-stage { + position: relative; + min-height: 280px; + height: min(66vh, 560px); + background: #050505; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + touch-action: none; +} +.attach-crop-img { + max-width: 100%; + max-height: 100%; + object-fit: contain; + user-select: none; + pointer-events: none; +} +.attach-crop-box { + position: absolute; + border: 2px solid var(--accent, var(--red)); + box-shadow: 0 0 0 9999px rgba(0,0,0,0.42); + cursor: move; + touch-action: none; + box-sizing: border-box; +} +.attach-crop-box::before, +.attach-crop-box::after { + content: ''; + position: absolute; + inset: 33.333% 0 auto; + border-top: 1px solid rgba(255,255,255,0.38); +} +.attach-crop-box::after { + inset: 66.666% 0 auto; +} +.attach-crop-handle { + position: absolute; + right: -9px; + bottom: -9px; + width: 20px; + height: 20px; + border-radius: 50%; + background: var(--accent, var(--red)); + border: 2px solid var(--bg); + box-sizing: border-box; + cursor: nwse-resize; +} +.attach-crop-actions { + display: flex; + gap: 8px; + padding: 10px; + background: color-mix(in srgb, var(--bg) 78%, transparent); +} +.attach-crop-btn { + flex: 1; + height: 34px; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); + color: var(--fg); + font: inherit; + font-size: 12px; +} +.attach-crop-primary { + background: var(--accent, var(--red)); + color: #fff; + border-color: var(--accent, var(--red)); +} +@media (max-width: 768px) { + /* Collapsed "N files" badge: use the same corner-X accent badge as image thumbs. */ + .thumb-collapsed { position: relative; } + .thumb-collapsed .thumb-collapsed-x { + position: absolute; + top: -7px; + right: -7px; + width: 24px; + height: 24px; + min-width: 0; + padding: 0; + border: 2px solid var(--bg); + border-radius: 50%; + background: var(--accent-primary, var(--red)); + color: #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + line-height: 1; + z-index: 3; + opacity: 1; + } + /* Bigger remove-X tap target for non-image (doc/text) chips on mobile too. */ + .thumb button { + height: 28px; + min-width: 28px; + font-size: 15px; + } +} +.thumb span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.thumb:hover { + background: color-mix(in srgb, var(--fg) 16%, transparent); + border-color: var(--fg); +} + +.thumb button { + height: 24px; + padding: 0 5px; + font-size: 0.95em; + border-radius: 4px; + color: var(--accent-primary, var(--red)); +} + +.thumb-collapsed { + cursor: pointer; + color: var(--red); + border-color: var(--red); + background: color-mix(in srgb, var(--red) 10%, transparent); + font-weight: 600; + gap: 8px; + border-radius: 999px; /* pill — rounder than the square file chips */ + padding-left: 12px; +} +.thumb-collapsed:hover { + background: color-mix(in srgb, var(--red) 20%, transparent); +} +.thumb-collapsed-label { white-space: nowrap; } +.thumb-collapsed-x { + height: 24px; padding: 0 7px; font-size: 13px; border-radius: 4px; + color: var(--accent-primary, var(--red)); + background: none; border: none; cursor: pointer; opacity: 0.6; +} +.thumb-collapsed-x:hover { opacity: 1; } + +/* Recording indicator */ +#recording-indicator { + position: fixed; + top: 10px; + left: 0; + right: 0; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(10px); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + margin: 10px; + z-index: 1000; + display: flex; + align-items: center; + justify-content: space-between; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +#recording-indicator.hidden { + display: none !important; +} + +.recording-content { + display: flex; + align-items: center; + gap: 12px; + color: white; +} + +.recording-icon { + color: var(--color-recording); + font-size: 20px; + animation: pulse 1.5s infinite; +} + +.recording-text { + font-size: 16px; + font-weight: 500; +} + +.stop-recording-btn { + background: var(--color-recording); + color: white; + border: none; + border-radius: 6px; + padding: 6px 12px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background 0.2s ease; +} + +.stop-recording-btn:hover { + background: var(--color-recording-hover); +} + +#recording-indicator.error { + background: rgba(173, 26, 26, 0.9); +} + +.recording-error { + color: var(--color-recording); + font-size: 14px; + margin-top: 4px; +} + +/* Mermaid diagram containers */ +.mermaid-container { + margin: 12px 0; + padding: 16px; + background: color-mix(in srgb, var(--bg) 95%, var(--fg)); + border: 1px solid var(--border); + border-radius: 8px; + overflow-x: auto; + text-align: center; +} +.mermaid-container svg { max-width: 100%; height: auto; } + +/* KaTeX math overrides */ +.katex-display { margin: 0.8em 0; overflow-x: auto; overflow-y: hidden; } +.katex { font-size: 1.1em; } + +/* Hide thinking sections globally via settings toggle */ +body.hide-thinking .thinking-section { display: none !important; } + +/* Widen chat area via settings toggle */ +body.fullwidth-chat .chat-history { + padding-left: 0 !important; + padding-right: 12px !important; +} + +/* Thinking process styles — colors follow theme accent */ +.msg .body .stream-content { + width: 100%; +} +.thinking-section { + margin: 12px 0; + width: 100%; + max-width: 100%; + box-sizing: border-box; + border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--red) 5%, transparent); + overflow: hidden; + transition: all 0.3s ease; +} + +.thinking-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 6px 12px; + cursor: pointer; + user-select: none; + background: color-mix(in srgb, var(--red) 8%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); + transition: background 0.2s ease; +} + +.thinking-header > .token-new { + display: none; +} + +.thinking-header > div:last-child { + flex-shrink: 0; +} + +.thinking-header:hover { + background: color-mix(in srgb, var(--red) 12%, transparent); +} + +.thinking-header-left { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.9em; + color: var(--red); + font-weight: 500; + overflow: hidden; + min-width: 0; +} +.thinking-header-left span { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + transition: opacity 0.2s ease; +} + +.thinking-icon { + font-size: 1.1em; +} + +.thinking-toggle { + font-size: 0.9em; + color: var(--red); + transition: transform 0.3s ease; +} +.thinking-toggle::after { + content: '\25BC'; /* ▼ */ +} + +.thinking-toggle.expanded { + transform: rotate(180deg); +} + +.thinking-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease, padding 0.3s ease; + padding: 0 12px; +} + +.thinking-content.expanded { + max-height: 300px; + overflow-y: auto; + padding: 12px; +} + +.thinking-content-inner { + font-size: 0.85em; + color: var(--fg); + opacity: 0.9; + line-height: 1.5; +} +.live-reply-content { + animation: fadeSlideIn 0.3s ease-out; +} +@keyframes fadeSlideIn { + from { opacity: 0; transform: translateY(4px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Thinking indicator animation */ +.thinking-indicator { + display: flex; + align-items: center; + gap: 4px; + color: var(--red); + font-style: italic; + padding: 8px 0; +} + +.thinking-dots::after { + content: '...'; + animation: thinking-dots 1.5s infinite; + display: inline-block; + width: 20px; + text-align: left; +} + +@keyframes thinking-dots { + 0%, 20% { content: '.'; } + 40% { content: '..'; } + 60%, 100% { content: '...'; } +} + +.thinking-complete { + color: var(--red); + font-size: 0.9em; + padding: 4px 0; + opacity: 0.8; +} + +.research-context-continue-hint { + display: inline-flex; + align-items: center; + gap: 4px; + margin: 5px 2px 0; + color: var(--fg-muted, var(--fg)); + font-size: 10px; + opacity: 0.55; +} +.research-context-continue-hint svg { + flex: 0 0 auto; + opacity: 0.8; +} + +/* ── Sources section — collapsible source citations ── */ +.sources-section { + margin: 8px 0 12px; + border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); + border-radius: 8px; + overflow: hidden; + transition: all 0.3s ease; +} +.sources-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 8px 12px; + cursor: pointer; + background: color-mix(in srgb, var(--red) 8%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); + transition: background 0.2s ease; + user-select: none; +} +.sources-header:hover { + background: color-mix(in srgb, var(--red) 12%, transparent); +} +.sources-header-left { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.85em; + font-weight: 500; + color: var(--red); +} +.sources-header-left svg { + width: 14px; + height: 14px; + flex-shrink: 0; + opacity: 0.7; +} +.sources-toggle { + font-size: 0.8em; + color: var(--red); + opacity: 0.7; + transition: none; +} +.sources-toggle::after { + content: '\25B6'; /* ▶ right arrow */ +} +.sources-toggle[data-arrow="down"]::after { + content: '\25BC'; /* ▼ down arrow */ +} +.sources-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease, padding 0.3s ease; + padding: 0 10px; +} +.sources-content.expanded { + max-height: 3000px; + padding: 8px 10px; +} +.sources-content-inner { + display: flex; + flex-direction: column; + gap: 4px; +} +.source-link { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 8px; + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + text-decoration: none; + color: var(--fg); + transition: background 0.15s ease; +} +.source-link:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.source-num { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + border-radius: 50%; + background: color-mix(in srgb, var(--fg) 15%, transparent); + color: var(--fg); + font-size: 0.7em; + font-weight: 600; + flex-shrink: 0; +} +.source-title { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 0.82em; +} +.source-domain { + font-size: 0.72em; + opacity: 0.45; + flex-shrink: 0; +} + +.ai-spinner { + color: var(--red); +} +/* Nudge the Tidy button 2px left. */ +#memory-tidy-btn { position: relative; left: -2px; } +/* Tidy button's whirlpool nudge — sits 1px lower so it visually centers on + the Tidy label baseline. */ +#memory-tidy-btn .ai-spinner-whirlpool, +#memory-tidy-btn .spinner-whirlpool { + position: relative; + top: 1px; +} +.list-item.stream-complete { + animation: none; +} +.session-run-state { + flex: 0 0 auto; + margin-left: 5px; + font-size: 10px; + line-height: 1; + color: var(--fg-dim, #888); + opacity: 0.9; +} +.session-run-state.is-working { + color: var(--accent, var(--red)); +} +.session-run-state .session-run-whirlpool { + width: 12px !important; + height: 12px !important; + margin: 0 !important; + display: inline-flex; + align-items: center; +} +.session-run-state .session-run-whirlpool .ai-spinner-whirlpool { + display: inline-flex; + width: 12px; + height: 12px; +} +.session-run-state.is-done { + color: var(--color-success, #4caf50); + font-weight: 600; +} +.cookbook-notif-active svg { opacity: 1 !important; } + +/* Rail notification dot — pulsing indicator on icon-rail buttons */ +.icon-rail-btn.rail-notify { + opacity: 1 !important; + position: relative; +} +.icon-rail-btn.rail-notify::before { + content: ''; + position: absolute; + top: 5px; + right: 4px; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent, var(--red)); + animation: rail-notif-pulse 2s ease-in-out infinite; + z-index: 1; +} +.icon-rail-btn.rail-notify.rail-notify-success::before { + background: var(--color-success, #4caf50); +} +@keyframes rail-notif-pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.4; transform: scale(0.8); } +} +@keyframes stream-complete-pulse { + 0%, 100% { box-shadow: none; } + 50% { box-shadow: inset 0 0 0 1.5px var(--accent); } +} + +/* ===== SLASH COMMAND RESPONSES ===== */ +.msg.msg-system { + padding: 6px 12px; margin: 4px auto 4px 8px; + max-width: 85%; background: none; border-left: 2px solid var(--border); +} +.msg.msg-system .body { padding: 0; } +.msg.msg-system pre { margin: 4px 0; white-space: pre-wrap; font-size: 0.85em; } +.dropdown-divider { + height: 1px; + margin: 3px 6px; + background: color-mix(in srgb, var(--border) 60%, transparent); +} +.msg.stream-done-toast { + cursor: pointer; + border-left-color: var(--accent, var(--border)); + border-radius: 6px; + background: color-mix(in srgb, var(--accent) 6%, transparent); + transition: background 0.15s; +} +.msg.stream-done-toast:hover { + background: color-mix(in srgb, var(--accent) 12%, transparent); +} +.msg.stream-done-toast .body { + display: flex; + align-items: center; + gap: 8px; +} +.stream-done-indicator { + font-family: monospace; + font-size: 1.1em; + line-height: 1; + color: var(--accent); + flex-shrink: 0; + animation: bar-pulse 1.2s ease-in-out infinite; +} +@keyframes bar-pulse { + 0%, 100% { opacity: 0.4; } + 50% { opacity: 1; } +} + +/* ===== AGENT MULTI-BUBBLE ===== */ +.msg.msg-tool { + display: none; /* legacy — hidden, replaced by agent-thread */ +} +.msg.msg-continuation { + margin-top: 2px; +} + +/* ===== AGENT THREAD TIMELINE ===== */ +.agent-thread { + position: relative; + margin: 2px 0 2px 28px; + padding: 4px 0 4px 22px; + max-width: calc(85% - 20px); + box-sizing: border-box; +} +.agent-thread::before { + content: ''; + position: absolute; + left: 5px; + top: 14px; + bottom: 14px; + width: 2px; + background: color-mix(in srgb, var(--red) 18%, transparent); + border-radius: 1px; +} +/* Extend line to connect to chat bubble above/below */ +.agent-thread.has-top::before { + top: -6.5px; +} +.agent-thread.has-bottom::before { + bottom: -5px; +} +.agent-thread.has-ask-user-bottom::before { + bottom: -12px; +} +/* Terminating dot at bottom when no bubble below and last node is expanded */ +.agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { + content: ''; + position: absolute; + /* Centre the 6px dot on the rail: the rail sits at left:5px + 2px wide, + so its centre is 6px from the thread's left edge and the dot's left + edge belongs at 3px — i.e. 3px - padding-left. Keep this in step with + .agent-thread-dot and with the mobile padding override below. */ + left: -19px; + bottom: 5px; + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--red); + box-shadow: 0 0 4px 1px color-mix(in srgb, var(--red) 55%, transparent), + 0 0 0 3px color-mix(in srgb, var(--red) 18%, transparent); +} + +/* Synapse pulse — a bright dot traveling down the line */ +.agent-thread::after { + content: ''; + position: absolute; + left: 4px; + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--red); + box-shadow: 0 0 3px 1px color-mix(in srgb, var(--red) 50%, transparent); + pointer-events: none; + top: 0%; + opacity: 0; +} +.agent-thread.streaming::after { + animation: synapse-capped-short 0.8s ease-in-out infinite; +} +.agent-thread.streaming.has-top::after { + animation: synapse-capped 0.8s ease-in-out infinite; +} +.agent-thread.streaming.has-bottom::after { + animation: synapse-travel-short 0.8s ease-in-out infinite; +} +.agent-thread.streaming.has-top.has-bottom::after { + animation: synapse-travel 0.8s ease-in-out infinite; +} +@keyframes synapse-travel { + 0% { top: 0%; opacity: 0; } + 5% { opacity: 0.5; } + 85% { opacity: 0.35; } + 100% { top: 100%; opacity: 0; } +} +@keyframes synapse-capped { + 0% { top: 0%; opacity: 0; } + 5% { opacity: 0.5; } + 70% { opacity: 0.35; top: calc(100% - 20px); } + 100% { opacity: 0; top: calc(100% - 20px); } +} +@keyframes synapse-travel-short { + 0% { top: 14px; opacity: 0; } + 5% { opacity: 0.5; } + 85% { opacity: 0.35; } + 100% { top: 100%; opacity: 0; } +} +@keyframes synapse-capped-short { + 0% { top: 14px; opacity: 0; } + 5% { opacity: 0.5; } + 70% { opacity: 0.35; top: calc(100% - 20px); } + 100% { opacity: 0; top: calc(100% - 20px); } +} +.agent-thread-node { + position: relative; + padding: 5px 0; +} +.agent-thread-node + .agent-thread-node { + margin-top: 2px; +} +/* Supervisor ladder cards — same chrome as tool cards but tinted so the + user can tell at a glance "this is the agent recovering" vs "this is + the agent doing work". Stop rung gets the red accent. */ +.agent-thread-node.supervisor-step .agent-thread-tool { + color: color-mix(in srgb, var(--accent, #c08a3e) 80%, var(--fg)); + font-style: italic; +} +.agent-thread-node.supervisor-step .agent-thread-dot { + background: color-mix(in srgb, var(--accent, #c08a3e) 60%, transparent); +} +.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-tool { + color: var(--red, #d65a5a); +} +.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-dot { + background: color-mix(in srgb, var(--red, #d65a5a) 60%, transparent); +} +.agent-thread-dot { + position: absolute; + left: -20px; + top: 10px; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--red); + border: 2px solid var(--bg); + z-index: 1; +} +.agent-thread-node.running .agent-thread-dot { + background: var(--red); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 25%, transparent); + animation: thread-pulse 1.5s ease-in-out infinite; + top: 10px; +} +@keyframes thread-pulse { + 0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 20%, transparent); } + 50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--red) 10%, transparent); } +} +.agent-thread-node.error .agent-thread-dot { + background: var(--color-error); +} +.agent-thread-header { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-size: 0.85em; + color: color-mix(in srgb, var(--fg) 70%, transparent); + user-select: none; + padding: 2px 0; +} +.agent-thread-header:hover { + color: var(--fg); +} +.agent-thread-icon { + font-size: 0.9em; + color: var(--red); +} +.agent-thread-tool-icon { + width: 14px; + height: 14px; + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 14px; + color: color-mix(in srgb, var(--fg) 64%, transparent); + opacity: 0.85; +} +.agent-thread-tool-icon svg { + display: block; + width: 12px; + height: 12px; +} +.agent-thread-tool-icon-link { + color: inherit; + text-decoration: none; + cursor: pointer; +} +.agent-thread-tool-icon-link:hover { + color: var(--red); + opacity: 1; +} +.agent-thread-node.error .agent-thread-icon { + color: var(--color-error); +} +.agent-thread-tool { + font-weight: 600; + color: var(--red); + text-transform: uppercase; + letter-spacing: 0.3px; + font-size: 0.9em; +} +.agent-thread-header-link { + width: 17px; + height: 17px; + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 17px; + margin-left: -3px; + color: var(--red); + opacity: 0.72; + text-decoration: none; + border: 0; + background: transparent; +} +.agent-thread-header-link:hover { + opacity: 1; + color: var(--red); +} +.agent-thread-header-link svg { + display: block; +} +.agent-thread-header-link-calendar { + transform: translateY(-2px); +} +.msg-ai .body a[href^="#event-"], +.msg-ai .body a[href^="#email-"], +.msg-ai .body a[href^="#document-"], +.msg-ai .body a[href^="#note-"], +.msg-ai .body a[href^="#task-"], +.msg-ai .body a[href^="#skill-"], +.msg-ai .body a[href^="#memory-"], +.msg-ai .body a[href^="#session-"] { + display: inline-flex; + align-items: baseline; + max-width: 100%; + padding: 1px 5px; + border-radius: 5px; + background: color-mix(in srgb, var(--red) 12%, transparent); + color: var(--red); + font-weight: 600; + text-decoration: none; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; +} +.msg-ai .body a[href^="#event-"], +.msg-ai .body a[href^="#note-"], +.msg-ai .body a[href^="#skill-"], +.msg-ai .body a[href^="#memory-"], +.msg-ai .body a[href^="#session-"] { + font-size: 11px; + line-height: 1.35; +} +.msg-ai .body p:has(> .emoji:first-child):has(> a.chat-link[href^="#note-"]) { + margin-top: 0.15em; + margin-bottom: 0.15em; + line-height: 1.35; +} +.msg-ai .body a[href^="#event-"]:hover, +.msg-ai .body a[href^="#email-"]:hover, +.msg-ai .body a[href^="#document-"]:hover, +.msg-ai .body a[href^="#note-"]:hover, +.msg-ai .body a[href^="#task-"]:hover, +.msg-ai .body a[href^="#skill-"]:hover, +.msg-ai .body a[href^="#memory-"]:hover, +.msg-ai .body a[href^="#session-"]:hover { + background: color-mix(in srgb, var(--red) 18%, transparent); + text-decoration: underline; +} +.agent-thread-status { + font-size: 0.85em; + opacity: 0.5; +} +.agent-thread-chevron { + width: 12px; + height: 12px; + flex: 0 0 12px; + display: inline-grid; + place-items: center; + opacity: 0.4; +} +.agent-thread-chevron::before { + content: ""; + width: 0; + height: 0; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 6px solid currentColor; + transform-origin: 50% 50%; + transition: transform 0.2s ease; +} +.agent-thread-node.open .agent-thread-chevron::before { + transform: rotate(90deg); +} +.agent-thread-node.browser-preview-node .agent-thread-chevron { + display: none; +} +.agent-thread-wave { + font-family: monospace; + font-size: 0.85em; + color: var(--red); + letter-spacing: -1px; +} +/* Live "cooking" timer on a running tool — prominent (accent, tabular) so a + long-running command always reads as alive, not frozen. */ +.agent-thread-elapsed { + margin: 0 6px 0 4px; + font-size: 11px; + font-weight: 600; + font-variant-numeric: tabular-nums; + color: var(--accent, var(--red)); +} +/* Stall watchdog banner — shown when the stream has been silent for ~1min. */ +.stall-banner { + display: flex; + align-items: center; + gap: 8px; + margin: 8px auto; + padding: 8px 12px; + max-width: 90%; + font-size: 12px; + border-radius: 8px; + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, var(--bg)); + border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 40%, transparent); +} +.stall-banner-txt { flex: 1; opacity: 0.85; } +.stall-banner-btn { + font-size: 11px; + font-weight: 600; + padding: 4px 10px; + border-radius: 6px; + border: none; + background: var(--accent, var(--red)); + color: #fff; + cursor: pointer; + flex-shrink: 0; +} +.stall-banner-stop { + background: none; + color: var(--fg); + border: 1px solid var(--border); +} +.agent-thread-content { + display: none; + padding: 4px 0 2px 0; + overflow-x: auto; + overflow-y: hidden; +} +.agent-thread-node.open .agent-thread-content { + display: block; +} +.agent-thread-cmd { + background: color-mix(in srgb, var(--fg) 5%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 12px; + margin: 4px 0; + color: var(--fg); + font-size: 0.85em; + white-space: pre-wrap; + word-break: break-word; + line-height: 1.4; + overflow-x: auto; +} +.agent-thread-summary { + display: inline-flex; + align-items: center; + gap: 6px; + max-width: 100%; + margin: 4px 0; + padding: 4px 8px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 26%, var(--border)); + border-radius: 6px; + background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); + color: color-mix(in srgb, var(--fg) 84%, var(--accent, var(--red))); + font-size: 12px; + font-variant-numeric: tabular-nums; + line-height: 1.35; +} +.agent-thread-summary-link { + cursor: pointer; + text-decoration: none; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.agent-thread-summary-link:hover { + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} + +.agent-image-progress { + margin: 6px 0 0; +} +.agent-image-progress-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + font-size: 11px; + line-height: 1.2; + color: color-mix(in srgb, var(--fg) 72%, transparent); +} +.agent-image-progress-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.agent-image-progress-value { + flex-shrink: 0; + font-variant-numeric: tabular-nums; + color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); +} +.agent-image-progress-track { + display: none !important; +} + +.private-browser-preview { + width: 100%; + max-height: min(300px, 36vh); + margin: 6px 0 2px; + display: flex; + flex-direction: column; + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--red) 5%, transparent); + color: var(--fg); + box-shadow: none; +} +.private-browser-preview-header { + height: 32px; + min-height: 32px; + display: flex; + align-items: center; + gap: 8px; + padding: 0 6px 0 10px; + border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); + background: color-mix(in srgb, var(--red) 10%, transparent); + font-size: 0.85em; + font-weight: 500; + cursor: pointer; +} +.private-browser-preview-title { + flex-shrink: 0; + color: var(--red); + font-size: inherit; + font-weight: inherit; + line-height: 1; + letter-spacing: 0; +} +.private-browser-preview-status { + min-width: 0; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-size: inherit; + line-height: 1; +} +.private-browser-preview-fold { + width: 18px; + height: 18px; + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + left: 2px; + flex-shrink: 0; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + color: var(--red); + cursor: pointer; + font-size: 0.95em; + font-family: inherit; + font-weight: 500; + line-height: 1; + text-shadow: none; + -webkit-text-stroke: 0; +} +.private-browser-preview-fold:hover { + background: transparent; + color: var(--red); +} +.private-browser-preview-frame { + position: relative; + min-height: 130px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + background: color-mix(in srgb, #000 72%, var(--bg)); + box-shadow: + inset 0 1px 0 color-mix(in srgb, #fff 8%, transparent), + inset 0 0 0 1px color-mix(in srgb, #000 35%, transparent), + 0 8px 18px color-mix(in srgb, #000 18%, transparent); +} +.private-browser-preview[data-browser-url] .private-browser-preview-frame { + cursor: zoom-in; +} +.private-browser-preview[data-browser-url] .private-browser-preview-frame::after { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + opacity: 0.55; + transition: opacity 150ms ease; + background: + radial-gradient(circle at center, transparent 42%, color-mix(in srgb, #000 30%, transparent) 100%), + color-mix(in srgb, var(--bg) 10%, transparent); +} +.private-browser-preview[data-browser-url] .private-browser-preview-frame:hover::after { + opacity: 0.9; +} +.private-browser-preview.folded .private-browser-preview-frame { + display: none; +} +.private-browser-preview.folded .private-browser-preview-header { + border-bottom: 0; +} +.private-browser-preview-frame img { + width: 100%; + height: auto; + max-height: calc(min(300px, 36vh) - 32px); + display: block; + object-fit: contain; + transform: scale(1.1); + transform-origin: center center; + animation: private-browser-image-in 180ms ease-out both; +} +.private-browser-preview-frame img:not([src]), +.private-browser-preview-frame img[src=""] { + display: none; +} +.private-browser-preview-empty { + padding: 22px 14px; + width: auto; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; + color: color-mix(in srgb, var(--fg) 48%, transparent); + font-size: 11px; + line-height: 1.35; +} +.private-browser-preview-spinner { + width: 13px; + height: 13px; + flex: 0 0 13px; + display: inline-flex; + align-items: center; + justify-content: center; +} +.private-browser-preview-spinner .spinner-whirlpool, +.private-browser-preview-spinner .ai-spinner-whirlpool { + margin: 0 !important; +} +@keyframes private-browser-image-in { + from { opacity: 0; transform: scale(1.08); } + to { opacity: 1; transform: scale(1.1); } +} + +@media (max-width: 700px) { + .private-browser-preview-frame img { + max-height: calc(36vh - 32px); + } +} + +/* Mobile: constrain thread content */ +@media (max-width: 768px) { + .agent-thread { + margin-left: 16px; + padding-left: 18px; + max-width: calc(90% - 16px); + } + .agent-thread-cmd { + font-size: 0.78em; + padding: 6px 8px; + max-width: 100%; + overflow-x: auto; + } + .agent-thread-content { + max-width: calc(100vw - 90px); + } + .agent-tool-output pre { + font-size: 0.8em; + max-width: 100%; + overflow-x: auto; + } + .agent-thread-header { + font-size: 0.8em; + } + .agent-thread-dot { + left: -16px; + top: 10px; + } + /* padding-left drops to 18px here, so the terminating dot needs its own + offset (3px - 18px) to stay centred on the rail. */ + .agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { + left: -15px; + } +} + +/* ===== AGENT TOOL OUTPUT (inside thread nodes) ===== */ +.agent-tool-output { + margin-top: 8px; + background: color-mix(in srgb, var(--red) 5%, transparent); + border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); + border-radius: 8px; + overflow: hidden; +} +.agent-tool-output summary { + color: var(--red); + background: color-mix(in srgb, var(--red) 10%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); + cursor: pointer; + font-size: 0.85em; + user-select: none; + padding: 6px 10px; + font-weight: 500; + /* Chevron on the right (like the thinking section) instead of the default + left-side disclosure triangle. */ + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + list-style: none; +} +.agent-tool-output summary::-webkit-details-marker { display: none; } +/* File-write diff — neutral chrome (not the red error tint) + colored lines */ +.agent-tool-diff { + background: color-mix(in srgb, var(--fg) 4%, transparent); + border-color: color-mix(in srgb, var(--fg) 18%, transparent); +} +.agent-tool-diff summary { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 7%, transparent); + border-bottom-color: color-mix(in srgb, var(--fg) 12%, transparent); +} +.agent-tool-diff .diff-stat { + font-weight: 600; + opacity: 0.7; + font-family: var(--mono, monospace); +} +/* Collapsed diff summary: filename + +adds/−dels (theme green/red). */ +.agent-tool-diff summary { + display: flex; + align-items: center; + gap: 8px; +} +.agent-tool-diff .diff-file { + font-family: var(--mono, monospace); + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.agent-tool-diff .diff-summary-stats { + margin-left: auto; + font-family: var(--mono, monospace); + font-weight: 600; + flex-shrink: 0; +} +.agent-tool-diff .diff-summary-stats .diff-stat-add { color: var(--green, #2ecc71); } +.agent-tool-diff .diff-summary-stats .diff-stat-del { color: var(--red, #e74c3c); } +.agent-tool-diff .diff-summary-stats .diff-stat-new { color: var(--accent, var(--red)); opacity: 0.85; } +.diff-pre { + margin: 0; + padding: 8px 10px; + overflow-x: auto; + font-family: var(--mono, monospace); + font-size: 0.82em; + line-height: 1.45; +} +.diff-pre span { display: block; white-space: pre; } +.diff-pre .diff-add { background: color-mix(in srgb, #2ecc71 22%, transparent); } +.diff-pre .diff-del { background: color-mix(in srgb, #e74c3c 22%, transparent); } +.diff-pre .diff-hunk { color: var(--accent); opacity: 0.85; } +.diff-pre .diff-meta { opacity: 0.55; } +.diff-pre .diff-ctx { opacity: 0.8; } +/* Suppress the global `summary::before { content: '▶' }` left arrow — this + section uses a right-side chevron instead. */ +.agent-tool-output summary::before { content: none; } +.agent-tool-output summary::after { + content: '\25BC'; /* ▼ */ + color: var(--red); + font-size: 0.9em; + transition: transform 0.3s ease; +} +.agent-tool-output[open] > summary::after { + transform: rotate(180deg); +} +.agent-tool-output summary:hover { + background: color-mix(in srgb, var(--red) 15%, transparent); +} +.agent-thinking-dots .ai-spinner { + font-size: 12px; + letter-spacing: 0.5px; +} + +.agent-tool-output[open] { + background: color-mix(in srgb, var(--red) 6%, transparent); +} +.agent-tool-output[open] > :not(summary) { + animation: detail-reveal 0.25s ease-out both; +} +@keyframes detail-reveal { + from { opacity: 0; transform: translateY(-4px); } + to { opacity: 1; transform: translateY(0); } +} +.agent-tool-output pre { + background: transparent; + border: none; + border-radius: 0; + padding: 10px 14px; + margin-top: 0; + max-height: 300px; + overflow-y: auto; + font-size: 0.95em; + color: var(--fg); + opacity: 0.85; + white-space: pre-wrap; + word-break: break-all; + line-height: 1.5; +} + +/* Mobile responsive styles */ +@media (max-width: 768px) { + #recording-indicator { + margin: 8px; + padding: 10px; + } + + .recording-text { + font-size: 14px; + } + + .stop-recording-btn { + padding: 6px 10px; + font-size: 12px; + } +} +/* ===== PANE STYLES (shared by compare) ===== */ + +.pane-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 0 4px; + border-bottom: 1px solid var(--border); +} +.close-split-btn, +.pane-close-btn { + background: none; + border: none; + color: var(--color-error); + font-size: 18px; + cursor: pointer; + padding: 0 4px; + line-height: 1; + opacity: 0; + transition: opacity 0.15s; +} +.pane-header:hover .close-split-btn, +.pane-header:hover .pane-close-btn { + opacity: 1; +} +.close-split-btn:hover, +.pane-close-btn:hover { + color: var(--color-error-light); +} + +/* Compare pane close controls use the same shared circular close treatment + as modal/title close buttons. */ +.compare-pane .pane-close-btn.close-btn { + width: 24px; + height: 24px; + min-width: 24px; + padding: 0; + border: 1px solid transparent; + border-radius: 999px; + background: transparent; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 0; + opacity: 0.5; + display: inline-flex; + align-items: center; + justify-content: center; + transition: background 0.15s, color 0.15s, opacity 0.15s; +} +.compare-pane .pane-close-btn.close-btn:hover, +.compare-pane .pane-close-btn.close-btn:focus-visible { + background: color-mix(in srgb, var(--fg) 10%, transparent); + border-color: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--fg); + opacity: 1; + outline: none; +} +.compare-pane .pane-action-btn[data-action="expand"] { + width: 24px; + height: 24px; + min-width: 24px; + padding: 0; + border: 1px solid transparent; + border-radius: 999px; + justify-content: center; + transition: background 0.15s, color 0.15s, opacity 0.15s; +} +.compare-pane .pane-action-btn[data-action="expand"]:hover, +.compare-pane .pane-action-btn[data-action="expand"]:focus-visible { + background: color-mix(in srgb, var(--fg) 10%, transparent); + border-color: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--fg); + opacity: 1; + outline: none; +} + + +/* ============================================ */ +/* RESEARCH DETAILS EXPANDABLE SECTION - UPDATED */ +/* ============================================ */ + +/* Style the details element */ +details { + background: color-mix(in srgb, var(--hl-string) 5%, transparent); + border: 1px solid color-mix(in srgb, var(--hl-string) 30%, transparent); + border-radius: 8px; + margin: 12px 0; + padding: 0; + overflow: hidden; + transition: all 0.3s ease; +} + +details[open] { + background: color-mix(in srgb, var(--hl-string) 8%, transparent); +} +details[open] > :not(summary) { + animation: detail-reveal 0.25s ease-out both; +} + +/* Style the summary (clickable header) - NO CURSIVE, NORMAL SIZE */ +summary { + cursor: pointer; + padding: 10px 14px; + background: color-mix(in srgb, var(--hl-string) 10%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--hl-string) 20%, transparent); + font-weight: 500; + font-size: 0.95em; + font-style: normal; + font-family: inherit; + color: var(--hl-string); + user-select: none; + list-style: none; + display: flex; + align-items: center; + gap: 8px; + transition: background 0.2s ease; +} + +summary:hover { + background: color-mix(in srgb, var(--hl-string) 15%, transparent); +} + +/* Add custom arrow */ +summary::before { + content: '▶'; + display: inline-block; + transition: transform 0.3s ease; + font-size: 0.75em; /* Smaller arrow */ +} + +details[open] summary::before { + transform: rotate(90deg); +} + +/* Hide default marker in webkit browsers */ +summary::-webkit-details-marker { + display: none; +} + +/* Style the content inside details - SMALLER, MORE COMPACT */ +details > div, +details > p, +details > ul, +details > ol { + padding: 12px 14px; /* Less padding */ + animation: fadeIn 0.3s ease; + font-size: 0.9em; /* Smaller text */ + line-height: 1.5; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Style research findings inside details - SMALLER HEADINGS */ +details h3 { + margin-top: 12px; + margin-bottom: 6px; + color: var(--hl-string); + font-size: 0.95em; + font-weight: 500; +} + +details h4 { + margin-top: 10px; + margin-bottom: 5px; + color: var(--hl-string); + font-size: 0.9em; + font-weight: 500; +} + +details ul { + margin-left: 18px; + margin-bottom: 10px; +} + +details li { + margin-bottom: 5px; + line-height: 1.4; + font-size: 0.85em; /* Smaller list items */ +} + +details strong { + color: var(--hl-string); + font-weight: 500; +} + +details a { + color: var(--accent); + text-decoration: none; + transition: color 0.2s ease; +} + +details a:hover { + color: var(--color-link-hover); + text-decoration: underline; +} + +/* Research metadata - SMALLER */ +details .research-meta { + font-size: 0.8em; + color: var(--fg); + opacity: 0.8; + margin-top: 4px; +} + +/* Source links - SMALLER */ +details .source-link { + display: inline-block; + margin-top: 3px; + padding: 2px 5px; + background: color-mix(in srgb, var(--accent) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); + border-radius: 4px; + font-size: 0.68em; + color: var(--accent); +} + +details .source-link:hover { + background: color-mix(in srgb, var(--accent) 20%, transparent); + border-color: var(--accent); +} + +/* Research report links - clean and subtle */ +details a { + color: var(--accent); + text-decoration: none; + font-weight: normal; /* Remove bold */ + font-size: 0.85em; + opacity: 0.9; + transition: all 0.2s ease; + word-break: break-all; /* Break long URLs nicely */ +} + +details a:hover { + color: var(--color-link-hover); + text-decoration: underline; + opacity: 1; +} + +/* ============================================ */ +/* CUSTOM SYNTAX HIGHLIGHTING - Theme-Reactive */ +/* ============================================ */ +.hljs { + background: var(--code-bg, var(--hl-bg, var(--panel))); + color: var(--code-fg, var(--hl-fg, var(--fg))); + padding: 8px; + border-radius: 4px; +} + +/* Keywords & control flow — purple/magenta */ +.hljs-keyword, +.hljs-selector-tag { color: var(--hl-keyword); } + +/* Strings & regex — warm yellow/orange */ +.hljs-string, +.hljs-regexp, +.hljs-addition { color: var(--hl-string); } + +/* Comments & docs — muted. font-style intentionally omitted: italics shift + glyph widths in the highlight overlay relative to the transparent textarea + above it, which makes the caret drift away from the visible character. */ +.hljs-comment, +.hljs-quote, +.hljs-meta { color: var(--hl-comment); } + +/* Functions & method names — blue */ +.hljs-function, +.hljs-title, +.hljs-title.function_, +.hljs-section { color: var(--hl-function); } + +/* Numbers & constants — distinct from strings */ +.hljs-number, +.hljs-literal { color: var(--hl-number, var(--hl-string)); } + +/* Built-ins & types — teal/cyan tint */ +.hljs-built_in, +.hljs-type, +.hljs-class, +.hljs-title.class_ { color: var(--hl-builtin, var(--hl-function)); } + +/* Variables & identifiers — fg with slight distinction */ +.hljs-variable, +.hljs-template-variable, +.hljs-attr { color: var(--hl-variable, var(--hl-fg, var(--fg))); } + +/* Operators & punctuation — slightly dimmed fg */ +.hljs-operator, +.hljs-punctuation { color: var(--hl-fg, var(--fg)); opacity: 0.8; } + +/* Parameters */ +.hljs-params { color: var(--hl-params, var(--hl-fg, var(--fg))); } + +/* Property access, attributes in HTML/XML */ +.hljs-property, +.hljs-selector-class, +.hljs-selector-id { color: var(--hl-variable, var(--hl-function)); } + +/* Tags (HTML) */ +.hljs-tag { color: var(--hl-keyword); } +.hljs-name { color: var(--hl-keyword); } + +/* Deletion/diff */ +.hljs-deletion { color: var(--red); } + +/* Symbol, special */ +.hljs-symbol { color: var(--hl-string); } +.hljs-link { color: var(--hl-function); text-decoration: underline; } + +/* Emphasis — applied globally (chat messages, rendered markdown in docs + preview, etc.) but explicitly NEUTRALIZED in the doc editor's overlay so + the textarea-and-overlay caret alignment stays bit-perfect. Bold / italic + shift glyph widths and that drifts the click-to-row mapping. */ +.hljs-emphasis { font-style: italic; } +.hljs-strong { font-weight: bold; } +.doc-editor-highlight .hljs-emphasis, +.doc-editor-highlight .hljs-strong { + font-style: normal !important; + font-weight: inherit !important; +} +/* ===== Markdown highlighting — document editor ===== + IMPORTANT: this is an overlay layered behind a transparent textarea, so + every styled token must occupy the EXACT same width as the corresponding + characters in the textarea. Anything that changes glyph metrics + (font-weight/style, padding, border, letter-spacing) makes the caret drift + away from the rendered glyph underneath. Color / background / text- + decoration are safe — they don't change layout width. */ +.doc-editor-highlight .language-markdown .hljs-section { + color: var(--hl-keyword, #c678dd); +} +.doc-editor-highlight .language-markdown .hljs-strong { + color: var(--hl-number, #d19a66); +} +.doc-editor-highlight .language-markdown .hljs-emphasis { + color: var(--hl-string, #e5c07b); +} +.doc-editor-highlight .language-markdown .hljs-bullet { + color: var(--hl-builtin, #56b6c2); +} +.doc-editor-highlight .language-markdown .hljs-code { + color: var(--hl-builtin, #56b6c2); + background: color-mix(in srgb, var(--hl-builtin, #56b6c2) 10%, transparent); + border-radius: 2px; +} +.doc-editor-highlight .language-markdown .hljs-link { + color: var(--hl-function, #61afef); + text-decoration: underline; +} +.doc-editor-highlight .language-markdown .hljs-quote { + color: var(--hl-comment, #828997); +} +.doc-editor-highlight .language-markdown .hljs-symbol { + color: var(--red); +} +/* Standalone [bracketed text] — scene directions, annotations */ +.doc-editor-highlight .language-markdown .md-bracket { + color: var(--hl-builtin, #56b6c2); + opacity: 0.85; +} +/* Heading # markers — dimmer than the heading text */ +.doc-editor-highlight .language-markdown .md-heading-marker { + color: var(--hl-comment, #828997); + font-weight: 400; +} + +/* ===== CUSTOM PRESET MODAL ===== */ + +.preset-modal-content { + width: min(460px, 90vw); + border-radius: 12px; + overflow: hidden; +} +.preset-modal-body { + display: flex; + flex-direction: column; + overflow-x: hidden; +} + +/* Footer Start/Cancel buttons get a leading icon. Done via ::before + a masked + SVG (not an inline child) because the labels are set with .textContent, + which would wipe a child element on every tab switch. background:currentColor + makes the icon follow the button's text color. */ +#save-custom-preset, +#cancel-custom-preset { + display: inline-flex; + align-items: center; + gap: 6px; +} +#save-custom-preset::before, +#cancel-custom-preset::before { + content: ""; + width: 13px; height: 13px; + flex-shrink: 0; + background-color: currentColor; + -webkit-mask: var(--_btn-ic) center / contain no-repeat; + mask: var(--_btn-ic) center / contain no-repeat; +} +#save-custom-preset::before { + --_btn-ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='5 3 19 12 5 21 5 3'/%3E%3C/svg%3E"); +} +#cancel-custom-preset::before { + --_btn-ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E"); +} + +.preset-tabs { + display: flex; + gap: 0; + border-bottom: 1px solid var(--border); + margin: 0 -10px 12px; + padding: 0 10px; + margin: 0 -16px; + padding: 0 16px; + margin-bottom: 12px; +} + +.preset-tab { + flex: 1; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 8px 10px; + background: none; + border: none; + border-bottom: 2px solid transparent; + color: var(--color-muted); + font-family: inherit; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.15s; +} +.preset-tab-icon { flex-shrink: 0; } +/* On narrow widths the icon + label can crowd 4 tabs — drop the labels to + icon-only so the row stays clean. */ +@media (max-width: 460px) { + .preset-tab span { display: none; } +} + +.preset-tab:hover { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 4%, transparent); +} + +.preset-tab.active { + color: var(--red); + border-bottom-color: var(--red); +} + +.preset-tab-content { + overflow: hidden; +} +.preset-tab-content.hidden { + display: none; +} + +.preset-templates-hint { + font-size: 11px; + color: var(--color-muted); + margin: 0 0 8px; +} + +.prompt-templates-list { + display: flex; + flex-direction: column; + gap: 6px; + max-height: 300px; + overflow-y: auto; +} + +.prompt-template-card { + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + cursor: pointer; + transition: all 0.15s; +} + +.prompt-template-card:hover { + background: color-mix(in srgb, var(--accent) 8%, transparent); + border-color: color-mix(in srgb, var(--accent) 30%, transparent); +} + +.prompt-template-card.selected { + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 10%, transparent); +} + +.prompt-template-name { + font-size: 12px; + font-weight: 600; + color: var(--fg); + margin-bottom: 4px; +} + +.prompt-template-preview { + font-size: 11px; + color: var(--color-muted); + line-height: 1.4; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.preset-slider-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 12px; + margin-bottom: 6px; +} + +.preset-slider-row label { + font-size: 13px; + color: var(--fg); + font-weight: 500; + margin: 0; +} + +.preset-slider-value { + font-size: 12px; + color: var(--fg); + font-weight: 600; + min-width: 40px; + text-align: right; +} + +.preset-range { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 6px; + background: var(--border); + border-radius: 4px; + outline: none; + margin-bottom: 4px; + box-sizing: border-box; + display: block; + padding: 0; + margin-left: 0; + margin-right: 0; +} + +.preset-range::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + border-radius: 50%; + background: var(--red, var(--fg)); + cursor: pointer; + border: 2px solid var(--panel); + box-shadow: 0 1px 4px rgba(0,0,0,0.2); +} + +.preset-range::-moz-range-thumb { + width: 18px; + height: 18px; + border-radius: 50%; + background: var(--red, var(--fg)); + cursor: pointer; + border: 2px solid var(--panel); + box-shadow: 0 1px 4px rgba(0,0,0,0.2); +} + +.preset-range::-webkit-slider-runnable-track { + height: 6px; + border-radius: 4px; +} + +.preset-range::-moz-range-track { + height: 6px; + background: var(--border); + border-radius: 4px; +} + +.preset-temp-hints { + display: flex; + font-size: 10px; + color: var(--color-muted); + margin-top: -4px; + margin-bottom: 10px; + padding: 0 2px; + opacity: 0.7; +} +.preset-temp-hints span { + flex: 1; +} +.preset-temp-hints span:nth-child(2) { + text-align: center; +} +.preset-temp-hints span:last-child { + text-align: right; +} + +.preset-clear-btn { + padding: 7px 14px; + background: none; + border: 1px solid var(--border); + color: var(--color-muted); + border-radius: 6px; + cursor: pointer; + font-size: 12px; + font-weight: 500; + transition: all 0.15s; +} + +.preset-clear-btn:hover { + color: var(--color-error); + border-color: var(--color-error); +} + +.preset-hint-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 15px; + height: 15px; + border-radius: 50%; + border: 1px solid var(--border); + font-size: 10px; + font-weight: 600; + color: var(--color-muted); + cursor: help; + vertical-align: middle; + margin-left: 4px; + transition: all 0.15s; +} +.preset-hint-icon:hover { + color: var(--fg); + border-color: var(--fg); +} + +.preset-section-header { + font-size: 11px; + font-weight: 600; + color: var(--color-muted); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 6px; + padding: 0 2px; +} + +.user-template-card { + position: relative; +} +.user-template-delete { + background: none; + border: none; + color: var(--color-muted); + font-size: 13px; + cursor: pointer; + padding: 0 2px; + line-height: 1; + opacity: 0; + transition: opacity 0.15s, color 0.15s; +} +.user-template-card:hover .user-template-delete { + opacity: 1; +} +.user-template-delete:hover { + color: var(--color-error); +} + +.preset-save-template-btn { + padding: 7px 14px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + border: 1px solid var(--border); + background: none; + color: var(--fg); + cursor: pointer; + transition: all 0.15s; +} +.preset-save-template-btn:hover { + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); +} + +.char-prompt-wrap { + position: relative; +} +.char-prompt-wrap textarea { + padding-bottom: 28px; +} +.char-expand-btn { + position: absolute; + bottom: 14px; + right: 6px; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--color-muted); + font-size: 11px; + padding: 2px 8px; + cursor: pointer; + transition: all 0.15s; + margin: 0; + z-index: 1; +} +.char-expand-btn:hover { + color: var(--red); + border-color: var(--red); +} +.char-expand-btn.expanding { + opacity: 0.5; + pointer-events: none; +} + +/* Memory scope bar (My Memories / Characters) */ +.memory-scope-bar { + display: flex; + gap: 0; + margin: 0 0 8px 0 !important; + padding: 0 !important; + border: 1px solid var(--border); + border-radius: 6px; + overflow: hidden; +} +.memory-scope-btn { + flex: 1; + padding: 8px 12px !important; + margin: 0 !important; + font-size: 11px !important; + font-weight: 600; + background: none; + border: none; + color: var(--fg); + opacity: 0.5; + cursor: pointer; + transition: all 0.15s; +} +.memory-scope-btn + .memory-scope-btn { + border-left: 1px solid var(--border); +} +.memory-scope-btn.active { + background: color-mix(in srgb, var(--red) 12%, transparent); + color: var(--red); + opacity: 1; +} diff --git a/static/css/04-memory.css b/static/css/04-memory.css new file mode 100644 index 000000000..71b2537bd --- /dev/null +++ b/static/css/04-memory.css @@ -0,0 +1,1675 @@ +/* 04-memory - 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-scope-btn:hover:not(.active) { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.memory-char-list { + display: flex; + gap: 4px; + flex-wrap: wrap; + margin-bottom: 8px; +} +.memory-char-chip { + padding: 4px 10px; + font-size: 11px; + font-weight: 500; + border: 1px solid var(--border); + border-radius: 12px; + background: none; + color: var(--fg); + cursor: pointer; + transition: all 0.15s; + margin: 0; +} +.memory-char-chip.active { + background: color-mix(in srgb, var(--red) 12%, transparent); + border-color: var(--red); + color: var(--red); +} +.memory-char-chip:hover:not(.active) { + background: color-mix(in srgb, var(--fg) 6%, transparent); +} + +/* Disabled state dims the form */ +#char-fields-wrap.disabled { + opacity: 0.35; + pointer-events: none; + filter: grayscale(0.5); + transition: opacity 0.2s, filter 0.2s; +} +#char-fields-wrap { + transition: opacity 0.2s, filter 0.2s; +} + +/* Name combo: input + delete btn */ +.char-name-combo { + display: flex; + gap: 4px; + align-items: center; + margin-bottom: 8px; +} +.char-name-combo input, +.char-name-combo select { + flex: 1; +} +.char-template-select { + background: var(--panel); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + padding: 6px 8px; + font-size: 13px; + font-family: inherit; + cursor: pointer; +} +.char-template-select:focus { + outline: none; + border-color: var(--red); +} +.char-action-btn { + background: none; + border: 1px solid var(--border); + border-radius: 6px; + color: var(--color-muted); + font-size: 11px; + padding: 4px 8px; + cursor: pointer; + transition: all 0.15s; + flex-shrink: 0; + margin: 0 !important; + white-space: nowrap; + /* Uniform width so the trailing button column matches between the select + row (+ New) and the name row (Reset) — that keeps the select and the + name input the same width, since both fields flex:1 into the leftover. */ + min-width: 64px; + text-align: center; +} +.char-action-btn:hover { + color: var(--fg); + border-color: var(--fg); +} +#char-delete-template-btn:hover { + color: var(--color-error); + border-color: var(--color-error); +} + +/* Character toggle row in preset modal */ +.preset-toggle-row { + display: flex; + align-items: center; + gap: 6px; + margin-top: 12px; + font-size: 13px; + color: var(--fg); +} +.preset-sub-option { + display: flex; + align-items: center; + gap: 6px; + margin-top: 6px; + padding: 8px 10px; + font-size: 12px; + color: var(--color-muted); + background: var(--panel); + border: 1px solid var(--border); + border-radius: 6px; +} +.preset-mem-choice { + flex: 1; + padding: 6px 10px; + font-size: 12px; + font-weight: 500; + border: 1px solid var(--border); + border-radius: 6px; + background: none; + color: var(--fg); + cursor: pointer; + transition: all 0.15s; + margin: 0; +} +.preset-mem-choice.active { + background: color-mix(in srgb, var(--red) 12%, transparent); + border-color: var(--red); + color: var(--red); +} +.preset-mem-choice:hover:not(.active) { + background: color-mix(in srgb, var(--fg) 6%, transparent); +} + + +/* ===== MEMORY MODAL ===== */ + +.memory-modal-content { + width: min(560px, 90vw); + max-height: 78vh; + font-size: 12px; + overflow: hidden; /* clip both axes; the inner .memory-modal-body owns scrolling. + (overflow-x alone promotes overflow-y to `auto` → a stray vertical scrollbar) */ + /* Subtle synapse-pulse — a soft radial glow that breathes in/out. + Layered over the existing modal bg so it shows through without + overpowering content. */ + position: relative; + isolation: isolate; +} +.memory-modal-content::before { content: none; } +@media (prefers-reduced-motion: reduce) { + .memory-modal-content::before { animation: none; opacity: 0.4; } +} +.memory-modal-content .modal-header h4 { + font-size: 1rem; +} + +.memory-modal-body { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + overflow-x: hidden; /* Stop synapse-pulse pseudo-elements from triggering a sideways scrollbar */ + overscroll-behavior: contain; + min-height: 0; + /* Fill the modal-content's height so the flex chain (tab-panel → admin-card + → list → expanded card) is bounded. Without this the chain grows to + content, so an expanded skill card pushed its footer off-screen; capping + the preview then left it floating too high. Bounding here lets the + preview flex to fill and the footer pin to the bottom naturally. */ + flex: 1 1 auto; +} +.memory-tabs { + display: flex; + gap: 0; + border-bottom: 1px solid var(--border); + margin: -4px -4px 0; + padding: 0 4px; + flex-shrink: 0; +} +#memory-modal [data-memory-scope].hidden { + display: none !important; +} +.memory-tab { + background: none; + border: none; + color: var(--fg); + opacity: 0.5; + font-size: 12px; + font-family: inherit; + padding: 8px 14px; + cursor: pointer; + border-bottom: 2px solid transparent; + transition: opacity 0.15s, border-color 0.15s, color 0.15s, background 0.15s; +} +.memory-tab:hover { + opacity: 0.8; + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.memory-tab.active { + opacity: 1; + color: var(--red); + border-bottom-color: var(--red); +} +.memory-tab-panel { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + /* overflow-y:auto makes the browser compute overflow-x to `auto` too, which + produced a stray horizontal scrollbar whenever a child was slightly too + wide (long unbroken memory text, or the skills two-column row). Clip X + explicitly — inner code blocks keep their own overflow-x:auto. */ + overflow-x: hidden; + flex: 1; + min-width: 0; + min-height: 0; +} +.memory-tab-panel.hidden { display: none; } +/* Browse: bounded flex column so #memory-list gets remaining height (not 0px). + height:min(78vh,max-content) gives a definite cap when long, natural height + when short. flex-basis:auto (not 0) on the list avoids collapse in auto-sized + parents. Toolbar siblings are flex-shrink:0; only #memory-list grows. */ +#memory-modal .memory-modal-content:has( + .memory-tab-panel[data-memory-panel="browse"]:not(.hidden) +) { + display: flex; + flex-direction: column; + max-height: 78vh; + height: 78vh; + overflow: hidden; +} +#memory-modal .memory-modal-content:has( + .memory-tab-panel[data-memory-panel="browse"]:not(.hidden) +) .modal-header, +#memory-modal .memory-modal-content:has( + .memory-tab-panel[data-memory-panel="browse"]:not(.hidden) +) .memory-tabs { + flex: 0 0 auto; +} +#memory-modal .memory-modal-content:has( + .memory-tab-panel[data-memory-panel="browse"]:not(.hidden) +) .memory-modal-body { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + overflow: hidden; +} +#memory-modal .memory-tab-panel[data-memory-panel="browse"] { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + overflow: hidden; +} +#memory-modal .memory-tab-panel[data-memory-panel="browse"] > .admin-card { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + overflow: hidden; +} +#memory-modal .memory-tab-panel[data-memory-panel="browse"] > .admin-card > *:not(#memory-list):not(#memory-suggestions-body) { + flex: 0 0 auto; +} +#memory-modal .memory-tab-panel[data-memory-panel="browse"] #memory-list:not(.hidden), +#memory-modal .memory-tab-panel[data-memory-panel="browse"] #memory-suggestions-body:not(.hidden) { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; +} +#memory-modal .memory-tab-panel[data-memory-panel="browse"] #memory-suggestions-body:not(.hidden) .memory-suggestions-header { + flex-shrink: 0; + position: sticky; + top: 0; + z-index: 1; + background: var(--bg); +} +#memory-modal .memory-tab-panel[data-memory-panel].hidden { + display: none; +} +/* Settings cards dim + mute when their toggle is OFF (matches the + .memory-toolbar-toggle "off" treatment elsewhere). */ +#memory-modal .memory-tab-panel[data-memory-panel="settings"] .admin-card { + transition: opacity 0.15s, border-color 0.15s, background 0.15s; +} +#memory-modal .memory-tab-panel[data-memory-panel="settings"] .admin-card:has(.admin-switch input:not(:checked)) { + opacity: 0.55; + border-color: color-mix(in srgb, var(--fg) 8%, transparent); + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +/* Skills tab — two-column layout: skills list (left, wider) + Add Skill + form (right, narrower). Collapses to a single column on narrow screens. */ +.memory-tab-panel[data-memory-panel="skills"] { + flex-direction: row; + align-items: stretch; + gap: 10px; +} +.memory-tab-panel[data-memory-panel="skills"] > .admin-card:first-of-type { + flex: 2 1 0; + min-width: 0; +} +/* The shared chip-scroll frame is a flex-growing row by default. In the + vertical Skills card it must stay content-sized so #skills-list keeps the + remaining height for the skill cards. */ +.memory-tab-panel[data-memory-panel="skills"] > .admin-card > .doclib-chip-scroll-frame { + flex: 0 0 auto; + min-height: 25px; +} +.memory-tab-panel[data-memory-panel="skills"] > .admin-card:nth-of-type(2) { + flex: 1 1 0; + margin-top: 0 !important; + min-width: 220px; +} +@media (max-width: 640px) { + .memory-tab-panel[data-memory-panel="skills"] { flex-direction: column; } + .memory-tab-panel[data-memory-panel="skills"] > .admin-card:nth-of-type(2) { + margin-top: 12px !important; + } +} +.memory-desc { + margin: 0; + font-size: 11px; + line-height: 1.5; + /* 65% keeps this description text above WCAG AA 4.5:1 (50% was ~3.9:1). */ + color: color-mix(in srgb, var(--fg) 65%, transparent); +} + +.memory-add-row { + display: flex; + gap: 6px; + align-items: center; + height: 32px; +} + +/* Skill Import beside URL field — match input height; cancel modal-body button margin. */ +.memory-add-row .theme-io-btn { + flex: none; + height: 28px; + box-sizing: border-box; + margin-top: 0; + padding: 5px 10px; +} + +.memory-add-input { + flex: 1; + height: 28px; + padding: 0 10px; + border-radius: 6px; + border: 1px solid var(--border); + background: var(--bg); + color: var(--fg); + font-family: inherit; + font-size: 12px; + box-sizing: border-box; +} +/* Textareas need explicit vertical padding — inputs vertically center text + via line-height/height; textareas would otherwise pin text to the top. */ +textarea.memory-add-input { + height: auto; + padding: 6px 10px; + line-height: 1.4; +} + +.memory-add-input:focus { + outline: none; + border-color: var(--red); +} + +.memory-add-btn { + width: 28px; + height: 28px; + border-radius: 6px; + border: 1px solid var(--border); + background: var(--bg); + color: var(--fg); + font-size: 16px; + box-sizing: border-box; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s; + flex-shrink: 0; +} + +.memory-add-btn:hover { + background: var(--panel); + border-color: var(--red); + color: var(--red); +} + +.memory-toolbar { + display: flex; + flex-direction: column; + gap: 6px; + padding: 4px 0 8px; +} + +.memory-toolbar-row { + display: flex; + align-items: center; + gap: 8px; +} + + + +.memory-toolbar-btn { + background: none; + border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + font-size: 11px; + height: 24px; + padding: 0 8px; + border-radius: 6px; + cursor: pointer; + font-family: inherit; + transition: all 0.15s; + white-space: nowrap; +} + + + +.memory-toolbar-btn:hover { + border-color: var(--fg); + color: var(--fg); +} + +.memory-toolbar-btn.active { + background: color-mix(in srgb, var(--red) 15%, transparent); + border-color: color-mix(in srgb, var(--red) 40%, transparent); + color: var(--red); +} +.email-note-actions .memory-toolbar-btn.active:hover { + color: var(--fg); +} + +.memory-toolbar-btn.danger { + color: var(--color-error); + border-color: var(--color-error); +} + +.memory-toolbar-btn.danger:hover { + background: color-mix(in srgb, var(--color-error) 10%, transparent); +} + +.memory-toolbar-btn:disabled { + opacity: 0.35; + cursor: default; + outline: none; +} +.memory-toolbar-btn.spinning { + border-color: transparent; + background: none; +} +.memory-toolbar-toggle { + display: flex; + align-items: center; + gap: 5px; + font-size: 11px; + height: 24px; + color: color-mix(in srgb, var(--fg) 60%, transparent); + cursor: pointer; + padding: 0 4px; + user-select: none; + transition: all 0.15s; +} +.memory-toolbar-toggle:hover { + color: var(--fg); +} +.memory-toolbar-toggle .admin-switch { + vertical-align: middle; +} +.memory-toolbar-toggle:has(input:not(:checked)) { + opacity: 0.7; +} + +/* Bulk action bar */ +.memory-bulk-bar { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 2px; + margin-bottom: 2px; + border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--red) 5%, transparent); + font-size: 11px; +} + +.memory-bulk-bar.hidden { + display: none; +} + +/* Custom Memory sort picker — replaces the native so each + option can carry its own SVG icon. Themed to match .email-filter-btn. */ +.memory-sort-picker { + position: relative; + top: 0; + flex-shrink: 0; +} +.memory-sort-btn { + display: flex; + align-items: center; + gap: 6px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + font-family: inherit; + font-size: 11px; + height: 24px; + padding: 0 8px; + cursor: pointer; + text-align: left; + transition: border-color 0.12s, background 0.12s; +} +.memory-sort-btn:hover { border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); } +.memory-sort-btn:focus-visible { + outline: none; + border-color: var(--accent, var(--red)); +} +.memory-sort-current { + display: inline-flex; + align-items: center; + gap: 6px; + min-width: 0; + white-space: nowrap; +} +.memory-sort-icon-cur { + display: inline-flex; + align-items: center; + justify-content: center; + width: 13px; + height: 13px; + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.memory-sort-icon-cur svg { width: 13px; height: 13px; } +.memory-sort-caret { + flex-shrink: 0; + opacity: 0.6; + transition: transform 0.15s; +} + +.memory-sort-menu { + position: absolute; + top: calc(100% + 4px); + left: 0; + min-width: 140px; + z-index: 100; + background: var(--panel, var(--bg)); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 6px 20px rgba(0,0,0,0.22); + padding: 4px; +} +.memory-sort-item { + all: unset; + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 6px 9px; + border-radius: 5px; + font-size: 12px; + cursor: pointer; + color: var(--fg); + box-sizing: border-box; +} +.memory-sort-item:hover, +.memory-sort-item:focus-visible { + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.memory-sort-item-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.memory-sort-item-icon svg { width: 13px; height: 13px; } +.memory-sort-item-label { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Enabled/Disabled text label next to an admin toggle. + Matches the .memory-desc treatment (same color + 11px size) so it + visually belongs to the section description, not the switch. */ +.admin-toggle-state { + font-size: 10px; + line-height: 1.5; + font-weight: normal; + letter-spacing: 0.02em; + color: color-mix(in srgb, var(--fg) 65%, transparent); +} +.admin-card > div:has(.admin-switch input) .admin-toggle-state::before { + content: "Disabled"; +} +.admin-card > div:has(.admin-switch input:checked) .admin-toggle-state::before { + content: "Enabled"; +} +/* Nudge the bulk-bar action buttons up 2px (and Memory's -2px left) to + align with the row baseline. Covers both the Memory bulk bar + (Cancel/Delete) and the Skills bulk bar (Cancel/Approve/Delete) — both + live inside #memory-modal. */ +#memory-modal .memory-bulk-bar #memory-bulk-cancel, +#memory-modal .memory-bulk-bar #memory-bulk-delete { + position: relative; + top: -3px; + left: -2px; +} +#memory-modal .memory-bulk-bar #skills-bulk-cancel, +#memory-modal .memory-bulk-bar #skills-bulk-publish, +#memory-modal .memory-bulk-bar #skills-bulk-audit, +#memory-modal .memory-bulk-bar #skills-bulk-delete-nonpassing, +#memory-modal .memory-bulk-bar #skills-bulk-delete { + position: relative; + top: -2px; +} +#skills-bulk-bar #skills-bulk-delete-nonpassing { + margin-left: auto; +} +#skills-select-btn .memory-select-btn-icon { + position: relative; + top: -2px; +} +#doclib-research-bulk .memory-toolbar-btn { + position: relative; + top: 3px; + right: 16px; +} +/* Archive bulk buttons — nudge down 1px to match research. */ +#doclib-arc-bulk .memory-toolbar-btn { + position: relative; + top: 1px; +} +/* Same right-aligned layout for the other bulk bars — Chats, Documents, + Archive, Skills, Memories. Cancel's auto-margin pushes the action group + to the right; 8px of extra right padding seats it off the edge (matching + the research bar's 8px nudge). */ +#doclib-chats-bulk #doclib-chats-bulk-archive, +#doclib-bulk-bar #doclib-bulk-archive, +#doclib-arc-bulk #doclib-arc-bulk-restore, +#email-lib-bulk #email-lib-bulk-actions, +#tasks-bulk-bar #tasks-bulk-delete, +#serve-bulk-bar #serve-bulk-delete, +#gallery-bulk-bar #gallery-bulk-actions, +#gallery-editor-drafts-bulk #gallery-editor-drafts-bulk-delete, +#memory-modal .memory-bulk-bar #memory-bulk-delete, +#memory-modal .memory-bulk-bar #skills-bulk-publish { + margin-left: auto; + position: relative; + top: -1px; +} + +/* X-icon Cancel button used in every bulk-select bar (Esc target). The bare SVG + sits slightly too high vs. the adjacent text buttons — nudge it down 2px. */ +[id$="-bulk-cancel"] { + background: color-mix(in srgb, var(--red) 15%, transparent); + border-color: color-mix(in srgb, var(--red) 40%, transparent); + color: var(--red); +} +[id$="-bulk-cancel"]:hover { + background: color-mix(in srgb, var(--red) 24%, transparent); + border-color: var(--red); + color: var(--red); +} +[id$="-bulk-cancel"] svg { + width: 12px; + height: 12px; + position: relative; + top: 2px; +} +#doclib-chats-bulk, +#doclib-bulk-bar, +#doclib-arc-bulk, +#email-lib-bulk, +#tasks-bulk-bar, +#serve-bulk-bar, +#gallery-bulk-bar, +#gallery-editor-drafts-bulk, +#memory-modal .memory-bulk-bar { + padding-right: 18px; +} +#email-lib-bulk-delete.email-bulk-loading, +#email-lib-bulk-actions.email-bulk-loading, +#tasks-bulk-delete.tasks-bulk-loading { + display: inline-flex; + align-items: center; + gap: 5px; + opacity: 0.9; + cursor: wait; +} +#email-lib-bulk-delete.email-bulk-loading .email-bulk-whirlpool, +#email-lib-bulk-actions.email-bulk-loading .email-bulk-whirlpool, +#tasks-bulk-delete.tasks-bulk-loading .tasks-bulk-whirlpool { + width: 12px; + height: 12px; + margin: 0; + position: relative; + top: -1px; +} +#email-lib-bulk-delete.email-bulk-loading .email-bulk-loading-label, +#email-lib-bulk-actions.email-bulk-loading .email-bulk-loading-label, +#tasks-bulk-delete.tasks-bulk-loading .tasks-bulk-loading-label { + position: relative; + top: 0; +} +/* Nudge the whole memory + skills bulk buttons (icon + label together) up. */ +#memory-modal #memory-bulk-bar .memory-toolbar-btn, +#memory-modal #skills-bulk-bar .memory-toolbar-btn { + position: relative; + top: -2px; +} + +.memory-bulk-check-all { + display: flex; + align-items: center; + gap: 5px; + cursor: pointer; + color: color-mix(in srgb, var(--fg) 60%, transparent); + font-size: 10px; + padding: 4px 8px; + border-radius: 4px; + user-select: none; + position: relative; + top: 0; +} +.memory-bulk-check-all:hover { + background: color-mix(in srgb, var(--fg) 6%, transparent); +} + +#memory-selected-count { + color: color-mix(in srgb, var(--fg) 50%, transparent); + font-size: 10px; + flex: 1; +} + +/* Custom checkbox — toggle dot (shared by select-all and per-item) */ +.memory-select-cb, +.memory-bulk-check-all input { + -webkit-appearance: none; + appearance: none; + width: 6px !important; + height: 6px !important; + min-width: 6px; + min-height: 6px; + max-width: 6px; + max-height: 6px; + padding: 0; + border: 1px solid var(--border); + border-radius: 50%; + background: transparent; + cursor: pointer; + flex-shrink: 0; + margin: 0; + align-self: center; + position: relative; + box-sizing: content-box; + transition: all 0.15s; +} + +.memory-select-cb:hover, +.memory-bulk-check-all input:hover { + border-color: var(--red); +} + +.memory-select-cb:checked, +.memory-bulk-check-all input:checked { + background: var(--red); + border-color: var(--red); +} + +.memory-count { + font-size: 11px; + color: var(--color-muted); +} + +.memory-search-input { + height: 24px; + margin-top: 6px; + padding: 0 8px; + border-radius: 6px; + border: 1px solid var(--border); + background: var(--bg); + color: var(--fg); + font-family: inherit; + font-size: 11px; + width: 100%; + box-sizing: border-box; +} + +.memory-search-input:focus { + outline: none; + border-color: var(--red); +} + +.memory-list { + flex: 1; + min-height: 0; /* Required so flex:1 inside a flex parent can shrink rather than push its parent past 85vh */ + overflow-y: auto; + overflow-x: hidden; /* Stop the synapse sweep from triggering a sideways scrollbar */ + overscroll-behavior: contain; + display: flex; + flex-direction: column; + gap: 4px; +} + +.memory-item.task-paused { + opacity: 0.45 !important; + filter: saturate(0.55); + background: repeating-linear-gradient( + 45deg, + color-mix(in srgb, var(--fg) 2%, transparent), + color-mix(in srgb, var(--fg) 2%, transparent) 8px, + color-mix(in srgb, var(--fg) 5%, transparent) 8px, + color-mix(in srgb, var(--fg) 5%, transparent) 16px + ) !important; +} +.task-status-badge { + appearance: none; + -webkit-appearance: none; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 5px; + margin: 0; + height: 20px; + min-height: 20px; + padding: 0 8px; + border-radius: 999px; + flex-shrink: 0; + cursor: pointer; + border: 1px solid transparent; + line-height: 1; + transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease, filter 0.12s ease; + user-select: none; +} +.task-state-badge > svg, +.task-state-badge > .task-state-label { + pointer-events: none; +} +.task-state-label { + font-size: 9px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.4px; + white-space: nowrap; +} +.task-state-label-mobile { + display: none; +} +.task-paused-badge { + color: var(--orange, #ffb86c); + background: color-mix(in srgb, var(--orange, #ffb86c) 22%, transparent); + border-color: color-mix(in srgb, var(--orange, #ffb86c) 35%, transparent); +} +.task-active-badge { + color: var(--green, #50fa7b); + background: color-mix(in srgb, var(--green, #50fa7b) 20%, transparent); + border-color: color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); +} +.task-run-now-badge { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 34%, transparent); +} +.task-card-run-btn { + appearance: none; + height: 20px; + min-height: 0; + box-sizing: border-box; + position: relative; + top: -6px; +} +.task-state-badge svg { + position: relative; + top: 0; +} +.task-status-badge:hover { + filter: brightness(1.08) saturate(1.15); +} +.task-paused-badge:hover { + background: color-mix(in srgb, var(--orange, #ffb86c) 30%, transparent); + border-color: color-mix(in srgb, var(--orange, #ffb86c) 55%, transparent); +} +.task-active-badge:hover { + background: color-mix(in srgb, var(--green, #50fa7b) 28%, transparent); + border-color: color-mix(in srgb, var(--green, #50fa7b) 55%, transparent); +} +.task-run-now-badge:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 52%, transparent); +} + +.task-builtin-badge { + font-size: 9px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.4px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + padding: 1px 6px; + border-radius: 8px; + flex-shrink: 0; + white-space: nowrap; +} +.task-builtin-badge.modified { + color: var(--orange, #ff9800); + border-color: color-mix(in srgb, var(--orange, #ff9800) 40%, transparent); + background: color-mix(in srgb, var(--orange, #ff9800) 12%, transparent); +} +.memory-item { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + max-height: 200px; + flex-shrink: 0; /* memory-list is a flex column; without this, items get squeezed to fit */ + transition: all 0.15s; +} + +.memory-item-title { + font-size: 12px; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.memory-item:hover, +.doclib-card:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); + border-color: color-mix(in srgb, var(--fg) 16%, transparent); +} +/* Synapse pulse — a brief horizontal light sweep runs left → right across + each memory like a signal traversing a neural pathway. Per-item stagger + via nth-child + varied durations keeps the list shimmering rather than + pulsing in sync. */ +#memory-list .memory-item { + position: relative; + overflow: hidden; +} +#memory-list .memory-item::after { + /* Sweep highlight rides the border ring only — gradient-fill + mask cutout + keeps the bright pulse on the 1px stroke instead of washing the body. */ + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + padding: 1px; + pointer-events: none; + background: linear-gradient( + to right, + transparent 0%, + transparent calc(var(--sweep, -20%) - 8%), + color-mix(in srgb, var(--red) 85%, transparent) var(--sweep, -20%), + transparent calc(var(--sweep, -20%) + 8%), + transparent 100% + ); + -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + animation: memory-synapse-sweep 6.2s linear infinite; + animation-delay: 0.4s; +} +#memory-list .memory-item:nth-child(2n)::after { animation-duration: 7.4s; animation-delay: 1.6s; } +#memory-list .memory-item:nth-child(3n)::after { animation-duration: 8.8s; animation-delay: 3.2s; } +#memory-list .memory-item:nth-child(5n)::after { animation-duration: 9.3s; animation-delay: 4.7s; } +#memory-list .memory-item:nth-child(7n)::after { animation-duration: 5.5s; animation-delay: 2.3s; } +#memory-list .memory-item:hover::after { animation: none; opacity: 0; } +@property --sweep { + syntax: ''; + inherits: false; + initial-value: -20%; +} +@media (prefers-reduced-motion: reduce) { + #memory-list .memory-item::after { animation: none; opacity: 0; } +} +.memory-pinned:hover { + background: color-mix(in srgb, var(--red) 4%, transparent); + border-color: var(--border); + border-left-color: var(--red); +} + +.memory-item-content { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 3px; +} + +.memory-item-text { + font-size: 11px; + line-height: 1.5; + word-break: break-word; + color: var(--fg); +} + +.memory-item-edit-input { + flex: 1; + padding: 3px 5px; + border-radius: 4px; + border: 1px solid var(--red); + background: var(--bg); + color: var(--fg); + font-family: inherit; + font-size: 11px; + min-width: 0; +} + + +/* Edit row: text input + category select side by side */ +.memory-edit-row { + display: flex; + gap: 4px; + flex: 1; + min-width: 0; +} + +.memory-edit-cat-select { + background: var(--bg); + color: var(--fg); + border: 1px solid var(--red); + border-radius: 4px; + font-family: inherit; + font-size: 9px; + padding: 2px 8px; + cursor: pointer; + flex-shrink: 0; +} + + +/* The Add-memory category select uses the same semantic control as inline + editing, but needs the full-size add-row dimensions. */ +.memory-add-row .memory-edit-cat-select { + height: 28px; + min-width: 92px; + box-sizing: border-box; + border-color: var(--border); + border-radius: 6px; + padding: 2px 8px; + font-size: 12px; +} + + +.memory-item-editing { + border-color: color-mix(in srgb, var(--red) 40%, transparent); + background: color-mix(in srgb, var(--red) 3%, transparent); +} + +.memory-menu-btn { + background: none; + border: 1px solid transparent; + color: var(--color-muted); + font-size: 18px; + width: 24px; + height: 24px; + line-height: 24px; + padding: 0; + border-radius: 6px; + cursor: pointer; + flex-shrink: 0; + opacity: 0; + transition: opacity 0.15s, background 0.15s, border-color 0.15s; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.memory-item:hover .memory-menu-btn { + opacity: 1; +} + +.memory-menu-btn:hover { + background: color-mix(in srgb, var(--fg) 7%, transparent); + border-color: var(--border); + color: var(--fg); +} + +.memory-item-dropdown { + display: none; + position: fixed; + z-index: 1000; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + padding: 4px; + box-shadow: 0 4px 16px rgba(0,0,0,0.3); + min-width: auto; + width: max-content; +} + +.memory-item-dropdown .dropdown-item-compact { + padding: 6px 8px; + font-size: 11px; + border-radius: 8px; + white-space: nowrap; +} + +.memory-item-dropdown .dropdown-item-compact:hover { + background: color-mix(in srgb, var(--accent) 10%, transparent); +} + +.memory-item-actions { + display: flex; + gap: 4px; + flex-shrink: 0; + margin-left: auto; + opacity: 0; + transition: opacity 0.15s; +} + +.memory-item:hover .memory-item-actions { + opacity: 1; +} + +/* Skill rows show actions at a dim opacity by default so view/run/delete are + always discoverable, then brighten on hover. */ +.memory-item.skill-row .memory-item-actions { + opacity: 0.35; + position: relative; + top: -1px; +} +.memory-item.skill-row:hover .memory-item-actions { + opacity: 1; +} + +.memory-item-btn { + background: none; + border: 1px solid transparent; + color: var(--color-muted); + font-size: 11px; + height: 22px; + padding: 0 6px; + border-radius: 6px; + cursor: pointer; + transition: all 0.15s; + display: flex; + align-items: center; +} +@media (max-width: 768px) { + /* Nudge the ••• menu button up on mobile so it visually aligns with the + title row rather than sitting a hair below it. */ + .memory-item-actions .memory-item-btn { transform: translateY(-3px); } + /* Email rows sit on a slightly different baseline (extra meta row / + nav-arrows cluster), so pull the menu button back down 1px. */ + #email-lib-modal .memory-item-actions .memory-item-btn { transform: translateY(-2px); } + /* Base rule above hides .memory-item-actions until hover. Mobile has no + hover → the ⋮ button in cookbook serve / library cards was invisible + and effectively unclickable. Force-show on mobile. */ + .memory-item-actions { opacity: 0.7 !important; } + .memory-item-actions .memory-item-btn { + width: 32px; + height: 32px; + min-width: 32px; + } +} + +/* Research-preview sub-sections — used by the research-tab expand pattern. */ +.doclib-research-section-label { + font-size: 9px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + opacity: 0.55; + margin: 8px 0 4px; +} +.doclib-research-sources ol { + margin: 0; + padding-left: 18px; + font-size: 11px; + line-height: 1.5; +} +.doclib-research-sources a { + color: var(--accent, var(--red)); + text-decoration: none; +} +.doclib-research-sources a:hover { text-decoration: underline; } +.doclib-research-summary { + font-size: 11px; + line-height: 1.5; +} +.doclib-research-summary p { margin: 4px 0; } + +.memory-item-btn:hover { + color: var(--fg); + border-color: var(--border); + background: color-mix(in srgb, var(--fg) 6%, transparent); +} + +.memory-item-btn.delete:hover { + color: var(--color-error); + border-color: var(--color-error); +} + +.memory-item-btn.save { + color: var(--red); +} + +.memory-item-btn.save:hover { + border-color: var(--red); +} + +.memory-item-btn.pin { + padding: 1px 4px; + opacity: 0.4; + display: flex; + align-items: center; + justify-content: center; +} +.memory-item-btn.pin.active { + opacity: 1; +} +.memory-pin-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--fg); + opacity: 0.5; + transition: all 0.15s; +} +.memory-item-btn.pin.active .memory-pin-dot { + background: var(--red); + opacity: 1; +} +.memory-pinned { + border-left: 3px solid var(--red); + border-radius: 4px; + background: color-mix(in srgb, var(--red) 4%, transparent); +} +.memory-pinned .memory-item-actions { + opacity: 1; +} +.memory-pinned .memory-item-actions .memory-item-btn:not(.pin) { + opacity: 0; +} +.memory-pinned:hover .memory-item-actions .memory-item-btn:not(.pin) { + opacity: 1; +} + +/* Category filter chips */ +.memory-category-filters { + display: flex; + gap: 4px; + flex-wrap: wrap; +} + +.memory-cat-chip { + background: none; + border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + font-size: 10px; + height: 22px; + padding: 0 8px; + display: inline-flex; + align-items: center; + border-radius: 10px; + cursor: pointer; + font-family: inherit; + transition: all 0.15s; + text-transform: lowercase; +} + +.memory-cat-chip:hover { + border-color: var(--red); + color: var(--red); +} + +.memory-cat-chip.active { + background: color-mix(in srgb, var(--red) 15%, transparent); + border-color: color-mix(in srgb, var(--red) 40%, transparent); + color: var(--red); +} + +@media (max-width: 768px) { + .memory-category-filters:has(.memory-cat-chip), + .gallery-tag-chips, + .gallery-album-chips, + .gallery-ai-tags { + flex-wrap: nowrap !important; + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + touch-action: pan-x; + } + .memory-category-filters:has(.memory-cat-chip)::-webkit-scrollbar, + .gallery-tag-chips::-webkit-scrollbar, + .gallery-album-chips::-webkit-scrollbar, + .gallery-ai-tags::-webkit-scrollbar { + display: none; + } + .memory-category-filters:has(.memory-cat-chip) .memory-cat-chip, + .gallery-tag-chips .gallery-chip, + .gallery-album-chips .gallery-chip, + .gallery-ai-tags .gallery-ai-chip { + flex: 0 0 auto; + } +} + +/* Sort select — styled to match the .email-filter-btn look: + appearance reset + custom caret so the native chevron doesn't + collide with our icon/border treatment. */ +.memory-sort-select { + position: relative; + top: 3px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + font-family: inherit; + font-size: 11px; + height: 24px; + padding: 0 22px 0 8px; + cursor: pointer; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 7px center; + background-size: 10px 10px; + transition: border-color 0.12s, background 0.12s; +} +.memory-sort-select:hover { + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); +} +.memory-sort-select:focus { + outline: none; + border-color: var(--red); +} + +/* Memory sort wrapper: positions the SVG icon over the left + edge of the native , mirroring the email filter btn. */ +.memory-sort-wrap { + position: relative; + display: inline-flex; + align-items: center; + flex-shrink: 0; + left: -2px; +} +.memory-sort-wrap .memory-sort-select { + padding-left: 26px; +} +.memory-sort-icon { + position: absolute; + left: 7px; + top: calc(50% + 1px); + transform: translateY(-50%); + display: inline-flex; + align-items: center; + justify-content: center; + width: 13px; + height: 13px; + color: var(--accent, var(--red)); + pointer-events: none; + z-index: 1; +} +.memory-sort-icon svg { width: 13px; height: 13px; } + +.email-folder-picker { + position: relative; + top: 0; +} +.email-folder-picker-icon { + position: absolute; + left: 8px; + top: calc(50% + 3px); + transform: translateY(-50%); + display: inline-flex; + align-items: center; + justify-content: center; + width: 13px; + height: 13px; + color: var(--accent, var(--red)); + pointer-events: none; + z-index: 1; +} +.email-folder-picker-icon svg { + width: 13px; + height: 13px; +} + +/* Custom email filter picker (All / Unread / Favorites / …). Replaces the + native so options can carry SVG icons. The hidden source + is the value store and dispatches + 'change' on selection. */ +.email-filter-picker { + position: relative; + top: 3px; +} +/* Desktop nudge — sits 2px higher than the mobile default so it aligns + with the neighbouring memory-sort-select baseline. */ +@media (min-width: 769px) { + .email-filter-picker { top: 1px; } +} +.email-filter-btn { + display: flex; + align-items: center; + gap: 6px; + width: 100%; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + font-family: inherit; + font-size: 11px; + height: 24px; + padding: 0 8px 0 8px; + cursor: pointer; + text-align: left; + transition: border-color 0.12s, background 0.12s; + position: relative; + top: -4px; +} +.email-filter-btn:hover { border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); } +.email-filter-btn:focus-visible { + outline: none; + border-color: var(--accent, var(--red)); +} +.email-filter-current { + flex: 1; + display: inline-flex; + align-items: center; + gap: 6px; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-filter-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 13px; + height: 13px; + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-filter-icon svg { width: 13px; height: 13px; } +.email-filter-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-filter-caret { + flex-shrink: 0; + opacity: 0.6; + transition: transform 0.15s; +} +.email-filter-picker .email-filter-btn[aria-expanded="true"] .email-filter-caret { + transform: rotate(180deg); +} + +.email-filter-menu { + position: absolute; + top: calc(100% + 4px); + left: 0; + right: auto; + z-index: 100; + width: max(100%, 190px); + max-height: 320px; + overflow-y: auto; + background: var(--panel, var(--bg)); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 6px 20px rgba(0,0,0,0.22); + padding: 4px; + display: flex; + flex-direction: column; + gap: 1px; +} +.email-filter-menu[hidden] { display: none; } +.email-filter-menu-title { + padding: 5px 9px 4px; + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + opacity: 0.65; + white-space: nowrap; +} +@media (max-width: 768px) { + .email-filter-menu { + width: min(210px, calc(100vw - 24px)); + } + .email-folder-menu { + width: min(230px, calc(100vw - 24px)); + } +} +.email-filter-group { + font-size: 9px; + font-weight: 600; + letter-spacing: 0.6px; + text-transform: uppercase; + opacity: 0.55; + padding: 8px 9px 3px; +} +.email-filter-item { + all: unset; + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 6px 9px; + border-radius: 5px; + font-size: 12px; + cursor: pointer; + color: var(--fg); + box-sizing: border-box; +} +.email-filter-item:hover, +.email-filter-item:focus-visible { + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.email-filter-item-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-filter-item-icon svg { width: 13px; height: 13px; } +.email-filter-item-label { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Item metadata row */ +.memory-item-meta { + display: flex; + gap: 6px; + align-items: center; + flex-wrap: wrap; +} + +/* Category badge on each item */ +.memory-cat-badge { + font-size: 10px; + padding: 2px 6px; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--accent, var(--red)); + text-transform: lowercase; +} + +.memory-cat-identity { + background: color-mix(in srgb, var(--hl-function) 15%, transparent); + color: var(--hl-function); +} +.memory-cat-preference { + background: color-mix(in srgb, var(--hl-keyword) 15%, transparent); + color: var(--hl-keyword); +} +.memory-cat-contact { + background: color-mix(in srgb, #98c379 15%, transparent); + color: #98c379; +} +.memory-cat-project { + background: color-mix(in srgb, var(--hl-string) 15%, transparent); + color: var(--hl-string); +} +.memory-cat-goal { + background: color-mix(in srgb, var(--red) 15%, transparent); + color: var(--red); +} +.memory-cat-task { + background: color-mix(in srgb, #d19a66 15%, transparent); + color: #d19a66; +} +.memory-cat-pinned { + background: color-mix(in srgb, var(--red) 15%, transparent); + color: var(--red); +} + +/* Source and time metadata */ +.memory-item-source, +.memory-item-time, +.memory-item-uses { + font-size: 9px; + color: color-mix(in srgb, var(--fg) 35%, transparent); +} +.memory-item-uses { + font-family: monospace; + color: color-mix(in srgb, var(--fg) 55%, transparent); +} + + +/* Empty state */ +.memory-empty { + padding: 24px 16px; + color: color-mix(in srgb, var(--fg) 35%, transparent); + text-align: center; + font-size: 11px; + font-style: italic; +} + +/* Suggestions area */ +.memory-suggestions { + display: flex; + flex-direction: column; + gap: 6px; + /* Bound the import-review list to the modal like the sibling .memory-list, + so a long list scrolls internally instead of overflowing the + overflow:hidden .admin-card — which clipped lower entries and their + save/discard controls with no usable scroll area. */ + flex: 1; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + /* Small gutter so the scrollbar doesn't sit flush against the item cards. */ + padding-right: 4px; +} + +.memory-suggestions.hidden { + display: none; +} + +.memory-suggestions-header { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 11px; + color: color-mix(in srgb, var(--fg) 70%, transparent); + padding-bottom: 4px; + border-bottom: 1px solid var(--border); + /* Pin the title + save all/back controls to the top of the scrolling + review list so they stay reachable while the items scroll under them. + Opaque background masks items passing beneath. */ + position: sticky; + top: 0; + z-index: 1; + background: var(--panel); +} +.memory-suggestions-actions, +.memory-suggestion-actions { + display: flex; + align-items: center; + gap: 5px; + flex-shrink: 0; +} diff --git a/static/css/style-part-03.css b/static/css/05-documents.css similarity index 55% rename from static/css/style-part-03.css rename to static/css/05-documents.css index 45ca958cd..2c79ef37f 100644 --- a/static/css/style-part-03.css +++ b/static/css/05-documents.css @@ -1,2095 +1,19 @@ -/* Fragment 4/9 of the former static/style.css. +/* 05-documents - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -/* ===== Markdown highlighting — document editor ===== - IMPORTANT: this is an overlay layered behind a transparent textarea, so - every styled token must occupy the EXACT same width as the corresponding - characters in the textarea. Anything that changes glyph metrics - (font-weight/style, padding, border, letter-spacing) makes the caret drift - away from the rendered glyph underneath. Color / background / text- - decoration are safe — they don't change layout width. */ -.doc-editor-highlight .language-markdown .hljs-section { - color: var(--hl-keyword, #c678dd); -} -.doc-editor-highlight .language-markdown .hljs-strong { - color: var(--hl-number, #d19a66); -} -.doc-editor-highlight .language-markdown .hljs-emphasis { - color: var(--hl-string, #e5c07b); -} -.doc-editor-highlight .language-markdown .hljs-bullet { - color: var(--hl-builtin, #56b6c2); -} -.doc-editor-highlight .language-markdown .hljs-code { - color: var(--hl-builtin, #56b6c2); - background: color-mix(in srgb, var(--hl-builtin, #56b6c2) 10%, transparent); - border-radius: 2px; -} -.doc-editor-highlight .language-markdown .hljs-link { - color: var(--hl-function, #61afef); - text-decoration: underline; -} -.doc-editor-highlight .language-markdown .hljs-quote { - color: var(--hl-comment, #828997); -} -.doc-editor-highlight .language-markdown .hljs-symbol { - color: var(--red); -} -/* Standalone [bracketed text] — scene directions, annotations */ -.doc-editor-highlight .language-markdown .md-bracket { - color: var(--hl-builtin, #56b6c2); - opacity: 0.85; -} -/* Heading # markers — dimmer than the heading text */ -.doc-editor-highlight .language-markdown .md-heading-marker { - color: var(--hl-comment, #828997); - font-weight: 400; -} - -/* ===== CUSTOM PRESET MODAL ===== */ - -.preset-modal-content { - width: min(460px, 90vw); - border-radius: 12px; - overflow: hidden; -} -.preset-modal-body { - display: flex; - flex-direction: column; - overflow-x: hidden; -} - -/* Footer Start/Cancel buttons get a leading icon. Done via ::before + a masked - SVG (not an inline child) because the labels are set with .textContent, - which would wipe a child element on every tab switch. background:currentColor - makes the icon follow the button's text color. */ -#save-custom-preset, -#cancel-custom-preset { - display: inline-flex; - align-items: center; - gap: 6px; -} -#save-custom-preset::before, -#cancel-custom-preset::before { - content: ""; - width: 13px; height: 13px; - flex-shrink: 0; - background-color: currentColor; - -webkit-mask: var(--_btn-ic) center / contain no-repeat; - mask: var(--_btn-ic) center / contain no-repeat; -} -#save-custom-preset::before { - --_btn-ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpolygon points='5 3 19 12 5 21 5 3'/%3E%3C/svg%3E"); -} -#cancel-custom-preset::before { - --_btn-ic: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.5' stroke-linecap='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E"); -} - -.preset-tabs { - display: flex; - gap: 0; - border-bottom: 1px solid var(--border); - margin: 0 -10px 12px; - padding: 0 10px; - margin: 0 -16px; - padding: 0 16px; - margin-bottom: 12px; -} - -.preset-tab { - flex: 1; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - padding: 8px 10px; - background: none; - border: none; - border-bottom: 2px solid transparent; - color: var(--color-muted); - font-family: inherit; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 0.15s; -} -.preset-tab-icon { flex-shrink: 0; } -/* On narrow widths the icon + label can crowd 4 tabs — drop the labels to - icon-only so the row stays clean. */ -@media (max-width: 460px) { - .preset-tab span { display: none; } -} - -.preset-tab:hover { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 4%, transparent); -} - -.preset-tab.active { - color: var(--red); - border-bottom-color: var(--red); -} - -.preset-tab-content { - overflow: hidden; -} -.preset-tab-content.hidden { - display: none; -} - -.preset-templates-hint { - font-size: 11px; - color: var(--color-muted); - margin: 0 0 8px; -} - -.prompt-templates-list { - display: flex; - flex-direction: column; - gap: 6px; - max-height: 300px; - overflow-y: auto; -} - -.prompt-template-card { - padding: 10px 12px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - cursor: pointer; - transition: all 0.15s; -} - -.prompt-template-card:hover { - background: color-mix(in srgb, var(--accent) 8%, transparent); - border-color: color-mix(in srgb, var(--accent) 30%, transparent); -} - -.prompt-template-card.selected { - border-color: var(--accent); - background: color-mix(in srgb, var(--accent) 10%, transparent); -} - -.prompt-template-name { - font-size: 12px; - font-weight: 600; - color: var(--fg); - margin-bottom: 4px; -} - -.prompt-template-preview { - font-size: 11px; - color: var(--color-muted); - line-height: 1.4; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.preset-slider-row { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 12px; - margin-bottom: 6px; -} - -.preset-slider-row label { - font-size: 13px; - color: var(--fg); - font-weight: 500; - margin: 0; -} - -.preset-slider-value { - font-size: 12px; - color: var(--fg); - font-weight: 600; - min-width: 40px; - text-align: right; -} - -.preset-range { - -webkit-appearance: none; - appearance: none; - width: 100%; - height: 6px; - background: var(--border); - border-radius: 4px; - outline: none; - margin-bottom: 4px; - box-sizing: border-box; - display: block; - padding: 0; - margin-left: 0; - margin-right: 0; -} - -.preset-range::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 18px; - height: 18px; - border-radius: 50%; - background: var(--red, var(--fg)); - cursor: pointer; - border: 2px solid var(--panel); - box-shadow: 0 1px 4px rgba(0,0,0,0.2); -} - -.preset-range::-moz-range-thumb { - width: 18px; - height: 18px; - border-radius: 50%; - background: var(--red, var(--fg)); - cursor: pointer; - border: 2px solid var(--panel); - box-shadow: 0 1px 4px rgba(0,0,0,0.2); -} - -.preset-range::-webkit-slider-runnable-track { - height: 6px; - border-radius: 4px; -} - -.preset-range::-moz-range-track { - height: 6px; - background: var(--border); - border-radius: 4px; -} - -.preset-temp-hints { - display: flex; - font-size: 10px; - color: var(--color-muted); - margin-top: -4px; - margin-bottom: 10px; - padding: 0 2px; - opacity: 0.7; -} -.preset-temp-hints span { - flex: 1; -} -.preset-temp-hints span:nth-child(2) { - text-align: center; -} -.preset-temp-hints span:last-child { - text-align: right; -} - -.preset-clear-btn { - padding: 7px 14px; - background: none; - border: 1px solid var(--border); - color: var(--color-muted); - border-radius: 6px; - cursor: pointer; - font-size: 12px; - font-weight: 500; - transition: all 0.15s; -} - -.preset-clear-btn:hover { - color: var(--color-error); - border-color: var(--color-error); -} - -.preset-hint-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 15px; - height: 15px; - border-radius: 50%; - border: 1px solid var(--border); - font-size: 10px; - font-weight: 600; - color: var(--color-muted); - cursor: help; - vertical-align: middle; - margin-left: 4px; - transition: all 0.15s; -} -.preset-hint-icon:hover { - color: var(--fg); - border-color: var(--fg); -} - -.preset-section-header { - font-size: 11px; - font-weight: 600; - color: var(--color-muted); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 6px; - padding: 0 2px; -} - -.user-template-card { - position: relative; -} -.user-template-delete { - background: none; - border: none; - color: var(--color-muted); - font-size: 13px; - cursor: pointer; - padding: 0 2px; - line-height: 1; - opacity: 0; - transition: opacity 0.15s, color 0.15s; -} -.user-template-card:hover .user-template-delete { - opacity: 1; -} -.user-template-delete:hover { - color: var(--color-error); -} - -.preset-save-template-btn { - padding: 7px 14px; - border-radius: 6px; - font-size: 12px; - font-weight: 500; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: all 0.15s; -} -.preset-save-template-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} - -.char-prompt-wrap { - position: relative; -} -.char-prompt-wrap textarea { - padding-bottom: 28px; -} -.char-expand-btn { - position: absolute; - bottom: 14px; - right: 6px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 4px; - color: var(--color-muted); - font-size: 11px; - padding: 2px 8px; - cursor: pointer; - transition: all 0.15s; - margin: 0; - z-index: 1; -} -.char-expand-btn:hover { - color: var(--red); - border-color: var(--red); -} -.char-expand-btn.expanding { - opacity: 0.5; - pointer-events: none; -} - -/* Memory scope bar (My Memories / Characters) */ -.memory-scope-bar { - display: flex; - gap: 0; - margin: 0 0 8px 0 !important; - padding: 0 !important; - border: 1px solid var(--border); - border-radius: 6px; - overflow: hidden; -} -.memory-scope-btn { - flex: 1; - padding: 8px 12px !important; - margin: 0 !important; - font-size: 11px !important; - font-weight: 600; - background: none; - border: none; - color: var(--fg); - opacity: 0.5; - cursor: pointer; - transition: all 0.15s; -} -.memory-scope-btn + .memory-scope-btn { - border-left: 1px solid var(--border); -} -.memory-scope-btn.active { - background: color-mix(in srgb, var(--red) 12%, transparent); - color: var(--red); - opacity: 1; -} -.memory-scope-btn:hover:not(.active) { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.memory-char-list { - display: flex; - gap: 4px; - flex-wrap: wrap; - margin-bottom: 8px; -} -.memory-char-chip { - padding: 4px 10px; - font-size: 11px; - font-weight: 500; - border: 1px solid var(--border); - border-radius: 12px; - background: none; - color: var(--fg); - cursor: pointer; - transition: all 0.15s; - margin: 0; -} -.memory-char-chip.active { - background: color-mix(in srgb, var(--red) 12%, transparent); - border-color: var(--red); - color: var(--red); -} -.memory-char-chip:hover:not(.active) { - background: color-mix(in srgb, var(--fg) 6%, transparent); -} - -/* Disabled state dims the form */ -#char-fields-wrap.disabled { - opacity: 0.35; - pointer-events: none; - filter: grayscale(0.5); - transition: opacity 0.2s, filter 0.2s; -} -#char-fields-wrap { - transition: opacity 0.2s, filter 0.2s; -} - -/* Name combo: input + delete btn */ -.char-name-combo { - display: flex; - gap: 4px; - align-items: center; - margin-bottom: 8px; -} -.char-name-combo input, -.char-name-combo select { - flex: 1; -} -.char-template-select { - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - padding: 6px 8px; - font-size: 13px; - font-family: inherit; - cursor: pointer; -} -.char-template-select:focus { - outline: none; - border-color: var(--red); -} -.char-action-btn { - background: none; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--color-muted); - font-size: 11px; - padding: 4px 8px; - cursor: pointer; - transition: all 0.15s; - flex-shrink: 0; - margin: 0 !important; - white-space: nowrap; - /* Uniform width so the trailing button column matches between the select - row (+ New) and the name row (Reset) — that keeps the select and the - name input the same width, since both fields flex:1 into the leftover. */ - min-width: 64px; - text-align: center; -} -.char-action-btn:hover { - color: var(--fg); - border-color: var(--fg); -} -#char-delete-template-btn:hover { - color: var(--color-error); - border-color: var(--color-error); -} - -/* Character toggle row in preset modal */ -.preset-toggle-row { - display: flex; - align-items: center; - gap: 6px; - margin-top: 12px; - font-size: 13px; - color: var(--fg); -} -.preset-sub-option { - display: flex; - align-items: center; - gap: 6px; - margin-top: 6px; - padding: 8px 10px; - font-size: 12px; - color: var(--color-muted); - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; -} -.preset-mem-choice { - flex: 1; - padding: 6px 10px; - font-size: 12px; - font-weight: 500; - border: 1px solid var(--border); - border-radius: 6px; - background: none; - color: var(--fg); - cursor: pointer; - transition: all 0.15s; - margin: 0; -} -.preset-mem-choice.active { - background: color-mix(in srgb, var(--red) 12%, transparent); - border-color: var(--red); - color: var(--red); -} -.preset-mem-choice:hover:not(.active) { - background: color-mix(in srgb, var(--fg) 6%, transparent); -} - - -/* ===== MEMORY MODAL ===== */ - -.memory-modal-content { - width: min(560px, 90vw); - max-height: 78vh; - font-size: 12px; - overflow: hidden; /* clip both axes; the inner .memory-modal-body owns scrolling. - (overflow-x alone promotes overflow-y to `auto` → a stray vertical scrollbar) */ - /* Subtle synapse-pulse — a soft radial glow that breathes in/out. - Layered over the existing modal bg so it shows through without - overpowering content. */ - position: relative; - isolation: isolate; -} -.memory-modal-content::before { content: none; } -@media (prefers-reduced-motion: reduce) { - .memory-modal-content::before { animation: none; opacity: 0.4; } -} -.memory-modal-content .modal-header h4 { - font-size: 1rem; -} - -.memory-modal-body { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - overflow-x: hidden; /* Stop synapse-pulse pseudo-elements from triggering a sideways scrollbar */ - overscroll-behavior: contain; - min-height: 0; - /* Fill the modal-content's height so the flex chain (tab-panel → admin-card - → list → expanded card) is bounded. Without this the chain grows to - content, so an expanded skill card pushed its footer off-screen; capping - the preview then left it floating too high. Bounding here lets the - preview flex to fill and the footer pin to the bottom naturally. */ - flex: 1 1 auto; -} -.memory-tabs { - display: flex; - gap: 0; - border-bottom: 1px solid var(--border); - margin: -4px -4px 0; - padding: 0 4px; - flex-shrink: 0; -} -#memory-modal [data-memory-scope].hidden { - display: none !important; -} -.memory-tab { - background: none; - border: none; - color: var(--fg); - opacity: 0.5; - font-size: 12px; - font-family: inherit; - padding: 8px 14px; - cursor: pointer; - border-bottom: 2px solid transparent; - transition: opacity 0.15s, border-color 0.15s, color 0.15s, background 0.15s; -} -.memory-tab:hover { - opacity: 0.8; - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.memory-tab.active { - opacity: 1; - color: var(--red); - border-bottom-color: var(--red); -} -.memory-tab-panel { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - /* overflow-y:auto makes the browser compute overflow-x to `auto` too, which - produced a stray horizontal scrollbar whenever a child was slightly too - wide (long unbroken memory text, or the skills two-column row). Clip X - explicitly — inner code blocks keep their own overflow-x:auto. */ - overflow-x: hidden; - flex: 1; - min-width: 0; - min-height: 0; -} -.memory-tab-panel.hidden { display: none; } -/* Browse: bounded flex column so #memory-list gets remaining height (not 0px). - height:min(78vh,max-content) gives a definite cap when long, natural height - when short. flex-basis:auto (not 0) on the list avoids collapse in auto-sized - parents. Toolbar siblings are flex-shrink:0; only #memory-list grows. */ -#memory-modal .memory-modal-content:has( - .memory-tab-panel[data-memory-panel="browse"]:not(.hidden) -) { - display: flex; - flex-direction: column; - max-height: 78vh; - height: 78vh; - overflow: hidden; -} -#memory-modal .memory-modal-content:has( - .memory-tab-panel[data-memory-panel="browse"]:not(.hidden) -) .modal-header, -#memory-modal .memory-modal-content:has( - .memory-tab-panel[data-memory-panel="browse"]:not(.hidden) -) .memory-tabs { - flex: 0 0 auto; -} -#memory-modal .memory-modal-content:has( - .memory-tab-panel[data-memory-panel="browse"]:not(.hidden) -) .memory-modal-body { - display: flex; - flex-direction: column; - flex: 1 1 auto; - min-height: 0; - overflow: hidden; -} -#memory-modal .memory-tab-panel[data-memory-panel="browse"] { - display: flex; - flex-direction: column; - flex: 1 1 auto; - min-height: 0; - overflow: hidden; -} -#memory-modal .memory-tab-panel[data-memory-panel="browse"] > .admin-card { - display: flex; - flex-direction: column; - flex: 1 1 auto; - min-height: 0; - overflow: hidden; -} -#memory-modal .memory-tab-panel[data-memory-panel="browse"] > .admin-card > *:not(#memory-list):not(#memory-suggestions-body) { - flex: 0 0 auto; -} -#memory-modal .memory-tab-panel[data-memory-panel="browse"] #memory-list:not(.hidden), -#memory-modal .memory-tab-panel[data-memory-panel="browse"] #memory-suggestions-body:not(.hidden) { - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -#memory-modal .memory-tab-panel[data-memory-panel="browse"] #memory-suggestions-body:not(.hidden) .memory-suggestions-header { - flex-shrink: 0; - position: sticky; - top: 0; - z-index: 1; - background: var(--bg); -} -#memory-modal .memory-tab-panel[data-memory-panel].hidden { - display: none; -} -/* Settings cards dim + mute when their toggle is OFF (matches the - .memory-toolbar-toggle "off" treatment elsewhere). */ -#memory-modal .memory-tab-panel[data-memory-panel="settings"] .admin-card { - transition: opacity 0.15s, border-color 0.15s, background 0.15s; -} -#memory-modal .memory-tab-panel[data-memory-panel="settings"] .admin-card:has(.admin-switch input:not(:checked)) { - opacity: 0.55; - border-color: color-mix(in srgb, var(--fg) 8%, transparent); - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -/* Skills tab — two-column layout: skills list (left, wider) + Add Skill - form (right, narrower). Collapses to a single column on narrow screens. */ -.memory-tab-panel[data-memory-panel="skills"] { - flex-direction: row; - align-items: stretch; - gap: 10px; -} -.memory-tab-panel[data-memory-panel="skills"] > .admin-card:first-of-type { - flex: 2 1 0; - min-width: 0; -} -/* The shared chip-scroll frame is a flex-growing row by default. In the - vertical Skills card it must stay content-sized so #skills-list keeps the - remaining height for the skill cards. */ -.memory-tab-panel[data-memory-panel="skills"] > .admin-card > .doclib-chip-scroll-frame { - flex: 0 0 auto; - min-height: 25px; -} -.memory-tab-panel[data-memory-panel="skills"] > .admin-card:nth-of-type(2) { - flex: 1 1 0; - margin-top: 0 !important; - min-width: 220px; -} -@media (max-width: 640px) { - .memory-tab-panel[data-memory-panel="skills"] { flex-direction: column; } - .memory-tab-panel[data-memory-panel="skills"] > .admin-card:nth-of-type(2) { - margin-top: 12px !important; - } -} -.memory-desc { - margin: 0; - font-size: 11px; - line-height: 1.5; - /* 65% keeps this description text above WCAG AA 4.5:1 (50% was ~3.9:1). */ - color: color-mix(in srgb, var(--fg) 65%, transparent); -} - -.memory-add-row { - display: flex; - gap: 6px; - align-items: center; - height: 32px; -} - -/* Skill Import beside URL field — match input height; cancel modal-body button margin. */ -.memory-add-row .theme-io-btn { - flex: none; - height: 28px; - box-sizing: border-box; - margin-top: 0; - padding: 5px 10px; -} - -.memory-add-input { - flex: 1; - height: 28px; - padding: 0 10px; - border-radius: 6px; - border: 1px solid var(--border); - background: var(--bg); - color: var(--fg); - font-family: inherit; - font-size: 12px; - box-sizing: border-box; -} -/* Textareas need explicit vertical padding — inputs vertically center text - via line-height/height; textareas would otherwise pin text to the top. */ -textarea.memory-add-input { - height: auto; - padding: 6px 10px; - line-height: 1.4; -} - -.memory-add-input:focus { - outline: none; - border-color: var(--red); -} - -.memory-add-btn { - width: 28px; - height: 28px; - border-radius: 6px; - border: 1px solid var(--border); - background: var(--bg); - color: var(--fg); - font-size: 16px; - box-sizing: border-box; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.15s; - flex-shrink: 0; -} - -.memory-add-btn:hover { - background: var(--panel); - border-color: var(--red); - color: var(--red); -} - -.memory-toolbar { - display: flex; - flex-direction: column; - gap: 6px; - padding: 4px 0 8px; -} - -.memory-toolbar-row { - display: flex; - align-items: center; - gap: 8px; -} - - - -.memory-toolbar-btn { - background: none; - border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-size: 11px; - height: 24px; - padding: 0 8px; - border-radius: 6px; - cursor: pointer; - font-family: inherit; - transition: all 0.15s; - white-space: nowrap; -} - - - -.memory-toolbar-btn:hover { - border-color: var(--fg); - color: var(--fg); -} - -.memory-toolbar-btn.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -.email-note-actions .memory-toolbar-btn.active:hover { - color: var(--fg); -} - -.memory-toolbar-btn.danger { - color: var(--color-error); - border-color: var(--color-error); -} - -.memory-toolbar-btn.danger:hover { - background: color-mix(in srgb, var(--color-error) 10%, transparent); -} - -.memory-toolbar-btn:disabled { - opacity: 0.35; - cursor: default; - outline: none; -} -.memory-toolbar-btn.spinning { - border-color: transparent; - background: none; -} -.memory-toolbar-toggle { - display: flex; - align-items: center; - gap: 5px; - font-size: 11px; - height: 24px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - cursor: pointer; - padding: 0 4px; - user-select: none; - transition: all 0.15s; -} -.memory-toolbar-toggle:hover { - color: var(--fg); -} -.memory-toolbar-toggle .admin-switch { - vertical-align: middle; -} -.memory-toolbar-toggle:has(input:not(:checked)) { - opacity: 0.7; -} - -/* Bulk action bar */ -.memory-bulk-bar { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 2px; - margin-bottom: 2px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - font-size: 11px; -} - -.memory-bulk-bar.hidden { - display: none; -} - -/* Custom Memory sort picker — replaces the native so each - option can carry its own SVG icon. Themed to match .email-filter-btn. */ -.memory-sort-picker { - position: relative; - top: 0; - flex-shrink: 0; -} -.memory-sort-btn { - display: flex; - align-items: center; - gap: 6px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - font-family: inherit; - font-size: 11px; - height: 24px; - padding: 0 8px; - cursor: pointer; - text-align: left; - transition: border-color 0.12s, background 0.12s; -} -.memory-sort-btn:hover { border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); } -.memory-sort-btn:focus-visible { - outline: none; - border-color: var(--accent, var(--red)); -} -.memory-sort-current { - display: inline-flex; - align-items: center; - gap: 6px; - min-width: 0; - white-space: nowrap; -} -.memory-sort-icon-cur { - display: inline-flex; - align-items: center; - justify-content: center; - width: 13px; - height: 13px; - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.memory-sort-icon-cur svg { width: 13px; height: 13px; } -.memory-sort-caret { - flex-shrink: 0; - opacity: 0.6; - transition: transform 0.15s; -} - -.memory-sort-menu { - position: absolute; - top: calc(100% + 4px); - left: 0; - min-width: 140px; - z-index: 100; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 6px 20px rgba(0,0,0,0.22); - padding: 4px; -} -.memory-sort-item { - all: unset; - display: flex; - align-items: center; - gap: 8px; - width: 100%; - padding: 6px 9px; - border-radius: 5px; - font-size: 12px; - cursor: pointer; - color: var(--fg); - box-sizing: border-box; -} -.memory-sort-item:hover, -.memory-sort-item:focus-visible { - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.memory-sort-item-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.memory-sort-item-icon svg { width: 13px; height: 13px; } -.memory-sort-item-label { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* Enabled/Disabled text label next to an admin toggle. - Matches the .memory-desc treatment (same color + 11px size) so it - visually belongs to the section description, not the switch. */ -.admin-toggle-state { - font-size: 10px; - line-height: 1.5; - font-weight: normal; - letter-spacing: 0.02em; - color: color-mix(in srgb, var(--fg) 65%, transparent); -} -.admin-card > div:has(.admin-switch input) .admin-toggle-state::before { - content: "Disabled"; -} -.admin-card > div:has(.admin-switch input:checked) .admin-toggle-state::before { - content: "Enabled"; -} -/* Nudge the bulk-bar action buttons up 2px (and Memory's -2px left) to - align with the row baseline. Covers both the Memory bulk bar - (Cancel/Delete) and the Skills bulk bar (Cancel/Approve/Delete) — both - live inside #memory-modal. */ -#memory-modal .memory-bulk-bar #memory-bulk-cancel, -#memory-modal .memory-bulk-bar #memory-bulk-delete { - position: relative; - top: -3px; - left: -2px; -} -#memory-modal .memory-bulk-bar #skills-bulk-cancel, -#memory-modal .memory-bulk-bar #skills-bulk-publish, -#memory-modal .memory-bulk-bar #skills-bulk-audit, -#memory-modal .memory-bulk-bar #skills-bulk-delete-nonpassing, -#memory-modal .memory-bulk-bar #skills-bulk-delete { - position: relative; - top: -2px; -} -#skills-bulk-bar #skills-bulk-delete-nonpassing { - margin-left: auto; -} -#skills-select-btn .memory-select-btn-icon { - position: relative; - top: -2px; -} -#doclib-research-bulk .memory-toolbar-btn { - position: relative; - top: 3px; - right: 16px; -} -/* Archive bulk buttons — nudge down 1px to match research. */ -#doclib-arc-bulk .memory-toolbar-btn { - position: relative; - top: 1px; -} -/* Same right-aligned layout for the other bulk bars — Chats, Documents, - Archive, Skills, Memories. Cancel's auto-margin pushes the action group - to the right; 8px of extra right padding seats it off the edge (matching - the research bar's 8px nudge). */ -#doclib-chats-bulk #doclib-chats-bulk-archive, -#doclib-bulk-bar #doclib-bulk-archive, -#doclib-arc-bulk #doclib-arc-bulk-restore, -#email-lib-bulk #email-lib-bulk-actions, -#tasks-bulk-bar #tasks-bulk-delete, -#serve-bulk-bar #serve-bulk-delete, -#gallery-bulk-bar #gallery-bulk-actions, -#gallery-editor-drafts-bulk #gallery-editor-drafts-bulk-delete, -#memory-modal .memory-bulk-bar #memory-bulk-delete, -#memory-modal .memory-bulk-bar #skills-bulk-publish { - margin-left: auto; - position: relative; - top: -1px; -} - -/* X-icon Cancel button used in every bulk-select bar (Esc target). The bare SVG - sits slightly too high vs. the adjacent text buttons — nudge it down 2px. */ -[id$="-bulk-cancel"] { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -[id$="-bulk-cancel"]:hover { - background: color-mix(in srgb, var(--red) 24%, transparent); - border-color: var(--red); - color: var(--red); -} -[id$="-bulk-cancel"] svg { - width: 12px; - height: 12px; - position: relative; - top: 2px; -} -#doclib-chats-bulk, -#doclib-bulk-bar, -#doclib-arc-bulk, -#email-lib-bulk, -#tasks-bulk-bar, -#serve-bulk-bar, -#gallery-bulk-bar, -#gallery-editor-drafts-bulk, -#memory-modal .memory-bulk-bar { - padding-right: 18px; -} -#email-lib-bulk-delete.email-bulk-loading, -#email-lib-bulk-actions.email-bulk-loading, -#tasks-bulk-delete.tasks-bulk-loading { - display: inline-flex; - align-items: center; - gap: 5px; - opacity: 0.9; - cursor: wait; -} -#email-lib-bulk-delete.email-bulk-loading .email-bulk-whirlpool, -#email-lib-bulk-actions.email-bulk-loading .email-bulk-whirlpool, -#tasks-bulk-delete.tasks-bulk-loading .tasks-bulk-whirlpool { - width: 12px; - height: 12px; - margin: 0; - position: relative; - top: -1px; -} -#email-lib-bulk-delete.email-bulk-loading .email-bulk-loading-label, -#email-lib-bulk-actions.email-bulk-loading .email-bulk-loading-label, -#tasks-bulk-delete.tasks-bulk-loading .tasks-bulk-loading-label { - position: relative; - top: 0; -} -/* Nudge the whole memory + skills bulk buttons (icon + label together) up. */ -#memory-modal #memory-bulk-bar .memory-toolbar-btn, -#memory-modal #skills-bulk-bar .memory-toolbar-btn { - position: relative; - top: -2px; -} - -.memory-bulk-check-all { - display: flex; - align-items: center; - gap: 5px; - cursor: pointer; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-size: 10px; - padding: 4px 8px; - border-radius: 4px; - user-select: none; - position: relative; - top: 0; -} -.memory-bulk-check-all:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); -} - -#memory-selected-count { - color: color-mix(in srgb, var(--fg) 50%, transparent); - font-size: 10px; - flex: 1; -} - -/* Custom checkbox — toggle dot (shared by select-all and per-item) */ -.memory-select-cb, -.memory-bulk-check-all input { - -webkit-appearance: none; - appearance: none; - width: 6px !important; - height: 6px !important; - min-width: 6px; - min-height: 6px; - max-width: 6px; - max-height: 6px; - padding: 0; - border: 1px solid var(--border); - border-radius: 50%; - background: transparent; - cursor: pointer; - flex-shrink: 0; - margin: 0; - align-self: center; - position: relative; - box-sizing: content-box; - transition: all 0.15s; -} - -.memory-select-cb:hover, -.memory-bulk-check-all input:hover { - border-color: var(--red); -} - -.memory-select-cb:checked, -.memory-bulk-check-all input:checked { - background: var(--red); - border-color: var(--red); -} - -.memory-count { - font-size: 11px; - color: var(--color-muted); -} - -.memory-search-input { - height: 24px; - margin-top: 6px; - padding: 0 8px; - border-radius: 6px; - border: 1px solid var(--border); - background: var(--bg); - color: var(--fg); - font-family: inherit; - font-size: 11px; - width: 100%; - box-sizing: border-box; -} - -.memory-search-input:focus { - outline: none; - border-color: var(--red); -} - -.memory-list { - flex: 1; - min-height: 0; /* Required so flex:1 inside a flex parent can shrink rather than push its parent past 85vh */ - overflow-y: auto; - overflow-x: hidden; /* Stop the synapse sweep from triggering a sideways scrollbar */ - overscroll-behavior: contain; - display: flex; - flex-direction: column; - gap: 4px; -} - -.memory-item.task-paused { - opacity: 0.45 !important; - filter: saturate(0.55); - background: repeating-linear-gradient( - 45deg, - color-mix(in srgb, var(--fg) 2%, transparent), - color-mix(in srgb, var(--fg) 2%, transparent) 8px, - color-mix(in srgb, var(--fg) 5%, transparent) 8px, - color-mix(in srgb, var(--fg) 5%, transparent) 16px - ) !important; -} -.task-status-badge { - appearance: none; - -webkit-appearance: none; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 5px; - margin: 0; - height: 20px; - min-height: 20px; - padding: 0 8px; - border-radius: 999px; - flex-shrink: 0; - cursor: pointer; - border: 1px solid transparent; - line-height: 1; - transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease, filter 0.12s ease; - user-select: none; -} -.task-state-badge > svg, -.task-state-badge > .task-state-label { - pointer-events: none; -} -.task-state-label { - font-size: 9px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.4px; - white-space: nowrap; -} -.task-state-label-mobile { - display: none; -} -.task-paused-badge { - color: var(--orange, #ffb86c); - background: color-mix(in srgb, var(--orange, #ffb86c) 22%, transparent); - border-color: color-mix(in srgb, var(--orange, #ffb86c) 35%, transparent); -} -.task-active-badge { - color: var(--green, #50fa7b); - background: color-mix(in srgb, var(--green, #50fa7b) 20%, transparent); - border-color: color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); -} -.task-run-now-badge { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 34%, transparent); -} -.task-card-run-btn { - appearance: none; - height: 20px; - min-height: 0; - box-sizing: border-box; - position: relative; - top: -6px; -} -.task-state-badge svg { - position: relative; - top: 0; -} -.task-status-badge:hover { - filter: brightness(1.08) saturate(1.15); -} -.task-paused-badge:hover { - background: color-mix(in srgb, var(--orange, #ffb86c) 30%, transparent); - border-color: color-mix(in srgb, var(--orange, #ffb86c) 55%, transparent); -} -.task-active-badge:hover { - background: color-mix(in srgb, var(--green, #50fa7b) 28%, transparent); - border-color: color-mix(in srgb, var(--green, #50fa7b) 55%, transparent); -} -.task-run-now-badge:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 52%, transparent); -} - -.task-builtin-badge { - font-size: 9px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.4px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - padding: 1px 6px; - border-radius: 8px; - flex-shrink: 0; - white-space: nowrap; -} -.task-builtin-badge.modified { - color: var(--orange, #ff9800); - border-color: color-mix(in srgb, var(--orange, #ff9800) 40%, transparent); - background: color-mix(in srgb, var(--orange, #ff9800) 12%, transparent); -} -.memory-item { - display: flex; - align-items: flex-start; - gap: 8px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - max-height: 200px; - flex-shrink: 0; /* memory-list is a flex column; without this, items get squeezed to fit */ - transition: all 0.15s; -} - -.memory-item-title { - font-size: 12px; - font-weight: 500; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.memory-item:hover, -.doclib-card:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); - border-color: color-mix(in srgb, var(--fg) 16%, transparent); -} -/* Synapse pulse — a brief horizontal light sweep runs left → right across - each memory like a signal traversing a neural pathway. Per-item stagger - via nth-child + varied durations keeps the list shimmering rather than - pulsing in sync. */ -#memory-list .memory-item { - position: relative; - overflow: hidden; -} -#memory-list .memory-item::after { - /* Sweep highlight rides the border ring only — gradient-fill + mask cutout - keeps the bright pulse on the 1px stroke instead of washing the body. */ - content: ''; - position: absolute; - inset: 0; - border-radius: inherit; - padding: 1px; - pointer-events: none; - background: linear-gradient( - to right, - transparent 0%, - transparent calc(var(--sweep, -20%) - 8%), - color-mix(in srgb, var(--red) 85%, transparent) var(--sweep, -20%), - transparent calc(var(--sweep, -20%) + 8%), - transparent 100% - ); - -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - animation: memory-synapse-sweep 6.2s linear infinite; - animation-delay: 0.4s; -} -#memory-list .memory-item:nth-child(2n)::after { animation-duration: 7.4s; animation-delay: 1.6s; } -#memory-list .memory-item:nth-child(3n)::after { animation-duration: 8.8s; animation-delay: 3.2s; } -#memory-list .memory-item:nth-child(5n)::after { animation-duration: 9.3s; animation-delay: 4.7s; } -#memory-list .memory-item:nth-child(7n)::after { animation-duration: 5.5s; animation-delay: 2.3s; } -#memory-list .memory-item:hover::after { animation: none; opacity: 0; } -@property --sweep { - syntax: ''; - inherits: false; - initial-value: -20%; -} -@media (prefers-reduced-motion: reduce) { - #memory-list .memory-item::after { animation: none; opacity: 0; } -} -.memory-pinned:hover { - background: color-mix(in srgb, var(--red) 4%, transparent); - border-color: var(--border); - border-left-color: var(--red); -} - -.memory-item-content { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 3px; -} - -.memory-item-text { - font-size: 11px; - line-height: 1.5; - word-break: break-word; - color: var(--fg); -} - -.memory-item-edit-input { - flex: 1; - padding: 3px 5px; - border-radius: 4px; - border: 1px solid var(--red); - background: var(--bg); - color: var(--fg); - font-family: inherit; - font-size: 11px; - min-width: 0; -} - - -/* Edit row: text input + category select side by side */ -.memory-edit-row { - display: flex; - gap: 4px; - flex: 1; - min-width: 0; -} - -.memory-edit-cat-select { - background: var(--bg); - color: var(--fg); - border: 1px solid var(--red); - border-radius: 4px; - font-family: inherit; - font-size: 9px; - padding: 2px 8px; - cursor: pointer; - flex-shrink: 0; -} - - -/* The Add-memory category select uses the same semantic control as inline - editing, but needs the full-size add-row dimensions. */ -.memory-add-row .memory-edit-cat-select { - height: 28px; - min-width: 92px; - box-sizing: border-box; - border-color: var(--border); - border-radius: 6px; - padding: 2px 8px; - font-size: 12px; -} - - -.memory-item-editing { - border-color: color-mix(in srgb, var(--red) 40%, transparent); - background: color-mix(in srgb, var(--red) 3%, transparent); -} - -.memory-menu-btn { - background: none; - border: 1px solid transparent; - color: var(--color-muted); - font-size: 18px; - width: 24px; - height: 24px; - line-height: 24px; - padding: 0; - border-radius: 6px; - cursor: pointer; - flex-shrink: 0; - opacity: 0; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.memory-item:hover .memory-menu-btn { - opacity: 1; -} - -.memory-menu-btn:hover { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: var(--border); - color: var(--fg); -} - -.memory-item-dropdown { - display: none; - position: fixed; - z-index: 1000; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - padding: 4px; - box-shadow: 0 4px 16px rgba(0,0,0,0.3); - min-width: auto; - width: max-content; -} - -.memory-item-dropdown .dropdown-item-compact { - padding: 6px 8px; - font-size: 11px; - border-radius: 8px; - white-space: nowrap; -} - -.memory-item-dropdown .dropdown-item-compact:hover { - background: color-mix(in srgb, var(--accent) 10%, transparent); -} - -.memory-item-actions { - display: flex; - gap: 4px; - flex-shrink: 0; - margin-left: auto; - opacity: 0; - transition: opacity 0.15s; -} - -.memory-item:hover .memory-item-actions { - opacity: 1; -} - -/* Skill rows show actions at a dim opacity by default so view/run/delete are - always discoverable, then brighten on hover. */ -.memory-item.skill-row .memory-item-actions { - opacity: 0.35; - position: relative; - top: -1px; -} -.memory-item.skill-row:hover .memory-item-actions { - opacity: 1; -} - -.memory-item-btn { - background: none; - border: 1px solid transparent; - color: var(--color-muted); - font-size: 11px; - height: 22px; - padding: 0 6px; - border-radius: 6px; - cursor: pointer; - transition: all 0.15s; - display: flex; - align-items: center; -} -@media (max-width: 768px) { - /* Nudge the ••• menu button up on mobile so it visually aligns with the - title row rather than sitting a hair below it. */ - .memory-item-actions .memory-item-btn { transform: translateY(-3px); } - /* Email rows sit on a slightly different baseline (extra meta row / - nav-arrows cluster), so pull the menu button back down 1px. */ - #email-lib-modal .memory-item-actions .memory-item-btn { transform: translateY(-2px); } - /* Base rule above hides .memory-item-actions until hover. Mobile has no - hover → the ⋮ button in cookbook serve / library cards was invisible - and effectively unclickable. Force-show on mobile. */ - .memory-item-actions { opacity: 0.7 !important; } - .memory-item-actions .memory-item-btn { - width: 32px; - height: 32px; - min-width: 32px; - } -} - -/* Research-preview sub-sections — used by the research-tab expand pattern. */ -.doclib-research-section-label { - font-size: 9px; - font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - opacity: 0.55; - margin: 8px 0 4px; -} -.doclib-research-sources ol { - margin: 0; - padding-left: 18px; - font-size: 11px; - line-height: 1.5; -} -.doclib-research-sources a { - color: var(--accent, var(--red)); - text-decoration: none; -} -.doclib-research-sources a:hover { text-decoration: underline; } -.doclib-research-summary { - font-size: 11px; - line-height: 1.5; -} -.doclib-research-summary p { margin: 4px 0; } - -.memory-item-btn:hover { - color: var(--fg); - border-color: var(--border); - background: color-mix(in srgb, var(--fg) 6%, transparent); -} - -.memory-item-btn.delete:hover { - color: var(--color-error); - border-color: var(--color-error); -} - -.memory-item-btn.save { - color: var(--red); -} - -.memory-item-btn.save:hover { - border-color: var(--red); -} - -.memory-item-btn.pin { - padding: 1px 4px; - opacity: 0.4; - display: flex; - align-items: center; - justify-content: center; -} -.memory-item-btn.pin.active { - opacity: 1; -} -.memory-pin-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--fg); - opacity: 0.5; - transition: all 0.15s; -} -.memory-item-btn.pin.active .memory-pin-dot { - background: var(--red); - opacity: 1; -} -.memory-pinned { - border-left: 3px solid var(--red); - border-radius: 4px; - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.memory-pinned .memory-item-actions { - opacity: 1; -} -.memory-pinned .memory-item-actions .memory-item-btn:not(.pin) { - opacity: 0; -} -.memory-pinned:hover .memory-item-actions .memory-item-btn:not(.pin) { - opacity: 1; -} - -/* Category filter chips */ -.memory-category-filters { - display: flex; - gap: 4px; - flex-wrap: wrap; -} - -.memory-cat-chip { - background: none; - border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-size: 10px; - height: 22px; - padding: 0 8px; - display: inline-flex; - align-items: center; - border-radius: 10px; - cursor: pointer; - font-family: inherit; - transition: all 0.15s; - text-transform: lowercase; -} - -.memory-cat-chip:hover { - border-color: var(--red); - color: var(--red); -} - -.memory-cat-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} - -@media (max-width: 768px) { - .memory-category-filters:has(.memory-cat-chip), - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags { - flex-wrap: nowrap !important; - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - touch-action: pan-x; - } - .memory-category-filters:has(.memory-cat-chip)::-webkit-scrollbar, - .gallery-tag-chips::-webkit-scrollbar, - .gallery-album-chips::-webkit-scrollbar, - .gallery-ai-tags::-webkit-scrollbar { - display: none; - } - .memory-category-filters:has(.memory-cat-chip) .memory-cat-chip, - .gallery-tag-chips .gallery-chip, - .gallery-album-chips .gallery-chip, - .gallery-ai-tags .gallery-ai-chip { - flex: 0 0 auto; - } -} - -/* Sort select — styled to match the .email-filter-btn look: - appearance reset + custom caret so the native chevron doesn't - collide with our icon/border treatment. */ -.memory-sort-select { - position: relative; - top: 3px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - font-family: inherit; - font-size: 11px; - height: 24px; - padding: 0 22px 0 8px; - cursor: pointer; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: right 7px center; - background-size: 10px 10px; - transition: border-color 0.12s, background 0.12s; -} -.memory-sort-select:hover { - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); -} -.memory-sort-select:focus { - outline: none; - border-color: var(--red); -} - -/* Memory sort wrapper: positions the SVG icon over the left - edge of the native , mirroring the email filter btn. */ -.memory-sort-wrap { - position: relative; - display: inline-flex; - align-items: center; - flex-shrink: 0; - left: -2px; -} -.memory-sort-wrap .memory-sort-select { - padding-left: 26px; -} -.memory-sort-icon { - position: absolute; - left: 7px; - top: calc(50% + 1px); - transform: translateY(-50%); - display: inline-flex; - align-items: center; - justify-content: center; - width: 13px; - height: 13px; - color: var(--accent, var(--red)); - pointer-events: none; - z-index: 1; -} -.memory-sort-icon svg { width: 13px; height: 13px; } - -.email-folder-picker { - position: relative; - top: 0; -} -.email-folder-picker-icon { - position: absolute; - left: 8px; - top: calc(50% + 3px); - transform: translateY(-50%); - display: inline-flex; - align-items: center; - justify-content: center; - width: 13px; - height: 13px; - color: var(--accent, var(--red)); - pointer-events: none; - z-index: 1; -} -.email-folder-picker-icon svg { - width: 13px; - height: 13px; -} - -/* Custom email filter picker (All / Unread / Favorites / …). Replaces the - native so options can carry SVG icons. The hidden source - is the value store and dispatches - 'change' on selection. */ -.email-filter-picker { - position: relative; - top: 3px; -} -/* Desktop nudge — sits 2px higher than the mobile default so it aligns - with the neighbouring memory-sort-select baseline. */ -@media (min-width: 769px) { - .email-filter-picker { top: 1px; } -} -.email-filter-btn { - display: flex; - align-items: center; - gap: 6px; - width: 100%; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - font-family: inherit; - font-size: 11px; - height: 24px; - padding: 0 8px 0 8px; - cursor: pointer; - text-align: left; - transition: border-color 0.12s, background 0.12s; - position: relative; - top: -4px; -} -.email-filter-btn:hover { border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); } -.email-filter-btn:focus-visible { - outline: none; - border-color: var(--accent, var(--red)); -} -.email-filter-current { - flex: 1; - display: inline-flex; - align-items: center; - gap: 6px; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-filter-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 13px; - height: 13px; - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-filter-icon svg { width: 13px; height: 13px; } -.email-filter-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-filter-caret { - flex-shrink: 0; - opacity: 0.6; - transition: transform 0.15s; -} -.email-filter-picker .email-filter-btn[aria-expanded="true"] .email-filter-caret { - transform: rotate(180deg); -} - -.email-filter-menu { - position: absolute; - top: calc(100% + 4px); - left: 0; - right: auto; - z-index: 100; - width: max(100%, 190px); - max-height: 320px; - overflow-y: auto; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 6px 20px rgba(0,0,0,0.22); - padding: 4px; - display: flex; - flex-direction: column; - gap: 1px; -} -.email-filter-menu[hidden] { display: none; } -.email-filter-menu-title { - padding: 5px 9px 4px; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - opacity: 0.65; - white-space: nowrap; -} -@media (max-width: 768px) { - .email-filter-menu { - width: min(210px, calc(100vw - 24px)); - } - .email-folder-menu { - width: min(230px, calc(100vw - 24px)); - } -} -.email-filter-group { - font-size: 9px; - font-weight: 600; - letter-spacing: 0.6px; - text-transform: uppercase; - opacity: 0.55; - padding: 8px 9px 3px; -} -.email-filter-item { - all: unset; - display: flex; - align-items: center; - gap: 8px; - width: 100%; - padding: 6px 9px; - border-radius: 5px; - font-size: 12px; - cursor: pointer; - color: var(--fg); - box-sizing: border-box; -} -.email-filter-item:hover, -.email-filter-item:focus-visible { - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.email-filter-item-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-filter-item-icon svg { width: 13px; height: 13px; } -.email-filter-item-label { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* Item metadata row */ -.memory-item-meta { - display: flex; - gap: 6px; - align-items: center; - flex-wrap: wrap; -} - -/* Category badge on each item */ -.memory-cat-badge { - font-size: 10px; - padding: 2px 6px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--accent, var(--red)); - text-transform: lowercase; -} - -.memory-cat-identity { - background: color-mix(in srgb, var(--hl-function) 15%, transparent); - color: var(--hl-function); -} -.memory-cat-preference { - background: color-mix(in srgb, var(--hl-keyword) 15%, transparent); - color: var(--hl-keyword); -} -.memory-cat-contact { - background: color-mix(in srgb, #98c379 15%, transparent); - color: #98c379; -} -.memory-cat-project { - background: color-mix(in srgb, var(--hl-string) 15%, transparent); - color: var(--hl-string); -} -.memory-cat-goal { - background: color-mix(in srgb, var(--red) 15%, transparent); - color: var(--red); -} -.memory-cat-task { - background: color-mix(in srgb, #d19a66 15%, transparent); - color: #d19a66; -} -.memory-cat-pinned { - background: color-mix(in srgb, var(--red) 15%, transparent); - color: var(--red); -} - -/* Source and time metadata */ -.memory-item-source, -.memory-item-time, -.memory-item-uses { - font-size: 9px; - color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.memory-item-uses { - font-family: monospace; - color: color-mix(in srgb, var(--fg) 55%, transparent); -} - - -/* Empty state */ -.memory-empty { - padding: 24px 16px; - color: color-mix(in srgb, var(--fg) 35%, transparent); - text-align: center; - font-size: 11px; - font-style: italic; -} - -/* Suggestions area */ -.memory-suggestions { - display: flex; - flex-direction: column; - gap: 6px; - /* Bound the import-review list to the modal like the sibling .memory-list, - so a long list scrolls internally instead of overflowing the - overflow:hidden .admin-card — which clipped lower entries and their - save/discard controls with no usable scroll area. */ - flex: 1; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; - /* Small gutter so the scrollbar doesn't sit flush against the item cards. */ - padding-right: 4px; -} - -.memory-suggestions.hidden { - display: none; -} - -.memory-suggestions-header { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 70%, transparent); - padding-bottom: 4px; - border-bottom: 1px solid var(--border); - /* Pin the title + save all/back controls to the top of the scrolling - review list so they stay reachable while the items scroll under them. - Opaque background masks items passing beneath. */ - position: sticky; - top: 0; - z-index: 1; - background: var(--panel); -} -.memory-suggestions-actions, -.memory-suggestion-actions { - display: flex; - align-items: center; - gap: 5px; - flex-shrink: 0; -} .memory-suggestions-actions { position: relative; left: -4px; @@ -5273,738 +3197,3 @@ body:has(.doc-version-panel:not(.hidden)) .hamburger-btn { cursor: pointer; transition: background 0.08s; } -.admin-slider::before { - content: ''; - position: absolute; - left: 2px; - top: 2px; - width: 12px; - height: 12px; - background: var(--panel); - border-radius: 50%; - transition: transform 0.08s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); -} -.admin-switch input:checked + .admin-slider { - background: var(--red); -} -.admin-switch input:checked + .admin-slider::before { - transform: translateX(14px); -} - -/* User rows */ -.admin-user-row { - display: flex; - flex-direction: column; - padding: 10px 12px; - border: 1px solid var(--border); - border-radius: 8px; - margin-bottom: 6px; - transition: border-color 0.15s; -} -.admin-user-row:hover { - border-color: color-mix(in srgb, var(--fg) 20%, var(--border)); -} -.admin-user-info { - display: flex; - align-items: center; - gap: 8px; -} -.admin-user-name { font-size: 13px; font-weight: 500; } -/* Privilege panel */ -.admin-priv-panel { - max-height: 600px; - transition: max-height 0.3s ease, opacity 0.2s ease, padding 0.2s ease; - overflow: hidden; -} -.admin-priv-panel.hidden { - max-height: 0 !important; - opacity: 0; - padding-top: 0 !important; - padding-bottom: 0 !important; - margin-top: 0 !important; - border-top: none !important; -} -/* Privilege toggle rows */ -.admin-priv-panel [data-priv] { - accent-color: var(--accent, var(--red)); -} -/* Section headers */ -.admin-priv-section { - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.5px; - opacity: 0.35; - font-weight: 600; - margin: 10px 0 4px; -} -.admin-priv-section:first-child { - margin-top: 0; -} -/* Model checkbox list */ -.priv-models-list { - max-height: 150px; - overflow-y: auto; - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 6px; - background: var(--bg); - scrollbar-width: thin; -} -.priv-models-list::-webkit-scrollbar { - width: 4px; -} -.priv-models-list::-webkit-scrollbar-thumb { - background: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 2px; -} -.priv-models-list label { - display: flex; - align-items: center; - gap: 6px; - padding: 3px 2px; - font-size: 11px; - cursor: pointer; - border-radius: 3px; - transition: background 0.1s; -} -.priv-models-list label:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.priv-models-list input[type="checkbox"] { - accent-color: var(--accent, var(--red)); - margin: 0; - flex-shrink: 0; -} -/* All/None links */ -.priv-models-all, -.priv-models-none { - font-size: 10px; - opacity: 0.5; - color: var(--fg); - text-decoration: none; - cursor: pointer; - transition: opacity 0.1s; -} -.priv-models-all:hover, -.priv-models-none:hover { - opacity: 1; -} - -/* MCP tool toggles panel */ -.mcp-tools-panel { - width: 100%; - padding: 8px 0 4px; - border-top: 1px solid var(--border); - margin-top: 8px; - max-height: 600px; - transition: max-height 0.3s ease, opacity 0.2s ease, padding 0.2s ease; - overflow: hidden; -} -.mcp-tools-panel.hidden { - max-height: 0 !important; - opacity: 0; - padding-top: 0 !important; - padding-bottom: 0 !important; - margin-top: 0 !important; -} -.mcp-tools-panel .mcp-tools-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 6px; -} -.mcp-tools-panel .mcp-tools-header span:first-child { - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.5px; - opacity: 0.35; - font-weight: 600; -} -.mcp-tools-panel .mcp-tools-header a { - font-size: 10px; - opacity: 0.5; - color: var(--fg); - text-decoration: none; - cursor: pointer; - transition: opacity 0.1s; -} -.mcp-tools-panel .mcp-tools-header a:hover { - opacity: 1; -} -.mcp-tools-list { - max-height: 200px; - overflow-y: auto; - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.mcp-tools-list::-webkit-scrollbar { - width: 4px; -} -.mcp-tools-list::-webkit-scrollbar-thumb { - background: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 2px; -} -.mcp-tools-list label { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 6px; - font-size: 11px; - cursor: pointer; - border-radius: 3px; - min-width: 0; -} -.mcp-tools-list label > span { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; -} -.mcp-tools-list label:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.mcp-tools-list input[type="checkbox"] { - accent-color: var(--accent, var(--red)); - margin: 0; - flex-shrink: 0; -} -/* Dot-style toggle (mirrors .note-check-dot) for the Add Models row, - replacing the native checkbox while keeping it for click handling. */ -.adm-cb-hidden { - position: absolute; - width: 0; - height: 0; - opacity: 0; - pointer-events: none; -} -.adm-check-dot { - /* Lock the dot to a fixed 12×12 box. The parent rule - `.mcp-tools-list label > span { flex: 1 }` was stretching this span - to fill the row when it didn't have its own flex override. */ - flex: 0 0 12px !important; - width: 12px !important; - height: 12px; - min-width: 12px; - max-width: 12px; - box-sizing: border-box; - border-radius: 50%; - border: 1.5px solid color-mix(in srgb, var(--fg) 35%, transparent); - position: relative; - transition: background 0.2s, border-color 0.2s, transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1); -} -.adm-model-row { - display: flex; - align-items: center; - gap: 8px; - min-height: 28px; - padding: 2px 4px; - box-sizing: border-box; - width: 100%; -} -.adm-model-label { - display: flex; - align-items: center; - gap: 8px; - flex: 1 1 auto; - min-width: 0; - cursor: pointer; -} -.adm-model-name { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - flex: 1; -} -.adm-model-tools-col { - display: inline-flex; - align-items: center; - gap: 6px; - flex: 0 0 auto; - margin-left: auto; -} -.adm-model-tools-label { - font-size: 10px; - opacity: 0.45; - flex-shrink: 0; - user-select: none; -} -.adm-model-tool-mode, -.admin-tools-select { - height: 24px; - min-height: 24px; - line-height: 22px; - font-size: 11px; - padding: 0 18px 0 6px; - width: 124px; - max-width: 130px; - flex-shrink: 0; - border-radius: 4px; - border: 1px solid var(--border); - background-color: var(--select-bg); - color: var(--select-fg); - box-sizing: border-box; - vertical-align: middle; -} -@media (max-width: 480px) { - .adm-model-tool-mode, - .admin-tools-select { - width: 105px; - max-width: 110px; - font-size: 10px; - padding: 0 14px 0 4px; - } -} -.adm-model-row:hover .adm-check-dot { - border-color: var(--accent, var(--red)); - transform: scale(1.15); -} -.adm-cb-hidden:checked + .adm-check-dot { - background: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -/* Added Models bulk selection uses the shared dot, without a check glyph. */ -.adm-endpoint-select-dot { - flex-basis: 11px !important; - width: 11px !important; - height: 11px; - min-width: 11px; - max-width: 11px; -} -/* Disabled endpoints — dim the row but keep the toggle/delete buttons - at full opacity so the user can re-enable or remove without squinting. */ -.admin-user-row.admin-ep-disabled { opacity: 0.55; } - - -.admin-user-row.adm-ep-just-added { - border-radius: 8px; - animation: adm-ep-just-added-glow 2.2s ease-out; -} -.admin-user-row.admin-ep-disabled .admin-btn-sm, -.admin-user-row.admin-ep-disabled .admin-btn-delete, -.admin-user-row.admin-ep-disabled .admin-badge-off { opacity: 1; } -/* Local / API subsection labels inside the Endpoints card */ -.adm-ep-section-head { - display: flex; - align-items: center; - font-size: 10px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; - color: var(--fg); - opacity: 0.5; - margin-bottom: 4px; -} - -/* Collapsible Add-Models subsections (API / Local) — the whole header row - acts as a toggle so a long cloud-API form can be tucked away when you - only want to paste a local URL. */ -.adm-section-toggle { - cursor: pointer; - user-select: none; - opacity: 0.8; - border: 1px solid var(--border); - border-radius: 6px; - padding: 6px 9px; - margin-bottom: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - transition: border-color 0.12s, background 0.12s, opacity 0.12s; -} -.adm-section-toggle:hover { - opacity: 1; - border-color: var(--red); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.adm-section-toggle .adm-section-caret { opacity: 0.6; } -/* When expanded, square off the bottom so the header reads as attached to - the form it controls. */ -.adm-add-section:not(.collapsed) .adm-section-toggle { - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - margin-bottom: 0; -} -.adm-section-caret { - margin-left: auto; - flex-shrink: 0; - transition: transform 0.18s ease; -} -.adm-quickstart-toggle { - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - user-select: none; - opacity: 0.72; - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 8px; - font-size: 11px; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.adm-quickstart-toggle:hover { - opacity: 1; - border-color: var(--red); -} -.adm-quickstart-body { - display: flex; - align-items: center; - gap: 6px; - border: 1px solid var(--border); - border-top: 0; - border-radius: 0 0 6px 6px; - padding: 6px 8px; -} - -/* Custom provider picker (logo + name) replacing the native */ -.adm-provider-picker { position: relative; margin-bottom: 6px; } -.adm-provider-combo { - display: flex; - align-items: stretch; -} -.adm-provider-combo input { - flex: 1; - min-width: 0; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.adm-provider-btn { - width: 100%; - display: flex; align-items: center; gap: 8px; - background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 5px 8px; font-family: inherit; font-size: 12px; - cursor: pointer; text-align: left; -} -.adm-provider-combo .adm-provider-btn { - width: 128px; - flex-shrink: 0; - border-left: 0; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - justify-content: space-between; -} -.adm-provider-btn:hover { border-color: color-mix(in srgb, var(--fg) 30%, var(--border)); } -.adm-provider-current { flex: 1; display: flex; align-items: center; gap: 8px; min-width: 0; } -.adm-provider-logo { - width: 14px; height: 14px; - display: inline-flex; align-items: center; justify-content: center; - flex-shrink: 0; color: var(--fg); opacity: 0.85; -} -.adm-provider-logo svg { width: 14px; height: 14px; } -/* Pull the settings-select 4px closer to its logo chip so the row - reads as logo+select instead of having an extra gap. */ -.adm-model-logo + .settings-select { margin-left: -4px; } -/* Fallback rows: stretch the model select so the trash button on - the right aligns with the right edge of the Endpoint and Model - dropdowns above the fallback list. Without flex:1 the model - select shrinks to its content and the trash button rides up - tight to it. */ -.settings-fallback-row > .settings-select { flex: 1 1 0; min-width: 0; } -/* Constrain logo SVGs in settings dropdown chips. Many provider - icons in providers.js declare only viewBox (no width/height), - so without this rule the browser falls back to 300x150 and - blows up the row. */ -.settings-fallback-row > span[style*="width:18px"] svg:not([width]), -.settings-row > span[style*="width:18px"] svg:not([width]), -span[id$="-logo"] svg:not([width]) { - width: 100%; - height: 100%; - display: block; -} -.adm-provider-logo:empty { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 50%; -} -.adm-provider-caret { flex-shrink: 0; transition: transform 0.15s; } -.adm-provider-picker:has(.adm-provider-menu:not(.hidden)) .adm-provider-caret { - transform: rotate(180deg); -} -.adm-provider-menu { - position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 100; - max-height: 280px; overflow-y: auto; - background: var(--panel); - border: 1px solid var(--border); border-radius: 6px; - box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); - padding: 4px; -} -.adm-provider-menu.hidden { display: none; } -.adm-provider-item { - display: flex; align-items: center; gap: 8px; - padding: 5px 8px; border-radius: 4px; - font-size: 12px; cursor: pointer; -} -.adm-provider-item:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } -.adm-provider-item.active { background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); } - -/* When the Appearance tab is open, the #settings-modal-opacity slider (in - the header) fades the modal so the user can see the rest of the UI react - as toggles flip. The fade is applied as a background color-mix in JS - (settings.js) rather than element opacity, so the controls/text stay - crisp — mirroring the Theme customizer's opacity slider. */ - -/* Search provider fallback chain — chips + drag-reorder */ -.search-fallback-chain { - flex: 1; display: flex; align-items: center; - flex-wrap: wrap; gap: 6px; min-height: 28px; -} -.search-fb-chip { - display: inline-flex; align-items: center; gap: 4px; - padding: 3px 4px 3px 6px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--border); border-radius: 10px; - font-size: 11px; cursor: grab; user-select: none; -} -.search-fb-chip.dragging { opacity: 0.4; } -.search-fb-grip { opacity: 0.35; font-size: 10px; line-height: 1; cursor: grab; } -.search-fb-logo { - width: 12px; height: 12px; - display: inline-flex; align-items: center; justify-content: center; - flex-shrink: 0; opacity: 0.85; -} -.search-fb-logo svg { width: 12px; height: 12px; } -.search-fb-remove { - background: none; border: none; color: var(--fg); - opacity: 0.45; cursor: pointer; - font-size: 14px; line-height: 1; padding: 0 2px; - transition: opacity 0.15s, color 0.15s; -} -.search-fb-remove:hover { opacity: 1; color: var(--red); } -.search-fb-add { - background: var(--bg); color: var(--fg); - border: 1px dashed var(--border); border-radius: 10px; - padding: 2px 6px; font-family: inherit; font-size: 11px; - outline: none; cursor: pointer; -} -.search-fb-add:focus, -.search-fb-add:hover { - border-color: var(--accent, var(--red)); - border-style: solid; -} - -.mcp-tools-search { - width: 100%; - box-sizing: border-box; - margin-bottom: 6px; - padding: 5px 8px; - font-size: 11px; - font-family: inherit; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - outline: none; -} -.mcp-tools-search:focus { - border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); -} -.mcp-tools-count { - font-size: 11px; - font-weight: 600; - opacity: 0.7; -} - -/* Badges */ -.admin-badge { - font-size: 10px; - padding: 2px 6px; - border-radius: 4px; - background: color-mix(in srgb, var(--red) 20%, transparent); - color: var(--red); - font-weight: 600; -} -.admin-badge-off { - background: color-mix(in srgb, var(--color-error) 20%, transparent); - color: var(--color-error); -} - -/* Buttons */ -.admin-btn-delete { - background: none; - border: 1px solid color-mix(in srgb, var(--color-error) 27%, transparent); - color: var(--color-error); - padding: 4px 10px; - border-radius: 6px; - cursor: pointer; - font-size: 11px; - font-family: inherit; - transition: all 0.15s; -} -.admin-btn-delete:hover { - background: var(--color-error); - border-color: var(--color-error); - color: #fff; -} -.admin-btn-add { - padding: 4px 10px; - border: 1px solid var(--accent, var(--red)); - border-radius: 6px; - background: var(--accent, var(--red)); - color: var(--bg); - cursor: pointer; - font-weight: 600; - font-size: 11px; - font-family: inherit; - box-sizing: border-box; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.admin-btn-add:hover { - background: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); - opacity: 0.9; -} -.admin-btn-add:disabled { - opacity: 0.5; - cursor: not-allowed; -} -.admin-btn-sm { - padding: 3px 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel); - color: var(--fg); - cursor: pointer; - font-size: 11px; -} -.admin-btn-sm:hover { - background: var(--border); - border-color: var(--red); -} -.admin-spinner { - display: inline-block; - width: 12px; - height: 12px; - border: 2px solid var(--border); - border-top-color: var(--red); - border-radius: 50%; - animation: admin-spin 0.6s linear infinite; - vertical-align: -2px; - margin-right: 2px; -} - -/* Forms */ -.admin-add-form { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 8px; -} -.admin-add-form input { - flex: 1; - min-width: 120px; - padding: 5px 8px; - height: 32px; - box-sizing: border-box; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-family: inherit; - font-size: 12px; -} -.admin-add-form input:focus { outline: none; border-color: var(--red); } -.admin-switch-inline { - display: flex; - align-items: center; - gap: 6px; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - cursor: default; -} - -.admin-model-form { - display: flex; - flex-direction: column; - gap: 6px; - margin-bottom: 8px; -} -.admin-model-form input, -.admin-model-form select { - padding: 5px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-model-form input:focus { outline: none; border-color: var(--red); } -.admin-model-form-row { - display: flex; - gap: 6px; - flex-wrap: wrap; /* let buttons drop to a new row on narrow widths - instead of overflowing the modal */ - align-items: center; -} -.admin-model-form-row input { - flex: 1 1 180px; /* api-key input takes the long axis but allows - dropping below the buttons at small widths */ - min-width: 0; /* don't refuse to shrink past content width */ -} -.admin-model-form-row select { - flex: 0 0 auto; -} -.admin-model-form-row button { - flex: 0 0 auto; - white-space: nowrap; -} -/* On narrow screens, give buttons their own row + push the Add button - to the right so it remains the obvious primary action. */ -@media (max-width: 540px) { - .admin-model-form-row input { flex-basis: 100%; } - .admin-model-form-row .admin-btn-add { margin-left: auto; } -} -.adm-ep-inline-msg { - min-height: 16px; - margin-top: 5px; - font-size: 11px; -} - -/* Endpoint items */ -.admin-ep-item { - display: flex; - align-items: center; - gap: 6px; - padding: 6px 8px; - background: var(--bg); - border-radius: 6px; - margin-bottom: 4px; - font-size: 12px; -} -.admin-ep-info { flex: 1; overflow: hidden; } -.admin-ep-name { color: var(--fg); font-weight: 500; } -.admin-ep-detail { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 10px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-top: -4px; -} - -/* Settings panel — tint each card's h2 title icon with the user's accent - colour. Direct-child selector (`> h2 > svg`) so SVGs nested inside - header buttons (Test, kebab, etc.) are untouched. Each title icon uses - stroke="currentColor", so setting color propagates; existing inline - opacity:0.6 stays so the tint reads soft. */ -/* Selected integration card — class beats inline borderColor reset */ -.intg-card.intg-card-active { border-color: var(--accent, var(--red)) !important; } - -/* Edge-dock resize handle: invisible at rest, accent stripe animates in - on hover. Inline background:transparent + transition come from - modalSnap.js; this rule paints the accent gradient on hover, beating - the inline style with !important. */ -.edge-dock-resize-handle-left:hover { - background: linear-gradient(to right, transparent 0 3px, color-mix(in srgb, var(--accent, var(--red)) 35%, transparent) 3px 7px, transparent 7px 10px) !important; -} -.edge-dock-resize-handle-right:hover { - background: linear-gradient(to left, transparent 0 3px, color-mix(in srgb, var(--accent, var(--red)) 35%, transparent) 3px 7px, transparent 7px 10px) !important; -} - -[data-settings-panel] .admin-card > h2 > svg, -#theme-popup .admin-card > h2 > svg, -[data-settings-panel="added-models"] .adm-ep-section-head > svg { - color: var(--accent, var(--red)); -} diff --git a/static/css/06-admin-settings.css b/static/css/06-admin-settings.css new file mode 100644 index 000000000..4c2e95a30 --- /dev/null +++ b/static/css/06-admin-settings.css @@ -0,0 +1,1426 @@ +/* 06-admin-settings - 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. + */ +.admin-slider::before { + content: ''; + position: absolute; + left: 2px; + top: 2px; + width: 12px; + height: 12px; + background: var(--panel); + border-radius: 50%; + transition: transform 0.08s; + box-shadow: 0 1px 2px rgba(0,0,0,0.25); +} +.admin-switch input:checked + .admin-slider { + background: var(--red); +} +.admin-switch input:checked + .admin-slider::before { + transform: translateX(14px); +} + +/* User rows */ +.admin-user-row { + display: flex; + flex-direction: column; + padding: 10px 12px; + border: 1px solid var(--border); + border-radius: 8px; + margin-bottom: 6px; + transition: border-color 0.15s; +} +.admin-user-row:hover { + border-color: color-mix(in srgb, var(--fg) 20%, var(--border)); +} +.admin-user-info { + display: flex; + align-items: center; + gap: 8px; +} +.admin-user-name { font-size: 13px; font-weight: 500; } +/* Privilege panel */ +.admin-priv-panel { + max-height: 600px; + transition: max-height 0.3s ease, opacity 0.2s ease, padding 0.2s ease; + overflow: hidden; +} +.admin-priv-panel.hidden { + max-height: 0 !important; + opacity: 0; + padding-top: 0 !important; + padding-bottom: 0 !important; + margin-top: 0 !important; + border-top: none !important; +} +/* Privilege toggle rows */ +.admin-priv-panel [data-priv] { + accent-color: var(--accent, var(--red)); +} +/* Section headers */ +.admin-priv-section { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; + opacity: 0.35; + font-weight: 600; + margin: 10px 0 4px; +} +.admin-priv-section:first-child { + margin-top: 0; +} +/* Model checkbox list */ +.priv-models-list { + max-height: 150px; + overflow-y: auto; + border: 1px solid var(--border); + border-radius: 6px; + padding: 4px 6px; + background: var(--bg); + scrollbar-width: thin; +} +.priv-models-list::-webkit-scrollbar { + width: 4px; +} +.priv-models-list::-webkit-scrollbar-thumb { + background: color-mix(in srgb, var(--fg) 15%, transparent); + border-radius: 2px; +} +.priv-models-list label { + display: flex; + align-items: center; + gap: 6px; + padding: 3px 2px; + font-size: 11px; + cursor: pointer; + border-radius: 3px; + transition: background 0.1s; +} +.priv-models-list label:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.priv-models-list input[type="checkbox"] { + accent-color: var(--accent, var(--red)); + margin: 0; + flex-shrink: 0; +} +/* All/None links */ +.priv-models-all, +.priv-models-none { + font-size: 10px; + opacity: 0.5; + color: var(--fg); + text-decoration: none; + cursor: pointer; + transition: opacity 0.1s; +} +.priv-models-all:hover, +.priv-models-none:hover { + opacity: 1; +} + +/* MCP tool toggles panel */ +.mcp-tools-panel { + width: 100%; + padding: 8px 0 4px; + border-top: 1px solid var(--border); + margin-top: 8px; + max-height: 600px; + transition: max-height 0.3s ease, opacity 0.2s ease, padding 0.2s ease; + overflow: hidden; +} +.mcp-tools-panel.hidden { + max-height: 0 !important; + opacity: 0; + padding-top: 0 !important; + padding-bottom: 0 !important; + margin-top: 0 !important; +} +.mcp-tools-panel .mcp-tools-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; +} +.mcp-tools-panel .mcp-tools-header span:first-child { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.5px; + opacity: 0.35; + font-weight: 600; +} +.mcp-tools-panel .mcp-tools-header a { + font-size: 10px; + opacity: 0.5; + color: var(--fg); + text-decoration: none; + cursor: pointer; + transition: opacity 0.1s; +} +.mcp-tools-panel .mcp-tools-header a:hover { + opacity: 1; +} +.mcp-tools-list { + max-height: 200px; + overflow-y: auto; + border: 1px solid var(--border); + border-radius: 6px; + padding: 4px 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); +} +.mcp-tools-list::-webkit-scrollbar { + width: 4px; +} +.mcp-tools-list::-webkit-scrollbar-thumb { + background: color-mix(in srgb, var(--fg) 15%, transparent); + border-radius: 2px; +} +.mcp-tools-list label { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 6px; + font-size: 11px; + cursor: pointer; + border-radius: 3px; + min-width: 0; +} +.mcp-tools-list label > span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; +} +.mcp-tools-list label:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.mcp-tools-list input[type="checkbox"] { + accent-color: var(--accent, var(--red)); + margin: 0; + flex-shrink: 0; +} +/* Dot-style toggle (mirrors .note-check-dot) for the Add Models row, + replacing the native checkbox while keeping it for click handling. */ +.adm-cb-hidden { + position: absolute; + width: 0; + height: 0; + opacity: 0; + pointer-events: none; +} +.adm-check-dot { + /* Lock the dot to a fixed 12×12 box. The parent rule + `.mcp-tools-list label > span { flex: 1 }` was stretching this span + to fill the row when it didn't have its own flex override. */ + flex: 0 0 12px !important; + width: 12px !important; + height: 12px; + min-width: 12px; + max-width: 12px; + box-sizing: border-box; + border-radius: 50%; + border: 1.5px solid color-mix(in srgb, var(--fg) 35%, transparent); + position: relative; + transition: background 0.2s, border-color 0.2s, transform 0.15s cubic-bezier(0.34, 1.56, 0.64, 1); +} +.adm-model-row { + display: flex; + align-items: center; + gap: 8px; + min-height: 28px; + padding: 2px 4px; + box-sizing: border-box; + width: 100%; +} +.adm-model-label { + display: flex; + align-items: center; + gap: 8px; + flex: 1 1 auto; + min-width: 0; + cursor: pointer; +} +.adm-model-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + flex: 1; +} +.adm-model-tools-col { + display: inline-flex; + align-items: center; + gap: 6px; + flex: 0 0 auto; + margin-left: auto; +} +.adm-model-tools-label { + font-size: 10px; + opacity: 0.45; + flex-shrink: 0; + user-select: none; +} +.adm-model-tool-mode, +.admin-tools-select { + height: 24px; + min-height: 24px; + line-height: 22px; + font-size: 11px; + padding: 0 18px 0 6px; + width: 124px; + max-width: 130px; + flex-shrink: 0; + border-radius: 4px; + border: 1px solid var(--border); + background-color: var(--select-bg); + color: var(--select-fg); + box-sizing: border-box; + vertical-align: middle; +} +@media (max-width: 480px) { + .adm-model-tool-mode, + .admin-tools-select { + width: 105px; + max-width: 110px; + font-size: 10px; + padding: 0 14px 0 4px; + } +} +.adm-model-row:hover .adm-check-dot { + border-color: var(--accent, var(--red)); + transform: scale(1.15); +} +.adm-cb-hidden:checked + .adm-check-dot { + background: var(--accent, var(--red)); + border-color: var(--accent, var(--red)); +} +/* Added Models bulk selection uses the shared dot, without a check glyph. */ +.adm-endpoint-select-dot { + flex-basis: 11px !important; + width: 11px !important; + height: 11px; + min-width: 11px; + max-width: 11px; +} +/* Disabled endpoints — dim the row but keep the toggle/delete buttons + at full opacity so the user can re-enable or remove without squinting. */ +.admin-user-row.admin-ep-disabled { opacity: 0.55; } + + +.admin-user-row.adm-ep-just-added { + border-radius: 8px; + animation: adm-ep-just-added-glow 2.2s ease-out; +} +.admin-user-row.admin-ep-disabled .admin-btn-sm, +.admin-user-row.admin-ep-disabled .admin-btn-delete, +.admin-user-row.admin-ep-disabled .admin-badge-off { opacity: 1; } +/* Local / API subsection labels inside the Endpoints card */ +.adm-ep-section-head { + display: flex; + align-items: center; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--fg); + opacity: 0.5; + margin-bottom: 4px; +} + +/* Collapsible Add-Models subsections (API / Local) — the whole header row + acts as a toggle so a long cloud-API form can be tucked away when you + only want to paste a local URL. */ +.adm-section-toggle { + cursor: pointer; + user-select: none; + opacity: 0.8; + border: 1px solid var(--border); + border-radius: 6px; + padding: 6px 9px; + margin-bottom: 6px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + transition: border-color 0.12s, background 0.12s, opacity 0.12s; +} +.adm-section-toggle:hover { + opacity: 1; + border-color: var(--red); + background: color-mix(in srgb, var(--red) 8%, transparent); +} +.adm-section-toggle .adm-section-caret { opacity: 0.6; } +/* When expanded, square off the bottom so the header reads as attached to + the form it controls. */ +.adm-add-section:not(.collapsed) .adm-section-toggle { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + margin-bottom: 0; +} +.adm-section-caret { + margin-left: auto; + flex-shrink: 0; + transition: transform 0.18s ease; +} +.adm-quickstart-toggle { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; + user-select: none; + opacity: 0.72; + border: 1px solid var(--border); + border-radius: 6px; + padding: 4px 8px; + font-size: 11px; + background: color-mix(in srgb, var(--fg) 3%, transparent); +} +.adm-quickstart-toggle:hover { + opacity: 1; + border-color: var(--red); +} +.adm-quickstart-body { + display: flex; + align-items: center; + gap: 6px; + border: 1px solid var(--border); + border-top: 0; + border-radius: 0 0 6px 6px; + padding: 6px 8px; +} + +/* Custom provider picker (logo + name) replacing the native */ +.adm-provider-picker { position: relative; margin-bottom: 6px; } +.adm-provider-combo { + display: flex; + align-items: stretch; +} +.adm-provider-combo input { + flex: 1; + min-width: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.adm-provider-btn { + width: 100%; + display: flex; align-items: center; gap: 8px; + background: var(--bg); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; + padding: 5px 8px; font-family: inherit; font-size: 12px; + cursor: pointer; text-align: left; +} +.adm-provider-combo .adm-provider-btn { + width: 128px; + flex-shrink: 0; + border-left: 0; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + justify-content: space-between; +} +.adm-provider-btn:hover { border-color: color-mix(in srgb, var(--fg) 30%, var(--border)); } +.adm-provider-current { flex: 1; display: flex; align-items: center; gap: 8px; min-width: 0; } +.adm-provider-logo { + width: 14px; height: 14px; + display: inline-flex; align-items: center; justify-content: center; + flex-shrink: 0; color: var(--fg); opacity: 0.85; +} +.adm-provider-logo svg { width: 14px; height: 14px; } +/* Pull the settings-select 4px closer to its logo chip so the row + reads as logo+select instead of having an extra gap. */ +.adm-model-logo + .settings-select { margin-left: -4px; } +/* Fallback rows: stretch the model select so the trash button on + the right aligns with the right edge of the Endpoint and Model + dropdowns above the fallback list. Without flex:1 the model + select shrinks to its content and the trash button rides up + tight to it. */ +.settings-fallback-row > .settings-select { flex: 1 1 0; min-width: 0; } +/* Constrain logo SVGs in settings dropdown chips. Many provider + icons in providers.js declare only viewBox (no width/height), + so without this rule the browser falls back to 300x150 and + blows up the row. */ +.settings-fallback-row > span[style*="width:18px"] svg:not([width]), +.settings-row > span[style*="width:18px"] svg:not([width]), +span[id$="-logo"] svg:not([width]) { + width: 100%; + height: 100%; + display: block; +} +.adm-provider-logo:empty { + background: color-mix(in srgb, var(--fg) 12%, transparent); + border-radius: 50%; +} +.adm-provider-caret { flex-shrink: 0; transition: transform 0.15s; } +.adm-provider-picker:has(.adm-provider-menu:not(.hidden)) .adm-provider-caret { + transform: rotate(180deg); +} +.adm-provider-menu { + position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 100; + max-height: 280px; overflow-y: auto; + background: var(--panel); + border: 1px solid var(--border); border-radius: 6px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25); + padding: 4px; +} +.adm-provider-menu.hidden { display: none; } +.adm-provider-item { + display: flex; align-items: center; gap: 8px; + padding: 5px 8px; border-radius: 4px; + font-size: 12px; cursor: pointer; +} +.adm-provider-item:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } +.adm-provider-item.active { background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); } + +/* When the Appearance tab is open, the #settings-modal-opacity slider (in + the header) fades the modal so the user can see the rest of the UI react + as toggles flip. The fade is applied as a background color-mix in JS + (settings.js) rather than element opacity, so the controls/text stay + crisp — mirroring the Theme customizer's opacity slider. */ + +/* Search provider fallback chain — chips + drag-reorder */ +.search-fallback-chain { + flex: 1; display: flex; align-items: center; + flex-wrap: wrap; gap: 6px; min-height: 28px; +} +.search-fb-chip { + display: inline-flex; align-items: center; gap: 4px; + padding: 3px 4px 3px 6px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid var(--border); border-radius: 10px; + font-size: 11px; cursor: grab; user-select: none; +} +.search-fb-chip.dragging { opacity: 0.4; } +.search-fb-grip { opacity: 0.35; font-size: 10px; line-height: 1; cursor: grab; } +.search-fb-logo { + width: 12px; height: 12px; + display: inline-flex; align-items: center; justify-content: center; + flex-shrink: 0; opacity: 0.85; +} +.search-fb-logo svg { width: 12px; height: 12px; } +.search-fb-remove { + background: none; border: none; color: var(--fg); + opacity: 0.45; cursor: pointer; + font-size: 14px; line-height: 1; padding: 0 2px; + transition: opacity 0.15s, color 0.15s; +} +.search-fb-remove:hover { opacity: 1; color: var(--red); } +.search-fb-add { + background: var(--bg); color: var(--fg); + border: 1px dashed var(--border); border-radius: 10px; + padding: 2px 6px; font-family: inherit; font-size: 11px; + outline: none; cursor: pointer; +} +.search-fb-add:focus, +.search-fb-add:hover { + border-color: var(--accent, var(--red)); + border-style: solid; +} + +.mcp-tools-search { + width: 100%; + box-sizing: border-box; + margin-bottom: 6px; + padding: 5px 8px; + font-size: 11px; + font-family: inherit; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--fg); + outline: none; +} +.mcp-tools-search:focus { + border-color: color-mix(in srgb, var(--accent) 60%, var(--border)); +} +.mcp-tools-count { + font-size: 11px; + font-weight: 600; + opacity: 0.7; +} + +/* Badges */ +.admin-badge { + font-size: 10px; + padding: 2px 6px; + border-radius: 4px; + background: color-mix(in srgb, var(--red) 20%, transparent); + color: var(--red); + font-weight: 600; +} +.admin-badge-off { + background: color-mix(in srgb, var(--color-error) 20%, transparent); + color: var(--color-error); +} + +/* Buttons */ +.admin-btn-delete { + background: none; + border: 1px solid color-mix(in srgb, var(--color-error) 27%, transparent); + color: var(--color-error); + padding: 4px 10px; + border-radius: 6px; + cursor: pointer; + font-size: 11px; + font-family: inherit; + transition: all 0.15s; +} +.admin-btn-delete:hover { + background: var(--color-error); + border-color: var(--color-error); + color: #fff; +} +.admin-btn-add { + padding: 4px 10px; + border: 1px solid var(--accent, var(--red)); + border-radius: 6px; + background: var(--accent, var(--red)); + color: var(--bg); + cursor: pointer; + font-weight: 600; + font-size: 11px; + font-family: inherit; + box-sizing: border-box; + transition: opacity 0.15s, background 0.15s, border-color 0.15s; +} +.admin-btn-add:hover { + background: var(--accent, var(--red)); + border-color: var(--accent, var(--red)); + opacity: 0.9; +} +.admin-btn-add:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.admin-btn-sm { + padding: 3px 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel); + color: var(--fg); + cursor: pointer; + font-size: 11px; +} +.admin-btn-sm:hover { + background: var(--border); + border-color: var(--red); +} +.admin-spinner { + display: inline-block; + width: 12px; + height: 12px; + border: 2px solid var(--border); + border-top-color: var(--red); + border-radius: 50%; + animation: admin-spin 0.6s linear infinite; + vertical-align: -2px; + margin-right: 2px; +} + +/* Forms */ +.admin-add-form { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 8px; +} +.admin-add-form input { + flex: 1; + min-width: 120px; + padding: 5px 8px; + height: 32px; + box-sizing: border-box; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font-family: inherit; + font-size: 12px; +} +.admin-add-form input:focus { outline: none; border-color: var(--red); } +.admin-switch-inline { + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + color: color-mix(in srgb, var(--fg) 60%, transparent); + cursor: default; +} + +.admin-model-form { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 8px; +} +.admin-model-form input, +.admin-model-form select { + padding: 5px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font-size: 12px; +} +.admin-model-form input:focus { outline: none; border-color: var(--red); } +.admin-model-form-row { + display: flex; + gap: 6px; + flex-wrap: wrap; /* let buttons drop to a new row on narrow widths + instead of overflowing the modal */ + align-items: center; +} +.admin-model-form-row input { + flex: 1 1 180px; /* api-key input takes the long axis but allows + dropping below the buttons at small widths */ + min-width: 0; /* don't refuse to shrink past content width */ +} +.admin-model-form-row select { + flex: 0 0 auto; +} +.admin-model-form-row button { + flex: 0 0 auto; + white-space: nowrap; +} +/* On narrow screens, give buttons their own row + push the Add button + to the right so it remains the obvious primary action. */ +@media (max-width: 540px) { + .admin-model-form-row input { flex-basis: 100%; } + .admin-model-form-row .admin-btn-add { margin-left: auto; } +} +.adm-ep-inline-msg { + min-height: 16px; + margin-top: 5px; + font-size: 11px; +} + +/* Endpoint items */ +.admin-ep-item { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 8px; + background: var(--bg); + border-radius: 6px; + margin-bottom: 4px; + font-size: 12px; +} +.admin-ep-info { flex: 1; overflow: hidden; } +.admin-ep-name { color: var(--fg); font-weight: 500; } +.admin-ep-detail { + color: color-mix(in srgb, var(--fg) 40%, transparent); + font-size: 10px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-top: -4px; +} + +/* Settings panel — tint each card's h2 title icon with the user's accent + colour. Direct-child selector (`> h2 > svg`) so SVGs nested inside + header buttons (Test, kebab, etc.) are untouched. Each title icon uses + stroke="currentColor", so setting color propagates; existing inline + opacity:0.6 stays so the tint reads soft. */ +/* Selected integration card — class beats inline borderColor reset */ +.intg-card.intg-card-active { border-color: var(--accent, var(--red)) !important; } + +/* Edge-dock resize handle: invisible at rest, accent stripe animates in + on hover. Inline background:transparent + transition come from + modalSnap.js; this rule paints the accent gradient on hover, beating + the inline style with !important. */ +.edge-dock-resize-handle-left:hover { + background: linear-gradient(to right, transparent 0 3px, color-mix(in srgb, var(--accent, var(--red)) 35%, transparent) 3px 7px, transparent 7px 10px) !important; +} +.edge-dock-resize-handle-right:hover { + background: linear-gradient(to left, transparent 0 3px, color-mix(in srgb, var(--accent, var(--red)) 35%, transparent) 3px 7px, transparent 7px 10px) !important; +} + +[data-settings-panel] .admin-card > h2 > svg, +#theme-popup .admin-card > h2 > svg, +[data-settings-panel="added-models"] .adm-ep-section-head > svg { + color: var(--accent, var(--red)); +} +.admin-ep-actions { + display: flex; + gap: 4px; + flex-shrink: 0; +} + +/* Status messages */ +.admin-error { color: var(--color-error); font-size: 11px; margin-top: 4px; } +.admin-success { color: var(--color-success); font-size: 11px; margin-top: 4px; } +.admin-empty { + color: color-mix(in srgb, var(--fg) 40%, transparent); + font-size: 12px; + padding: 10px 0; + text-align: center; +} +/* Endpoint-list empty states ("No endpoints configured" / "None") get a + left-aligned, accent-colored treatment so they read as "this row is your + placeholder, click Add" rather than a centered "nothing here" grey blob. */ +#adm-epList-local .admin-empty, +#adm-epList-api .admin-empty { + text-align: left; + padding: 8px 4px; + color: var(--accent-primary, var(--accent, var(--red))); +} + +/* RAG upload zone */ +.admin-rag-upload-zone { + border: 2px dashed var(--border); + border-radius: 8px; + padding: 14px; + text-align: center; + cursor: pointer; + transition: border-color 0.2s, background 0.2s; + margin-bottom: 8px; +} +.admin-rag-upload-zone:hover, +.admin-rag-upload-zone.dragover { + border-color: var(--red); + background: color-mix(in srgb, var(--red) 7%, transparent); +} +.admin-rag-upload-zone p { + color: color-mix(in srgb, var(--fg) 50%, transparent); + font-size: 11px; + margin-top: 4px; +} +.admin-rag-icon { font-size: 18px; } +.admin-rag-dir-row { + display: flex; + gap: 6px; + margin-bottom: 8px; +} +.admin-rag-dir-row input { + flex: 1; + padding: 5px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font-size: 12px; +} +.admin-rag-dir-row input:focus { outline: none; border-color: var(--red); } +.admin-rag-dir-row button { + padding: 5px 8px; + border: none; + border-radius: 6px; + background: var(--red); + color: #fff; + cursor: pointer; + font-size: 12px; + white-space: nowrap; +} +.admin-rag-toolbar { + display: flex; + justify-content: flex-end; + margin-bottom: 6px; +} +.admin-rag-section-label { + font-size: 10px; + color: color-mix(in srgb, var(--fg) 50%, transparent); + text-transform: uppercase; + letter-spacing: 0.05em; + margin: 8px 0 4px; +} +.admin-rag-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 8px; + background: var(--bg); + border-radius: 6px; + margin-bottom: 4px; + font-size: 12px; +} +.admin-rag-item-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--fg); +} +.admin-rag-item-meta { + color: color-mix(in srgb, var(--fg) 40%, transparent); + font-size: 10px; + margin: 0 6px; + flex-shrink: 0; +} +.admin-rag-status { + font-size: 11px; + color: color-mix(in srgb, var(--fg) 50%, transparent); + margin-top: 6px; +} + +/* Token reveal */ +.admin-token-reveal { + margin-top: 8px; + padding: 8px; + background: color-mix(in srgb, var(--red) 8%, var(--bg)); + border: 1px solid color-mix(in srgb, var(--red) 25%, var(--border)); + border-radius: 8px; +} + +/* Selects in admin cards */ +.admin-card select { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font-size: 12px; +} +.admin-card input { + font-family: inherit; + font-size: 12px; +} +.admin-card label { + font-size: 12px; +} + +/* ---- Document Library ---- */ + +.doclib-modal-content { + width: min(600px, 92vw); + max-height: 85vh; + font-size: 12px; + background: var(--bg); +} +/* Anchor doclib modal to top so only the bottom edge moves on resize */ +#doclib-modal { + align-items: flex-start; + padding-top: 8vh; +} +.doclib-modal-content .modal-header h4 { + font-size: 1rem; +} +/* The "open in new tab" email modal inherited the 1rem doclib header, which + read way bigger than the email subject in the regular (inline) reader. Pin + it smaller so the two views are consistent. */ +.email-reader-tab-modal .modal-header h4 { + font-size: 13px; + font-weight: 600; +} +/* Match the sticky modal-header to the doclib modal-content body color + (var(--bg)) instead of the global var(--panel) default — otherwise the + header reads as a darker stripe above the email list. */ +.doclib-modal-content > .modal-header { + background: var(--bg); +} +.doclib-stats { + font-size: 11px; + color: var(--fg-muted); + margin-bottom: 8px; +} +.doclib-toolbar { + display: flex; + gap: 8px; + margin-bottom: 8px; + align-items: center; +} +.doclib-search { + flex: 1; + padding: 6px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + outline: none; + transition: border-color 0.15s; +} +#archive-select-btn { height: auto; padding: 5px 8px; } +.doclib-search:focus { + border-color: var(--red); +} +.doclib-sort { + padding: 6px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + outline: none; + cursor: pointer; +} +.doclib-chips { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-bottom: 8px; +} +.doclib-chip { + padding: 2px 10px; + border-radius: 12px; + font-size: 10px; + border: 1px solid var(--border); + background: transparent; + color: var(--fg-muted); + cursor: pointer; + user-select: none; + transition: background 0.15s, border-color 0.15s; +} +.doclib-chip:hover { + border-color: var(--red); +} +.doclib-chip.active { + background: color-mix(in srgb, var(--red) 15%, transparent); + border-color: color-mix(in srgb, var(--red) 40%, transparent); + color: var(--red); +} +/* Document library — language chip row */ +.doclib-lang-chips { + display: flex; + flex-wrap: wrap; + gap: 4px; + padding: 2px 0; + max-height: 52px; + overflow-y: auto; +} +.doclib-lang-chips:empty { display: none; } +/* Mobile: keep tag chips on ONE row and scroll horizontally instead of wrapping + into a tall multi-line block (Serve, library — anywhere .doclib-lang-chips is used). */ +@media (max-width: 768px) { + .doclib-lang-chips { + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + max-height: none; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + } + .doclib-lang-chips::-webkit-scrollbar { display: none; } + .doclib-lang-chips > * { flex-shrink: 0; } +} +#doclib-tidy-btn, #doclib-select-btn, #doclib-chats-tidy-btn { + position: relative; + top: -3px; +} +/* Document library — list layout */ +.doclib-grid { + display: flex; + flex-direction: column; + gap: 4px; + max-height: 400px; + overflow-y: auto; + padding: 0; + position: relative; +} + +/* Gallery grid domino-in cascade on open — same recipe as the document/email + libraries. Applied to #gallery-grid in gallery.js on the first render after + each open, removed after the longest delay so re-renders feel instant. */ +.gallery-just-opened > .gallery-card { + animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; +} +.gallery-just-opened > :nth-child(1) { animation-delay: 0.02s; } +.gallery-just-opened > :nth-child(2) { animation-delay: 0.04s; } +.gallery-just-opened > :nth-child(3) { animation-delay: 0.06s; } +.gallery-just-opened > :nth-child(4) { animation-delay: 0.08s; } +.gallery-just-opened > :nth-child(5) { animation-delay: 0.10s; } +.gallery-just-opened > :nth-child(6) { animation-delay: 0.12s; } +.gallery-just-opened > :nth-child(7) { animation-delay: 0.14s; } +.gallery-just-opened > :nth-child(8) { animation-delay: 0.16s; } +.gallery-just-opened > :nth-child(9) { animation-delay: 0.18s; } +.gallery-just-opened > :nth-child(10) { animation-delay: 0.20s; } +.gallery-just-opened > :nth-child(11) { animation-delay: 0.22s; } +.gallery-just-opened > :nth-child(12) { animation-delay: 0.24s; } +.gallery-just-opened > :nth-child(13) { animation-delay: 0.26s; } +.gallery-just-opened > :nth-child(14) { animation-delay: 0.28s; } +.gallery-just-opened > :nth-child(15) { animation-delay: 0.30s; } +.gallery-just-opened > :nth-child(16) { animation-delay: 0.32s; } +.gallery-just-opened > :nth-child(17) { animation-delay: 0.34s; } +.gallery-just-opened > :nth-child(18) { animation-delay: 0.36s; } +.gallery-just-opened > :nth-child(19) { animation-delay: 0.38s; } +.gallery-just-opened > :nth-child(20) { animation-delay: 0.40s; } +.gallery-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } + +/* Tasks list domino-in cascade on open — mirrors gallery / doclib. Applied to + #tasks-list in tasks.js on the first render after each open, removed after + the longest delay so re-renders feel instant. */ +.tasks-just-opened > .task-card { + animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; +} +.tasks-just-opened > :nth-child(1) { animation-delay: 0.02s; } +.tasks-just-opened > :nth-child(2) { animation-delay: 0.04s; } +.tasks-just-opened > :nth-child(3) { animation-delay: 0.06s; } +.tasks-just-opened > :nth-child(4) { animation-delay: 0.08s; } +.tasks-just-opened > :nth-child(5) { animation-delay: 0.10s; } +.tasks-just-opened > :nth-child(6) { animation-delay: 0.12s; } +.tasks-just-opened > :nth-child(7) { animation-delay: 0.14s; } +.tasks-just-opened > :nth-child(8) { animation-delay: 0.16s; } +.tasks-just-opened > :nth-child(9) { animation-delay: 0.18s; } +.tasks-just-opened > :nth-child(10) { animation-delay: 0.20s; } +.tasks-just-opened > :nth-child(11) { animation-delay: 0.22s; } +.tasks-just-opened > :nth-child(12) { animation-delay: 0.24s; } +.tasks-just-opened > :nth-child(13) { animation-delay: 0.26s; } +.tasks-just-opened > :nth-child(14) { animation-delay: 0.28s; } +.tasks-just-opened > :nth-child(15) { animation-delay: 0.30s; } +.tasks-just-opened > :nth-child(16) { animation-delay: 0.32s; } +.tasks-just-opened > :nth-child(17) { animation-delay: 0.34s; } +.tasks-just-opened > :nth-child(18) { animation-delay: 0.36s; } +.tasks-just-opened > :nth-child(19) { animation-delay: 0.38s; } +.tasks-just-opened > :nth-child(20) { animation-delay: 0.40s; } +.tasks-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } + +/* Document library cascade — same recipe as email library, applied per-tab + the first time content loads (chats / archive / research / documents). + Module-level Set in documentLibrary.js prevents re-firing on tab swaps + or filter/sort re-renders within the same page session. */ +.doclib-just-opened > .memory-item, +.doclib-just-opened > .doclib-card { + animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; +} +.doclib-just-opened > :nth-child(1) { animation-delay: 0.02s; } +.doclib-just-opened > :nth-child(2) { animation-delay: 0.04s; } +.doclib-just-opened > :nth-child(3) { animation-delay: 0.06s; } +.doclib-just-opened > :nth-child(4) { animation-delay: 0.08s; } +.doclib-just-opened > :nth-child(5) { animation-delay: 0.10s; } +.doclib-just-opened > :nth-child(6) { animation-delay: 0.12s; } +.doclib-just-opened > :nth-child(7) { animation-delay: 0.14s; } +.doclib-just-opened > :nth-child(8) { animation-delay: 0.16s; } +.doclib-just-opened > :nth-child(9) { animation-delay: 0.18s; } +.doclib-just-opened > :nth-child(10) { animation-delay: 0.20s; } +.doclib-just-opened > :nth-child(11) { animation-delay: 0.22s; } +.doclib-just-opened > :nth-child(12) { animation-delay: 0.24s; } +.doclib-just-opened > :nth-child(13) { animation-delay: 0.26s; } +.doclib-just-opened > :nth-child(14) { animation-delay: 0.28s; } +.doclib-just-opened > :nth-child(15) { animation-delay: 0.30s; } +.doclib-just-opened > :nth-child(16) { animation-delay: 0.32s; } +.doclib-just-opened > :nth-child(17) { animation-delay: 0.34s; } +.doclib-just-opened > :nth-child(18) { animation-delay: 0.36s; } +.doclib-just-opened > :nth-child(19) { animation-delay: 0.38s; } +.doclib-just-opened > :nth-child(20) { animation-delay: 0.40s; } +.doclib-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } + +/* Domino cascade on first open of the email library — mirrors the sidebar + .section-just-expanded recipe so list items spring in staggered. Class + is applied to #email-lib-grid in emailLibrary.js on the first render + only, then removed after the longest delay so re-renders feel instant. */ +.email-lib-just-opened :is(.doclib-card, .date-section-header) { + animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; +} +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(1) { animation-delay: 0.02s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(2) { animation-delay: 0.04s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(3) { animation-delay: 0.06s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(4) { animation-delay: 0.08s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(5) { animation-delay: 0.10s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(6) { animation-delay: 0.12s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(7) { animation-delay: 0.14s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(8) { animation-delay: 0.16s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(9) { animation-delay: 0.18s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(10) { animation-delay: 0.20s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(11) { animation-delay: 0.22s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(12) { animation-delay: 0.24s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(13) { animation-delay: 0.26s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(14) { animation-delay: 0.28s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(15) { animation-delay: 0.30s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(16) { animation-delay: 0.32s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(17) { animation-delay: 0.34s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(18) { animation-delay: 0.36s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(19) { animation-delay: 0.38s; } +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(20) { animation-delay: 0.40s; } +/* Cap the cascade at 20 — beyond that they animate together so opening a + long inbox doesn't take forever to settle. */ +.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(n+21) { animation-delay: 0.42s; } + +/* Inside the email library modal, the grid needs to grow with the modal — + but `flex: 1 1 0` collapses to 0 when the parent isn't height-constrained + (which is the non-fullscreen default). Use `auto` basis + a sensible + `min-height` floor so it shows ~400px naturally and absorbs more when + the modal is resized / fullscreened. */ +#email-lib-modal .doclib-grid { + max-height: none; + height: auto; + flex: 1 1 auto; + min-height: 400px; +} + +/* ── Mobile compose FAB (email) ── + Replaces the top-right "New" button on phones with a round mail-icon button + bottom-right that collapses to a circle while the list scrolls and springs + back out to "New" when scrolling stops. Desktop is unchanged. */ +.email-lib-fab { display: none; } +@media (max-width: 768px) { + /* The absolute FAB anchors to the email panel card. */ + #email-lib-modal .admin-card { position: relative; } + /* Hide the toolbar "New" button — the FAB is the mobile entry point. */ + #email-lib-compose-btn { display: none; } + + #email-lib-modal .email-lib-fab { + --fab-size: 56px; + display: flex; + align-items: center; + gap: 9px; + /* Hidden until the email list has rendered; JS adds .fab-revealed to pop + it in (scale-from-center). Avoids the button flashing at the top and + sliding down before _positionFab() places it. */ + transform: scale(0); + opacity: 0; + transform-origin: center center; + position: absolute; + right: calc(16px + env(safe-area-inset-right, 0px)); + bottom: calc(18px + env(safe-area-inset-bottom, 0px)); + height: var(--fab-size); + min-width: var(--fab-size); + padding: 0 20px 0 18px; + border: none; + border-radius: 999px; + background: var(--accent-primary, var(--red)); + color: #fff; + font-family: inherit; + font-size: 15px; + font-weight: 600; + line-height: 1; + cursor: pointer; + box-shadow: 0 6px 18px rgba(0,0,0,.38), 0 2px 6px rgba(0,0,0,.28); + z-index: 30; + pointer-events: auto; + /* This (base) transition governs the EXPAND — slower + graceful. */ + transition: + padding 420ms cubic-bezier(0.22, 1, 0.36, 1), + box-shadow 240ms ease, + transform 140ms ease, + background 140ms ease; + will-change: padding, transform; + } + #email-lib-modal .email-lib-fab svg { flex: 0 0 auto; display: block; } + #email-lib-modal .email-lib-fab .email-lib-fab-label { + overflow: hidden; + white-space: nowrap; + max-width: 120px; + opacity: 1; + /* EXPAND timing — label eases out a touch after the width opens up. */ + transition: + max-width 420ms cubic-bezier(0.22, 1, 0.36, 1), + opacity 300ms 60ms cubic-bezier(0.22, 1, 0.36, 1), + margin 420ms cubic-bezier(0.22, 1, 0.36, 1); + } + /* Collapsed (while scrolling) → pure circle, icon only. We DON'T set an + explicit width: the container is auto-width and shrinks smoothly as the + padding + label max-width animate (min-width keeps it circular). Setting a + fixed width here made the collapse snap, since auto↔px width can't tween. + This (.collapsed) transition governs the COLLAPSE — kept snappy. */ + #email-lib-modal .email-lib-fab.collapsed { + padding: 0; + justify-content: center; + transition: + padding 230ms cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 200ms ease, + transform 140ms ease; + } + #email-lib-modal .email-lib-fab.collapsed .email-lib-fab-label { + max-width: 0; + opacity: 0; + margin-left: -9px; + transition: + max-width 230ms cubic-bezier(0.4, 0, 0.2, 1), + opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), + margin 230ms cubic-bezier(0.4, 0, 0.2, 1); + } + /* Entrance: circle-expand-from-middle pop, played once when revealed. No + `forwards` fill so the resting transform (scale 1) comes from this rule — + that keeps the :active press transform working afterwards. */ + #email-lib-modal .email-lib-fab.fab-revealed { + transform: scale(1); + opacity: 1; + animation: emailFabPop 380ms cubic-bezier(0.34, 1.56, 0.64, 1); + } + #email-lib-modal .email-lib-fab:active { + transform: scale(0.93); + filter: brightness(0.92); + box-shadow: 0 3px 10px rgba(0,0,0,.4); + } +} +@keyframes emailFabPop { + from { transform: scale(0); opacity: 0; } + 60% { transform: scale(1.06); opacity: 1; } + to { transform: scale(1); opacity: 1; } +} +@media (prefers-reduced-motion: reduce) { + #email-lib-modal .email-lib-fab, + #email-lib-modal .email-lib-fab .email-lib-fab-label { transition-duration: 1ms; } + #email-lib-modal .email-lib-fab.fab-revealed { animation: none; } +} +/* When the modal-content has explicit height (fullscreen or user-resized), + drop the floor so the grid sizes purely from flex. */ +#email-lib-modal.email-lib-fullscreen .doclib-grid, +#email-lib-modal .modal-content[style*="height"] .doclib-grid { + min-height: 0; +} +/* Document library: same fix as the email library (#74). When fullscreened + the modal-content gets an inline `height: 100vh`, but the inner + .doclib-grid stays capped at the 400px default so the list looks like a + tiny strip floating in a giant empty modal. Mirror the email recipe: + make the modal a flex column, give the body/admin-card claim of the + remaining height, and let the grid take the rest. Scoped to the + fullscreen class so windowed-mode layout is unchanged. */ +/* Inner flex chain that lets the chats/documents grid claim the full + remaining height of the modal. Applies in BOTH fullscreen AND + right-docked states — without the docked state included, dragging a + fullscreen doclib to the right snap zone breaks the flex layout + because exitFullscreen removes .doclib-fullscreen, and the grid + falls back to its base max-height: 400px showing only ~8 items. + The CSS variable :is() selector lets both states share one rule. */ +#doclib-modal.doclib-fullscreen .doclib-modal-content, +#doclib-modal.modal-right-docked .doclib-modal-content, +#doclib-modal.modal-left-docked .doclib-modal-content { + display: flex; + flex-direction: column; + overflow: hidden; +} +/* Fullscreen positioning — scoped to NOT apply when also right-docked. + Without this exclusion, dragging a fullscreen doclib to the right snap + zone keeps .doclib-fullscreen on the modal and these !important rules + override the dock's inline styles, leaving the modal stuck fullscreen + instead of becoming a side panel. */ +#doclib-modal.doclib-fullscreen:not(.modal-right-docked) .doclib-modal-content { + position: fixed !important; + top: 0 !important; + left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; + right: 0 !important; + bottom: 0 !important; + width: auto !important; + max-width: none !important; + height: 100vh !important; + max-height: 100vh !important; + border-radius: 0 !important; + transform: none !important; +} +#doclib-modal.doclib-fullscreen .modal-header, +#doclib-modal.doclib-fullscreen .lib-tabs, +#doclib-modal.modal-right-docked .modal-header, +#doclib-modal.modal-right-docked .lib-tabs, +#doclib-modal.modal-left-docked .modal-header, +#doclib-modal.modal-left-docked .lib-tabs { + flex: 0 0 auto; +} +#doclib-modal.doclib-fullscreen .modal-body, +#doclib-modal.modal-right-docked .modal-body, +#doclib-modal.modal-left-docked .modal-body { + flex: 1 1 0; + min-height: 0; + overflow: hidden; + display: flex; + flex-direction: column; +} +#doclib-modal.doclib-fullscreen .admin-card, +#doclib-modal.modal-right-docked .admin-card, +#doclib-modal.modal-left-docked .admin-card { + flex: 1 1 0; + min-height: 0; + display: flex; + flex-direction: column; +} +#doclib-modal.doclib-fullscreen .doclib-grid:not(:has(.doclib-card-expanded)), +#doclib-modal.modal-right-docked .doclib-grid:not(:has(.doclib-card-expanded)), +#doclib-modal.modal-left-docked .doclib-grid:not(:has(.doclib-card-expanded)) { + max-height: none; + height: auto; + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; +} +/* "Reply" docks the open email modal to the left as a sidebar so the doc + panel (which slides in from the right of the chat area) is visible + side-by-side. The .modal backdrop is already pointer-events:none for + email modals — only the content takes pointer events — so the chat / + doc panel underneath stays interactive. */ +.modal.email-snap-left { + align-items: stretch; + justify-content: flex-start; + left: 0 !important; + width: 100% !important; +} +.modal.email-snap-left .modal-content { + left: var(--email-doc-split-left-x, 0px) !important; + width: var(--email-doc-split-email-w, 420px) !important; + max-width: var(--email-doc-split-email-w, 420px) !important; + border-right: 1px solid var(--border); + box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); + border-radius: 0; +} +@media (min-width: 769px) { + body.email-doc-split-active.doc-view #email-lib-modal, + body.email-doc-split-active.doc-view.email-front #email-lib-modal, + body.email-doc-split-active.doc-view .modal[id^="email-reader-"], + body.email-doc-split-active.doc-view.email-front .modal[id^="email-reader-"] { + z-index: 150 !important; + } + body.email-doc-split-active #email-lib-modal.email-snap-left, + body.email-doc-split-active #email-lib-modal.modal-left-docked { + left: var(--email-doc-split-left-x, 0px) !important; + width: var(--email-doc-split-email-w, 420px) !important; + overflow: hidden !important; + z-index: 155 !important; + } + body.email-doc-split-active #email-lib-modal.email-snap-left:not(.modal-dragging) .modal-content, + body.email-doc-split-active #email-lib-modal.modal-left-docked:not(.modal-dragging) .modal-content, + body.email-doc-split-active .modal[id^="email-reader-"].email-snap-left:not(.modal-dragging) .modal-content, + body.email-doc-split-active .modal[id^="email-reader-"].modal-left-docked:not(.modal-dragging) .modal-content { + position: absolute !important; + left: 0 !important; + top: 0 !important; + bottom: 0 !important; + right: auto !important; + width: var(--email-doc-split-email-w, 420px) !important; + max-width: var(--email-doc-split-email-w, 420px) !important; + height: 100vh !important; + max-height: 100vh !important; + transform: none !important; + margin: 0 !important; + } + body.email-doc-split-active.doc-view .doc-divider { + display: none !important; + } + body.email-doc-split-active.doc-view .doc-editor-pane { + position: fixed !important; + left: var(--email-doc-split-right-x, 420px) !important; + right: var(--right-dock-w, 0px) !important; + top: 0 !important; + bottom: 0 !important; + width: auto !important; + max-width: none !important; + height: 100vh !important; + z-index: 260 !important; + margin-top: 0 !important; + transform: none !important; + border-left: none !important; + } +} + +/* Edge docking. Docked panels are fixed to the viewport edge; the workspace + panes reserve room with margins so left + right docks can be active at the + same time without skewing the entire body box. */ +body.right-dock-active { + padding-right: 0; +} +body.left-dock-active { + padding-left: 0; +} +body.left-dock-active:not(.email-doc-split-active) .chat-container { + margin-left: var(--left-dock-w, 0px); +} +body.right-dock-active .chat-container, +body.right-dock-active:not(.email-doc-split-active) .doc-editor-pane { + margin-right: var(--right-dock-w, 0px); +} +.modal.modal-right-docked { + align-items: stretch; + justify-content: flex-end; +} +.modal.modal-right-docked .modal-content { + border-left: 1px solid var(--border); + box-shadow: -4px 0 14px rgba(0, 0, 0, 0.18); + border-radius: 0; +} diff --git a/static/css/07-documents.css b/static/css/07-documents.css new file mode 100644 index 000000000..ea843c0ca --- /dev/null +++ b/static/css/07-documents.css @@ -0,0 +1,1283 @@ +/* 07-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. + */ +.modal.modal-left-docked { + align-items: stretch; + justify-content: flex-start; +} +.modal.modal-left-docked .modal-content { + border-right: 1px solid var(--border); + box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); + border-radius: 0; + /* Pin transitions OFF on the dock's positioning properties. The wide + sidebar collapse/expand changes --sidebar-w instantly, which means + the docked modal's `left: calc(...)` jumps by ~240px. Any CSS + transition on `left` (inherited or otherwise) would animate that + jump as a fly-across. Same defense for right-docked / fullscreen + panels in case future themes add a generic transition. */ + transition: none !important; +} +.modal.modal-right-docked .modal-content, +#email-lib-modal.email-lib-fullscreen .modal-content, +#doclib-modal.doclib-fullscreen .doclib-modal-content { + transition: none !important; +} +.modal.modal-right-docked .email-reader-header, +.modal.modal-left-docked .email-reader-header { + flex-direction: column; + /* Stretch the meta to the full header width so the absolute + action cluster's right:0 lands at the right edge (was sizing + meta to chip content width and centering it). */ + align-items: stretch; + gap: 6px; +} +/* Docked panes: 4px between From→To and 4px between To→Cc. + Meta has its own 2px gap (From→details) so an extra 2px on + details margin-top gives a 4px From→To distance, and gap:4 on + the details flex column gives 4px between To and Cc. */ +.modal.modal-right-docked .email-reader-meta-details, +.modal.modal-left-docked .email-reader-meta-details { + margin-top: 2px; + gap: 4px; +} +/* Docked: always show To/Cc — there's vertical room reserved for + the action cluster already and the chevron toggle just adds + friction. Force the hidden attribute open and hide the chevron. */ +.modal.modal-right-docked .email-reader-meta-details[hidden], +.modal.modal-left-docked .email-reader-meta-details[hidden] { + display: flex !important; +} +.modal.modal-right-docked .email-reader-meta-toggle, +.modal.modal-left-docked .email-reader-meta-toggle { + display: none; +} +.modal.modal-right-docked .email-reader-header > .email-reader-actions, +.modal.modal-left-docked .email-reader-header > .email-reader-actions { + position: static; + align-self: flex-end; + background: none; + padding-left: 0; +} +/* Docked modal: keep all meta rows as flex so From / To / Cc + labels align at the same x. Chips already overflow-scroll + inside each row when there are too many. No per-row margin-top + here — the spacing between rows comes entirely from + .email-reader-meta-details gap so From→To and To→Cc match. */ +.modal.modal-right-docked .email-reader-meta-row:not(.email-reader-meta-from), +.modal.modal-left-docked .email-reader-meta-row:not(.email-reader-meta-from) { + padding-right: 0; +} +.modal.modal-right-docked .email-reader-meta-row strong, +.modal.modal-left-docked .email-reader-meta-row strong { + min-width: 0; +} +.modal.modal-right-docked .recipient-chip, +.modal.modal-left-docked .recipient-chip { + max-width: 100%; +} +.archive-list { + margin-top: 8px; + border-top: 1px solid var(--border); + padding-top: 8px; +} +#archive-modal .memory-sort-select, +#archive-modal .memory-toolbar-btn { + height: 32px; +} +.archive-menu-btn { + position: relative; + top: 2px; +} +#archive-chips .doclib-chip { + height: 22px; + display: inline-flex; + align-items: center; + font-size: 10px; + padding: 0 8px; +} +/* Library tab panels — Chats / Archive / Research / Documents share the + same toolbar dimensions so the sort dropdown + Select button line up + identically across tabs. */ +#doclib-panel-chats .memory-sort-select, +#doclib-panel-archive .memory-sort-select, +#doclib-panel-research .memory-sort-select, +[data-doclib-panel="documents"] .memory-sort-select { + height: 24px; + font-size: 11px; + width: 86px; +} +#doclib-panel-chats .memory-toolbar-btn, +#doclib-panel-archive .memory-toolbar-btn, +#doclib-panel-research .memory-toolbar-btn, +[data-doclib-panel="documents"] .memory-toolbar-btn { + height: 24px; + font-size: 11px; + position: relative; + top: -3px; +} +/* Research's Recent (sort) + Select sat 1px lower than the other tabs — nudge up. */ +#doclib-panel-research .memory-sort-select { position: relative; top: 1.5px; } +#doclib-panel-research .memory-toolbar-btn { top: -4.5px; } +.library-sort-picker { + width: 108px; + flex: 0 0 108px; + top: 1px; +} +.library-sort-picker .library-sort-btn { + top: 0; + width: 100%; +} +.library-sort-picker .library-sort-menu { + top: calc(100% + 4px); + width: max(100%, 154px); +} +.library-sort-picker .library-sort-item[aria-selected="true"] { + background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); +} +#doclib-panel-chats #doclib-chats-tidy-btn, +#doclib-panel-chats #doclib-chats-select-btn, +#doclib-panel-archive #doclib-arc-select-btn { + top: 1px; +} +#doclib-panel-chats .doclib-chip-scroll-arrow { + top: calc(50% - 9px) !important; +} +[data-doclib-panel="documents"] .doclib-chip-scroll-arrow { + top: calc(50% - 9px) !important; +} +#doclib-modal .doclib-chip-scroll-arrow { + top: calc(50% - 19px) !important; +} +[data-doclib-panel="documents"] #doclib-tidy-btn, +[data-doclib-panel="documents"] #doclib-select-btn { + top: 1px; +} +#doclib-panel-research #doclib-research-tidy-btn, +#doclib-panel-research #doclib-research-select-btn { + top: -0.5px; +} +#doclib-panel-research .library-sort-picker { top: -0.5px; } +[data-doclib-panel] { padding-top: 6px !important; } +#doclib-panel-chats, #doclib-panel-archive { padding-top: 14px !important; } +#doclib-panel-chats .memory-desc, #doclib-panel-archive .memory-desc, #doclib-panel-research .memory-desc { margin-top: 7px; } +#doclib-panel-chats .memory-search-input, +#doclib-panel-archive .memory-search-input { + height: 30px !important; + min-height: 30px !important; + flex-shrink: 0; + font-size: 11px; +} +/* Unified search bar across Library tabs + Memory modal — same height, + same magnifying-glass icon at the start, consistent padding. */ +#doclib-panel-chats .memory-search-input, +#doclib-panel-archive .memory-search-input, +#doclib-panel-research .memory-search-input, +[data-doclib-panel="documents"] .memory-search-input, +#memory-modal .memory-search-input, +#tasks-modal .memory-search-input { + height: 30px; + min-height: 30px; + font-size: 11px; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: 9px center; + padding-left: 28px; +} +#doclib-panel-chats .doclib-chip { + height: 28px; +} +#doclib-panel-archive .doclib-chip { + height: 28px; +} +#doclib-panel-chats .doclib-chip, +#doclib-panel-archive .doclib-chip { + display: inline-flex; + align-items: center; + font-size: 9px; + padding: 0 8px; + border-radius: 10px; +} +.doclib-grid:has(.doclib-card-expanded) > .doclib-card:not(.doclib-card-expanded) { + display: none; +} +/* Hide everything except the grid when a card is expanded */ +.admin-card:has(.doclib-card-expanded) > *:not(.doclib-grid):not(.hwfit-cached-list) { + opacity: 0; + max-height: 0; + overflow: hidden; + pointer-events: none; + margin: 0 !important; + padding: 0 !important; + transition: opacity 0.12s ease, max-height 0.2s ease; +} +.admin-card:has(.doclib-card-expanded) > .memory-bulk-bar { + display: none; +} +.admin-card:has(.doclib-card-expanded) > .doclib-grid, +.admin-card:has(.doclib-card-expanded) > .hwfit-cached-list { + max-height: none !important; + min-height: 0 !important; + overflow: visible !important; +} +/* Firefox-mobile (no :has()) fallback: when reading an email, fully remove the + list-mode chrome (accounts row, toolbar, bulk bar, desc) — display:none so no + residual flex-gap leaves an empty strip above the reader — and zero the + admin-card gap so the grid/reader starts flush at the top. */ +#email-lib-modal.email-reading .admin-card > *:not(.doclib-grid):not(.hwfit-cached-list) { + display: none !important; +} +#email-lib-modal .admin-card:has(.doclib-card-expanded), +#email-lib-modal.email-reading .admin-card { + gap: 0 !important; + padding: 0 !important; + margin-bottom: 0 !important; + border: 0 !important; + border-radius: 0 !important; + background: transparent !important; +} +/* Override for chat-rows + research-rows: those expand inline with a + bounded preview (max-height: 60vh), so the grid must stay clipped + and scrollable. Without this scoping the overflow:visible above lets + the expanded chat preview escape the grid and overlap whatever's + underneath/beside the modal (e.g., a docked sibling panel) — what + the user calls "merging with other windows". */ +.admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { + overflow: hidden auto !important; +} +/* Email modal needs the grid to STAY a constrained flex container when + expanded — overflow:visible (set above for cookbook) lets content escape + instead of letting the expanded reader fill via flex:1. We keep the same + max-height:none + min-height:0 but flip overflow back so the reader + actually fills the modal. */ +#email-lib-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid, +#email-lib-modal.email-reading .admin-card > .doclib-grid { + overflow: hidden !important; + display: flex !important; + flex: 1 1 auto !important; +} +#email-lib-modal.email-reading #email-lib-grid > .date-section-header { + display: none !important; +} +#email-lib-modal.email-reading .doclib-modal-content { + min-height: var(--email-reading-modal-min-h, auto); +} +#email-lib-modal .doclib-card.doclib-card-expanded { + flex: 1 1 auto !important; + height: 100% !important; + /* Neutral frame on the active email — the accent border + glow felt + overbearing on desktop; the size jump alone is enough signal. */ + border: 1px solid var(--border) !important; + box-shadow: 0 6px 18px rgba(0,0,0,0.12) !important; + animation: none !important; +} +/* Desktop-only, ONLY on the currently-expanded email card. Nudges the title + row down 6px / right 2px, bolds the subject, hides the timestamp from the + meta line (the reader header carries its own date), and pulls the prev/next + nav arrows in 4px. */ +@media (min-width: 769px) { + #email-lib-modal .doclib-card-expanded .email-card-titlerow, + #email-lib-modal .doclib-card-expanded .email-card-titlerow > * { + position: relative; + top: 6px; + left: 2px; + } + /* Smaller subject — the reader header below already carries weight. */ + #email-lib-modal .doclib-card-expanded .email-card-titlerow .memory-item-title { + font-weight: 700; + font-size: 12px; + } + /* A bit more breathing room around the title row so the smaller bold text + doesn't crowd the meta line below. (Timestamp kept — user changed mind.) */ + #email-lib-modal .doclib-card-expanded .email-card-titlerow { + padding: 4px 0 6px; + line-height: 1.35; + } + /* Nudge the date down/left in the meta row, and give the meta line +4px of + extra bottom space so the shifted date isn't clipped. */ + #email-lib-modal .doclib-card-expanded .email-meta-date { + position: relative; + top: 4px; + left: 0; + } + #email-lib-modal .doclib-card-expanded .memory-item-meta { + padding-bottom: 4px; + } + #email-lib-modal .doclib-card-expanded .email-card-nav-arrows { + position: relative; + left: -4px; + } +} +/* Skills modal: same mechanism as email above. The default expanded-grid + rule (overflow:visible, no flex) lets the card expand inline to ~content + height instead of filling — which is why the skill preview stopped at + partial height. Flip the grid back to a constrained flex container and + let the expanded card fill it, exactly like the email reader. */ +#memory-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid { + overflow: hidden !important; + display: flex !important; + flex-direction: column !important; + flex: 1 1 auto !important; +} +#memory-modal .doclib-card.doclib-card-expanded { + flex: 1 1 auto !important; + height: 100% !important; +} +#email-lib-modal .modal-content { + transition: width 0.22s ease-out, height 0.22s ease-out; +} +@media (min-width: 769px) { + body:not(.email-doc-split-active) #email-lib-modal:not(.email-lib-fullscreen):not(.modal-left-docked):not(.modal-right-docked) .modal-content:not([style*="height"]) { + min-height: min(560px, 85vh); + } +} + +/* Serve panel specifically: the admin-card inline style is + `overflow:hidden` (so the toolbar/header don't drift), and the list + inside has overflow:visible. On short windows that combination + clipped the cards off the bottom with no scrollbar. Make the list + itself the scroll surface so the rest of the card stays put. */ +.cookbook-group[data-backend-group="Serve"] > .admin-card { + min-height: 0; +} +.cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list, +.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list { + flex: 1 1 0; + min-height: 0; + overflow-y: auto !important; + overscroll-behavior: contain; +} +.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list .doclib-card.doclib-card-expanded { + flex: 0 0 auto !important; + overflow: visible !important; +} +/* Launch model cards use the same domino entrance as the other library lists + when a cached-model scan finishes. The class is removed after the one-shot + entrance, so filtering and sorting stay instantaneous. */ +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { + animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; +} +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(1) { animation-delay: 0.04s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(2) { animation-delay: 0.08s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(3) { animation-delay: 0.12s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(4) { animation-delay: 0.16s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(5) { animation-delay: 0.20s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(6) { animation-delay: 0.24s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(7) { animation-delay: 0.28s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(8) { animation-delay: 0.32s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(9) { animation-delay: 0.36s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(10) { animation-delay: 0.40s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(11) { animation-delay: 0.44s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(12) { animation-delay: 0.48s; } +#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(n+13) { animation-delay: 0.48s; } +@media (prefers-reduced-motion: reduce) { + #cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { + animation: none; + } +} +/* Drag-and-drop visual hint for the email compose pane. Subtle accent + outline + tinted overlay so it's obvious files will attach if dropped. */ +.doc-editor-pane.email-dragover { + outline: 2px dashed var(--accent, #2563eb); + outline-offset: -8px; + background: color-mix(in srgb, var(--accent, #2563eb) 6%, transparent); +} +.doc-editor-pane.email-dragover::after { + content: 'Drop to attach'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: var(--accent, #2563eb); + color: #fff; + padding: 8px 16px; + border-radius: 6px; + font-size: 12px; + pointer-events: none; + z-index: 1000; +} + +/* Email reader window — make the body always fill the available vertical + space regardless of how the window is positioned/resized. The user can + drag the bottom-right corner to resize; the body re-flexes automatically. */ +.email-window-modal .modal-content { + display: flex !important; + flex-direction: column !important; + resize: both; + overflow: hidden; + min-width: 320px; + min-height: 240px; + /* When user resizes/drags so the window covers a side, force the inner + body to fill all the way to the bottom. The default max-height: 85vh + (set inline by _makeDraggable's exitFullscreen) and 80vh from the + opener both cap at the *viewport* — if the browser window then changes + size (e.g. OS aero-snap), the modal stays at the old height and + truncates rows. Drop the cap whenever an explicit width OR height has + been written inline by the resize handle / drag handler. */ +} +.email-window-modal .modal-content[style*="width"], +.email-window-modal .modal-content[style*="height"] { + max-height: calc(100vh - 16px) !important; +} +.email-window-modal .email-window-body { + flex: 1 1 auto !important; + min-height: 0 !important; + /* Explicit basis of 0 makes the body grow to fill remaining space rather + than its content's intrinsic height (which was capping the thread). */ + flex-basis: 0 !important; + max-height: none !important; + overflow: auto !important; + overscroll-behavior: contain; +} + +/* Individual email reader window — fullscreen mode (drag-to-top snap or + double-click header). Same recipe as the library modal: pin to viewport + edges and let the inner body absorb the remaining height. */ +body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .modal-content { + position: fixed !important; + inset: 0 !important; + left: 0 !important; + top: 0 !important; + width: 100vw !important; + max-width: 100vw !important; + height: 100vh !important; + max-height: 100vh !important; + border-radius: 0 !important; + transform: none !important; + display: flex !important; + flex-direction: column !important; +} +body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .email-window-body { + flex: 1 1 auto !important; + min-height: 0 !important; + max-height: none !important; +} + +/* Email library modal: fullscreen mode + larger mobile sheet (mirrors the + cookbook treatment so the toolbar and bulk actions stay reachable). + Leaves whichever navigation strip is currently visible on the left + (the wide sidebar OR the icon rail — they're mutually exclusive, so + one of the two CSS vars is 0 at any given time, set by init.js). */ +body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { + position: fixed !important; + top: 0 !important; + left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; + right: 0 !important; + bottom: 0 !important; + width: auto !important; + max-width: none !important; + height: 100vh !important; + max-height: 100vh !important; + border-radius: 0 !important; + transform: none !important; +} +/* Mobile: the /email route adds .email-lib-fullscreen, but the desktop full- + bleed rule above squares off the corners and offsets the email by the icon + rail. On phones the email should be the normal bottom-sheet — full width, + rounded top corners. Same specificity as the rule above + later in source, + so it wins on mobile. */ +@media (max-width: 768px) { + body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { + left: 0 !important; + right: 0 !important; + width: 100vw !important; + max-width: 100vw !important; + height: 100dvh !important; + max-height: 100dvh !important; + border-radius: 14px 14px 0 0 !important; + border-top: 1px solid var(--border) !important; + } +} +/* Make the inner panes actually grow to fill the fullscreen container + instead of staying at their natural size. The body owns the remaining + height below the header; the admin-card + grid then expand into it. */ +#email-lib-modal.email-lib-fullscreen .modal-body { + flex: 1 1 auto !important; + min-height: 0 !important; + overflow: hidden !important; +} +#email-lib-modal.email-lib-fullscreen .modal-body > .admin-card { + flex: 1 1 auto !important; + min-height: 0 !important; +} +#email-lib-modal.email-lib-fullscreen .doclib-grid { + flex: 1 1 auto !important; + min-height: 0 !important; + max-height: none !important; + overflow: auto !important; +} +@media (max-width: 768px) { + /* Mobile email modal sizing — keep in sync with the rule earlier in the + file. 75dvh tall always so flex children (the expanded email reader) + have a real height to grow into. */ + #email-lib-modal .modal-content { + max-height: 90dvh !important; + max-height: 90vh !important; + height: 90dvh !important; + height: 90vh !important; + } + /* Inner panes grow to fill the modal-content — without flex:1 on the + body, the expanded email reader sits in a tiny box because there's + nothing pushing it to take the remaining height. overflow:hidden + + min-height:0 lets each layer pass its constraints down. */ + #email-lib-modal .modal-body, + #email-lib-modal .admin-card { + flex: 1 1 auto !important; + min-height: 0 !important; + overflow: hidden !important; + max-height: none !important; + } + #email-lib-modal .doclib-grid { + flex: 1 1 auto !important; + min-height: 0 !important; + max-height: none !important; + overflow-y: auto !important; + } + /* modal-content keeps its scroll OFF here — the inner flex children + (doclib-grid for the list view, email-reader-body for the expanded + reader) own the scroll surfaces. Without this, double-scroll layouts + trap touches and the reader can't claim full height. */ + #email-lib-modal .modal-content { + overflow: hidden !important; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; + touch-action: pan-y; + } + /* Attachment row on phones: cap to ~2 rows so it never dominates the + reader. If more chips exist, the row scrolls vertically. Smaller chip + padding + max-width keeps each chip compact so two rows usually fit + everything anyway. */ + #email-lib-modal .email-reader-atts { + padding: 4px 10px !important; + gap: 3px !important; + max-height: calc(2 * 22px + 12px); /* 2 rows × chip height + padding */ + overflow-y: auto; + align-content: flex-start; + } +#email-lib-modal .email-attachment-chip { + padding: 1px 6px !important; + font-size: 10px !important; + max-width: min(240px, calc(100vw - 44px)) !important; + line-height: 18px !important; + } +} + + +/* Mobile: only ONE scroll surface inside the cookbook modal. The + modal-content is the scroller. Everything inside (cookbook-body, + cookbook-group, all the inner lists/panels) gets overflow: visible + so touch-pan never gets trapped in a nested scroller. + Without this, three levels of overflow:auto + max-height combinations + produce dead-zone areas where swipes do nothing. */ +@media (max-width: 768px) { + #cookbook-modal .modal-content { + overflow-y: auto !important; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; + touch-action: pan-y; + } + #cookbook-modal .cookbook-body, + #cookbook-modal .cookbook-group, + #cookbook-modal .cookbook-section-body, + #cookbook-modal .hwfit-cached-list, + #cookbook-modal .doclib-grid, + #cookbook-modal .hwfit-list, + #cookbook-modal .hwfit-panel-cmd { + overflow: visible !important; + max-height: none !important; + min-height: 0 !important; + } + /* Running tmux output: cap how tall an expanded card gets (a long-lived job + can leave thousands of lines) so it doesn't balloon, but keep its own + scroll so you can read it. Outputs default collapsed on mobile now, so + you're usually looking at one expanded card at a time — no wall of nested + scrollers, and you collapse it to move past. */ + #cookbook-modal .cookbook-output-pre, + #cookbook-modal .cookbook-task .cookbook-output-pre { + max-height: 45vh !important; + min-height: 0 !important; + overflow-y: auto !important; + overflow-x: hidden !important; + overscroll-behavior: auto; /* chain to the modal at the scroll boundary */ + } + /* cookbook-body's flex:1 was needed when it owned scrolling — drop it + so the inner content drives modal-content's scroll height. */ + #cookbook-modal .cookbook-body { + flex: 0 0 auto !important; + height: auto !important; + } + /* Launch tab's cached-list normally has `flex: 1 1 0; min-height: 0` + (so it fills the modal on desktop). On mobile the parent now has + `height: auto`, which collapses `flex: 1 1 0` to ZERO PX — + models render but the list area is invisible because the flex + basis is 0 and there's no free space to grow into. Switch to + content-sized flex so the list grows with its children. */ + #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list, + #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list { + flex: 0 0 auto !important; + overflow: visible !important; + } +} +#cookbook-modal .hwfit-cached-list { + flex-shrink: 0; +} +.memory-toolbar { + transition: opacity 0.12s ease, max-height 0.2s ease; + max-height: 120px; +} +.doclib-card { + background: color-mix(in srgb, var(--fg) 3%, transparent); + border: 1px solid var(--border); + border-radius: 8px; + cursor: pointer; + transition: all 0.15s, opacity 0.3s, max-height 0.3s ease; + position: relative; +} +.doclib-card.doclib-card-deleting { + opacity: 0; + max-height: 0 !important; + overflow: hidden; + margin: 0; + padding: 0; + border-color: transparent; +} +.doclib-card.selected, +/* Universal selection highlight: any card-shaped row (documents, chats, + archive, research, skills, memories, tasks) gets the same accent outline + when its checkbox is checked. Done via :has() so the highlight follows + selection without per-renderer JS changes. The canonical checkbox class + is .memory-select-cb across every list. */ +.memory-item:has(.memory-select-cb:checked), +.doclib-card:has(.memory-select-cb:checked), +.doclib-chat-row:has(.memory-select-cb:checked) { + border-color: color-mix(in srgb, var(--red) 40%, transparent); + background: color-mix(in srgb, var(--red) 4%, transparent); + /* 2px accent outline overlaid on the 1px border — reads as a thicker + selected border without shifting layout (every card would otherwise + need a 2px transparent border to keep the same width). */ + outline: 2px solid var(--accent-primary, var(--red)); + outline-offset: -1px; +} +.doclib-card-header { + display: flex; + align-items: center; + padding: 7px 8px; + gap: 6px; + min-width: 0; +} +/* Mobile only: push the unexpanded email card's title-row text down 4px so + it sits visually centered with the surrounding card padding. Targets the + actual title-row class (.email-card-titlerow) — the earlier attempt used + .doclib-card-header which the email card builds differently. */ +@media (max-width: 768px) { + #email-lib-modal .doclib-card:not(.doclib-card-expanded) .email-card-titlerow { + margin-top: 4px; + } +} +.doclib-card-title { + font-size: 11px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; + flex: 1; +} +.doclib-card-ver { + padding: 1px 6px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + color: var(--accent, var(--red)); + font-size: 9px; + flex-shrink: 0; + font-weight: 600; + letter-spacing: 0.3px; + min-width: 24px; + text-align: left; + box-sizing: border-box; +} +.doclib-card-ver-muted { + background: color-mix(in srgb, var(--fg) 6%, transparent); + color: color-mix(in srgb, var(--fg) 35%, transparent); +} +.doclib-card-lang { + padding: 1px 6px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent) 10%, transparent); + color: var(--accent); + font-size: 9px; + text-align: left; + flex-shrink: 0; + min-width: 52px; + box-sizing: border-box; +} +.doclib-card-session { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 60px; + width: 100px; + max-width: 100px; + font-size: 9px; + color: var(--fg-muted); + flex-shrink: 0; + text-align: left; +} +.doclib-card-time { + flex-shrink: 0; + opacity: 0.5; + font-size: 9px; + color: var(--fg-muted); + min-width: 50px; + text-align: left; +} +/* Footer — used by archive cards */ +.doclib-card-footer { + display: contents; +} +/* Preview — hidden by default, shown on expand */ +.doclib-card-preview { + display: none; + position: relative; + padding: 8px 12px; + font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; + font-size: 9.5px; + line-height: 1.5; + color: var(--code-fg, var(--hl-fg, var(--fg))); + border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); + margin: 0; +} +.doclib-card-preview pre { + margin: 0; + white-space: pre-wrap; + word-break: break-all; +} +.doclib-card-expanded .doclib-card-preview pre { + flex: 1 1 0; + overflow-y: auto; + min-height: 0; +} +.doclib-card-preview code.hljs { + background: none; + padding: 0; +} +/* Expanded-only action bar — inside preview. Buttons shifted up 4px by + trimming the row's top margin so they sit closer to the preview text. */ +.doclib-card-expanded-actions { + display: none; + align-items: flex-start; + gap: 6px; + padding: 8px 0 2px; + border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); + margin-top: 4px; +} +.doclib-card-expanded-actions > .doclib-card-action-btn, +.doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { + position: relative; + top: -4px; +} +.doclib-action-group { + display: flex; + flex-direction: column; + gap: 3px; +} +.doclib-action-btn-row { + display: flex; + gap: 6px; +} +.doclib-action-hint-row { + display: flex; + gap: 6px; +} +/* Match the chat/research footer buttons exactly: flat, bordered, + app font, accent on hover. */ +.doclib-card-action-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + box-sizing: border-box; + font-size: 10px; + padding: 3px 8px; + border-radius: 4px; + background: none; + border: 1px solid var(--border); + color: var(--fg-muted); + cursor: pointer; + /* These buttons live inside .doclib-card-preview, which forces a + monospace font. The chat/research footer buttons instead inherit the + modal font, which is the app font (Fira Code) on mobile and Inter on + desktop (.modal-content's Inter rule is inside @media min-width:769px). + Mirror that here so all three footers match in both contexts. */ + font-family: var(--font-family, 'Fira Code', monospace); + transition: border-color 0.15s, color 0.15s; +} +@media (min-width: 769px) { + .doclib-card-action-btn { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + letter-spacing: -0.015em; + } +} +.doclib-card-action-btn:hover { + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); +} +.doclib-card-text-btn-danger { + color: var(--color-danger, #e06c75); + border-color: var(--color-danger, #e06c75); +} +.doclib-card-text-btn-danger:hover { + color: #ff4d4d; + border-color: #ff4d4d; +} +.doclib-card-expanded-actions > .doclib-card-action-btn { + font-size: 10px; + padding: 3px 8px; +} +/* Push the Open/Clone group to the right edge of the action bar — Delete + anchors the left, primary actions sit opposite. */ +.doclib-card-expanded-actions > .doclib-action-group { + margin-left: auto; +} +.doclib-card-expanded-actions > .doclib-expanded-delete-btn, +.doclib-card-expanded-actions > .doclib-expanded-archive-btn { + left: 0 !important; +} +.doclib-expanded-mobile-more { display: none; } +.doclib-btn-hint { + font-weight: normal; + opacity: 0.35; + font-size: 8px; + min-width: 58px; + padding: 0 8px; + box-sizing: border-box; +} +@media (max-width: 768px) { + .doclib-btn-hint { display: none !important; } +} + +/* Chat row expand preview — matches the documents-tab expand style. */ +.doclib-card-chevron { + display: inline-flex; + align-items: center; + justify-content: center; + align-self: center; + flex-shrink: 0; + width: 14px; + height: 14px; + line-height: 0; + opacity: 0.3; + transform: rotate(0deg); + transition: transform 0.2s ease, opacity 0.15s; +} +.doclib-card-chevron svg { display: block; } +.doclib-card-expanded .doclib-card-chevron, +.task-card.expanded .doclib-card-chevron { + opacity: 0.8; + transform: rotate(180deg); +} +.doclib-card > .memory-item-actions { order: 1; } +.doclib-card > .doclib-card-chevron { order: 2; } +.doclib-chat-header .memory-item-actions, +.task-card .memory-item-actions { order: 2; } +.doclib-chat-header .doclib-card-chevron, +.task-card .doclib-card-chevron { order: 3; } +.hwfit-cached-list > .doclib-card > .memory-item-actions { order: 2; } +.hwfit-cached-list > .doclib-card > .doclib-card-chevron { order: 3; } +#email-lib-modal .doclib-card > .memory-item-actions { order: 1; align-self: center; } +#email-lib-modal .doclib-card > .doclib-card-chevron { display: none; } +.doclib-chat-row { flex-direction: column; align-items: stretch; } +.doclib-chat-row .doclib-card-chevron { + display: inline-flex; + opacity: 0.55; + transition: transform 0.2s ease, opacity 0.15s; + flex-shrink: 0; +} +.doclib-chat-row:not(.doclib-card-expanded) .doclib-card-chevron { + transform: rotate(0deg); +} +.doclib-chat-row.doclib-card-expanded .doclib-card-chevron { + opacity: 0.8; + transform: rotate(180deg); +} +/* Release .memory-item's base max-height: 200px when a chat row is + expanded. Without this, the row clips at 200px on desktop and the + preview's messages/actions visually spill past the row's box — + "colliding" with the next chat in the column. The mobile takeover + below already handles this via the same override; this rule is the + desktop-or-any-viewport equivalent. */ +.doclib-chat-row.doclib-card-expanded { + max-height: none; + overflow: visible; +} +/* When a chat row is expanded, take over the grid the same way documents + do: hide siblings, hide the admin-card toolbar, and let this row plus + its preview claim the full available height. .memory-item's default + max-height: 200px must be overridden or the preview will be tiny. */ +/* Mobile-only "card takeover" for an expanded chat — hides siblings, + hides the admin-card toolbar, and lets this row + its preview claim + the full available height. On desktop the expanded preview just + renders inline below the row with its messages constrained by + max-height + overflow:auto so the user gets a normal scroll. */ +@media (max-width: 820px) { + .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { + display: none; + } + .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { + display: none; + } + .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { + flex: 1 1 auto !important; + max-height: none !important; + overflow-y: auto !important; + } + .doclib-chat-row.doclib-card-expanded { + flex: 1 1 auto !important; + max-height: none !important; + min-height: 0 !important; + overflow: hidden !important; + display: flex !important; + flex-direction: column !important; + align-items: stretch !important; + } + .doclib-chat-row.doclib-card-expanded .doclib-chat-header { + flex: 0 0 auto !important; + } + .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { + flex: 1 1 auto !important; + min-height: 0 !important; + overflow: hidden !important; + display: flex !important; + flex-direction: column !important; + padding-bottom: 4px !important; + } + .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { + -webkit-overflow-scrolling: touch; + max-height: none !important; + flex: 1 1 auto !important; + min-height: 0 !important; + } +} +.doclib-chat-preview { + font-size: 11px; + padding: 0 4px 6px; +} +.doclib-chat-preview .doclib-chat-open-btn { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10px; + padding: 3px 8px; + border-radius: 4px; + color: var(--fg-muted); + border: 1px solid var(--border); + background: none; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s, color 0.15s; +} +.doclib-chat-preview .task-completed-report-btn { + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); +} +.doclib-chat-preview .doclib-chat-open-btn:hover { + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); +} +.doclib-chat-preview-messages { + margin-top: 6px; + max-height: 320px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + padding-right: 4px; +} +/* Desktop expanded state — match the documents-tab behavior: hide + sibling chats, hide the admin-card toolbar/header, and let the + expanded row + its messages preview claim the full modal height. + Chat rows use `memory-item doclib-chat-row` (no `doclib-card` class) + so the document-tab's global rules at line 12284-12304 skip them — + these mirror those rules scoped to chat rows. The mobile media query + above (820px) keeps the same takeover behavior for phones. */ +@media (min-width: 821px) { + .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { + display: none; + } + .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { + display: none; + } + .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { + flex: 1 1 auto !important; + max-height: none !important; + overflow: hidden auto !important; + } + .doclib-chat-row.doclib-card-expanded { + flex: 1 1 auto !important; + max-height: none !important; + min-height: 0 !important; + display: flex !important; + flex-direction: column !important; + } + .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { + flex: 1 1 auto !important; + min-height: 0 !important; + display: flex !important; + flex-direction: column !important; + } + .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { + flex: 1 1 auto !important; + min-height: 0 !important; + max-height: none !important; + overflow-y: auto !important; + } +} +.doclib-chat-preview-actions { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 8px; + padding: 2px 0 2px; + border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); + margin-top: 2px; + flex-shrink: 0; + /* Whole action footer nudged down 5px. */ + position: relative; + top: 5px; +} +.doclib-chat-delete-btn, +.doclib-chat-archive-btn, +.doclib-chat-restore-btn, +.doclib-chat-discuss-btn, +.doclib-chat-copy-btn { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10px; + padding: 3px 8px; + border-radius: 4px; + background: none; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s, color 0.15s; +} +.doclib-chat-archive-btn, +.doclib-chat-restore-btn, +.doclib-chat-discuss-btn, +.doclib-chat-copy-btn { + color: var(--fg-muted); + border: 1px solid var(--border); +} +.doclib-chat-restore-btn:hover { + color: var(--accent, var(--red)); + border-color: var(--accent, var(--red)); +} +.doclib-chat-archive-btn:hover, +.doclib-chat-discuss-btn:hover, +.doclib-chat-copy-btn:hover { + color: var(--accent, var(--red)); + border-color: var(--accent, var(--red)); +} +.doclib-chat-delete-btn { + color: var(--color-danger, #e06c75); + border: 1px solid var(--color-danger, #e06c75); +} +.doclib-chat-delete-btn:hover { + color: #ff4d4d; + border-color: #ff4d4d; +} +/* When a chat is archived there's no Archive button, so Delete becomes + the only left-side action and needs the auto margin to push Open right. */ +.doclib-chat-preview-actions:not(:has(.doclib-chat-archive-btn)) > .doclib-chat-delete-btn { + margin-right: auto; +} +/* Hide the "..." menu while the chat card is expanded — archive + + delete live in the preview footer instead. */ +.doclib-chat-row.doclib-card-expanded ._chat-menu { display: none; } +.doclib-chat-preview-actions.doclib-chat-preview-actions-top { + border-top: none; + border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); + margin-top: 4px; + margin-bottom: 6px; + padding: 0 0 6px; + justify-content: flex-start; +} +.doclib-chat-preview-messages .doclib-chat-msg { + margin: 4px 0 10px; + padding-left: 8px; + border-left: 2px solid color-mix(in srgb, var(--border) 70%, transparent); +} +.doclib-chat-preview-messages .doclib-chat-msg.user { + border-left-color: color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); +} +.doclib-chat-preview-messages .doclib-chat-msg-role { + font-size: 9px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + opacity: 0.55; + margin-bottom: 2px; +} +.doclib-chat-preview-messages .doclib-chat-msg.user .doclib-chat-msg-role { + color: var(--accent, var(--red)); + opacity: 0.85; +} +.doclib-chat-preview-messages .doclib-chat-msg-body { + white-space: pre-wrap; + word-break: break-word; + opacity: 0.85; + line-height: 1.45; +} +/* Chat-bubble preview — mirrors the real chat layout. User bubbles hug + right with an accent tint; assistant bubbles hug left with a neutral + panel tint and a small model tag at the top. */ +.doclib-chat-bubble-row { + display: flex; + margin: 6px 0; +} +.doclib-chat-bubble-row.user { justify-content: flex-end; } +.doclib-chat-bubble-row.assistant { justify-content: flex-start; } +.doclib-chat-bubble { + max-width: 85%; + padding: 6px 10px 8px; + border-radius: 14px; + font-size: 11px; + line-height: 1.45; + border: 1px solid var(--border); + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.doclib-chat-bubble-row.user .doclib-chat-bubble { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); + border-bottom-right-radius: 4px; +} +.doclib-chat-bubble-row.assistant .doclib-chat-bubble { + border-bottom-left-radius: 4px; +} +.doclib-chat-msg-model { + display: inline-block; + font-size: 8px; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + opacity: 0.55; + margin-bottom: 3px; + padding: 1px 6px; + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg-muted, var(--fg)); +} +.doclib-chat-bubble-body { word-break: break-word; } +.doclib-chat-bubble-body p { margin: 4px 0; } +.doclib-chat-bubble-body p:first-child { margin-top: 0; } +.doclib-chat-bubble-body p:last-child { margin-bottom: 0; } +.doclib-chat-bubble-body pre { + font-size: 10px; + margin: 4px 0; + padding: 6px 8px; + background: color-mix(in srgb, var(--bg) 60%, transparent); + border-radius: 6px; + overflow-x: auto; +} +.doclib-chat-bubble-body code { + font-size: 10px; + padding: 1px 4px; + border-radius: 3px; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.doclib-chat-bubble-body pre code { background: none; padding: 0; } +.doclib-chat-bubble-body ul, +.doclib-chat-bubble-body ol { + margin: 4px 0; + padding-left: 18px; +} +.doclib-card-collapse-chevron { + display: none; + align-items: center; + justify-content: center; + width: 24px; + height: 22px; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + background: transparent; + color: var(--color-muted, var(--fg)); + font: inherit; + opacity: 0.3; + cursor: pointer; + flex-shrink: 0; + margin: 0 0 0 4px; + transition: opacity 0.15s, background 0.15s, border-color 0.15s; +} +.doclib-card-collapse-chevron:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); +} +.cookbook-serve-title { + display: flex; + align-items: center; + min-width: 0; +} +.cookbook-serve-title .doclib-card-collapse-chevron { + display: inline-flex; + margin-left: auto; + margin-top: 0; +} +.doclib-card-expanded .doclib-card-collapse-chevron { + display: inline-flex; +} +/* Expanded card — fills the whole grid */ +.doclib-card.doclib-card-expanded { + flex: 1; + min-height: 0; + background: color-mix(in srgb, var(--fg) 3%, transparent); + border: 1px solid var(--border); + border-radius: 8px; + display: flex; + flex-direction: column; + overflow: hidden; + animation: doclib-expand 0.2s ease-out; +} +.doclib-card.doclib-card-expanded { + flex-direction: column !important; + align-items: stretch !important; + max-height: none !important; + gap: 4px !important; +} +.doclib-card.doclib-card-expanded > .memory-item-actions { display: none; } +/* Non-preview children of an expanded doc card carry an inline flex:1 from + the row layout. With the card now flex-column those children would steal + half the height and shrink the preview — pin them at auto height. */ +.doclib-card.doclib-card-expanded > div:not(.doclib-card-preview):not(.doclib-card-header):not(.memory-item-actions):not(.email-card-reader) { + flex: 0 0 auto !important; +} +/* The email reader IS the scroll container — keep its flex:1 so its + internal body can claim the rest of the card's height and scroll. */ +.doclib-card.doclib-card-expanded > .email-card-reader { + flex: 1 1 auto !important; + min-height: 0 !important; +} +.doclib-card.doclib-card-expanded .doclib-card-preview { + display: flex; + flex-direction: column; + flex: 1 1 auto !important; + /* The preview matches chat rows: document content scrolls, while the + footer stays in the card rather than becoming the last scroll item. */ + overflow: hidden; + min-height: 0; +} +.doclib-card.doclib-card-expanded .doclib-card-expanded-actions { + display: flex; + flex-shrink: 0; +} diff --git a/static/css/08-skills.css b/static/css/08-skills.css new file mode 100644 index 000000000..9f01ee333 --- /dev/null +++ b/static/css/08-skills.css @@ -0,0 +1,1088 @@ +/* 08-skills - 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. + */ +/* Document cards can be expanded in the normal windowed library, where the + grid has no definite height. A zero-basis then collapses to nothing + while the footer remains visible. Keep the document text's intrinsic height + in that layout; bounded/fullscreen layouts can still shrink it and scroll. */ +#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview > pre { + flex: 1 1 auto; + min-height: 1.5em; +} + +/* Collapsible skills section headers (Your skills / Built-in). */ +.skills-section-header { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; + user-select: none; + padding: 6px 4px 4px; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.06em; + opacity: 0.6; + font-weight: 600; +} +.skills-section-header:hover { opacity: 0.9; } +.skills-section-chevron { transition: transform 0.15s ease; flex-shrink: 0; } +.skills-section-header.collapsed .skills-section-chevron { transform: rotate(-90deg); } +.skills-section-count { + margin-left: auto; + opacity: 0.6; + font-variant-numeric: tabular-nums; + font-weight: normal; +} +.skill-card-section-hidden { display: none !important; } +/* Warning banner shown when previewing/editing a built-in capability. */ +.skill-builtin-warn { + font-size: 9.5px; + line-height: 1.45; + color: var(--color-warning, #f0ad4e); + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 35%, transparent); + border-radius: 6px; + padding: 6px 9px; + margin: 0 0 8px; + flex-shrink: 0; +} + +/* #skills-list wears both .memory-list and .doclib-grid. doclib-grid's + max-height:400px would cap the list AND clamp an expanded card — keep + the memory-list fill-the-modal behaviour instead. */ +#skills-list.doclib-grid { + max-height: none; + flex: 1; + min-height: 0; +} +#skills-list.doclib-grid:has(.doclib-card-expanded) { + overflow: hidden; /* expanded card owns its own scroll */ +} +/* When a skill is expanded, hide the sibling Add-Skill form card so the + expanded SKILL.md uses the full panel. Otherwise the two admin-cards + split the height ~50/50 and the expanded skill is stuck in its half + while the Add form idles in the other. */ +.memory-tab-panel[data-memory-panel="skills"]:has(.doclib-card-expanded) > .admin-card:nth-of-type(2) { + display: none !important; +} + +/* Skills cards reuse the doclib expand/footer machinery. The SKILL.md + preview + the edit textarea need to fill the expanded card and own + their own scroll, same as a document preview. */ +/* Collapsed skill rows used a smaller (0.9em code) title, so the click row + read slimmer than document/chat/library cards. Give the header a min-height + so the tap target matches the other library items. */ +.skill-card > .skill-card-header { + min-height: 53px; + box-sizing: border-box; + display: flex; + align-items: center; + gap: 6px; +} +/* Skill rows share Tasks' card hover colors. Draft state belongs in the + status badge, not in a second opacity layer across the whole row. */ +/* Name + description column. Name wraps to 2 lines (skill slugs are long — + use the space rather than truncating to "check-model-downl…"). */ +.skill-card-textcol { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; +} +.skill-card-title-row { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.skill-card-name { + font-family: inherit; + font-weight: 500; + font-size: 12px; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + word-break: normal; + line-height: normal; +} +.skill-card-title-row .skill-card-name { min-width: 0; } +.skill-status-published { + width: 20px; + height: 20px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--color-success, #4ade80); + background: color-mix(in srgb, var(--color-success, #4ade80) 15%, transparent); + border-color: color-mix(in srgb, var(--color-success, #4ade80) 32%, transparent); + border-radius: 50%; +} +.skill-stats .skill-status-published { + width: 15px; + height: 15px; + margin-left: 3px; + vertical-align: middle; +} +.skill-stats .skill-status-published svg { + width: 9px; + height: 9px; +} +.skill-card-desc { + font-size: 10px; + opacity: 0.4; + margin-top: -1px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.skills-audit-model-field { + display: flex; + align-items: center; + gap: 8px; + margin-top: 10px; + font-size: 11px; +} +.skills-audit-model-field > span { + flex: 0 0 auto; + color: color-mix(in srgb, var(--fg) 68%, transparent); +} +.skills-audit-model-field select { + min-width: 0; + flex: 1; + height: 26px; + padding: 0 6px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + color: var(--fg); + font: inherit; +} +.skill-card-tags { + display: flex; + gap: 4px; + flex-wrap: wrap; + margin-top: 4px; +} +.skill-tag-pill { + padding: 1px 5px; + font-size: 9px; +} +.skill-usage-pill { + margin-left: auto; + color: color-mix(in srgb, var(--fg) 62%, transparent); +} +.skill-tag-filter-chip { cursor: pointer; } + +@media (max-width: 768px) { + .skill-card > .skill-card-header { + align-items: flex-start; + } + .skill-card-textcol { + min-width: 0; + } + .skill-card-tags { + max-width: 100%; + overflow: hidden; + } + .skill-card-tags .skill-tag-pill { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} +/* Right cluster — pills (right-aligned), stats, then the menu/chevron. */ +.skill-card-right { + display: flex; + align-items: center; + gap: 6px; + flex-shrink: 0; + margin-left: auto; +} +.skill-card-right .memory-cat-badge { flex-shrink: 0; } +.skill-status-bin { + background: color-mix(in srgb, var(--color-danger, #d9534f) 20%, transparent); + color: var(--color-danger, #d9534f); +} +.skill-model-pill { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.skill-model-student { + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.skill-audit-pass { + color: var(--color-success, #4ade80); + background: color-mix(in srgb, var(--color-success, #4ade80) 16%, transparent); +} +.skill-audit-needs_work { + color: var(--color-warning, #f0ad4e); + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 16%, transparent); +} +.skill-audit-fail { + color: var(--color-danger, #e06c75); + background: color-mix(in srgb, var(--color-danger, #e06c75) 16%, transparent); +} +.skill-audit-inconclusive, +.skill-audit-skipped, +.skill-audit-unknown { + color: color-mix(in srgb, var(--fg) 64%, transparent); + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.skill-model-teacher { + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 24%, transparent); + color: var(--color-warning, #f0ad4e); +} +.skill-necessity-pill { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.skill-necessity-duplicate { + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 22%, transparent); + color: var(--color-warning, #f0ad4e); +} +.skill-necessity-trivial { + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: color-mix(in srgb, var(--fg) 62%, transparent); + border-color: color-mix(in srgb, var(--fg) 16%, transparent); +} +.skill-necessity-irrelevant { + background: color-mix(in srgb, var(--color-danger, #e06c75) 18%, transparent); + color: var(--color-danger, #e06c75); +} +.skills-summary-strip { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + overscroll-behavior-x: contain; + touch-action: pan-x; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + margin: -3px 0 8px; +} +.skills-summary-strip::-webkit-scrollbar { display: none; } +.skills-summary-strip:empty { + display: none; +} +.skills-summary-chip { + display: inline-flex; + align-items: center; + gap: 5px; + height: 23px; + padding: 0 8px; + border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + color: color-mix(in srgb, var(--fg) 70%, transparent); + font-size: 10px; + line-height: 1; + cursor: pointer; + flex: 0 0 auto; +} +.skills-summary-chip:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: color-mix(in srgb, var(--fg) 18%, transparent); +} +.skills-summary-chip.active { + background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + color: var(--fg); +} +.skills-summary-chip strong { + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 10px; + color: var(--fg); +} + +/* Shared filter/tag strip contract. These rows all sit immediately below + their screen's search or primary toolbar and stay one line at every width. */ +:is( + .skills-summary-strip, + #memory-category-filters, + .memory-category-filters:has(> .memory-cat-chip), + .doclib-lang-chips, + .doclib-chips, + .notes-labels-bar, + .tasks-activity-filters, + .gallery-tag-chips, + .gallery-album-chips, + .gallery-ai-tags, + .cal-filters +) { + align-items: center; + flex-wrap: nowrap !important; + gap: 6px; + width: 100%; + max-width: 100%; + min-width: 0; + max-height: none; + overflow-x: auto !important; + overflow-y: hidden !important; + overscroll-behavior-x: contain; + touch-action: pan-x; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + box-sizing: border-box; + padding: 0; + margin: -3px 0 8px; +} +:is( + .skills-summary-strip, + #memory-category-filters, + .memory-category-filters:has(> .memory-cat-chip), + .doclib-lang-chips, + .doclib-chips, + .notes-labels-bar, + .tasks-activity-filters, + .gallery-tag-chips, + .gallery-album-chips, + .gallery-ai-tags, + .cal-filters +)::-webkit-scrollbar { display: none; } +:is( + .skills-summary-strip > .skills-summary-chip, + #memory-category-filters > .skills-summary-chip, + .memory-category-filters > .memory-cat-chip, + .doclib-lang-chips > :is(.memory-cat-chip, .skills-summary-chip), + .doclib-chips > .doclib-chip, + .notes-labels-bar > .notes-label-chip, + .tasks-activity-filters > .memory-cat-chip, + .gallery-tag-chips > .gallery-chip, + .gallery-album-chips > .gallery-chip, + .gallery-ai-tags > .gallery-ai-chip, + .cal-filters > .cal-filter-item +) { + flex: 0 0 auto; + box-sizing: border-box; + height: 23px !important; + min-height: 23px; + padding: 0 8px !important; + border-radius: 6px !important; + font-size: 10px !important; + line-height: 1; + white-space: nowrap; +} +.doclib-lang-chips { + flex: 0 0 auto; + min-height: 25px; + padding: 1px 0 !important; + scroll-padding-inline: 1px; +} +.doclib-chip-scroll-frame { + position: relative; + display: flex; + align-items: center; + flex: 1 1 100%; + min-width: 0; + max-width: 100%; +} +.doclib-chip-scroll-frame > .chip-scroll-strip { + flex: 1 1 auto; + min-width: 0; + margin-left: 0; + margin-right: 0; +} +.doclib-chip-scroll-arrow { + display: none; + position: absolute; + top: calc(50% - 1px) !important; + transform: translateY(-50%); + margin-top: 0; + z-index: 2; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + border-radius: 50%; + box-sizing: border-box; + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + font-size: 18px; + line-height: 18px; + cursor: pointer; + opacity: 0.84; + transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease; +} +.doclib-chip-scroll-arrow-glyph { + display: inline-block; + position: relative; + top: -1px; + line-height: 1; +} +.doclib-chip-scroll-arrow:hover { + opacity: 1; + background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); +} +@media (max-width: 768px) { + .doclib-chip-scroll-arrow { display: none !important; } +} +.doclib-chip-scroll-frame.has-overflow > .doclib-chip-scroll-arrow:not([hidden]) { + display: inline-flex; +} +.doclib-chip-scroll-arrow.left { left: 0; } +.doclib-chip-scroll-arrow.right { right: 0; } +.doclib-chip-scroll-frame.has-overflow > .chip-scroll-strip { + padding-left: 0 !important; + padding-right: 22px !important; +} +.doclib-chip-scroll-frame > .chip-scroll-strip.is-pointer-dragging { + cursor: grabbing; + user-select: none; +} +.doclib-chip-scroll-frame > .chip-scroll-strip:not(.is-pointer-dragging) { + cursor: grab; +} +/* Calendar tags are wrapped by chipScroll.js after render. Keep that wrapper + content-sized inside the calendar's column layout; the default flex growth + otherwise turns the one-line tag strip into a large empty panel. */ +#cal-body > .doclib-chip-scroll-frame:has(> .cal-filters) { + flex: 0 0 auto; + min-height: 0; +} +/* The completed-task filters are a compact toolbar, not a flexible panel. + Without this override the shared chip frame grows into the available + height of the card, which is especially visible the first time the mobile + view switches to Completed. */ +.tasks-completed-card > .doclib-chip-scroll-frame:has(> #tasks-completed-status-chips) { + flex: 0 0 auto; + min-height: 0; +} +/* The active Tasks filter row is also a compact toolbar. Keep its wrapper + content-sized so changing a filter cannot make the mobile panel grow a + large empty gap between the chips and the task cards. */ +.admin-card > .doclib-chip-scroll-frame:has(> #tasks-filter-chips) { + flex: 0 0 auto; + min-height: 0; +} +#memory-category-filters { + flex: 0 0 auto; + min-height: 25px; + padding: 1px 0 !important; + scroll-padding-inline: 1px; +} +.cookbook-serve-tag-label { + display: inline-flex; + align-items: center; + min-width: 0; +} +#serve-search, +#hwfit-cache-select, +#serve-tags { + position: relative; + top: -2px; +} +#hwfit-cache-select { + top: -4px; +} +/* Calendar previously framed its tags as a separate card. It now follows the + same unframed placement as the other filter strips. */ +.cal-filters { + border: 0; + background: none; + box-shadow: none; +} +.skill-duplicate-keep { + background: color-mix(in srgb, var(--color-success, #4ade80) 22%, transparent); + color: var(--color-success, #4ade80); +} +.skill-duplicate-group { + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + color: color-mix(in srgb, var(--fg) 62%, transparent); + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.skill-duplicate-lower { + background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); + color: var(--color-danger, #e06c75); +} +.skill-stats { + font-size: 9px; + font-family: monospace; + white-space: nowrap; + color: color-mix(in srgb, var(--fg) 45%, transparent); +} +.skill-turns-saved { + font-size: 9px; + opacity: 0.82; + font-variant-numeric: tabular-nums; + white-space: nowrap; + flex: 0 0 auto; +} +.skill-expanded-savings { + display: flex; + align-items: center; + min-height: 18px; + margin: 0 0 5px; +} +.skill-turns-positive { + color: color-mix(in srgb, var(--color-success, #4ade80) 82%, var(--fg)); +} +.skill-turns-zero, +.skill-turns-unknown { + color: color-mix(in srgb, var(--fg) 48%, transparent); +} +.skill-turns-negative { + color: color-mix(in srgb, var(--color-danger, #e06c75) 80%, var(--fg)); +} +.skill-conf { + display: inline-block; + min-width: 4ch; + text-align: right; + font-weight: 700; + color: var(--accent, var(--red)); +} +.skill-verified, +.skill-teachermark, +.skill-needsmark { + display: inline-flex; + align-items: center; + vertical-align: middle; + margin-right: 3px; +} +.skill-verified { color: var(--accent, #4ade80); } +.skill-teachermark { color: var(--color-warning, #f0ad4e); } +.skill-needsmark { color: var(--color-danger, #e06c75); cursor: help; } + +/* The card currently being processed by an "Audit now" run glows + pulses so + it's obvious which one the audit is on. */ +@keyframes skill-audit-pulse { + 0%, 100% { box-shadow: 0 0 6px 0 color-mix(in srgb, var(--accent, #4ade80) 55%, transparent); } + 50% { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent, #4ade80) 85%, transparent); } +} +.skill-card.skill-audit-active { + border-color: var(--accent, #4ade80) !important; + animation: skill-audit-pulse 1.3s ease-in-out infinite; +} + +/* A test is running for this skill — the app's whirlpool spinner is injected + next to the name from JS (see _setCardRunning) so the collapsed/folded card + still makes it obvious work is in progress. */ +/* Collapsed bar shows the kebab menu; expanded shows an up-chevron to + collapse. (Toggled by the .doclib-card-expanded class.) */ +.skill-kebab-btn { + display: inline-flex; + align-items: center; + justify-content: center; + background: none; + border: none; + padding: 4px; + margin: 0; + cursor: pointer; + color: var(--fg); + opacity: 0.5; + border-radius: 5px; + flex-shrink: 0; + position: relative; + top: -2px; +} +.skill-kebab-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 10%, transparent); } +.skill-chevron-up { display: none; align-items: center; opacity: 0.5; flex-shrink: 0; } +/* Kebab dropdown */ +.skill-kebab-menu { + position: fixed; + /* z-index is set inline via topPortalZ() at open time (#4720); a static + value here loses once the modal bring-to-front counter climbs past it. */ + min-width: auto; + width: max-content; + padding: 4px; + background: var(--panel, var(--bg)); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 6px 20px rgba(0,0,0,0.22); + display: flex; + flex-direction: column; + gap: 1px; +} +.skill-kebab-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 6px 8px; + background: none; + border: none; + border-radius: 8px; + cursor: pointer; + color: var(--fg); + font-size: 11px; + text-align: left; +} +.skill-kebab-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); } +.skill-kebab-item.danger { color: var(--color-error, #e55); } +.skill-kebab-item.danger:hover { background: color-mix(in srgb, var(--color-error, #e55) 15%, transparent); } +/* Section labels separating "Your skills" from the read-only "Built-in + capabilities" list. */ +.skills-section-label { + font-size: 10px; + opacity: 0.5; + text-transform: uppercase; + letter-spacing: 0.04em; + margin: 10px 2px 4px; + flex-shrink: 0; +} +.skills-section-label:first-child { margin-top: 0; } +/* When a learned skill is expanded the grid hides sibling cards; hide the + section labels too so they don't orphan above the collapsed list. */ +#skills-list:has(.doclib-card-expanded) .skills-section-label { display: none; } +/* Built-in cards are read-only — no expand/hover-pointer affordance. */ +.skill-builtin-card { cursor: default; } +.skill-builtin-card:hover { border-color: var(--border); } +.skill-card-preview .skill-md-pre { + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 11.5px; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; +} +.skill-preview-copy { + position: absolute; + top: 6px; + right: 8px; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + opacity: 0; + color: var(--fg); + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + cursor: pointer; + transition: opacity .15s, color .15s, border-color .15s; +} +.skill-card-preview:hover .skill-preview-copy { opacity: 0.7; } +.skill-preview-copy:hover, +.skill-preview-copy:focus-visible { opacity: 1 !important; } +.skill-preview-copy.copied { + opacity: 1; + color: var(--color-save-green, #4caf50); + border-color: var(--color-save-green, #4caf50); +} +/* :has()-free expand layout. skills.js adds .skills-has-expanded to the + skills admin-card when a skill opens, so this works on engines that don't + support :has() (e.g. older Firefox mobile, where the expand stuck at + ~50%). Hide everything but the list so the grid fills the card; the + absolutely-positioned expanded card (mobile) then fills the grid. */ +.admin-card.skills-has-expanded > *:not(#skills-list) { + display: none !important; +} +.admin-card.skills-has-expanded > #skills-list.doclib-grid { + flex: 1 1 auto !important; + height: 100% !important; + min-height: 0 !important; + overflow: hidden !important; +} +/* Expand animation — a clean opacity fade. The mobile card snaps to a + full-screen overlay (position:absolute), so any translate/scale on it + reads as a jarring "explosion"; a pure fade just appears smoothly. (No + transform also keeps skills.js's height measurement accurate.) */ +@keyframes skill-card-expand { + from { opacity: 0; } + to { opacity: 1; } +} +.skill-card.doclib-card-expanded { + animation: skill-card-expand 0.18s ease-out; +} +/* Switching directly from one expanded card to another: no fade (it would + show the previous card collapsing through the semi-transparent new one). */ +.skill-card.doclib-card-expanded.skill-expand-instant { + animation: none !important; +} +@media (prefers-reduced-motion: reduce) { + .skill-card.doclib-card-expanded, + .skill-card.doclib-card-expanded .doclib-card-preview { + animation: none !important; + } +} +/* The preview WRAPPER inherits flex:1 (grow) from the generic doclib expand + rule — that's what stretched the card and left a void / pushed the footer + out. Size it to content (shrink + scroll only when long) so the footer + sits right under the preview and stays fully visible. */ +.skill-card.doclib-card-expanded .doclib-card-preview { + flex: 0 1 auto !important; + min-height: 0; + /* The footer lives INSIDE this wrapper. Don't let the wrapper scroll, or + the footer scrolls off with the content. Keep it clipped; the + inside is the scroller, so the footer stays pinned at the bottom. */ + overflow: hidden !important; +} +.skill-card.doclib-card-expanded .skill-md-pre { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; +} +/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them + back down 4px so they're vertically centered in the skill card footer. */ +.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn, +.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { + top: 0; +} +/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */ +#skills-bulk-bar .memory-toolbar-btn { + position: relative; + top: -2px; +} +/* ── Audit-all progress panel ── */ +.skills-audit-panel { + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px 10px; + margin: 6px 0; + background: color-mix(in srgb, var(--fg) 3%, transparent); +} +.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } +.skills-audit-title { font-size: 12px; font-weight: 600; } +.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; } +.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; } +.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; } +.skills-audit-log { + max-height: 22vh; overflow-y: auto; + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 10.5px; line-height: 1.5; opacity: 0.8; +} +/* ── Skill test monitor + AI eval verdict ── */ +.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; } +.skill-test-log { + flex: 1 1 auto; + min-height: 120px; + max-height: 48vh; + overflow-y: auto; + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 9.5px; + line-height: 1.45; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + white-space: pre-wrap; + word-break: break-word; +} +.skill-test-log > div { margin: 1px 0; } +.skill-test-meta { opacity: 0.5; } +.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; } +.skill-test-round { opacity: 0.6; margin-top: 6px !important; } +.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; } +.skill-test-out { opacity: 0.7; padding-left: 10px; } +.skill-test-say { white-space: pre-wrap; } +.skill-test-err { color: var(--color-danger, #e06c75); } +.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } +.skill-eval-badge { + font-size: 10px; font-weight: 700; letter-spacing: 0.03em; + padding: 2px 8px; border-radius: 10px; flex-shrink: 0; +} +.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); } +.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); } +.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); } +.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); } +.skill-eval-summary { font-size: 11px; opacity: 0.85; } +.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; } +.skill-eval-issues li { margin: 2px 0; } +.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } +.skill-eval-approve.suggested { + border-color: var(--color-success, #4ade80) !important; + color: var(--color-success, #4ade80) !important; +} +/* Already published — the button confirms the state (click to unpublish). */ +.skill-eval-approve.is-approved { + border-color: var(--color-success, #4ade80) !important; + color: var(--color-success, #4ade80) !important; + background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important; +} +/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title, + When to use, How, Tags) is accent-colored while the rest stays muted — real + placeholders can't be partially colored. The native placeholder is a single + space so :placeholder-shown still toggles the overlay. */ +.skill-ph-wrap { position: relative; } +.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; } +.skill-rich-ph { + position: absolute; + left: 11px; right: 11px; top: 50%; + transform: translateY(-50%); + pointer-events: none; + font-size: 12px; + line-height: 1.2; + color: color-mix(in srgb, var(--fg) 40%, transparent); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +/* Textarea: align the hint to the first line instead of vertical-centering. */ +.skill-rich-ph-top { top: 7px; transform: none; } +.skill-rich-ph .k { color: var(--accent, var(--red)); } +/* Keep the memory-input enter hint pinned to the field's right edge instead + of letting it drift after the example text. */ +#new-memory-input ~ .skill-rich-ph > svg { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + margin-left: 0 !important; +} +/* Hide the overlay once the field has real content. */ +.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; } +.skill-md-editor { + flex: 1 1 auto; + min-height: 0; + width: 100%; + box-sizing: border-box; + resize: none; + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 11.5px; + line-height: 1.5; + padding: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); +} +.doclib-empty { + text-align: center; + color: color-mix(in srgb, var(--fg) 35%, transparent); + padding: 32px 16px; + font-size: 12px; + font-style: italic; +} +/* Unified loading row across every Library tab (Chats / Documents / Research / + Archive): one opacity, one size, with the whirlpool spinner next to it. */ +.lib-loading-row { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 28px 16px; + font-size: 12px; + color: color-mix(in srgb, var(--fg) 45%, transparent); +} +.pdf-loading-state { + min-height: 120px; + margin: 0; + box-sizing: border-box; + flex-direction: column; + gap: 10px; + border: 0; + border-radius: 0; + background: transparent; + color: color-mix(in srgb, var(--fg) 68%, transparent); + box-shadow: none; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.01em; +} +.pdf-loading-state .ai-spinner-whirlpool { + color: var(--accent, var(--red)); + opacity: 0.95; +} +.pdf-loading-state .ai-spinner-whirlpool canvas { + display: block; +} +.doclib-load-more { + display: block; + margin: 10px auto 0; + padding: 6px 16px; + background: transparent; + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg-muted); + font-size: 11px; + cursor: pointer; + transition: border-color 0.15s, color 0.15s; + flex-shrink: 0; + position: relative; + top: 10px; +} +/* Soft dark gradient above the button — blends the list content into the + load-more strip instead of a hard cutoff. Spans the modal width and + sits behind/above the button. */ +.doclib-load-more::before { + content: ""; + position: absolute; + left: 50%; + transform: translateX(-50%); + bottom: 100%; + margin-bottom: 4px; + width: min(600px, 92vw); + height: 34px; + pointer-events: none; + background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent); +} +.doclib-load-more:hover { + border-color: var(--red); + color: var(--red); +} +.doclib-inline-load-more { + border: 0; + border-radius: 0; + padding: 0; + background: none; + top: 0; + font-size: 9px !important; + line-height: 1.2 !important; +} + +/* Document library toolbar buttons */ +.doclib-toolbar-btn { + background: none; + border: 1px solid var(--border); + color: var(--fg-muted); + font-size: 11px; + padding: 5px 10px; + border-radius: 6px; + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; + font-family: inherit; + display: inline-flex; + align-items: center; + gap: 3px; +} +.doclib-toolbar-btn:hover { + color: var(--fg); + border-color: var(--fg); +} +.doclib-toolbar-btn.active { + background: color-mix(in srgb, var(--red) 15%, transparent); + border-color: color-mix(in srgb, var(--red) 40%, transparent); + color: var(--red); +} +.doclib-toolbar-btn.danger { + color: var(--color-error, #e55); +} +.doclib-toolbar-btn.danger:hover:not(:disabled) { + border-color: var(--color-error, #e55); +} +.doclib-toolbar-btn:disabled { + opacity: 0.4; + cursor: default; +} + +/* Bulk action bar */ +.doclib-bulk-bar { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 8px; + border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); + border-radius: 6px; + background: color-mix(in srgb, var(--red) 5%, transparent); + font-size: 10px; + margin-bottom: 8px; +} +.doclib-bulk-bar.hidden { + display: none; +} +.doclib-bulk-check-all { + display: flex; + align-items: center; + gap: 3px; + cursor: pointer; + color: color-mix(in srgb, var(--fg) 60%, transparent); + font-size: 10px; +} +#doclib-bulk-bar .memory-toolbar-btn { + position: relative; + top: -3px; +} + +/* Custom checkboxes */ +.doclib-select-cb, +.doclib-bulk-check-all input { + -webkit-appearance: none; + appearance: none; + width: 13px; + height: 13px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + cursor: pointer; + flex-shrink: 0; + margin: 0; + position: relative; + transition: all 0.15s; +} +.doclib-select-cb:hover, +.doclib-bulk-check-all input:hover { + border-color: var(--red); +} +.doclib-select-cb:checked, +.doclib-bulk-check-all input:checked { + background: var(--red); + border-color: var(--red); +} +.doclib-select-cb:checked::after, +.doclib-bulk-check-all input:checked::after { + content: ''; + position: absolute; + left: 3px; + top: 0px; + width: 4px; + height: 8px; + border: solid var(--bg); + border-width: 0 1.5px 1.5px 0; + transform: rotate(45deg); +} + + +/* + button outside scroll area (when doc is on right side) */ + +@media (max-width: 600px) { + .doclib-toolbar { + flex-direction: column; + } + .doclib-card-session, + .doclib-card-time { + display: none; + } + .doclib-card-preview { + max-height: 40vh; + } +} + +/* Collapse card tags before they compete with attachment/favorite controls. */ +.email-card-tags .email-tags-more { + color: var(--email-tags-more-color, var(--fg)); + background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent); +} +.email-tags-more-single, +[data-email-tags-count-mini] { display: none; } +.email-tags-more-single svg { display: none; } +.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag, +.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra { + display: none; +} +.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single, +.email-card-tags.email-tags-expanded .email-tags-more-single { + display: inline-flex; +} +.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; } +.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini], +.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; } + +/* Keep the reader actions in drafting order. */ +.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; } +.email-reader-actions-inline > [data-act="reply"] { order: 2; } +.email-reader-actions-inline > [data-act="reply-all"] { order: 3; } +.email-reader-actions-inline > [data-act="forward"] { order: 4; } diff --git a/static/css/09-gallery.css b/static/css/09-gallery.css new file mode 100644 index 000000000..6554a5813 --- /dev/null +++ b/static/css/09-gallery.css @@ -0,0 +1,1382 @@ +/* 09-gallery - 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. + */ +.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed { + display: none !important; +} + +/* ── Archive browser ── */ +.archive-list { + grid-template-columns: 1fr !important; + gap: 4px !important; +} +.archive-row { + flex-direction: row !important; +} +.archive-row .doclib-card-header { + flex: 1; + padding: 8px 10px; +} +.archive-row .doclib-card-title { + flex: 1; + min-width: 0; +} +/* Archive column layout */ +.archive-header { + display: flex; + align-items: center; + padding: 4px 10px; + gap: 6px; + font-size: 9px; + font-weight: 600; + opacity: 0.35; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.archive-col-title { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-left: -4px; +} +.archive-col-msgs { + width: 40px; + flex-shrink: 0; + text-align: left; + margin-left: -8px; +} +.archive-col-model { + width: 90px; + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.archive-col-time { + width: 55px; + flex-shrink: 0; + text-align: right; +} +.archive-col-menu { + width: 24px; + flex-shrink: 0; +} +.archive-menu-btn { + background: transparent; + border: none; + color: var(--fg); + opacity: 0.3; + cursor: pointer; + padding: 4px; + line-height: 0; + border-radius: 4px; + transition: background .15s, opacity .15s; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + margin-left: 4px; + vertical-align: middle; + height: 100%; +} +.archive-menu-btn svg { + display: block; + margin-top: -6px; +} +.archive-menu-btn:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); + opacity: 1; +} + +/* ── Gallery (image library) ── */ + +.gallery-modal-content { + width: min(820px, 94vw); + max-height: 92vh; + font-size: 12px; +} +/* Keep the Gallery frame anchored while tabs or bulk-selection controls + change the content height. Centering the frame made every shorter tab move + the whole window vertically. */ +@media (min-width: 769px) { + #calendar-modal, + #gallery-modal, + #tasks-modal, + #memory-modal, + #doclib-modal, + #compare-model-overlay, + #research-overlay, + #theme-modal, + #settings-modal, + #email-lib-modal, + #cookbook-modal { + align-items: flex-start; + padding-top: 4vh; + box-sizing: border-box; + } +} +/* While the Edit tab is active the editor needs a *definite* height so + the right-side tool panel (`.ge-right-panel { overflow-y:auto }`) + scrolls INTERNALLY. Without this the modal only has `max-height`, so + `.gallery-editor { height:100% }` can't resolve — the editor grows to + its full content height, overflows the modal body, and the modal-body + scrollbar that appears can't be wheel-scrolled because the editor's + `overscroll-behavior:contain` blocks the chain. Pinning a real height + here makes the panel bounded and scrollable as designed. Scoped to + the editor view (matched via the container's inline `display:flex`) + so the Photos/Albums views keep sizing to their content. */ +.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) { + height: 92vh; +} +/* The editor tab owns the modal body's scroll context. Keep the landing + chooser and the mounted editor inside the available sheet height so a long + saved-project list cannot push the launch controls below the viewport. */ +.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body { + display: flex; + min-height: 0; + overflow: hidden; +} +.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container { + min-height: 0; + overflow: hidden; +} +/* Photo-detail view sizing (issue #314). + The detail view is rendered as a `position:absolute; inset:0` overlay + *inside* `.gallery-images-container`, painted over the photo grid. Because + it's absolutely positioned it can't contribute to the container's height — + the container (and therefore the overlay's `inset:0` box) collapses to the + height of the grid sitting behind it. When the library only has a few + photos that grid is short, so the detail view is crushed: the image is + clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a + tiny, internally-scrolling strip. (With a large library the grid is tall, + which is why it looked fine in the demo video but cramped for users with + few photos.) + Fix: when the detail view is open, hide the grid-view siblings and drop the + overlay into normal flow. The container — and the window, up to its 92vh + max-height — then sizes to the detail's own content (image + metadata), so + nothing is clipped or squeezed regardless of how many photos exist. Scoped + via the detail element's inline `display:flex` so the grid / albums views + keep sizing to their own content. Works on both desktop and the mobile + full-screen sheet. */ +#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) { + display: none !important; +} +#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail { + position: static; +} +/* Containing block for the photo-detail overlay — keeps it inside the body + so it sits below the modal header and the tab strip instead of covering them. */ +.gallery-images-container { + position: relative; +} +.gallery-modal-content .modal-header h4 { + font-size: 1rem; +} +.gallery-stats { + font-size: 10px; + color: color-mix(in srgb, var(--fg) 50%, transparent); + margin-bottom: 4px; +} +.gallery-toolbar { + display: flex; + gap: 6px; + margin-bottom: 4px; + align-items: center; +} +.gallery-toolbar-break { display: none; } +/* Keep the hint anchored while independently seating the input in the row. */ +.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; } +.gallery-search { + position: relative; + top: 4px; + flex: 1; + width: 100%; + padding: 4px 8px; + padding-right: 78px; /* room for the enter hint */ + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + font: inherit; + font-size: 12px; + outline: none; +} +.gallery-search-enter-hint { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10px; + font-weight: 600; + color: var(--accent, var(--red)); + pointer-events: none; + opacity: 0; + transition: opacity 0.12s; + white-space: nowrap; +} +/* Show the hint only once the user has typed something (placeholder gone). */ +.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; } +.gallery-search:focus { + border-color: var(--red); +} +.gallery-model-filter, +.gallery-sort { + padding: 4px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; + position: relative; + top: 4px; +} +.gallery-selection-bar { + margin-bottom: 4px; + position: relative; + top: 2px; + padding-right: 18px; +} +.gallery-tag-chips { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-bottom: 4px; +} +.gallery-chip { + height: 28px; + box-sizing: border-box; + padding: 0 13px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + border-radius: 14px; + font-size: 12px; + line-height: 1; + border: 1px solid var(--border); + background: none; + color: var(--fg); + cursor: pointer; + transition: border-color 0.15s, background 0.15s; +} +.gallery-chip:hover { + border-color: var(--red); +} +.gallery-chip.active { + background: color-mix(in srgb, var(--red) 15%, transparent); + border-color: color-mix(in srgb, var(--red) 40%, transparent); + color: var(--red); +} +/* Album chips row */ +.gallery-album-chips { + display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px; +} +.gallery-chip-fav { color: var(--red); } +.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); } +.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; } +/* Active-album indicator — appears when the Photos grid is filtered to one album. */ +.gallery-chip-active-album, +.gallery-active-filter-pill { + display: inline-flex; align-items: center; gap: 4px; + background: color-mix(in srgb, var(--red) 14%, transparent); + border-color: color-mix(in srgb, var(--red) 45%, transparent); + position: relative; + top: 6px; +} +.gallery-active-filter-pill { + background: color-mix(in srgb, var(--fg) 7%, transparent); + border-color: color-mix(in srgb, var(--fg) 18%, transparent); + color: color-mix(in srgb, var(--fg) 86%, transparent); +} +.gallery-clear-all-filters { + height: 28px; + border-style: dashed; + opacity: 0.72; +} +.gallery-clear-all-filters:hover { + opacity: 1; + color: var(--red); +} +.gallery-chip-clear { + background: none; + border: none; + color: inherit; + opacity: 0.6; + cursor: pointer; + padding: 0; + margin-left: 1px; + width: 12px; + height: 12px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 12px; + line-height: 1; + position: relative; + top: -4px; +} +.gallery-chip-clear:hover { opacity: 1; } + + + +/* Favorite button on card */ +/* Video cards: fills the same slot as , plus a small play badge + so it's clear the thumbnail represents a video. */ +.gallery-card video { + width: 100%; + aspect-ratio: 1 / 1; + object-fit: cover; + background: #000; + display: block; +} +.gallery-card-play { + position: absolute; + left: 50%; top: 50%; + transform: translate(-50%, -50%); + width: 36px; height: 36px; + display: flex; align-items: center; justify-content: center; + background: rgba(0, 0, 0, 0.55); + color: #fff; + border-radius: 50%; + pointer-events: none; + z-index: 1; +} +.gallery-card-play svg { margin-left: 2px; } + +.gallery-fav-btn { + position: absolute; top: 0px; right: 4px; z-index: 2; + background: rgba(0,0,0,0.4); border: none; border-radius: 50%; + width: 26px; height: 26px; font-size: 14px; + color: rgba(255,255,255,0.6); cursor: pointer; + display: flex; align-items: center; justify-content: center; + opacity: 0; transition: opacity 0.15s; + padding: 0; +} +.gallery-fav-btn.gallery-fav-active { opacity: 1; color: var(--red); } + +.gallery-dl-btn { + position: absolute; top: 0px; left: 4px; z-index: 2; + background: rgba(0,0,0,0.4); border: none; border-radius: 50%; + width: 26px; height: 26px; + color: rgba(255,255,255,0.75); cursor: pointer; + display: flex; align-items: center; justify-content: center; + opacity: 0; transition: opacity 0.15s, background 0.15s; + padding: 0; +} +.gallery-dl-btn:hover { background: rgba(0,0,0,0.7); color: #fff; } +/* In select mode the per-thumbnail hover buttons (favorite + download) just + get in the way of picking — hide them so the card is a clean select target. + `body.gallery-selecting` is the authoritative signal (one class for the + whole grid) — the per-card .gallery-card-selectable is kept as a backup + in case the body class is missed. */ +body.gallery-selecting .gallery-fav-btn, +body.gallery-selecting .gallery-dl-btn, +.gallery-card-selectable .gallery-fav-btn, +.gallery-card-selectable .gallery-dl-btn { + display: none !important; +} + +/* AI tag chips in detail view */ +.gallery-ai-tags { display: flex; gap: 4px; flex-wrap: wrap; } +.gallery-ai-chip { + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); + border-radius: 10px; padding: 3px 8px; font-size: 10px; + color: var(--fg); opacity: 0.8; + font: inherit; font-size: 10px; + cursor: pointer; + transition: background 0.12s, opacity 0.12s, border-color 0.12s; +} +.gallery-ai-chip:hover { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); +} +/* AI-generated tags: muted neutral with a tiny sparkle marker so the + user can tell at a glance these came from the model, not them. */ +.gallery-aitag-chip { + background: color-mix(in srgb, var(--fg) 6%, transparent); + border-color: color-mix(in srgb, var(--fg) 18%, transparent); + display: inline-flex; + align-items: center; + gap: 4px; +} +.gallery-aitag-chip:hover { + background: color-mix(in srgb, var(--fg) 12%, transparent); + border-color: color-mix(in srgb, var(--fg) 35%, transparent); +} +.gallery-aitag-mark { + font-size: 8px; + opacity: 0.55; + color: var(--accent, var(--red)); +} +/* User-applied tags keep the accent-colored chip — they're the personal/ + curated tags, so they get the strongest visual weight. */ +.gallery-user-chip { + font-weight: 600; +} +/* × to remove a user tag (appears inside the chip). */ +.gallery-tag-x { + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 5px; + width: 13px; + height: 13px; + border-radius: 50%; + font-size: 12px; + line-height: 1; + opacity: 0.55; + transition: opacity 0.12s, background 0.12s, color 0.12s; +} +.gallery-tag-x:hover { + opacity: 1; + background: color-mix(in srgb, var(--red, #ff5555) 22%, transparent); + color: var(--red, #ff5555); +} + +.gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + align-content: start; + gap: 8px; + max-height: calc(92vh - 165px); + overflow-y: auto; + padding: 2px; + transition: border-color 0.15s, background 0.15s; +} +.gallery-grid.gallery-dragover { + border: 2px dashed var(--red); + border-radius: 8px; + background: color-mix(in srgb, var(--red) 5%, transparent); +} +.gallery-card { + position: relative; + aspect-ratio: 1; + border-radius: 6px; + overflow: hidden; + border: 1px solid var(--border); + cursor: pointer; + transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s; +} +.gallery-card.gallery-card-focus { + border-color: var(--accent); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 36%, transparent); +} +/* Upload-affordance tile pinned to the top-left of the Photos grid. Matches + the Upload album tile in the Albums tab — dashed border, centered icon. */ +.gallery-card-upload { + border-style: dashed; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + opacity: 0.75; + transition: opacity 0.12s, border-color 0.15s, transform 0.15s; +} +/* The "Start AI tag" button turns into a Cancel control during a tag run. */ +.gallery-tag-cancelling { + color: var(--red) !important; + border-color: color-mix(in srgb, var(--red) 45%, var(--border)) !important; +} +/* Skeleton/shimmer placeholder tiles shown while the first page of photos loads. */ +.gallery-card-skeleton { + cursor: default; + background: linear-gradient(100deg, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, + color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); + background-size: 200% 100%; + animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; +} +.gallery-card-skeleton:hover { transform: none; box-shadow: none; border-color: var(--border); } +/* Chat attachment image: shimmer skeleton + centered whirlpool shown while a + sent photo uploads / its thumbnail loads (see chatRenderer buildAttachCards). */ +.attach-image-skeleton { + position: relative; + width: 160px; height: 120px; + border-radius: 6px; + display: flex; align-items: center; justify-content: center; + background: linear-gradient(100deg, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, + color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); + background-size: 200% 100%; + animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; +} +.attach-image-skeleton > .spinner-whirlpool { margin: 0 !important; } + +/* Corner "Aa" button on a chat photo thumbnail — opens the vision/OCR editor + so the user can correct what the vision model fed to the LLM. */ +.attach-image-preview { position: relative; } +.attach-vision-model, +.attach-image-name { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.attach-vision-model { + font-size: 10px; + color: var(--accent, var(--red)); + opacity: 0.85; + margin-top: 3px; +} +.attach-image-name { + font-size: 10px; + opacity: 0.5; + margin-top: 1px; +} +.attach-ocr-btn { + position: absolute; top: 4px; right: 4px; + z-index: 5; /* sit above msg-action overlays so the click lands here */ + height: 22px; + background: rgba(0,0,0,0.55); color: #fff; + border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; + padding: 0 8px 0 6px; cursor: pointer; + display: inline-flex; align-items: center; justify-content: center; gap: 5px; + font-size: 11px; font-weight: 500; line-height: 1; + opacity: 0.75; transition: opacity 0.15s; +} +.attach-ocr-btn:hover { opacity: 1; } +.attach-ocr-btn svg { display: block; } +/* On mobile keep just the (larger) edit icon — the "Caption" label crowds + the corner of the small chat photo. */ +@media (max-width: 768px) { + .attach-ocr-btn .attach-ocr-label { display: none; } + .attach-ocr-btn { + width: 28px; height: 28px; + padding: 0; + } + .attach-ocr-btn svg { width: 16px; height: 16px; } +} + +/* Vision/OCR text editor modal — opened from the "Aa" button above. */ +.vision-editor-overlay { + position: fixed; inset: 0; z-index: 9999; + background: rgba(0,0,0,0.55); + display: flex; align-items: center; justify-content: center; + padding: 16px; +} +.vision-editor-panel { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 10px; + padding: 14px 16px; + width: min(560px, 92vw); + max-height: 88vh; + display: flex; flex-direction: column; gap: 8px; + box-shadow: 0 14px 40px rgba(0,0,0,0.4); +} +.vision-editor-title { + font-size: 13px; font-weight: 600; color: var(--fg); + display: flex; align-items: center; gap: 6px; +} +.vision-editor-desc { font-size: 11px; opacity: 0.6; line-height: 1.4; } +.vision-editor-text { + width: 100%; min-height: 180px; + background: var(--panel); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; + padding: 8px 10px; + font-family: inherit; font-size: 12px; line-height: 1.5; + resize: vertical; outline: none; +} +.vision-editor-text:focus { border-color: var(--accent-primary, var(--red)); } +.vision-editor-hint { font-size: 10.5px; opacity: 0.55; line-height: 1.4; } +.vision-editor-actions { + display: flex; gap: 8px; justify-content: flex-end; + margin-top: 4px; +} +.vision-editor-btn { + background: var(--panel); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 14px; font-size: 12px; cursor: pointer; + transition: border-color 0.15s, background 0.15s; + display: inline-flex; align-items: center; gap: 5px; +} +.vision-editor-btn svg { display: block; } +/* Optical nudge: the button text sits 1px higher than the SVG glyphs + alongside it. Drop the text down 1px so they line up. */ +.vision-editor-btn .vision-btn-label { position: relative; top: 1px; } +.vision-editor-btn:hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); } +.vision-editor-btn-primary { + background: var(--accent-primary, var(--red)); + color: #fff; border-color: transparent; +} +.vision-editor-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } + +/* Quick full-size lightbox shown when the user taps a chat photo thumbnail. */ +.attach-lightbox { + position: fixed; inset: 0; z-index: 9998; + background: rgba(0, 0, 0, 0.85); + display: flex; align-items: center; justify-content: center; + padding: 16px; cursor: zoom-out; + animation: attach-lightbox-fade 0.12s ease-out; +} +.attach-lightbox img { + max-width: 100%; max-height: 100%; + border-radius: 6px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + object-fit: contain; +} +.attach-lightbox-err { + position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); + background: var(--bg); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 10px; font-size: 12px; +} +@keyframes attach-lightbox-fade { + from { opacity: 0; } + to { opacity: 1; } +} + +.gallery-card-upload:hover { + opacity: 1; + border-color: var(--red); + transform: translateY(-1px); +} +.gallery-card-upload-inner { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + color: var(--fg); +} +.gallery-card-upload-label { + font-size: 12px; + font-weight: 500; + opacity: 0.8; +} +.gallery-card:hover { + border-color: var(--red); + box-shadow: 0 2px 8px color-mix(in srgb, var(--red) 15%, transparent); + transform: translateY(-1px); +} +.gallery-card-overlay { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 8px; + background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 34%, rgba(0,0,0,0.22)); + color: #fff; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s; +} +.gallery-card-badges { + display: flex; + gap: 4px; + flex-wrap: wrap; + align-items: flex-end; + justify-content: flex-end; + margin-top: auto; + margin-bottom: 34px; +} +.gallery-card-badge { + padding: 2px 6px; + border-radius: 999px; + background: rgba(0, 0, 0, 0.55); + border: 1px solid rgba(255, 255, 255, 0.14); + color: rgba(255, 255, 255, 0.88); + font-size: 9px; + font-weight: 600; + line-height: 1.1; +} +.gallery-card-badge-fav { + color: #ff5f6f; +} +.gallery-select-dot { + position: absolute; top: 6px; left: 6px; z-index: 2; + display: block; + width: 10px; height: 10px; border-radius: 50%; cursor: pointer; + background: color-mix(in srgb, var(--fg) 30%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 50%, transparent); + transition: background 0.15s; +} +.gallery-select-dot.selected { background: var(--red); border-color: var(--red); } +.gallery-select-dot:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } +.gallery-select-btn { + padding: 9px 10px 11px; background: transparent; color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; cursor: pointer; + font-size: 11px; font-family: inherit; opacity: 0.6; transition: all 0.15s; + margin-top: -4px !important; +} +.gallery-select-btn:hover { opacity: 1; } +/* Match the library Select toggle's red tint (.memory-toolbar-btn.active). + Bumped contrast so the toggled state actually reads on mobile — the prior + 15% red on transparent was nearly invisible against the page background. */ +.gallery-select-btn.active { + background: color-mix(in srgb, var(--red) 28%, transparent); + color: var(--red); + border-color: var(--red); + font-weight: 600; + opacity: 1; +} +/* Toolbar action buttons sit 2px lower than the generic select-btn so + they baseline-align with the toolbar's selects/inputs instead of + floating above them. */ +.gallery-toolbar-action { + margin-top: 0 !important; + display: inline-flex; + align-items: center; + gap: 2px; +} +.gallery-bulk-bar { + display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 12px; + justify-content: flex-end; +} +.gallery-bulk-bar.hidden { display: none; } +.gallery-bulk-all { + display: inline-flex; align-items: center; gap: 4px; + font-size: 11px; opacity: 0.75; cursor: pointer; margin-right: auto; +} +.gallery-bulk-all input { accent-color: var(--accent, var(--red)); cursor: pointer; } +.gallery-bulk-delete { + padding: 3px 10px; background: transparent; color: var(--red); + border: 1px solid var(--red); border-radius: 4px; cursor: pointer; + font-size: 11px; font-family: inherit; +} +.gallery-bulk-delete:hover { background: var(--red); color: #fff; } +.gallery-bulk-cancel { + padding: 3px 10px; background: transparent; color: var(--fg); + border: 1px solid var(--border); border-radius: 4px; cursor: pointer; + font-size: 11px; font-family: inherit; opacity: 0.6; +} +.gallery-bulk-cancel:hover { opacity: 1; } +.gallery-card img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +.gallery-card-info { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 6px 8px; + background: linear-gradient(transparent, rgba(0,0,0,0.75)); + color: #fff; +} +.gallery-card-prompt { + font-size: 10px; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.gallery-card-meta { + display: flex; + align-items: center; + gap: 6px; + margin-top: 2px; + font-size: 9px; + opacity: 0.8; +} +.gallery-card-model { + padding: 1px 5px; + border-radius: 6px; + background: rgba(255,255,255,0.15); +} +.gallery-empty { + text-align: center; + color: color-mix(in srgb, var(--fg) 35%, transparent); + padding: 32px 16px; + font-size: 12px; + font-style: italic; +} +.gallery-load-more { + display: block; + margin: 10px auto 0; + padding: 6px 16px; + background: none; + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 11px; + transition: border-color 0.15s, color 0.15s; +} +.gallery-load-more:hover { + border-color: var(--red); + color: var(--red); +} + +/* Gallery detail overlay */ +.gallery-detail { + position: absolute; + inset: 0; + background: var(--panel); + z-index: 10; + display: flex; + flex-direction: column; + overflow-y: auto; +} +.gallery-detail-header { + display: flex; + align-items: center; + gap: 6px; + padding: 2px 8px 4px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + /* Pin to top so a tall (portrait) photo can't cover the Back / ⋮ + menu when the detail body grows past the visible area. */ + position: sticky; + top: 0; + background: var(--panel); + /* Above the image's rotate/nav buttons (z-index:3) so they scroll cleanly + behind the header instead of colliding with it. */ + z-index: 5; + min-height: 0; +} +.gallery-detail-title { + max-width: min(360px, 40vw); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 12px; + font-weight: 600; + opacity: 0.82; + position: relative; + top: 1px; +} +.gallery-detail-back { + background: none; + border: none; + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 12px; + padding: 4px 8px; + border-radius: 4px; + position: relative; + top: -2px; +} +/* Slightly larger header icons (back/edit/heart/⋮) — override the inline 14px. */ +.gallery-detail-header svg { width: 16px; height: 16px; } +.gallery-detail-back:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.gallery-detail-action { + background: none; + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 11px; + padding: 3px 8px; + border-radius: 4px; + transition: border-color 0.15s; +} +.gallery-detail-action:hover { + border-color: var(--fg); +} +.gallery-detail-action.danger { + color: var(--color-error, #e55); +} +/* Overflow menu — replaces the seven individual action buttons that used + to crowd the right side of the detail header. */ +.gallery-detail-menu-wrap { position: relative; } +.gallery-detail-menu-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 26px; + padding: 0; + position: relative; + top: -2px; +} +.gallery-detail-menu { + position: absolute; + top: calc(100% + 4px); + right: 0; + min-width: 150px; + padding: 3px; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); + z-index: 12; + display: flex; + flex-direction: column; + gap: 0; +} +.gallery-detail-menu[hidden] { display: none; } +.gallery-detail-menu .dropdown-item-compact { + width: 100%; + background: none; + border: none; + text-align: left; + font: inherit; + /* Tighter than the global default — the photo-detail menu has 8 + items so every pixel of vertical padding adds up. */ + padding: 4px 8px; + font-size: 11px; + gap: 8px; + line-height: 1.2; +} +.gallery-detail-menu .dropdown-item-compact .dropdown-icon, +.gallery-detail-menu .dropdown-item-compact .dropdown-icon svg { + width: 12px; + height: 12px; +} +.gallery-detail-body { + display: flex; + gap: 16px; + padding: 12px; + flex: 1; + min-height: 0; + overflow: hidden; +} +.gallery-detail-image { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + min-width: 0; + min-height: 0; + height: 100%; + padding-inline: 56px; + box-sizing: border-box; + overflow: hidden; +} +/* Shrink-wraps the actual image so overlay children (heart, face boxes) + anchor to the IMAGE bounds, not the wider letterboxed wrapper. */ +.gallery-detail-img-frame { + position: relative; + display: inline-flex; + max-width: 100%; + max-height: 100%; +} +.gallery-detail-nav { + position: absolute; + top: 50%; + transform: translateY(-50%); + background: rgba(0, 0, 0, 0.45); + color: rgba(255, 255, 255, 0.85); + border: none; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s, background 0.15s; + z-index: 3; + padding: 0; +} +.gallery-detail-nav:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } +.gallery-detail-nav-prev { left: 8px; } +.gallery-detail-nav-next { right: 8px; } + +/* Rotate buttons — same pattern as the prev/next arrows but pinned to the + top corners. Hover-revealed so they don't clutter the image. */ +.gallery-detail-rotate { + position: absolute; + top: 8px; + background: rgba(0, 0, 0, 0.45); + color: rgba(255, 255, 255, 0.85); + border: none; + border-radius: 50%; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s, background 0.15s; + z-index: 3; + padding: 0; +} +.gallery-detail-rotate:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } +.gallery-detail-rotate-ccw { left: 8px; } +.gallery-detail-rotate-cw { right: 8px; } +/* Inline heart that lives next to the "Date" label in the sidebar. + Tiny outline icon by default; fills red when favorited. */ +.gallery-detail-date-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; +} +.gallery-detail-fav-inline { + background: none; + border: none; + padding: 0; + /* Nudge up 8px so the heart sits visually above the Date label + baseline rather than centred on it. */ + margin-top: -8px; + border-radius: 4px; + color: var(--fg-muted); + opacity: 0.6; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; + transition: opacity 0.15s, color 0.15s; +} +/* Shrink the SVG so the heart matches the height of the small uppercase + Date label sitting next to it. */ +.gallery-detail-fav-inline svg { + width: 12px; + height: 12px; + display: block; +} +.gallery-detail-fav-inline:hover { + opacity: 1; +} +.gallery-detail-fav-inline.active { + opacity: 1; + color: var(--red); +} +.gallery-detail-sidebar { + width: 290px; + flex-shrink: 0; + /* Vertical scroll for long metadata, but never horizontal — overflow:auto + alone would let a wide child (e.g. long album name in the select) push + the sidebar into horizontal-scroll mode. */ + overflow-x: hidden; + overflow-y: auto; + min-width: 0; +} +.gallery-detail-inspector-head { + padding-bottom: 10px; + margin-bottom: 10px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); +} +.gallery-detail-eyebrow { + font-size: 9px; + font-weight: 700; + color: var(--accent, var(--red)); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 3px; +} +.gallery-detail-inspector-title { + font-size: 14px; + font-weight: 650; + line-height: 1.25; + word-break: break-word; +} +.gallery-detail-meta-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 6px; + margin: 4px 0 12px; +} +.gallery-detail-meta-card { + min-width: 0; + border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); + background: color-mix(in srgb, var(--fg) 4%, transparent); + border-radius: 7px; + padding: 7px 8px; +} +.gallery-detail-meta-card span { + display: block; + font-size: 9px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 48%, transparent); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 3px; +} +.gallery-detail-meta-card strong { + display: block; + min-width: 0; + font-size: 11px; + font-weight: 600; + line-height: 1.25; + word-break: break-word; +} +.gallery-detail-inline-link { + appearance: none; + background: none; + border: none; + color: var(--accent, var(--red)); + padding: 0; + font: inherit; + font-weight: inherit; + cursor: pointer; + text-align: left; +} +.gallery-detail-mini-actions { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 7px; +} +.gallery-detail-mini-btn { + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 4px 8px; + font: inherit; + font-size: 10.5px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 4px; +} +.gallery-detail-mini-btn:hover { + border-color: color-mix(in srgb, var(--fg) 42%, var(--border)); +} +/* Constrain any child that could otherwise stretch the sidebar wider than + its declared width (selects with long option text, long URLs, etc.). */ +.gallery-detail-sidebar select, +.gallery-detail-sidebar input, +.gallery-detail-sidebar .gallery-detail-prompt { + max-width: 100%; + box-sizing: border-box; +} +.gallery-date-rel { + opacity: 0.55; + margin-left: 4px; + font-size: 11px; + font-style: italic; +} +.gallery-detail-section label { + display: block; + font-size: 10px; + font-weight: 600; + opacity: 0.6; + margin-bottom: 3px; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.gallery-detail-section div { + font-size: 12px; + word-break: break-word; +} +.gallery-detail-prompt { + white-space: pre-wrap; + line-height: 1.4; +} +.gallery-tag-input, +.gallery-detail-name-input { + width: 100%; + padding: 5px 8px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; + font: inherit; + font-size: 11px; + margin-bottom: 4px; +} +.gallery-tag-input:focus, +.gallery-detail-name-input:focus { + border-color: var(--red); + outline: none; +} +/* ↵ enter hint inside the Add-a-tag field (accent), replacing the "press Enter" + placeholder text. */ +.gallery-tag-input-wrap { position: relative; } +.gallery-tag-enter-hint { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + margin-top: -2px; /* the input has 4px bottom margin; center on the box */ + color: var(--accent, var(--red)); + font-size: 13px; + line-height: 1; + pointer-events: none; + opacity: 0.8; +} +.gallery-detail-name-input { font-size: 13px; font-weight: 500; } +/* ↵ enter hint on the image name field — accent, shown while editing to signal + that Enter saves. */ +.gallery-name-wrap { position: relative; } +.gallery-name-enter { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + margin-top: -2px; + color: var(--accent, var(--red)); + opacity: 0.85; + pointer-events: none; + display: none; +} +.gallery-tag-save { + background: none; + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 10px; + padding: 3px 10px; + border-radius: 4px; + transition: border-color 0.15s, color 0.15s; +} +.gallery-tag-save:hover { + border-color: var(--red); + color: var(--red); +} + +@media (max-width: 600px) { + .gallery-grid { + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 6px; + } + /* Single-row toolbar on mobile: search + Sources + Recent (sort) all share + one row. The search bar shrinks so the dropdowns fit beside it. */ + .gallery-toolbar { + flex-wrap: nowrap; + gap: 4px; + } + .gallery-search-wrap { + flex: 1 1 0; + min-width: 0; + } + .gallery-search { + /* Drop the desktop right-padding reserved for the "enter to tag" hint — + hide the hint on mobile (below) so the input can use its full width. */ + padding-right: 8px; + } + .gallery-search-enter-hint { display: none; } + /* Disable the row break so the filters stay inline with search. */ + .gallery-toolbar-break { display: none; } + .gallery-model-filter, + .gallery-sort { + flex: 0 0 auto; + /* Cap each dropdown so search keeps a usable amount of width. */ + max-width: 100px; + font-size: 11px; + padding: 4px 6px; + /* Keep the same vertical alignment as the desktop toolbar. */ + position: relative; + top: 4px; + } + .gallery-select-btn { + padding: 6px 8px 8px; + font-size: 9px; + flex-shrink: 0; + /* Push Select to the far right of the single-row toolbar, regardless + of its DOM position (sits between search and the dropdowns). */ + order: 99; + margin-left: auto; + } + /* Tabs scroll horizontally rather than wrapping when narrow. */ + .gallery-tabs { + overflow-x: auto; + flex-wrap: nowrap; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + } + .gallery-tabs::-webkit-scrollbar { display: none; } + .gallery-tab { flex-shrink: 0; } + /* Album chips also scroll horizontally so they don't stack. */ + .gallery-album-chips { + overflow-x: auto; + flex-wrap: nowrap; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-bottom: 4px; + } + .gallery-album-chips::-webkit-scrollbar { display: none; } + .gallery-chip { flex-shrink: 0; } + /* Tasks filter chips — same horizontal-swipe behaviour as the gallery + albums / library chips on mobile: one row, slide-to-see-more. */ + .tasks-activity-filters { + overflow-x: auto; + flex-wrap: nowrap !important; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-bottom: 4px; + } + .tasks-activity-filters::-webkit-scrollbar { display: none; } + .tasks-activity-filters > * { flex-shrink: 0; } + /* Detail view */ + .gallery-detail-body { + flex-direction: column; + overflow: visible; + } + .gallery-detail-image { + flex: 0 0 auto; + width: 100%; + height: clamp(280px, 58vh, 520px); + padding-inline: 48px; + } + .gallery-detail-sidebar { + width: 100%; + } + .gallery-detail-title { + display: none; + } + .gallery-detail-meta-grid { + grid-template-columns: 1fr; + } + /* Album grid: smaller tile minimum on mobile. */ + .gallery-albums-grid { + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; + } +} + + +/* ── Scoreboard table ── */ +.scoreboard-table { + width: 100%; + border-collapse: collapse; + font-size: 0.88em; +} +.scoreboard-table th { + text-align: left; + padding: 6px 10px; + border-bottom: 1px solid var(--border); + font-weight: 600; + font-size: 0.85em; + color: color-mix(in srgb, var(--fg) 55%, transparent); + text-transform: uppercase; + letter-spacing: 0.5px; +} +.scoreboard-table th:not(:first-child) { + text-align: center; +} +.scoreboard-table td { + padding: 5px 10px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); + text-align: center; +} +.scoreboard-table td.scoreboard-model { + text-align: left; + font-weight: 500; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.scoreboard-table td.scoreboard-pct { + font-weight: 600; + color: var(--red); +} diff --git a/static/css/style-part-05.css b/static/css/10-cookbook.css similarity index 55% rename from static/css/style-part-05.css rename to static/css/10-cookbook.css index 5008414f9..8acded779 100644 --- a/static/css/style-part-05.css +++ b/static/css/10-cookbook.css @@ -1,14 +1,1639 @@ -/* Fragment 6/9 of the former static/style.css. +/* 10-cookbook - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ +.scoreboard-table tbody tr:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} + +#scoreboard-overlay svg { + color: var(--accent, var(--red)); +} + +/* ── Compare search results ── */ +.compare-search-results { + display: flex; + flex-direction: column; + gap: 2px; +} +.compare-search-result { + padding: 6px 0; + border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); +} +.compare-search-result:last-child { + border-bottom: none; +} +.search-result-title { + color: var(--color-accent); + text-decoration: none; + font-weight: 500; + font-size: 0.9em; + display: block; + line-height: 1.3; +} +.search-result-title:hover { + color: var(--color-link-hover); + text-decoration: underline; +} +.search-result-snippet { + color: color-mix(in srgb, var(--fg) 70%, transparent); + font-size: 0.82em; + line-height: 1.4; + margin-top: 2px; +} +.search-result-url { + color: color-mix(in srgb, var(--fg) 35%, transparent); + font-size: 0.75em; + margin-top: 3px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + + +/* ── Cookbook Tool ── */ + +/* ── Cookbook ── */ +.cookbook-serve-preset { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + margin-bottom: 3px; + cursor: pointer; + transition: background 0.15s; +} +.cookbook-serve-preset:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } +.cookbook-serve-preset-name { + font-size: 11px; + font-weight: 500; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.cookbook-serve-preset-meta { + font-size: 9px; + font-family: 'Fira Code', monospace; + color: var(--fg-muted); + opacity: 0.5; +} +.cookbook-serve-preset-launch { + background: none; + border: none; + color: #50fa7b; + cursor: pointer; + font-size: 10px; + opacity: 0.6; + padding: 0 2px; + position: relative; + top: -4px; +} +.cookbook-serve-preset-launch:hover { opacity: 1; } +.cookbook-serve-preset-rm { + background: none; + border: none; + color: var(--fg-muted); + cursor: pointer; + font-size: 10px; + opacity: 0.3; + padding: 0 2px; + position: relative; + top: -4px; +} +.cookbook-serve-preset-rm:hover { opacity: 0.8; color: var(--red); } +.cookbook-body { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + flex: 1; + min-height: 0; + scrollbar-width: thin; +} +#cookbook-modal .modal-content { + display: flex; + flex-direction: column; + overflow: hidden; +} +#cookbook-modal .modal-header { + flex: 0 0 auto; +} +#cookbook-modal .cookbook-body { + min-height: 0; + overflow-y: auto; + overflow-x: hidden; +} +#cookbook-modal .cookbook-group { + min-height: 0; +} +#cookbook-modal .cookbook-group > .admin-card { + min-height: 0; + /* Let .cookbook-body be the SINGLE scroll surface. Nesting another + overflow:auto here trapped the wheel inside the cached-list when a + serve panel expanded — the page couldn't scroll past the panel's + bottom (Launch button got hidden). */ + overflow: visible !important; +} +#cookbook-modal .cookbook-section-body { + min-height: 0; +} +.cookbook-group { + display: flex; + flex-direction: column; + gap: 8px; + flex: 1; + min-height: 0; +} + +/* Cards — match admin-card */ +.cookbook-card { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + display: flex; + flex-direction: column; + gap: 6px; +} +.cookbook-card-header { + display: flex; + align-items: center; + justify-content: space-between; +} +.cookbook-card-header-actions { display: flex; gap: 4px; } +.cookbook-card-title { font-size: 13px; font-weight: 600; } +.cookbook-card-desc { + font-size: 11px; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} + +/* Fields grid */ +.cookbook-fields { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 6px; + margin-top: 4px; +} +.cookbook-field-label { + display: flex; + flex-direction: column; + font-size: 11px; + color: color-mix(in srgb, var(--fg) 60%, transparent); + gap: 3px; +} + +/* Inputs — match admin-add-form input */ +.cookbook-field-input { + padding: 5px 8px; + font-size: 12px; + font-family: inherit; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + outline: none; +} +.cookbook-field-input:focus { border-color: var(--red); } +#hwfit-dl-server, #hwfit-usecase, #hwfit-server-select, #hwfit-cache-server, #serve-sort { height: 28px; width: 88px; } +/* Serve tab — match Documents sizing, but leave room for the active "Cancel" + label and center it vertically so the descenders don't clip. */ +#hwfit-cache-select { + min-width: 58px; + height: 28px; + position: relative; + top: -3px; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +/* Command preview */ +.cookbook-cmd-preview { + margin: 4px 0 0; + padding: 8px 10px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + font-family: 'Fira Code', monospace; + font-size: 11px; + white-space: pre-wrap; + word-break: break-all; + color: var(--fg); +} + +/* Buttons — match admin-btn-sm */ +.cookbook-actions { display: flex; gap: 6px; margin-top: 2px; } +.cookbook-btn { + padding: 4px 10px; + min-width: 54px; + text-align: center; + font-size: 11px; + font-family: inherit; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); + cursor: pointer; + transition: all 0.15s; +} +.cookbook-btn:hover { + background: var(--border); + border-color: var(--accent); +} +.cookbook-btn:disabled { opacity: 0.5; cursor: not-allowed; } +.cookbook-run-btn { + background: var(--accent); + color: var(--panel); + border-color: var(--accent); + font-weight: 600; +} +.cookbook-run-btn:hover { opacity: 0.85; } +.cookbook-stop-btn { + background: var(--color-error); + border-color: var(--color-error); +} + +/* Output */ +.cookbook-output-wrap { + position: relative; + margin: 0; +} +.cookbook-output-kill { + position: absolute; + top: 4px; + right: 34px; + width: 22px; + height: 22px; + padding: 0; + background: none; + border: 1px solid transparent; + border-radius: 4px; + color: var(--fg-muted); + font-size: 16px; + line-height: 1; + cursor: pointer; + opacity: 0; + transition: opacity .15s, color .15s; + display: flex; + align-items: center; + justify-content: center; +} +.cookbook-output-kill:hover { color: var(--color-error, var(--warn)); border-color: var(--border); } +.cookbook-output-wrap .copy-code { + position: absolute; + top: 6px; + right: 6px; +} +.cookbook-output-wrap:hover .copy-code { opacity: 0.7; } +.cookbook-output-pre { + margin: 0; + padding: 8px 10px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + font-family: 'Fira Code', monospace; + font-size: 11px; + white-space: pre-wrap; + word-break: break-all; + max-height: 180px; + overflow-y: auto; +} +.cookbook-output-error { color: var(--color-error); } + +/* Downloaded/cached models */ +.hwfit-cached-item { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + padding: 6px 8px; + margin: 2px 0; + border-radius: 6px; + border: 1px solid var(--border); + background: color-mix(in srgb, var(--fg) 3%, transparent); + cursor: default; + font-size: 12px; +} +.hwfit-cached-item .hwfit-serve-panel { + flex-basis: 100%; + margin-top: 4px; +} +.hwfit-cached-item:hover { + background: color-mix(in srgb, var(--fg) 6%, transparent); +} +.hwfit-cached-header { + font-size: 10px; + opacity: 0.4; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + border: none !important; + background: none !important; + padding: 2px 8px; + cursor: default !important; +} +.hwfit-cached-header:hover { background: none !important; } +.hwfit-cached-name { + font-weight: 600; + flex-shrink: 0; +} +.hwfit-cached-repo { + flex: 1; + color: var(--fg-muted); + font-size: 11px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hwfit-cached-size, +.hwfit-cached-files { + font-size: 10px; + color: var(--fg-muted); + white-space: nowrap; +} +.hwfit-cached-serve, +.hwfit-cached-delete { + flex-shrink: 0; +} +.hwfit-cached-delete { + opacity: 0.4; + font-size: 10px; +} +.hwfit-cached-delete:hover { + opacity: 1; + color: var(--color-error, var(--warn)); +} +.hwfit-cached-badge { + font-size: 9px; + padding: 1px 6px; + border-radius: 3px; + font-weight: 600; + white-space: nowrap; +} +.hwfit-cached-ready { + background: color-mix(in srgb, var(--color-success, #4caf50) 20%, transparent); + color: var(--color-success, #4caf50); +} +.hwfit-cached-dl { + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 20%, transparent); + color: var(--color-warning, #f0ad4e); +} + +/* Cookbook notification dot */ +#tool-cookbook-btn { + position: relative; +} +#rail-cookbook { + position: relative; +} +.cookbook-open-loading { + display: inline-flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + flex-shrink: 0; + margin-left: 2px; + color: var(--accent, var(--red)); + opacity: 0.9; +} +.cookbook-open-loading .ai-spinner, +.cookbook-open-loading .ai-spinner-whirlpool { + display: inline-flex !important; + width: 14px; + height: 14px; + margin: 0 !important; + gap: 0 !important; +} +.cookbook-open-loading canvas { + display: block; +} +.cookbook-section-loading-wp, +.cookbook-section-loading-wp canvas { + width: 22px !important; + height: 22px !important; + display: block !important; +} +#rail-cookbook .cookbook-open-loading { + position: absolute; + right: 2px; + top: -2px; + width: 12px; + height: 12px; + margin-left: 0; +} +/* The serve-list 'downloading' / 'download stalled' status text sits a hair + low against the rest of the meta line on mobile — nudge it up 2px. */ +@media (max-width: 768px) { + .cookbook-dl-status { + position: relative; + top: -2px; + } +} +.cookbook-notif-dot { + width: 6px; + height: 6px; + flex-shrink: 0; + border-radius: 50%; + background: var(--color-success, #4caf50); + /* Drives the breathing glow in the keyframes (matches email's dot). */ + --notif-glow: var(--color-success, #4caf50); + animation: cookbook-notif-pulse 2s ease-in-out infinite; +} +.cookbook-notif-dot.cookbook-notif-error { + background: var(--color-error, #f44); + --notif-glow: var(--color-error, #f44); + position: relative; + left: -2px; + top: -1px; +} +.rail-notify-error { color: var(--color-error, #f44) !important; } +.cookbook-tab-error-dot { + display: inline-block; width: 5px; height: 5px; border-radius: 50%; + background: var(--color-error, #f44); margin-left: 4px; vertical-align: middle; + animation: cookbook-notif-pulse 2s ease-in-out infinite; +} +@keyframes cookbook-notif-pulse { + 0%, 100% { + opacity: 1; + box-shadow: 0 0 0 0 color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 60%, transparent); + } + 50% { + opacity: 0.85; + box-shadow: 0 0 6px 2px color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 55%, transparent); + } +} +.cookbook-clear-btn { + font-size: 10px !important; + padding: 0 8px !important; + height: 22px !important; + border-radius: 6px !important; + border: 1px solid var(--border) !important; + color: color-mix(in srgb, var(--fg) 40%, transparent) !important; + opacity: 1 !important; + background: none !important; +} +.cookbook-clear-btn:hover { + color: var(--red) !important; + border-color: var(--red) !important; + background: color-mix(in srgb, var(--red) 8%, transparent) !important; +} +.cookbook-bulk-bar { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + margin-bottom: 4px; + background: color-mix(in srgb, var(--red) 6%, transparent); + border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); + border-radius: 6px; + font-size: 11px; +} +.cookbook-bulk-bar.hidden { display: none; } +.serve-select-cb { + width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; cursor: pointer; + background: var(--border); transition: background 0.15s; + align-self: center; + position: relative; + top: -2px; +} +#serve-bulk-bar { + position: relative; + top: -6px; +} +#serve-bulk-bar #serve-bulk-cancel { + top: -2px !important; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 28px; + height: 24px; + line-height: 1; +} +#serve-bulk-bar #serve-bulk-cancel svg { + top: 0; +} +.serve-select-cb.selected { background: var(--red); } +.serve-select-cb:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } +#hwfit-cache-select.active { + background: var(--red); + color: #fff; + border-color: var(--red); +} +.cookbook-serve-dirs { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} +.cookbook-serve-dir-pill { + font-size: 10px; + font-family: 'Fira Code', monospace; + padding: 2px 8px; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); + color: var(--fg-muted); + letter-spacing: 0.2px; +} +/* Status pills shown inline in a Serve-tab card title (next to the model + name) when the model has a live serve / download task. Shared base + class so "running" and "downloading" sit on the same row with the + same chrome; only color varies. */ +.cookbook-serve-running-pill, +.cookbook-serve-downloading-pill { + display: inline-block; + margin-left: 6px; + padding: 1px 7px; + border-radius: 10px; + font-size: 9px; + font-weight: 500; + text-transform: lowercase; + letter-spacing: 0.3px; + vertical-align: 2px; + position: relative; + top: 1px; /* nudged down 2px from the old -1px so it sits + flush with the cap-height of the title text */ +} +.cookbook-serve-running-pill { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); +} +.cookbook-serve-downloading-pill { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); + opacity: 0.85; +} +.cookbook-serve-downloading-pill.is-stalled { + /* Stalled downloads stay visible but read as warning, not progress. */ + color: var(--fg-muted, #888); + background: color-mix(in srgb, var(--fg-muted, #888) 10%, transparent); + border-color: color-mix(in srgb, var(--fg-muted, #888) 30%, transparent); + opacity: 1; +} +.cookbook-serve-running-pill.is-clickable { + cursor: pointer; + transition: background 0.12s, border-color 0.12s; +} +.cookbook-serve-running-pill.is-clickable:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); +} + +.cookbook-serve-dir-edit { + font-size: 9px; + color: var(--fg-muted); + opacity: 0.4; + cursor: pointer; + text-decoration: underline; + text-underline-offset: 2px; +} +.cookbook-serve-dir-edit:hover { + opacity: 0.7; + color: var(--accent, var(--red)); +} +.cookbook-gpu-group { + display: flex; + gap: 3px; + margin-top: -4px; +} +.cookbook-gpu-btn { + width: 30px; height: 30px; + font-size: 12px; + font-family: 'Fira Code', monospace; + border: 1px solid var(--border); + border-radius: 4px; + background: none; + color: var(--fg-muted); + cursor: pointer; + opacity: 0.4; + transition: all 0.15s; + padding: 0; + display: flex; + align-items: center; + justify-content: center; +} +.cookbook-gpu-btn:hover { + opacity: 0.7; + border-color: var(--fg-muted); +} +.cookbook-gpu-btn.active { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent); + border-color: color-mix(in srgb, var(--fg) 55%, transparent); + color: var(--fg); + font-weight: 600; +} +/* Probe annotation states (set by "Free?" button) */ +.cookbook-gpu-btn.gpu-free { + opacity: 1; + border-color: #4ade80; + color: #4ade80; +} +.cookbook-gpu-btn.gpu-busy { + opacity: 0.85; + border-color: color-mix(in srgb, var(--red) 70%, transparent); + color: var(--red); + background: color-mix(in srgb, var(--red) 8%, transparent); +} +.cookbook-gpu-btn.gpu-missing { + opacity: 0.2; + text-decoration: line-through; + cursor: not-allowed; +} +/* "Clear Server" lives in the actions row next to "Probe GPUs". + Inherits .cookbook-btn sizing; this rule just adds the destructive + red tint on hover so the "this kills processes" cue stays. */ +.cookbook-gpu-clear:hover:not(:disabled) { + color: var(--red); + border-color: color-mix(in srgb, var(--red) 60%, var(--border)); + background: color-mix(in srgb, var(--red) 8%, transparent); +} +.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } +.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } +/* GPU probe popup — per-GPU process list with kill buttons */ +.cookbook-gpu-popup { + /* Fixed positioning (relative to viewport) so we never get pulled into + a scrolling/transform stacking context from an ancestor. Z-index has + to clear the cookbook modal (260) and the rest of the high-z UI + layers (themed-confirm and various overlays sit around 9000-10000). */ + position: fixed; + z-index: 10010; + min-width: 280px; + max-width: 420px; + background: var(--panel, #1a1a1a); + border: 1px solid var(--border); + border-radius: 6px; + box-shadow: 0 8px 24px rgba(0,0,0,0.4); + font-family: 'Fira Code', monospace; + font-size: 11px; + color: var(--fg); +} +.cookbook-gpu-popup-head { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + border-bottom: 1px solid var(--border); + font-weight: 600; +} +.cookbook-gpu-popup-stats { + flex: 1; + font-weight: 400; + color: var(--fg-muted); + font-size: 10px; +} +.cookbook-gpu-popup-close { + background: none; + border: none; + color: var(--fg-muted); + cursor: pointer; + font-size: 14px; + padding: 0 4px; +} +.cookbook-gpu-popup-close:hover { color: var(--fg); } +.cookbook-gpu-popup-body { + padding: 4px 0; + max-height: 320px; + overflow-y: auto; +} +.cookbook-gpu-popup-empty { + padding: 10px; + color: var(--fg-muted); + font-style: italic; +} +.cookbook-gpu-proc { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 10px; +} +.cookbook-gpu-proc:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); } +.cookbook-gpu-proc-info { + flex: 1; + display: flex; + gap: 8px; + align-items: center; + min-width: 0; +} +.cookbook-gpu-proc-pid { + color: var(--fg-muted); + width: 56px; + flex-shrink: 0; +} +.cookbook-gpu-proc-name { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.cookbook-gpu-proc-mem { + color: var(--fg-muted); + flex-shrink: 0; +} +.cookbook-gpu-proc-actions { + display: flex; + gap: 4px; + flex-shrink: 0; +} +.cookbook-gpu-kill { + border: 1px solid color-mix(in srgb, var(--red) 60%, transparent); + background: none; + color: var(--red); + border-radius: 3px; + padding: 2px 8px; + cursor: pointer; + font-family: inherit; + font-size: 10px; +} +.cookbook-gpu-kill[data-sig="KILL"] { + background: color-mix(in srgb, var(--red) 12%, transparent); +} +.cookbook-gpu-kill:hover:not(:disabled) { + background: color-mix(in srgb, var(--red) 20%, transparent); +} +.cookbook-gpu-kill:disabled { opacity: 0.4; cursor: wait; } +.cookbook-serve-title { + display: flex; + align-items: center; + gap: 4px; + min-width: 0; +} +.cookbook-serve-title-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.cookbook-hf-link { + font-size: 9px; + text-decoration: none; + color: var(--fg-muted); + opacity: 0.5; + padding: 1px 5px; + border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); + border-radius: 3px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + vertical-align: 1px; + letter-spacing: 0.3px; + font-weight: 600; + flex-shrink: 0; +} +.cookbook-hf-link:hover { + opacity: 0.8; + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); +} +/* In the Trending models list the HF pill is the row's main affordance — + tint it accent so it stands out next to the model name. */ +#cookbook-hf-latest-list .cookbook-hf-link { + color: var(--accent, var(--red)); + opacity: 0.85; + border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); +} + +/* Running tab sections */ +.cookbook-saved-section, +.cookbook-serve-section, +.cookbook-dl-section { + border: 1px solid var(--border); + border-radius: 6px; + margin-bottom: 6px; + overflow: hidden; +} +/* Divider between server-group sections in the Active tab so the + transition between e.g. local + remote-host server blocks reads + visually instead of running into each other. */ +.cookbook-group[data-backend-group="Running"] > [class*="cookbook-server-section-"] + [class*="cookbook-server-section-"] { + margin-top: 14px; + padding-top: 14px; + border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); +} +.cookbook-section-header { + display: flex; + align-items: center; + gap: 6px; + padding: 7px 10px; + cursor: pointer; + user-select: none; + /* Persistent surface + border so it reads as a clickable bar instead of + blending into the panel background. */ + background: color-mix(in srgb, var(--fg) 5%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + transition: background 0.15s, border-color 0.15s; +} +.cookbook-section-header:hover { + background: color-mix(in srgb, var(--fg) 9%, transparent); + border-color: color-mix(in srgb, var(--fg) 22%, transparent); +} +.cookbook-section-header.has-server-color { + color: var(--fg); + border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 45%, var(--border)); + background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 4%, var(--bg)); + box-shadow: inset 6px 0 0 var(--cookbook-server-accent, var(--cookbook-server-color)); +} +.cookbook-section-header.has-server-color:hover { + background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 7%, var(--bg)); + border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 60%, var(--border)); +} +.cookbook-section-header.has-server-color .cookbook-section-title, +.cookbook-section-header.has-server-color .cookbook-section-chevron { + color: var(--cookbook-server-accent, var(--cookbook-server-color)); +} +.cookbook-section-header.has-server-color .cookbook-srv-status.ok { + background: var(--cookbook-server-accent, var(--cookbook-server-color)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 18%, transparent), + 0 0 8px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 55%, transparent); +} +.cookbook-section-header .cookbook-clear-btn { + margin-left: 0; + position: relative; + top: -3px; +} +@media (max-width: 768px) { + .cookbook-section-header .cookbook-clear-btn { + top: -3px; + } + .cookbook-task-check { + top: 4px; + } +} +/* "Stop all" sits just left of "Clear finished"; it carries the auto margin so + the pair is pushed together to the right of the section title. */ +.cookbook-section-header .cookbook-stop-all-btn { + margin-left: auto; + margin-right: 6px; + position: relative; + top: -3px; +} +.cookbook-stop-all-btn { + font-size: 10px !important; + padding: 0 8px !important; + height: 22px !important; + border-radius: 6px !important; + border: 1px solid var(--border) !important; + color: color-mix(in srgb, var(--fg) 40%, transparent) !important; + opacity: 1 !important; + background: none !important; +} +.cookbook-stop-all-btn:hover { + color: var(--warn, #f0ad4e) !important; + border-color: var(--warn, #f0ad4e) !important; + background: color-mix(in srgb, var(--warn, #f0ad4e) 8%, transparent) !important; +} +.cookbook-section-chevron { + flex-shrink: 0; + opacity: 0.6; + transition: transform 0.15s ease, opacity 0.15s ease; +} +.cookbook-section-body { + padding: 0; +} +.cookbook-saved-toggle-header { + font-size: 10px; + font-weight: 600; + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: 0.3px; + margin-bottom: 4px; +} +.cookbook-saved-toggle:hover { color: var(--fg); } +.cookbook-saved-item { + margin: 2px 0; + border-radius: 4px; + font-size: 11px; +} +.cookbook-saved-header { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 6px; + border-radius: 4px; + transition: background 0.08s; +} +.cookbook-saved-header:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.cookbook-saved-item .hwfit-serve-panel { + margin: 4px 0 4px 6px; +} +.cookbook-saved-host { + font-size: 10px; + color: var(--fg-muted); + font-family: 'Fira Code', monospace; +} +.cookbook-saved-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.cookbook-saved-launch { + font-size: 10px; + padding: 2px 8px; +} +.cookbook-saved-del { + background: none; + border: none; + color: var(--fg-muted); + opacity: 0.3; + cursor: pointer; + font-size: 11px; + padding: 0 2px; +} +.cookbook-saved-del:hover { + opacity: 1; + color: var(--color-error, var(--warn)); +} + +/* Serve config panel */ +.hwfit-serve-panel { + margin-top: 6px; + padding: 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + flex: 1; + display: flex; + flex-direction: column; + position: relative; +} +.cookbook-serve-slots { + display: flex; + gap: 3px; + justify-content: flex-end; + margin-bottom: 4px; +} +/* When the Save split sits inside Row 1 (next to GPUs), align it with the + input baseline (the row's grid cells stretch top-down; without this the + Save buttons sit above the GPU button group). */ +.hwfit-serve-row .cookbook-serve-slots { + align-self: end; + margin-bottom: 4px; +} +.cookbook-slot-btn { + min-width: 22px; height: 28px; + padding: 0 6px; + font-size: 10px; font-weight: 600; + border: 1px solid var(--border); + border-radius: 4px; + background: none; + white-space: nowrap; + max-width: 80px; + overflow: hidden; + text-overflow: ellipsis; + color: var(--fg-muted); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.5; + transition: all 0.15s; +} +.cookbook-slot-btn:hover { opacity: 0.9; border-color: var(--fg-muted); } +/* Saved-configs split button: "Save" + dropdown-arrow joined into one control, + but each segment keeps its own style — Save uses the filled accent + "Done"-button look; the arrow stays a subtle outlined menu trigger. */ +.cookbook-saved-split { gap: 0; } +.cookbook-saved-save, +.cookbook-saved-arrow { max-width: none; } +.cookbook-saved-save { + padding: 0 10px; + gap: 4px; + background: var(--bg); + color: var(--fg); + border-color: var(--border); + font-weight: 600; + opacity: 1; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.cookbook-saved-save:hover { + background: var(--border); + border-color: var(--accent); + opacity: 1; +} +.cookbook-saved-arrow { + padding: 0 6px; + background: var(--bg); + color: var(--fg); + border-color: var(--border); + opacity: 1; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: none; +} +.cookbook-saved-menu .cookbook-saved-favorite { + border-left: 3px solid var(--red); + background: color-mix(in srgb, var(--red) 4%, transparent); +} +.cookbook-saved-fav-btn { + width: 20px; + height: 20px; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + background: none; + color: var(--fg-muted); + opacity: 0.55; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +.cookbook-saved-fav-btn:hover, +.cookbook-saved-fav-btn.active { + color: var(--red); + opacity: 1; + background: color-mix(in srgb, var(--red) 10%, transparent); +} +.cookbook-saved-fav-badge { + flex-shrink: 0; + background: transparent; + padding: 0; +} +.cookbook-serve-favorite-model { + border-left-color: var(--red) !important; + background: color-mix(in srgb, var(--red) 4%, transparent); +} +.cookbook-serve-fav-badge { + display: inline-flex; + align-items: center; + vertical-align: 1px; + margin-left: 5px; + margin-right: 2px; + background: transparent; + padding: 0; +} +.cookbook-slot-saved { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); } +.cookbook-slot-saved:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); } +.cookbook-slot-btn.active { opacity: 1; background: var(--accent); color: #fff; border-color: var(--accent); +} +.cookbook-slot-wrap { + position: relative; + display: inline-flex; +} +.cookbook-slot-del { + position: absolute; + top: -5px; + right: -5px; + width: 14px; + height: 14px; + padding: 0; + border: 1px solid var(--border); + border-radius: 50%; + background: var(--bg, #1a1a1a); + color: var(--fg-muted); + font-size: 12px; + line-height: 1; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s, color 0.15s, border-color 0.15s; + display: flex; + align-items: center; + justify-content: center; +} +.cookbook-slot-del:hover { + color: #f44; + border-color: #f44; +} +.cookbook-card-backend { display: none; } +/* Dependencies row — aligned tags */ +.cookbook-dep-row { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel); +} +/* Let the deps list fill the available window height instead of being + capped at the default .doclib-grid 400-px box. */ +[data-backend-group="Dependencies"] #cookbook-deps-list { + flex: 1; + max-height: none; +} + +/* Settings tab: stack the HF token + Servers cards as distinct blocks + (matches the Download tab's block-per-section layout). */ +.cookbook-settings-stack { + display: flex; + flex-direction: column; + gap: 10px; + flex: 1; + /* Scroll the whole settings panel so the Servers card can grow to hold every + server (it used to be a cramped internal scroll box that clipped them). */ + overflow-y: auto; + min-height: 0; +} +.cookbook-settings-stack.hidden { display: none; } +.cookbook-dep-row.cookbook-dep-blocked { opacity: 0.4; } +.cookbook-dep-info { flex: 1; min-width: 0; } +.cookbook-dep-section { + display: flex; + align-items: baseline; + gap: 8px; + margin: 12px 2px 4px; +} +.cookbook-dep-section-title { + font-size: 11px; + font-weight: 600; + letter-spacing: 0.03em; +} +.cookbook-dep-section-note { + font-size: 10px; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.cookbook-dep-subgroup { + margin: 6px 0 8px 12px; + padding: 7px 8px 8px; + border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); + background: color-mix(in srgb, var(--fg) 3%, transparent); + border-radius: 0 6px 6px 0; + display: flex; + flex-direction: column; + gap: 6px; +} +.cookbook-dep-subgroup-title { + display: flex; + align-items: baseline; + gap: 7px; + margin: 0 1px 1px; + font-size: 10.5px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 78%, transparent); +} +.cookbook-dep-subgroup-title em { + font-size: 9.5px; + font-style: normal; + font-weight: 500; + color: color-mix(in srgb, var(--fg) 48%, transparent); +} +.cookbook-dep-tag { + font-size: 9px; + padding: 0 8px; + border-radius: 4px; + white-space: nowrap; + text-align: center; + min-width: 62px; + box-sizing: border-box; + line-height: 1; + /* Explicit height so the tags (Install / Installed) are the EXACT + same height as the sibling tags — Firefox gives buttons a taller + native box otherwise. Mirrors the server-row tag rule (height:24px). */ + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; +} +.cookbook-dep-target { + border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.cookbook-dep-cat { + background: color-mix(in srgb, var(--fg) 10%, transparent); + color: color-mix(in srgb, var(--fg) 60%, transparent); +} +/* Rebuild tag — same look as the LLM category tag, sits to its left. */ +.cookbook-dep-rebuild { + background: color-mix(in srgb, var(--fg) 10%, transparent); + color: color-mix(in srgb, var(--fg) 75%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent); + cursor: pointer; + font-family: inherit; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + position: relative; + top: -2px; +} +.cookbook-dep-reinstall { + top: -3px; +} +.cookbook-dep-rebuild:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); +} +.cookbook-dep-installed { + background: color-mix(in srgb, var(--green, #50fa7b) 18%, transparent); + color: var(--green, #50fa7b); + border: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); + /* Match Install + Installed ▾ so all three variants align in mixed rows. */ + width: 87.7px; + min-width: 87.7px; + padding: 0 10px; + box-sizing: border-box; +} +.cookbook-dep-na { + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); + cursor: help; + /* Match other dep tag widths so N/A rows line up with Install / Installed. */ + min-width: 75.85px; + padding: 0 10px; + box-sizing: border-box; +} +.cookbook-dep-install { + background: var(--accent, var(--red)); + color: #fff; + border: none; + cursor: pointer; + font-family: inherit; + font-weight: 500; + position: relative; + top: -3px; + width: 87.7px; + min-width: 87.7px; + padding: 0 10px; + /* Strip the native button box so it's the same height as the sibling tags + (Firefox renders taller otherwise); height comes from .cookbook-dep-tag. */ + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; +} +.cookbook-dep-build-deps { + width: auto; + min-width: 87.7px; + max-width: 100%; + white-space: normal; + overflow-wrap: anywhere; + line-height: 1.15; + text-align: center; + height: auto; + min-height: 24px; +} +/* The install state is the terminal control in a dependency row. Keep it on + the far edge even when optional rebuild/category controls are present. */ +.cookbook-dep-row > .cookbook-dep-installed, +.cookbook-dep-row > .cookbook-dep-na, +.cookbook-dep-row > .cookbook-dep-install:not(.cookbook-dep-build-deps) { + margin-left: auto; +} +/* Center the "?" glyph inside the help chip. Without text-align it sits 0.5px + left of true center because of the character's natural baseline offset. */ +.hwfit-help-chip { + text-align: center; + padding-left: 0.5px; +} +.cookbook-dep-install:hover { opacity: 0.85; } +/* Installed split button: "Installed" label + separator + ▾ caret; clicking it + opens the actions menu (Update). Replaces the old ⋮ button. */ +.cookbook-dep-installed-btn { + padding: 0; + width: 87.7px; + min-width: 87.7px; + cursor: pointer; + font-family: inherit; + overflow: hidden; + position: relative; + top: -3px; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; +} +.cookbook-dep-installed-btn .cookbook-dep-caret { + display: inline-flex; + align-items: center; + align-self: stretch; + padding: 0 5px; + font-size: 14px; + line-height: 1; + position: relative; + top: 1px; + left: 4px; + opacity: 0.85; + border-left: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); +} +.hwfit-serve-row { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 6px; + margin-bottom: 6px; +} +/* Tighter min on the core-knobs row only so Dtype / TP / GPU Mem / Max + Seqs fit on one line without crushing Row 1's GPU button strip or + the Backend picker. */ +.hwfit-serve-row-core { + grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); +} +/* Save / saved-config split aligned to the right end of Row 1 and + nudged up 4px so it sits flush with the rest of the row. */ +.hwfit-serve-row .cookbook-serve-slots { + justify-self: end; + position: relative; + top: -4px; +} +/* The Settings label wraps the Save split — right-align its title text + so the word "Settings" sits above the right-aligned Save button + instead of floating off to the left of its grid cell. 8px right + padding now (16px → 8px) per follow-up tweak, brings the title back + over the actual Save button instead of overshooting it. */ +.hwfit-serve-row label:has(> .cookbook-serve-slots) { + grid-column: -2 / -1; + justify-self: end; + text-align: right; + padding-right: 0; +} +.hwfit-serve-row label:has(> .cookbook-serve-slots) > span { + display: inline-block; + position: relative; + left: -33px; +} +.hwfit-serve-topline { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; + margin: 0 0 6px; +} +.hwfit-serve-topline .hwfit-serve-runtime-text { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hwfit-serve-preset-row { + display: flex; + justify-content: flex-end; + margin: 0; + flex: 0 0 auto; + position: relative; + top: -6px; +} +.hwfit-serve-row label { + font-size: 10px; + color: var(--fg-muted); + white-space: nowrap; + letter-spacing: 0.3px; +} +.hwfit-serve-row-core .hwfit-context-label { + grid-column: span 2; + width: auto; + min-width: 0; + max-width: none; + justify-self: start; +} +.hwfit-context-control { + display: block; + position: relative; + align-items: center; + margin-top: 2px; + width: 100px; +} +.hwfit-context-control .hwfit-sf[data-field="ctx"] { + min-width: 0; + width: 100%; + padding-right: 40px; +} +.hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]) { + position: relative; + left: -66px; +} +.hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { + position: relative; + left: -64px; +} +.hwfit-serve-row-core .hwfit-gpus-label { + position: relative; + left: -70px; +} +.hwfit-context-calc-btn { + position: absolute; + right: 3px; + top: -4px; + width: 34px; + height: 28px; + min-width: 34px; + padding: 0; + font-size: 10px; + line-height: 1; + display: inline-flex; + align-items: center; + justify-content: center; + text-align: center; + overflow: hidden; + white-space: nowrap; +} +.hwfit-context-calc-btn .spinner-whirlpool { + width: 12px !important; + height: 12px !important; + margin: 0 auto !important; +} +.hwfit-context-calc-btn .ai-spinner-whirlpool { + width: 12px !important; + height: 12px !important; +} +@media (min-width: 769px) { + [data-llama-mode-toggle].mode-toggle.mode-toggle-three .mode-toggle-btn > span { + top: -6px !important; + } + .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { + font-size: 0; + } + .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { + content: 'Unif'; + font-size: 11px; + } +} +@media (max-width: 768px) { + .hwfit-serve-row-core .hwfit-context-label { + display: flex; + flex-direction: column; + align-items: flex-start; + } + .hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]), + .hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { + left: 0; + } + details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_env_preset"]), + details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="extra_env"]) { + grid-column: 1 / -1 !important; + } + .hwfit-context-control { + display: inline-flex; + align-items: center; + gap: 4px; + width: auto; + } + .hwfit-context-control .hwfit-sf[data-field="ctx"] { + width: 100px; + padding-right: 6px; + } + .hwfit-context-calc-btn { + position: relative; + top: -2px; + left: 2px; + right: auto; + flex: 0 0 34px; + width: 34px; + min-width: 34px; + } +} +.hwfit-auto-ctx-note { + display: block; + margin-top: 3px; + font-size: 9px; + opacity: 0.6; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* Ctx slider — ported from origin/main. Sits in the Scan/Download toolbar + next to the quant dropdown. Drives _ctxValue() in cookbook-hwfit.js. */ +.hwfit-ctx-control { + height: 28px; + min-width: 134px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + gap: 5px; + padding: 0 7px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + font-size: 11px; /* slightly smaller than .cookbook-field-input so Context reads quieter next to Engine/Quant */ +} +.hwfit-ctx-control span { + /* Match Quant/Engine select label style: no uppercase, no letter-spacing. */ + text-transform: none; + letter-spacing: 0; + opacity: 0.9; +} +.hwfit-ctx-control > span:first-child { + font-size: 9.5px; +} +/* Editor-style slider (same look as the gallery editor sliders): thin pill + rail that fattens on interaction, circular red thumb that grows on hover. */ +.hwfit-ctx-control input[type="range"] { + width: 64px; + min-width: 64px; + height: 4px; + padding: 0; + border: 0; + -webkit-appearance: none; + appearance: none; + /* Hard-coded grey so the rail is GUARANTEED visible regardless of theme — + every theme-derived color we tried (--fg-muted, --border, accent-bg mix) + kept blending into the panel background on at least one theme. */ + background: rgba(150, 150, 150, 0.65); + border-radius: 999px; + accent-color: var(--red); + cursor: pointer; + transition: height 0.15s ease, background 0.15s ease; +} +.hwfit-ctx-control input[type="range"]:hover, +.hwfit-ctx-control input[type="range"]:focus, +.hwfit-ctx-control input[type="range"]:active { + background: var(--fg); +} +.hwfit-ctx-control input[type="range"]:hover, +.hwfit-ctx-control input[type="range"]:focus, +.hwfit-ctx-control input[type="range"]:active { + height: 10px; +} +.hwfit-ctx-control input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--red); + border: none; + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.hwfit-ctx-control input[type="range"]::-moz-range-thumb { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--red); + border: none; + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.hwfit-ctx-control input[type="range"]:hover::-webkit-slider-thumb, +.hwfit-ctx-control input[type="range"]:focus::-webkit-slider-thumb, +.hwfit-ctx-control input[type="range"]:active::-webkit-slider-thumb { + width: 18px; + height: 18px; +} +.hwfit-ctx-control input[type="range"]:hover::-moz-range-thumb, +.hwfit-ctx-control input[type="range"]:focus::-moz-range-thumb, +.hwfit-ctx-control input[type="range"]:active::-moz-range-thumb { + width: 18px; + height: 18px; +} +.hwfit-ctx-control output { + min-width: 28px; + text-align: right; + color: var(--fg); + font-weight: 600; +} +.hwfit-sf { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--fg); + font-family: inherit; + font-size: 12px; + padding: 0 6px; + height: 28px; + box-sizing: border-box; +} +.hwfit-sf[data-field="backend"], +.hwfit-sf[data-field="dtype"], +.hwfit-sf[data-field="tp"] { + height: 32px; + box-sizing: border-box; + width: 100%; +} +.hwfit-sf:focus { + border-color: var(--accent, var(--red)); + outline: none; +} +.hwfit-sf[type="text"] { width: 100%; } +.hwfit-sf.hwfit-sf-wide { width: 100%; } +.hwfit-serve-checks { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 6px; +} +.hwfit-sf-cb { + display: flex; + flex-direction: row; + align-items: center; + gap: 6px; + font-size: 11px; + color: var(--fg-muted); + cursor: pointer; + padding: 3px 0; +} .hwfit-sf-cb:hover { color: var(--fg); } /* Speculative method + tokens controls — render inline beside the checkbox */ .hwfit-spec-group .hwfit-spec-method, @@ -2697,3315 +4322,3 @@ font-size: 11px; } } - -/* Snapped/narrow Settings window: move the tab rail to the top. Viewport media - alone misses desktop right-half snapping, where the modal is narrow but the - browser window is not. */ -@container settings-modal (max-width: 620px) { - .settings-layout { - flex-direction: column; - } - .settings-sidebar { - width: auto; - max-height: none; - flex-direction: row; - border-right: none; - border-bottom: 1px solid var(--border); - overflow-x: auto; - overflow-y: hidden; - padding: 6px; - scrollbar-width: none; - } - .settings-sidebar::-webkit-scrollbar { - display: none; - } - .settings-sidebar-divider, - .settings-sidebar-label { - display: none; - } - .settings-nav-item { - flex: 0 0 auto; - padding: 6px 10px; - font-size: 11px; - } - .settings-panels { - padding: 12px 14px; - } -} - -/* ── Entrance Animations ── */ - -/* Welcome name — left-to-right wipe with a touch of horizontal stretch. - Fires on initial render via the .welcome-name rule; restarts on Nobody⇄ - Odysseus toggle via JS reflow trick. */ -.welcome-name { - transform-origin: left center; - animation: welcome-name-reveal 0.55s cubic-bezier(0.34, 1.32, 0.55, 1) both; -} -/* Hold the welcome-screen entrance animations until the app has finished its - initial load (fonts loaded + layout settled). Running them mid-load made the - splash jump/flicker as the page reflowed ("haywire"). Until body.welcome-ready - is set by JS, the splash sits in its final, static state (no flash, since the - non-animated state IS the resting state); adding the class then plays the - entrance once, cleanly. */ -body:not(.welcome-ready) #welcome-screen, -body:not(.welcome-ready) .welcome-name { - animation: none !important; -} -/* Hold the splash invisible (the entrance's start state) until ready, so when - the animation is released it fades/wipes in smoothly instead of flashing - from fully-visible to the animation's hidden first frame. */ -body:not(.welcome-ready) #welcome-screen { - opacity: 0; -} -@keyframes welcome-name-reveal { - from { - clip-path: inset(0 100% 0 0); - transform: scaleX(0.96); - opacity: 0.5; - } - to { - clip-path: inset(0 0 0 0); - transform: scaleX(1); - opacity: 1; - } -} - -@keyframes welcome-enter { - from { - opacity: 0; - transform: translate(-50%, -45%); - } - to { - opacity: 1; - transform: translate(-50%, -50%); - } -} - -@keyframes msg-enter { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes picker-roll-up { - from { - opacity: 0; - transform: scaleY(0.4) translateY(8px); - } - to { - opacity: 1; - transform: scaleY(1) translateY(0); - } -} -@keyframes picker-roll-down { - from { - opacity: 1; - transform: scaleY(1) translateY(0); - } - to { - opacity: 0; - transform: scaleY(0.4) translateY(8px); - } -} -@keyframes model-picker-refresh-spin { - to { transform: rotate(360deg); } -} - -@keyframes modal-enter { - from { - opacity: 0; - transform: scale(0.95) translateY(8px); - } - to { - opacity: 1; - transform: scale(1) translateY(0); - } -} -@keyframes modal-exit { - from { - opacity: 1; - transform: scale(1) translateY(0); - } - to { - opacity: 0; - transform: scale(0.97) translateY(6px); - } -} -.modal-content.modal-closing { - animation: modal-exit 0.18s ease-in both; -} - -#cookbook-modal .modal-content.cookbook-modal-entering { - animation: cookbook-modal-enter 0.28s cubic-bezier(0.22, 1.35, 0.36, 1) both; -} -/* Mobile: real bottom-sheet slide-up — the existing 12px translate is - too subtle on a phone, the modal effectively just appeared. */ -@media (max-width: 768px) { - #cookbook-modal .modal-content.cookbook-modal-entering { - animation: cookbook-modal-enter-mobile 0.32s cubic-bezier(0.22, 1, 0.36, 1) both; - } -} -#cookbook-modal .modal-content:not(.cookbook-modal-entering):not(.modal-closing) { - animation: none; -} -#cookbook-modal .modal-content.modal-closing { - animation: modal-exit 0.18s cubic-bezier(0.4, 0, 1, 1) both; -} - -/* Per-token streaming fade — new words materialize */ -.token-new { - animation: token-fade 0.4s ease-out both; -} - -@keyframes token-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -/* Disable entrance animations when loading chat history (bulk render) */ -.chat-history.no-animate .msg { - animation: none; -} - -/* Prefer reduced motion */ -@media (prefers-reduced-motion: reduce) { - .msg, - .modal-content, - #welcome-screen, - .toast, - .token-new { - animation: none !important; - transition: none !important; - } -} - -@media (min-width: 769px) { - #tool-tasks-btn #assistant-notif-dot.sidebar-notif-dot, - #tool-tasks-btn.task-failure-pending::after, - #tool-tasks-btn.task-completion-pending::after { - transform: translateX(-2px); - } -} -.tasks-modal-content { max-width: 600px; width: min(600px, 92vw); background: var(--bg); font-size: 12px; } -.task-completed-preview-row { - cursor: default; - margin-bottom: 6px; - padding: 8px 10px 10px; - max-height: none; - overflow: visible; -} -.task-completed-preview-head { - display: flex; - align-items: center; - gap: 7px; - min-height: 20px; - font-size: 11px; -} -.task-completed-chat-preview { - padding: 0 0 4px; -} -.task-completed-chat-preview .doclib-chat-preview-messages { - max-height: 220px; - margin-top: 7px; -} -.task-completed-chat-preview .doclib-chat-bubble { - max-width: 92%; -} -.task-completed-chat-preview .doclib-chat-preview-actions { - top: 2px; - margin-top: 4px; -} -.task-completed-preview-row .task-log-time { - margin-left: 8px; -} - -/* Tasks tabs reuse the .memory-tab look. The Brain window's tab bar is - full-bleed (its underline spans the whole modal width). The Tasks bar is a - direct child of .modal-content (padding:10px), so cancel that padding with - negative side margins to span edge-to-edge, then re-inset the tabs by 10px - so they line up with the rest of the modal content — matching the Brain bar. */ -.tasks-modal-content .tasks-tabs { - margin: -2px -10px 8px; - padding: 0 10px; -} - -/* Activity log — compact by default. Click the row to expand body+actions. */ -.task-log-row { - --cat-hue: 220; - border: 1px solid var(--border); - border-radius: 8px; - padding: 4px 10px; - margin-bottom: 3px; - background: color-mix(in srgb, var(--fg) 2%, transparent); - position: relative; - cursor: pointer; - transition: padding .12s ease; -} -.task-log-row:hover { - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.task-log-row.task-log-row-error { - border-color: color-mix(in srgb, var(--red, #f87171) 35%, var(--border)); - background: color-mix(in srgb, var(--red, #f87171) 4%, transparent); -} -.task-log-row.task-log-row-error:hover { - background: color-mix(in srgb, var(--red, #f87171) 7%, transparent); -} -.task-log-row.expanded { padding: 8px 10px 6px; } -.task-log-row-head { - display: flex; - align-items: center; - gap: 8px; - font-size: 11px; - margin-bottom: 0; -} -.task-log-row.expanded .task-log-row-head { margin-bottom: 4px; } -/* Collapsed: body + footer hidden. Expanded: visible. */ -.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-body, -.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-actions, -.task-log-row:not(.expanded):not(.is-skipped) .task-log-prompt { - display: none; -} -.task-log-name { - font-weight: 600; - /* Pull saturation from the per-row hue but mix with the foreground so the - title still reads in dark mode. Lightness stays adaptive. */ - color: hsl(var(--cat-hue) 60% 60%); -} -.task-log-failed-tag { - color: var(--red, #f87171); - font-size: 10px; - font-weight: 700; - line-height: 1; - white-space: nowrap; - margin-left: -2px; -} -.task-log-task-icon { - display: inline-flex; - align-items: center; - flex: 0 0 auto; - margin-right: -3px; -} -.task-log-row.task-log-row-error .task-log-name, -.task-log-row.task-log-row-error .task-log-task-icon { - color: var(--red, #f87171); -} -.task-log-task-icon svg { - top: 0 !important; - opacity: 0.46 !important; -} -.task-log-row-head .task-ai-mark { - top: 0; - margin-left: -4px; -} -.task-log-repeat { - font-size: 10px; - font-weight: 500; - line-height: 1; - color: color-mix(in srgb, var(--fg) 62%, transparent); - border-radius: 999px; - padding: 1px 0; - white-space: nowrap; -} -@media (prefers-color-scheme: light) { - .task-log-name { color: hsl(var(--cat-hue) 65% 38%); } -} -/* Per-account prefix in fan-out results — e.g. "[Default] No recent emails" - becomes a compact accent chip + plain message. Makes multi-account activity - rows readable instead of a bracket soup. */ -/* Running / queued status line in the Activity tab — whirlpool + label. */ -.task-log-running { - display: inline-flex; - align-items: center; - gap: 0; - font-size: 11px; - opacity: 0.75; -} -.task-log-running-label { font-style: normal; } - -/* New right-side placement: "Running " sits where the timestamp - normally would, on the head row's right edge. */ -.task-log-running-inline { - display: inline-flex; - align-items: center; - gap: 0; - font-size: 11px; - opacity: 0.75; -} -.task-log-force-run { - appearance: none; - box-sizing: border-box; - border: 0; - background: color-mix(in srgb, var(--fg) 7%, transparent); - box-shadow: none; - color: inherit; - opacity: .82; - margin-left: 7px; - padding: 1px 6px 1px 4px; - height: 18px; - min-height: 16px; - max-height: 18px; - border-radius: 999px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 3px; - font-family: inherit; - font-size: 10px; - line-height: 1; - cursor: pointer; - position: relative; - top: -3px; -} -.task-log-force-run svg, -.task-log-stop svg { - display: block; - flex-shrink: 0; - transform: translateY(1px); -} -.task-log-force-run:hover { - opacity: 1; - background: color-mix(in srgb, var(--green, #50fa7b) 16%, transparent); - color: var(--green, #50fa7b); -} -.task-log-stop { - border: 0; - background: transparent; - color: inherit; - opacity: .72; - padding: 0; - margin-left: 6px; - width: 12px; - height: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - position: relative; - top: -3px; -} -.task-log-stop:hover { - opacity: 1; - color: var(--red, #f87171); -} - -/* Slim single-line row for skipped (noop) runs — body/actions stripped, font - shrunk, opacity dropped. Distinguishes "task ran but had nothing to do" - from a "real" entry without flooding the feed visually. */ -.task-log-row.is-skipped { - padding: 4px 8px; - opacity: 0.45; - font-size: 11px; - background: transparent; -} -.task-log-row.is-skipped .task-log-row-head { padding: 0; } -.task-log-row.is-skipped .task-log-name { font-weight: 500; } -.task-log-row.is-skipped .task-log-skipped-reason { - flex: 1 1 auto; - margin-left: 6px; - font-style: italic; - opacity: 0.85; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.task-log-row.is-skipped:hover { opacity: 0.7; } - -.task-log-account-tag { - display: inline-block; - font-size: 10px; - font-weight: 600; - letter-spacing: 0.02em; - padding: 1px 7px; - margin-right: 4px; - border-radius: 10px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); - vertical-align: 1px; -} -.task-log-status { - width: 7px; - height: 7px; - border-radius: 50%; - flex-shrink: 0; - background: color-mix(in srgb, var(--fg) 30%, transparent); -} -.task-log-status-ok { background: #4ade80; box-shadow: 0 0 6px #4ade80, 0 0 3px #4ade80; } -.task-log-status-error { background: var(--red, #f87171); box-shadow: 0 0 6px var(--red, #f87171), 0 0 3px var(--red, #f87171); } -.task-log-status-info { background: color-mix(in srgb, var(--fg) 25%, transparent); } -.task-log-status-queued { background: #fbbf24; box-shadow: 0 0 0 2px color-mix(in srgb, #fbbf24 30%, transparent); } -.task-log-status-running { background: #60a5fa; animation: task-log-pulse 1.4s ease-in-out infinite; } -.task-log-status-skipped { background: color-mix(in srgb, var(--fg) 20%, transparent); } -.task-log-status-aborted { background: color-mix(in srgb, var(--fg) 30%, transparent); border: 1px dashed color-mix(in srgb, var(--fg) 50%, transparent); } -.task-log-row-body { - font-size: 12px; - line-height: 1.5; - color: color-mix(in srgb, var(--fg) 85%, transparent); - overflow-wrap: anywhere; - word-break: break-word; -} -.task-log-row-body p { margin: 0 0 6px 0; } -.task-log-row-body p:last-child { margin-bottom: 0; } -.task-log-row-body pre { - margin: 4px 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - border-radius: 4px; - white-space: pre-wrap; - word-break: break-word; - font-size: 11px; - max-height: 180px; - overflow: auto; -} -.task-log-row-body code { - font-size: 11px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - padding: 1px 4px; - border-radius: 3px; -} -.task-log-row-body ul, .task-log-row-body ol { - margin: 4px 0; - padding-left: 20px; -} -/* Markdown headings inside a task result — without these, ## headings - render at browser-default huge sizes and blow out the cramped row. */ -.task-log-row-body h1, -.task-log-row-body h2, -.task-log-row-body h3, -.task-log-row-body h4 { - margin: 10px 0 4px; - line-height: 1.3; - font-weight: 650; - color: var(--fg); -} -.task-log-row-body h1 { font-size: 14px; } -.task-log-row-body h2 { font-size: 13px; } -.task-log-row-body h3, -.task-log-row-body h4 { font-size: 12px; opacity: 0.9; } -.task-log-row-body h1:first-child, -.task-log-row-body h2:first-child, -.task-log-row-body h3:first-child { margin-top: 0; } -.task-log-row-body blockquote { - margin: 4px 0; - padding: 2px 0 2px 10px; - border-left: 2px solid color-mix(in srgb, var(--fg) 20%, transparent); - opacity: 0.85; -} -.task-log-row-body a { color: var(--accent, #60a5fa); text-decoration: none; } -.task-log-row-body a:hover { text-decoration: underline; } -.task-log-row-body strong { font-weight: 650; color: var(--fg); } -/* Email-summary tables — render compactly instead of default browser ugliness. */ -.task-log-row-body table { - border-collapse: collapse; - width: 100%; - margin: 6px 0; - font-size: 11px; -} -.task-log-row-body th, -.task-log-row-body td { - border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); - padding: 3px 6px; - text-align: left; - vertical-align: top; -} -.task-log-row-body th { - background: color-mix(in srgb, var(--fg) 5%, transparent); - font-weight: 600; -} -.task-log-row-actions { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-top: 4px; -} -.task-log-open-chat, -.task-log-open-report, -.task-log-copy, -.task-log-clear-cache, -.task-log-run-again, -.task-log-force-run-action { - display: inline-flex; - align-items: center; - gap: 3px; - background: none; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 5px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-family: inherit; - font-size: 10px; - padding: 1px 6px; - cursor: pointer; - transition: background .15s, color .15s, border-color .15s; - line-height: 1.4; -} -.task-log-open-chat:hover, -.task-log-open-report:hover, -.task-log-copy:hover, -.task-log-clear-cache:hover, -.task-log-run-again:hover, -.task-log-force-run-action:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--fg) 30%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.task-log-row-actions > .task-log-open-chat, -.task-log-row-actions > .task-log-copy { - margin-left: auto; -} -.task-log-clear-cache svg { - position: relative; - top: 0; -} -/* Activity filter chips — toggle-out model: ON by default (solid), - click to toggle OFF (dimmed + strikethrough) to hide that group. */ -.tasks-af-chip { - font-size: 11px; - padding: 3px 10px; - border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); - border-radius: 12px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: var(--fg); - cursor: pointer; - font-family: inherit; - transition: opacity .12s, background .12s, border-color .12s; -} -.tasks-af-chip:hover { border-color: color-mix(in srgb, var(--fg) 32%, transparent); } -.tasks-af-chip.off { - opacity: 0.4; - text-decoration: line-through; - background: transparent; -} -.tasks-af-chip.active { - border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - opacity: 1; -} -.tasks-af-chip-error { border-color: color-mix(in srgb, var(--red, #f87171) 45%, transparent); } -.tasks-af-chip-error:not(.off) { color: var(--red, #f87171); } - -/* "Open in Deep Research" is now a regular clickable chat link (a - `#research-` markdown anchor in the assistant's message), rendered as - an a.chat-link. Nudge it 4px right so it sits slightly inset. */ -a.chat-link[href^="#research-"] { - margin-left: 4px; -} -.task-log-row.is-long .task-log-row-body { - max-height: 8.5em; - overflow: hidden; - position: relative; - mask-image: linear-gradient(to bottom, #000 70%, transparent); - -webkit-mask-image: linear-gradient(to bottom, #000 70%, transparent); -} -.task-log-row.is-long.expanded .task-log-row-body { - max-height: none; - overflow: visible; - mask-image: none; - -webkit-mask-image: none; -} -.task-log-row-toggle { - margin-top: 6px; - background: none; - border: none; - font-family: inherit; - font-size: 11px; - cursor: pointer; - padding: 2px 0; - color: transparent; /* hide raw "Show more" text */ -} -.task-log-row:not(.expanded) .task-log-row-toggle::before { content: 'Show more'; } -.task-log-row.expanded .task-log-row-toggle::before { content: 'Show less'; } -.task-log-prompt { - margin-top: 6px; - font-size: 11px; -} -.task-log-prompt summary { - cursor: pointer; - opacity: 0.5; - user-select: none; -} -.task-log-prompt summary:hover { opacity: 0.8; } -.task-log-prompt pre { - margin: 4px 0 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-radius: 4px; - white-space: pre-wrap; - word-break: break-word; - font-size: 11px; - opacity: 0.75; - max-height: 200px; - overflow: auto; -} - -/* Tasks is the shared theme reference for procedural cards. Keep the tint - together so Skills cannot drift when theme colors change. Hover and - selection remain owned by the shared card state rules above. */ -.task-card, -.skill-card { - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -.task-card { - border: 1px solid var(--border); - border-radius: 8px; - padding: 7px 8px; - margin-bottom: 0; - min-height: 46px; - box-sizing: border-box; - align-items: center; -} -/* When expanded, height grows to fit the detail panel. */ -.task-card.expanded { align-items: flex-start; } -.task-card .memory-item-title { - position: relative; - top: -2px; -} -/* Always show the per-card ⋮ kebab on task cards (the default opacity:0 + - hover-reveal is unreliable on touch and easy to miss on desktop). - pointer-events / z-index belt-and-braces against any sibling overlay, - margin reset undoes the global `.modal-body button { margin-top:6px }` - that was punting the hit-target down. */ -.task-card .memory-item-actions { - opacity: 0.55; - pointer-events: auto; - position: relative; - z-index: 5; -} -.task-card:hover .memory-item-actions, -.task-card .memory-item-actions:hover { opacity: 1; } -.task-card .memory-item-actions .memory-item-btn { - margin: 0 !important; - pointer-events: auto; - position: relative; - z-index: 5; - width: 28px; - min-width: 28px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; -} -/* Make the SVG inside the kebab transparent to pointer events so the click - always lands on the BUTTON itself (some browsers / nested SVG events lose - the click when hitting the inner glyph). */ -.task-card .memory-item-actions .memory-item-btn svg { pointer-events: none; } -.task-ai-mark { - flex: 0 0 auto; - color: var(--accent, var(--red)); - opacity: 0.78; -} -/* Per-card select checkbox rides up to the title line. The "All" checkbox is - #tasks-select-all (not .memory-select-cb), so it stays put. */ -.task-card .memory-select-cb { position: relative; } -.task-card.task-card-deleting { - opacity: 0.68; - pointer-events: none; - position: relative; -} -.task-card-delete-busy { - position: absolute; - right: 11px; - top: 50%; - transform: translateY(-50%); - display: inline-flex; - align-items: center; - gap: 5px; - height: 22px; - padding: 0 8px; - border-radius: 999px; - color: var(--red); - background: color-mix(in srgb, var(--red) 12%, var(--panel, #111)); - border: 1px solid color-mix(in srgb, var(--red) 24%, transparent); - font-size: 10px; - font-weight: 600; - line-height: 1; - z-index: 7; -} -.task-card-delete-busy-spin { - width: 10px; - height: 10px; - border-radius: 999px; - border: 1.5px solid color-mix(in srgb, currentColor 28%, transparent); - border-top-color: currentColor; - animation: spin 0.8s linear infinite; -} -#tasks-pause-all-btn { position: relative; top: -2px; } -#tasks-pause-all-btn:has(input:disabled) { opacity: 0.4; } -/* Bigger ⋮ dropdown on mobile (its buttons carry inline styles → !important). */ -@media (max-width: 768px) { - .task-card .task-status-badge { - touch-action: manipulation; - -webkit-tap-highlight-color: transparent; - } - .task-card .task-state-badge svg, - .task-card .task-card-run-btn svg { - width: 10px; - height: 10px; - } -} - -.task-card-header { - display: flex; - align-items: center; - gap: 4px; - flex-wrap: wrap; -} - -.task-card-name { - font-size: 12px; - font-weight: 600; -} - -.task-card-schedule { - font-size: 10.5px; - opacity: 0.5; - margin-left: auto; -} - -.task-card-output { - font-size: 10px; - opacity: 0.45; -} - -.task-card-prompt { - font-size: 11px; - opacity: 0.55; - margin: 4px 0; - line-height: 1.4; -} - -.task-card-meta { - font-size: 10.5px; - opacity: 0.45; - margin-bottom: 6px; -} - -.task-card-actions { - display: flex; - gap: 4px; - flex-wrap: wrap; -} - -.task-btn { - font-family: inherit; - font-size: 11px; - padding: 4px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: none; - color: var(--fg); - cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s, background 0.15s; -} -.task-btn:hover { opacity: 1; } -.task-btn:disabled { opacity: 0.3; cursor: not-allowed; } - -.task-btn-primary { - background: var(--red); - color: #fff; - border-color: transparent; - opacity: 1; -} -.task-btn-primary:hover { opacity: 0.85; } - -.task-btn-danger { color: var(--red); border-color: color-mix(in srgb, var(--red) 30%, transparent); } -.task-btn-danger:hover { opacity: 1; border-color: var(--red); } - -.tasks-clock { - font-size: 10px; - opacity: 0.35; - text-align: center; - padding: 6px 0 8px; -} - -.task-preset-grid { - display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; -} -.task-preset-card { - display: flex; flex-direction: column; align-items: flex-start; gap: 2px; - padding: 12px; border: 1px solid var(--border); border-radius: 8px; - background: var(--bg); cursor: pointer; transition: all 0.15s; text-align: left; - color: var(--fg); -} -.task-preset-card:hover { - border-color: var(--accent, #cc6a3a); background: color-mix(in srgb, var(--accent, #cc6a3a) 8%, var(--bg)); -} -.task-preset-icon { font-size: 20px; margin-bottom: 2px; } -.task-preset-label { font-size: 12px; font-weight: 600; } -.task-preset-desc { font-size: 10px; opacity: 0.5; line-height: 1.3; } - -.task-form { display: flex; flex-direction: column; gap: 4px; } -.task-form-label { - font-size: 11px; - opacity: 0.55; - font-weight: 500; - display: block; - /* Consistent rhythm: 8px above each label (section gap), 4px below to the - field — same spacing for Name/Prompt/Trigger/Output/Model/Chain. */ - margin: 8px 0 4px; -} -.task-form-input { - font-family: inherit; - font-size: 11px; - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - transition: border-color 0.15s; - box-sizing: border-box; - width: 100%; - max-width: 100%; - min-width: 0; -} -.task-form-input:focus { - outline: none; - border-color: var(--red); -} -.task-form-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } - -.task-form-notif-toggle { - display: flex; - align-items: center; - gap: 8px; - margin: 9px 0 3px; - cursor: pointer; - color: var(--fg); -} -.task-form-notif-toggle input { - position: absolute; - opacity: 0; - pointer-events: none; -} -.task-form-notif-switch { - width: 30px; - height: 16px; - border-radius: 999px; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 8%, transparent); - position: relative; - flex: 0 0 auto; - transition: background 0.15s, border-color 0.15s; -} -.task-form-notif-toggle input:checked + .task-form-notif-switch { - border-color: color-mix(in srgb, var(--accent, var(--red)) 58%, transparent); - background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); -} -.task-form-notif-copy { - display: flex; - flex-direction: column; - gap: 1px; - font-size: 11px; - font-weight: 500; -} -.task-form-notif-copy span:last-child { - font-size: 10px; - font-weight: 400; - opacity: 0.52; -} - -.task-form-toggle { - display: flex; gap: 4px; -} -.task-toggle-btn { - flex: 1; padding: 4px 8px; font-size: 10px; font-family: inherit; - border: 1px solid var(--border); border-radius: 6px; background: transparent; - color: var(--fg); opacity: 0.4; cursor: pointer; transition: all 0.15s; -} -.task-toggle-btn:hover { opacity: 0.7; border-color: var(--fg); } -.task-toggle-btn.active { - opacity: 1; border-color: var(--red); color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); -} - -.task-time-picker, .task-date-picker { - display: flex; - align-items: center; - gap: 4px; -} -.task-time-select, .task-date-select { - width: auto; - min-width: 48px; - font-size: 11px; -} -.task-time-sep { opacity: 0.4; font-size: 11px; } - -.task-history-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 8px; -} - -.task-runs-list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; } -.task-run-item { - border: 1px solid var(--border); - border-radius: 6px; - padding: 8px 10px; -} -.task-run-item-header { - display: flex; - align-items: center; - gap: 4px; - font-size: 11px; -} -.task-run-time { margin-left: auto; font-size: 10px; opacity: 0.45; } -.task-run-result { - font-size: 11px; - opacity: 0.6; - margin-top: 4px; - line-height: 1.4; - white-space: pre-wrap; - word-break: break-word; -} - -/* ── Settings Form Utilities ── */ - -.settings-row { - display: flex; - align-items: center; - gap: 8px; -} -.settings-col { - display: flex; - flex-direction: column; - gap: 6px; -} -.settings-label { - font-size: 12px; - min-width: 70px; -} -.settings-select { - flex: 1; - /* Allow the select to shrink below its longest option's width inside a flex - row — without this a long model name (e.g. the Vision picker) overflowed - the card off-screen on mobile. */ - min-width: 0; - max-width: 100%; - padding: 5px 8px; - font-family: inherit; - font-size: 12px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - transition: border-color 0.15s; - appearance: none; - -webkit-appearance: none; - -moz-appearance: textfield; - outline: none; -} -input.settings-select::-webkit-outer-spin-button, -input.settings-select::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -input.settings-select::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } -.settings-select:focus { - outline: none; - border-color: var(--red); -} - -/* Deep Research uses long labels and previously fixed-width fields. Keep - every control constrained by the card at intermediate desktop widths too. */ -.admin-card:has(#set-researchSearch) .settings-row { min-width: 0; } -.admin-card:has(#set-researchSearch) .settings-row > .settings-label { flex: 0 0 112px; min-width: 0; } -#set-researchSearch, -#set-researchMaxTokens, -#set-researchRunTimeout, -.settings-row div:has(> #set-researchExtractTimeout), -.settings-row div:has(> #set-researchExtractConcurrency) { - width: auto !important; - max-width: 100% !important; - min-width: 0 !important; - flex: 1 1 0 !important; -} - -@media (max-width: 768px) { - /* Deep Research settings contain desktop-sized inline widths. On mobile, - let each control claim the card width instead of escaping the admin block. */ - .settings-row:has(#set-researchSearch), - .settings-row:has(#set-researchMaxTokens), - .settings-row:has(#set-researchExtractTimeout), - .settings-row:has(#set-researchExtractConcurrency), - .settings-row:has(#set-researchRunTimeout) { - flex-wrap: wrap; - align-items: stretch; - } - .settings-row:has(#set-researchSearch) .settings-label, - .settings-row:has(#set-researchMaxTokens) .settings-label, - .settings-row:has(#set-researchExtractTimeout) .settings-label, - .settings-row:has(#set-researchExtractConcurrency) .settings-label, - .settings-row:has(#set-researchRunTimeout) .settings-label { - flex: 1 1 auto; - } - #set-researchSearch, - #set-researchMaxTokens, - #set-researchRunTimeout, - .settings-row div:has(> #set-researchExtractTimeout), - .settings-row div:has(> #set-researchExtractConcurrency) { - width: 100% !important; - max-width: 100% !important; - flex: 1 1 100% !important; - margin-left: 0 !important; - } -} - -/* Default-chat fallback chain editor. Each row mirrors the primary - endpoint/model selectors, indented under them to read as a chain. */ -.settings-fallbacks { - display: flex; - flex-direction: column; - gap: 6px; -} -.settings-fallbacks:not(:empty) { margin-top: 2px; } -.settings-fallback-row { - display: flex; - align-items: center; - gap: 6px; - padding-left: 12px; - border-left: 2px solid color-mix(in srgb, var(--fg) 12%, transparent); - max-width: 100%; - box-sizing: border-box; -} -.settings-fallback-num { - font-size: 11px; - opacity: 0.4; - min-width: 14px; - text-align: right; -} -.settings-fallback-row .settings-select { - width: 0; - min-width: 0; - min-inline-size: 0; - box-sizing: border-box; - overflow: hidden; - text-overflow: ellipsis; -} -.settings-fallback-row .settings-select:first-of-type { - flex: 0 1 128px; - width: 128px; - max-width: 34%; -} -.settings-fallback-row .settings-select:nth-of-type(2) { - flex: 1 1 0; - width: 0; - max-width: 100%; -} -details.hwfit-serve-advanced > .hwfit-serve-row, -details.hwfit-serve-advanced > .hwfit-serve-checks, -details.hwfit-serve-advanced > .hwfit-serve-cmd-wrap, -details.hwfit-serve-advanced > .hwfit-serve-extra { - margin-top: 0; - margin-bottom: 0; -} -/* Inside Advanced, tighten the per-row grid gap and the label → - select gap so rows of single-line dropdowns don't visually sit - on top of half a row of empty space. Outside Advanced (Core, - etc.) the original ~6px breathing room stays. */ -details.hwfit-serve-advanced > .hwfit-serve-row { - gap: 4px; - row-gap: 4px; -} -details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp, -details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-llamacpp { - margin-top: -20px; - position: relative; - left: 4px; -} -details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp { - column-gap: 8px; - row-gap: 2px; -} -details.hwfit-serve-advanced > .hwfit-llama-placement-row { - margin-top: -6px !important; -} -details.hwfit-serve-advanced > .hwfit-llama-memory-row { - margin-top: -20px !important; -} -.cookbook-llama-cpu-mode details.hwfit-serve-advanced > .hwfit-llama-memory-row { - margin-top: -8px !important; -} -details.hwfit-serve-advanced > .hwfit-llama-batch-row, -details.hwfit-serve-advanced > .hwfit-llama-monitor-row, -details.hwfit-serve-advanced > .hwfit-llama-checks-row { - margin-top: -18px !important; -} -details.hwfit-serve-advanced > .hwfit-diff-settings-row { - row-gap: 2px; -} -details.hwfit-serve-advanced > .hwfit-diff-checks-row, -details.hwfit-serve-advanced > .hwfit-diff-harmonize-row { - margin-top: -10px !important; -} -details.hwfit-serve-advanced > .hwfit-diff-checks-row { - row-gap: 2px; -} -details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-vram-monitor { - gap: 4px !important; - grid-template-columns: max-content minmax(0, 1fr); - white-space: nowrap; -} -details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_block_size"] { - width: calc(100% - 6px); -} -details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_kv_cache_dtype"] { - width: 60px; - min-width: 60px; -} -details.hwfit-serve-advanced > .hwfit-serve-checks { - gap: 4px; - row-gap: 4px; -} -/* Pull the vLLM/SGLang checks row, Extra args, and the trailing - model-specific (Speculative) checks row up tight against the row - above — the previous 4px gap plus per-row baseline padding left a - ~8px gap that read as too airy in the Advanced fold. */ -details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-vllm, -details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-sglang, -details.hwfit-serve-advanced > .hwfit-serve-extra { - margin-top: -8px; -} -details.hwfit-serve-advanced > .hwfit-serve-extra { - margin-top: -18px; -} -details.hwfit-serve-advanced > .hwfit-serve-row:last-of-type, -details.hwfit-serve-advanced > .hwfit-serve-checks:last-of-type { - margin-bottom: 0; -} - -.settings-fallback-remove { - flex-shrink: 0; - width: 32px; - height: 32px; - display: inline-flex; - align-items: center; - justify-content: center; - border: 1px solid var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 55%, transparent); - cursor: pointer; - transition: border-color 0.12s, color 0.12s, background 0.12s; -} -.settings-fallback-remove:hover { - border-color: var(--red); - color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); -} -.settings-fallback-add { - align-self: flex-start; - margin-top: 2px; - font-size: 11px; - padding: 3px 9px; - border: 1px dashed var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 65%, transparent); - cursor: pointer; - transition: border-color 0.12s, color 0.12s; -} -.settings-fallback-add:hover { - border-color: var(--red); - color: var(--red); -} -.settings-input { - flex: 1; - padding: 5px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-family: inherit; - font-size: 12px; - transition: border-color 0.15s; -} -.settings-input:focus { - outline: none; - border-color: var(--red); -} -/* ── Contacts manager (Settings → Integrations → CardDAV) ── */ -.contacts-add-row { - display: flex; - gap: 6px; - margin-bottom: 8px; - align-items: center; -} -.contacts-list { - display: flex; - flex-direction: column; - gap: 2px; - max-height: 360px; - overflow-y: auto; -} -.contact-row { - padding: 6px 4px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 35%, transparent); -} -.contact-row:last-child { border-bottom: none; } -.contact-row-edit { margin-top: 4px; } - -/* ── Sort Dropdown ── */ - -.sort-dropdown { - position: absolute; - right: 0; - top: 100%; - z-index: 1000; - min-width: 120px !important; - width: max-content; - padding: 4px !important; - margin-top: 4px; - background: var(--panel) !important; - border: 1px solid var(--border) !important; - border-radius: 8px !important; - box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; - backdrop-filter: none !important; -} -.sort-dropdown-item { - cursor: pointer; - padding: 6px 8px 6px 6px !important; - font-size: 11px !important; - border-radius: 6px !important; - border-bottom: none !important; - white-space: nowrap; - transition: background 0.1s; -} -.sort-dropdown-item:hover { - background: color-mix(in srgb, var(--accent) 10%, transparent) !important; -} -.sort-dropdown-item.ai-sort-option { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); -} -.sort-dropdown-item.ai-sort-option .auto-sort-icon { - color: var(--accent, var(--red)); - display: inline-flex; - align-items: center; -} -#auto-sort-sessions-btn { - transform: translateX(-4px); -} -.sort-dropdown-item.ai-sort-option:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent) !important; -} -.sort-dropdown-sep { - border-top: 1px solid var(--border); - margin-top: 2px; - padding-top: 6px; -} - -/* Mobile: bigger taps for the chats sort/funnel menu and the select-mode - bulk-action bar. Also drop the "Rearrange" item — it's a touch-finicky - feature better left to desktop. */ -@media (max-width: 768px) { - /* Nudge the funnel/sort button right on mobile so it doesn't hug the - chevron next to it. */ - #session-sort-btn { transform: translateX(13px); } - #session-sort-dropdown.sort-dropdown { - min-width: 200px !important; - padding: 6px !important; - } - #session-sort-dropdown .sort-dropdown-item { - padding: 12px 14px !important; - font-size: 14px !important; - } - #session-rearrange-toggle { display: none !important; } - - /* Select-mode bar — make Archive / Delete / Cancel buttons + the - Select-all toggle finger-sized. */ - .session-bulk-bar { - padding: 8px 10px; - font-size: 14px; - gap: 10px; - } - .session-bulk-btn { - padding: 10px; - min-width: 44px; - min-height: 44px; - } - .session-bulk-btn svg { width: 20px; height: 20px; } - #session-select-all-dot { font-size: 22px !important; padding: 6px; } - #session-select-all-label { font-size: 14px !important; padding: 4px; } -} - - -/* ── Admin: Built-in tools ── */ -.admin-tool-category { - border: 1px solid var(--border); - border-radius: 6px; - margin-bottom: 6px; - overflow: hidden; - transition: border-color 0.15s; -} -.admin-tool-category:hover { - border-color: color-mix(in srgb, var(--fg) 20%, var(--border)); -} -.admin-tool-cat-header { - font-size: 12px; - font-weight: 500; - padding: 8px 10px; - transition: background 0.15s; -} -.admin-tool-cat-header:hover { - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.admin-tool-cat-body { - border-top: 1px solid var(--border); - padding: 4px 0; -} -.admin-tool-cat-body.hidden { - display: none; -} -.admin-tool-row { - display: flex; - align-items: center; - gap: 8px; - padding: 5px 10px; - padding-right: 28px; /* align toggles with header toggle (chevron 12px + gap 6px + padding) */ - transition: background 0.08s; -} -.admin-tool-row:hover { - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.admin-tool-info { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 1px; -} -.admin-tool-name { - font-size: 11px; - font-weight: 500; -} -.admin-tool-desc { - font-size: 10px; - opacity: 0.4; -} -.admin-tool-ctx { - font-size: 9px; - opacity: 0.3; - flex-shrink: 0; - min-width: 35px; - text-align: right; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; -} - -/* Cookbook serve param hints */ -.hwfit-hint { - display: inline-flex; - align-items: center; - justify-content: center; - width: 12px; - height: 12px; - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - font-size: 8px; - font-weight: 600; - opacity: 0.35; - cursor: help; - margin-left: 2px; - vertical-align: middle; - transition: opacity 0.1s; -} -.hwfit-hint:hover { - opacity: 0.8; - background: color-mix(in srgb, var(--accent) 15%, transparent); -} -.hwfit-dl-dot { - color: var(--green); - font-size: 8px; - margin-left: 4px; - opacity: 0.7; -} -.hwfit-parser-tag { - font-size: 9px; - opacity: 0.4; - background: color-mix(in srgb, var(--fg) 8%, transparent); - padding: 1px 5px; - border-radius: 3px; - margin-left: 4px; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; -} - -/* Cookbook output copy — show only on hover */ -.cookbook-output-wrap .cookbook-output-copy { - opacity: 0; - transition: opacity 0.1s; -} - -/* Cached model menu button */ -.hwfit-cached-menu-btn { - background: none; - border: none; - color: var(--fg); - font-size: 16px; - cursor: pointer; - opacity: 0; - padding: 2px 4px; - border-radius: 4px; - transition: opacity 0.1s; - flex-shrink: 0; - position: relative; - top: -2px; -} -.memory-item:hover .hwfit-cached-menu-btn, -.hwfit-cached-item:hover .hwfit-cached-menu-btn { opacity: 0.4; } -.hwfit-cached-menu-btn:hover { opacity: 1 !important; } -.hwfit-cached-menu-btn svg { pointer-events: none; } -@media (max-width: 768px) { - #cookbook-modal .hwfit-cached-menu-btn { - opacity: 0.72 !important; - width: 32px; - height: 32px; - min-width: 32px; - padding: 0; - justify-content: center; - top: -4px; - } - #cookbook-modal .hwfit-cached-menu-btn:active { - opacity: 1 !important; - background: color-mix(in srgb, var(--fg) 9%, transparent); - } -} - -/* Quick run button */ -.cookbook-run-btn { - background: var(--accent, var(--red)) !important; - color: var(--panel) !important; - border-color: var(--accent, var(--red)) !important; - font-weight: 600; -} -.cookbook-run-btn:hover { - opacity: 0.9; -} -#hwfit-cache-scan { - position: relative; - top: 1px; - width: 26px; - height: 24px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -#hwfit-cache-scan.spinning svg { - animation: modelPickerRefreshSpin 0.75s linear infinite; -} -#serve-search { - height: 28px; - min-height: 28px; - font-family: inherit; - font-size: 11px; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: 9px center; - padding-left: 28px; -} -.cookbook-sort-select-wrap { - position: relative; - display: inline-flex; - flex-shrink: 0; - align-items: center; -} -.cookbook-sort-select-wrap > svg { - position: absolute; - left: 9px; - z-index: 1; - pointer-events: none; - opacity: 0.65; - transform: translateY(4px); -} - -/* Cookbook model directory tags */ -.cookbook-modeldir-tag { - font-size: 11px; - padding: 3px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - display: inline-flex; - align-items: center; - gap: 4px; - font-family: 'Berkeley Mono', 'SF Mono', monospace; -} -.cookbook-modeldir-default { - opacity: 0.5; -} -.cookbook-server-bottom-actions { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 4px; - margin-top: 5px; -} -.cookbook-server-bottom-actions > button { - height: 22px; - box-sizing: border-box; - display: inline-flex; - align-items: center; -} -/* Default-server radio-check in a Settings server title (same look as the - model-dir download target). Active = accent-tinted. */ -.cookbook-srv-default { - cursor: pointer; - opacity: 0.35; - display: inline-flex; - align-items: center; - gap: 3px; - line-height: 1; - transition: opacity 0.12s, color 0.12s; -} -.cookbook-srv-default:hover { opacity: 0.8; } -.cookbook-srv-default.active { opacity: 1; color: var(--accent, var(--red)); } -.cookbook-srv-default-label { font-size: 10px; font-weight: 600; letter-spacing: 0.02em; } -.cookbook-srv-default-icon { - display: inline-flex; - align-items: center; - line-height: 0; -} -.cookbook-srv-default-icon svg { - width: 13px; - height: 13px; -} -.cookbook-srv-default-icon, -.cookbook-srv-default-label { - position: relative; - top: 2px; -} -.email-settings-default, -.email-settings-default .cookbook-srv-default-icon, -.email-settings-default .cookbook-srv-default-label { - position: relative; - top: -2px; -} -/* Download-target toggle inside a model-dir tag */ -.cookbook-modeldir-dl { - cursor: pointer; - opacity: 0.35; - display: inline-flex; - align-items: center; - line-height: 0; - transition: opacity 0.12s, color 0.12s; -} -.cookbook-modeldir-dl:hover { opacity: 0.8; } -.cookbook-modeldir-dl.active { opacity: 1; color: var(--accent, var(--red)); } -/* The tag currently flagged as the download target — clearly highlighted so - it's obvious where downloads land. */ -.cookbook-modeldir-target { - opacity: 1; - color: var(--accent, var(--red)); - font-weight: 600; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); -} -.cookbook-modeldir-rm { - cursor: pointer; - opacity: 0.4; - font-size: 10px; -} -.cookbook-modeldir-rm:hover { - opacity: 1; -} -.cookbook-modeldir-add { - background: none; - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - font-size: 10px; - font-weight: 500; - height: 19.5px; - box-sizing: border-box; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 2px; - position: relative; - top: -3px; - padding: 0 9px; - transition: border-color 0.12s, background 0.12s; -} -.cookbook-modeldir-add:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); } - -/* Cookbook serve optimizations */ -.hwfit-serve-opts { - display: flex; - align-items: center; - gap: 8px; - margin: 4px 0; -} -.hwfit-apply-opts { - font-size: 11px !important; - min-width: auto !important; - white-space: nowrap; -} -.hwfit-opts-desc { - font-size: 9px; - opacity: 0.4; - font-style: italic; -} - -/* Library modal tabs */ -.lib-tabs, -.admin-tabs { - display: flex; - gap: 0; - margin-bottom: 8px; - border-bottom: 1px solid var(--border); -} -.lib-tab, -.admin-tab { - background: none; - border: none; - border-bottom: 2px solid transparent; - color: var(--fg-muted); - font-size: 12px; - font-family: inherit; - padding: 6px 14px; - cursor: pointer; - transition: color 0.1s, border-color 0.1s; -} -.lib-tab:hover, -.admin-tab:hover { color: var(--fg); } -.lib-tab.active, -.admin-tab.active { - color: var(--accent, var(--red)); - border-bottom-color: var(--accent, var(--red)); -} - -/* Cookbook tab count badge */ -.cookbook-tab-count { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 16px; - height: 16px; - padding: 0 4px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); - color: var(--accent, var(--red)); - font-size: 9px; - font-weight: 700; - margin-left: 4px; - line-height: 1; -} - -/* ── Generate Visual Report button (deep research) ── */ -.view-report-btn { - display: inline-flex; - align-items: center; - gap: 6px; - margin-top: 12px; - padding: 8px 16px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 10%, transparent); - color: var(--fg); - font-family: inherit; - font-size: 0.88em; - font-weight: 500; - transition: background 0.15s ease; - cursor: pointer; -} -.view-report-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--red) 18%, transparent); -} -.view-report-btn svg { - flex-shrink: 0; -} -.report-btn-wrap { - margin-top: 12px; - display: flex; - flex-wrap: wrap; - gap: 8px; -} -.view-report-btn.chat-about-btn { - border-color: color-mix(in srgb, var(--fg) 22%, transparent); - background: color-mix(in srgb, var(--fg) 6%, transparent); -} -.view-report-btn.chat-about-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--fg) 12%, transparent); -} -.view-report-btn:disabled { - opacity: 0.6; - cursor: progress; -} -.report-spinner { - display: flex; - align-items: center; - gap: 6px; - font-size: 0.88em; - color: var(--fg); - opacity: 0.7; -} -.report-spinner-text { - font-weight: 500; -} -.report-spinner-dots::after { - content: ''; - animation: report-dots 1.4s steps(4, end) infinite; -} -@keyframes report-dots { - 0% { content: ''; } - 25% { content: '.'; } - 50% { content: '..'; } - 75% { content: '...'; } - 100% { content: ''; } -} - -/* ── Continue research hint ────────────────────────── */ -.continue-research-wrap { - margin-top: 8px; -} -.continue-research-hint { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 0.78em; - color: var(--fg-dim, var(--fg)); - opacity: 0.5; - padding: 4px 0; -} -.continue-research-hint svg { - flex-shrink: 0; - opacity: 0.6; -} - -/* ── Research reconnect & timer ────────────────────── */ -.msg.research-reconnect { - border-left: 3px solid var(--red); - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.research-timer { - font-size: 0.8em; - opacity: 0.6; - margin-top: 4px; - font-family: monospace; - color: var(--fg-dim, var(--fg)); -} - -/* ── Research synapse visualization ─────────────────────────────── - Live SVG graph of an in-flight deep-research run. The query is the - central node; sub-questions branch off per round; sources are leaf - nodes that pop in as they're captured. Pulses indicate activity. */ -.research-synapse { - margin: 6px 0 4px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 24%, var(--border)); - border-radius: 7px; - background: - linear-gradient(color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), - linear-gradient(90deg, color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), - radial-gradient(ellipse at 50% 48%, color-mix(in srgb, var(--accent, var(--red)) 13%, transparent) 0%, transparent 62%), - color-mix(in srgb, var(--panel) 68%, var(--bg)); - background-size: 18px 18px, 18px 18px, auto, auto; - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--fg) 5%, transparent); - overflow: hidden; -} -.research-synapse .rs-stage { - height: 400px; - position: relative; -} -.research-synapse .rs-meta-header { - position: absolute; - z-index: 4; - top: 0; - left: 0; - right: 0; - border-top: 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - padding: 5px 8px 6px; - background: color-mix(in srgb, var(--panel) 76%, transparent); -} -.research-synapse-ascii-filter > .rs-stage > svg { - display: none; -} -.research-synapse-ascii-filter .rs-ascii-overlay { - position: absolute; - z-index: 3; - inset: 28px 10px 10px; - margin: 0; - overflow: hidden; - pointer-events: none; - color: color-mix(in srgb, var(--accent, var(--red)) 76%, var(--fg)); - font: 600 10px/1.35 ui-monospace, SFMono-Regular, Consolas, monospace; - white-space: pre; - text-align: center; - opacity: 1; - text-shadow: 0 0 7px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); - mix-blend-mode: screen; -} -.research-synapse-compact .rs-meta { padding: 5px 8px 6px; font-size: 10px; } -.research-synapse-compact .rs-label-sub { font-size: 8px; } -.research-synapse .rs-stage-head { - position: absolute; - z-index: 2; - top: 7px; - left: 9px; - right: 9px; - display: flex; - align-items: center; - gap: 5px; - color: var(--fg-dim, var(--fg)); - font: 600 8px/1 ui-monospace, "JetBrains Mono", monospace; - text-transform: uppercase; - pointer-events: none; -} -.research-synapse .rs-live-dot { - width: 5px; - height: 5px; - border-radius: 50%; - background: var(--accent, var(--red)); - box-shadow: 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 75%, transparent); - animation: rs-live 1.8s ease-in-out infinite; -} -.research-synapse .rs-phase-chip { - margin-left: auto; - padding: 3px 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, var(--border)); - border-radius: 3px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, var(--bg)); - color: var(--accent, var(--red)); -} -.research-synapse svg { - display: block; - width: 100%; - height: 100%; -} -.research-synapse .rs-edge { - stroke: color-mix(in srgb, var(--accent, var(--red)) 32%, var(--border)); - stroke-width: 1.35; - fill: none; - opacity: 0.72; -} -.research-synapse .rs-node { - fill: var(--bg); - stroke: var(--accent, var(--red)); - stroke-width: 1.5; - transition: all 0.3s ease; - transform-box: fill-box; - transform-origin: center; -} -.research-synapse .rs-source-index { - fill: var(--fg); - font: 650 4.5px/1 ui-monospace, "JetBrains Mono", monospace; - pointer-events: none; - opacity: 0.88; -} -.research-synapse .rs-node-expanded { animation: rs-hub-expand 0.75s ease-out; } -.research-synapse .rs-node-new { - animation: rs-pop 0.6s ease-out; -} -.research-synapse .rs-pulse { - fill: var(--accent, var(--red)); - opacity: 0; - animation: rs-pulse 2.6s ease-out infinite; - transform-box: fill-box; - transform-origin: center; -} -.research-synapse .rs-label { - fill: var(--fg); - font-size: 10px; - font-family: ui-monospace, "JetBrains Mono", monospace; - pointer-events: none; - opacity: 0.85; -} -.research-synapse .rs-label-sub { - font-size: 9px; - opacity: 0.7; -} -.research-synapse .rs-meta { - display: grid; - grid-template-columns: minmax(0, 1fr) auto auto auto; - align-items: center; - gap: 10px; - padding: 6px 10px 8px; - font-family: ui-monospace, "JetBrains Mono", monospace; - font-size: 11px; - color: var(--fg-dim, var(--fg)); - opacity: 0.92; - border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - background: color-mix(in srgb, var(--panel) 76%, transparent); -} -.research-synapse .rs-meta .rs-status { - display: none; - color: var(--accent, var(--red)); - font-weight: 600; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.research-synapse .rs-meta b { - color: var(--fg); - font-weight: 600; -} -.research-synapse.rs-complete .rs-meta .rs-status { color: var(--color-success, #4caf50); } -.research-synapse.rs-error .rs-meta .rs-status { color: var(--red); } -.research-synapse.rs-phase-reading .rs-phase-chip, -.research-synapse.rs-phase-analyzing .rs-phase-chip { color: var(--gold, #d4a72c); } -.research-synapse.rs-phase-writing .rs-phase-chip { color: var(--color-success, #4caf50); } - -@media (prefers-reduced-motion: reduce) { - .research-synapse .rs-live-dot, - .research-synapse .rs-pulse, - .research-synapse .rs-node-new, - .research-synapse .rs-node-expanded, - .research-synapse .rs-edge-firing { animation: none !important; } -} - -/* ── Raw findings items (inside sources-style box) ── */ -.finding-item { - display: flex; - flex-direction: column; - gap: 4px; -} -.finding-item .source-link { - border-radius: 6px 6px 0 0; -} -.finding-summary { - padding: 6px 8px 8px; - font-size: 0.82em; - line-height: 1.5; - color: var(--fg-dim, var(--fg)); - opacity: 0.85; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); - margin-bottom: 4px; - white-space: pre-wrap; - word-break: break-word; -} -.finding-item:last-child .finding-summary { - border-bottom: none; - margin-bottom: 0; -} - -/* ═══════════════════════════════════════════════════════════ - Gallery Editor - ═══════════════════════════════════════════════════════════ */ - -/* Tab bar */ -.gallery-tabs { - display: flex; - gap: 0; - border-bottom: 1px solid var(--border); - padding: 0 16px; - background: var(--panel); -} -.gallery-tab { - padding: 8px 18px; - background: none; - border: none; - border-bottom: 2px solid transparent; - color: var(--fg); - opacity: 0.6; - cursor: pointer; - font-size: 13px; - font-weight: 500; - transition: opacity 0.15s, border-color 0.15s; -} -.gallery-tab:hover { opacity: 0.85; } -.gallery-tab.active { - opacity: 1; - border-bottom-color: var(--red); -} - -/* Narrow modal tab strips should stay on one row. Resized docked windows can - be much narrower than the viewport, so this cannot live only in mobile media - queries. */ -.cookbook-tabs, -.memory-tabs, -.admin-tabs, -.lib-tabs, -.gallery-tabs, -.preset-tabs { - flex-wrap: nowrap !important; - overflow-x: auto !important; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - overscroll-behavior-x: contain; - scrollbar-width: none; -} -.cookbook-tabs::-webkit-scrollbar, -.memory-tabs::-webkit-scrollbar, -.admin-tabs::-webkit-scrollbar, -.lib-tabs::-webkit-scrollbar, -.gallery-tabs::-webkit-scrollbar, -.preset-tabs::-webkit-scrollbar { - display: none; -} -.cookbook-tabs > *, -.memory-tabs > *, -.admin-tabs > *, -.lib-tabs > *, -.gallery-tabs > *, -.preset-tabs > * { - flex: 0 0 auto; -} -.cookbook-tab, -.memory-tab, -.admin-tab, -.lib-tab, -.gallery-tab, -.preset-tab { - display: inline-flex; - align-items: center; - justify-content: center; - white-space: nowrap; - line-height: 1; -} -.gallery-tab { - gap: 6px; -} - -@container (max-width: 360px) { - .cookbook-tab:has(svg), - .memory-tab:has(svg), - .admin-tab:has(svg), - .lib-tab:has(svg), - .gallery-tab:has(svg), - .preset-tab:has(svg) { - width: 34px; - min-width: 34px; - padding-left: 0; - padding-right: 0; - font-size: 0; - } - - .cookbook-tab:has(svg) svg, - .memory-tab:has(svg) svg, - .admin-tab:has(svg) svg, - .lib-tab:has(svg) svg, - .gallery-tab:has(svg) svg, - .preset-tab:has(svg) svg { - width: 14px; - height: 14px; - margin-right: 0 !important; - vertical-align: middle !important; - } - - .memory-tab:has(svg) .memory-count, - .gallery-tab:has(svg) .gallery-tab-label, - .gallery-tab:has(svg) .gallery-tab-close, - .cookbook-tab:has(svg) .cookbook-tab-count, - .preset-tab:has(svg) .preset-count { - display: none !important; - } -} -/* Icon + label layout inside each tab. */ -.gallery-tab { - display: inline-flex; - align-items: center; - gap: 6px; -} -.gallery-tab-icon { - display: inline-flex; - align-items: center; - opacity: 0.85; -} -.gallery-tab.active .gallery-tab-icon { opacity: 1; } -/* Close × on the Edit tab — appears on hover. */ -.gallery-tab-close { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - margin-left: 2px; - border-radius: 4px; - opacity: 0; - transition: opacity 0.12s, background 0.12s; - /* Use a slightly larger glyph at a larger line-height so the × sits - visually centered — the U+00D7 character has uneven em-box metrics. */ - font-size: 16px; - line-height: 16px; - font-weight: 500; - cursor: pointer; - padding-bottom: 2px; - box-sizing: border-box; -} -.gallery-tab:hover .gallery-tab-close, -.gallery-tab.active .gallery-tab-close { opacity: 0.65; } -/* Hide the close × entirely on the Edit tab when there's no edit open. */ -.gallery-tab[data-tab="editor"]:not(.has-edit) .gallery-tab-close { display: none !important; } -.gallery-tab-close:hover { opacity: 1 !important; background: color-mix(in srgb, var(--red) 25%, transparent); color: var(--red); } -/* Inline rename input shown when the Edit tab is double-clicked. */ -.gallery-tab-rename-input { - background: var(--bg); - border: 1px solid var(--red); - border-radius: 3px; - color: var(--fg); - font: inherit; - font-size: 13px; - padding: 1px 4px; - width: 140px; - outline: none; -} - -/* Albums tab — grid of album cards with cover thumbnails. */ -.gallery-albums-container { - padding: 12px 4px; - max-height: calc(92vh - 120px); - overflow-y: auto; - border: 2px dashed transparent; - border-radius: 8px; - transition: border-color 0.15s, background 0.15s; -} -.gallery-settings-container { - padding: 8px 4px; - max-height: calc(92vh - 120px); - overflow-y: auto; -} -.gallery-albums-container.gallery-dragover { - border-color: var(--red); - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.gallery-albums-empty { - display: flex; flex-direction: column; align-items: center; gap: 12px; - padding: 48px 16px; - opacity: 0.7; - font-size: 13px; -} -.gallery-albums-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 12px; -} -.gallery-album-card { - position: relative; - display: flex; - flex-direction: column; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - overflow: hidden; - cursor: pointer; - transition: transform 0.12s, border-color 0.12s, box-shadow 0.12s; -} -.gallery-album-card:hover { - transform: translateY(-2px); - border-color: var(--red); - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 12%, transparent); -} -.gallery-album-menu-btn { - position: absolute; - top: 6px; right: 6px; - width: 24px; height: 24px; - display: flex; align-items: center; justify-content: center; - background: color-mix(in srgb, var(--bg) 70%, transparent); - color: var(--fg); - border: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - border-radius: 6px; - cursor: pointer; - opacity: 0; - transition: opacity 0.12s, background 0.12s; - z-index: 2; - backdrop-filter: blur(4px); - /* Plain "…" text — the previous SVG was rendering as a flat black - block on some themes when its currentColor didn't contrast. */ - font-size: 16px; - line-height: 1; - font-weight: 600; - padding: 0 0 4px; -} -.gallery-album-card:hover .gallery-album-menu-btn, -.gallery-album-menu-btn:focus-visible { opacity: 1; } -.gallery-album-menu-btn:hover { background: var(--bg); } -.gallery-album-menu-pop { - position: absolute; - top: 34px; right: 6px; - min-width: 140px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); - display: flex; flex-direction: column; - padding: 4px; - z-index: 3; -} -.gallery-album-menu-pop[hidden] { display: none; } -.gallery-album-cover { - aspect-ratio: 4 / 3; - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - display: flex; align-items: center; justify-content: center; - overflow: hidden; -} -.gallery-album-placeholder { - opacity: 0.4; - font-size: 40px; - display: flex; align-items: center; justify-content: center; - width: 100%; height: 100%; -} -.gallery-album-info { - padding: 8px 10px; - display: flex; flex-direction: column; gap: 2px; -} -.gallery-album-name { - font-size: 13px; - font-weight: 500; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -.gallery-album-count { - font-size: 11px; - opacity: 0.55; -} -.gallery-album-card-add { - border-style: dashed; - opacity: 0.7; -} -.gallery-album-card-add:hover { opacity: 1; } -.gallery-album-action-tile { - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); -} - -/* Edit-tab empty state — shown before any image is loaded. */ -.gallery-editor-landing { - display: flex; flex-direction: column; align-items: center; justify-content: center; - gap: 10px; - padding: 28px 16px; - flex: 1; - text-align: center; - color: var(--fg); -} -.gallery-editor-landing h3 { - margin: 0; - font-size: 16px; - font-weight: 600; -} -.gallery-editor-heading { - display: inline-flex; - align-items: center; - gap: 7px; -} -.gallery-editor-heading > svg { opacity: 0.6; flex: 0 0 auto; } -/* "vision model" link in the AI-tagging description → opens Settings. */ -.ge-vision-link { - color: var(--accent, var(--red)); - text-decoration: underline; - text-underline-offset: 2px; - cursor: pointer; -} -.ge-vision-link:hover { opacity: 0.8; } -.ge-alpha-tag { - font-size: 9px; - font-weight: 700; - letter-spacing: 0.06em; - text-transform: uppercase; - vertical-align: middle; - padding: 1px 5px; - border-radius: 4px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - position: relative; - top: -1px; -} -.gallery-editor-landing-actions { - display: flex; gap: 10px; - margin-top: 8px; -} -/* Bigger primary action buttons specifically inside the editor landing — - the gallery-wide .gallery-select-btn is a compact toolbar style which is - too small for the empty-state hero. */ -.gallery-editor-landing-actions .gallery-select-btn { - padding: 7px 22px 17px; - font-size: 13px; - opacity: 0.85; -} -/* Visible template picker: every canvas ratio is inspectable without opening - a native menu, and its SVG outline previews the resulting canvas shape. */ -.gallery-editor-template-picker { - width: min(760px, 96%); - margin-top: 10px; -} -.gallery-editor-template-picker h4 { - margin: 0 0 7px; - font-size: 11px; - opacity: 0.55; - text-transform: uppercase; - letter-spacing: 0.06em; -} -.gallery-editor-template-options { - display: grid; - grid-template-columns: repeat(4, minmax(105px, 1fr)); - gap: 6px; -} -.gallery-editor-template-option { - height: 72px; - min-width: 0; - padding: 6px 7px; - display: grid; - grid-template-rows: 28px auto auto; - justify-items: center; - align-items: center; - background: var(--panel); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 5px; - font: inherit; - font-size: 11px; - cursor: pointer; - overflow: hidden; -} -.gallery-editor-template-option svg { color: var(--accent, var(--red)); } -.gallery-editor-template-option:hover, -.gallery-editor-template-option:focus-visible { - outline: none; - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 7%, var(--panel)); -} -.gallery-editor-template-option span { - width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-weight: 600; -} -.gallery-editor-template-option small { - width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 9px; - opacity: 0.52; -} -@media (max-width: 620px) { - .gallery-editor-landing { - justify-content: flex-start; - gap: 7px; - padding: 18px 12px; - overflow-y: auto; - } - .gallery-editor-landing-actions { - width: 100%; - gap: 6px; - justify-content: center; - } - .gallery-editor-landing-actions .gallery-select-btn { - flex: 1 1 0; - min-width: 0; - max-width: 180px; - padding: 6px 8px 10px; - font-size: 11px; - } - .gallery-editor-template-picker { - width: 100%; - margin-top: 4px; - } - .gallery-editor-template-options { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - /* The common square, widescreen, portrait, and Instagram presets are - enough to start on a phone. Keep the print and 4K presets on desktop. */ - .gallery-editor-template-option:nth-child(n + 5) { - display: none; - } -} -/* Saved-drafts grid on the editor landing. Surfaces every persisted - in-progress project so the user can resume without re-opening the - original photo. Each card: thumbnail + name + last-saved hint + ×. */ -.gallery-editor-drafts { - width: min(720px, 92%); - margin: 28px auto 0; /* centered */ - padding-top: 18px; - border-top: 1px solid var(--border); - display: flex; - flex-direction: column; - gap: 10px; -} -.gallery-editor-drafts { - position: relative; -} -.gallery-editor-drafts-loading { - position: absolute; - inset: 30px 0 0 0; - z-index: 4; - display: flex; - align-items: center; - justify-content: center; - background: var(--panel); - border-radius: 6px; - min-height: 80px; -} -.gallery-editor-drafts-header { - display: flex; - align-items: center; - justify-content: center; /* center the title / search / select */ - gap: 8px; - margin-bottom: 8px; -} -.gallery-editor-drafts-title { - margin: 0; - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.06em; - opacity: 0.55; - font-weight: 600; - text-align: left; - flex-shrink: 0; -} -.gallery-editor-drafts-search { - flex: 1 1 auto; - min-width: 0; - max-width: none; - height: 26px; /* same thickness as the Select button */ - box-sizing: border-box; - padding: 0 8px; - font-size: 12px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; -} -.gallery-editor-drafts-search:focus { outline: none; border-color: var(--red); } -/* The shared .gallery-select-btn has a -4px top margin + tall asymmetric padding - meant for the main gallery toolbar; in this header it makes the button a - different height / offset from the search box. Normalize so it lines up. */ -.gallery-editor-drafts-header .gallery-select-btn { - margin-top: 0 !important; - height: 26px; /* match the search bar */ - box-sizing: border-box; - padding: 0 10px; - font-size: 12px; - line-height: 1; - flex-shrink: 0; -} - -.cookbook-official-filter { - display: inline-flex; - align-items: center; - gap: 4px; - flex: 0 1 auto; - min-width: 0; - max-width: 100%; - color: var(--fg); - font-size: 10px; - opacity: 0.72; - white-space: nowrap; - position: relative; - top: 1px; -} -.cookbook-official-filter > span:first-child { - position: relative; - top: 1px; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-size: 9px; -} -.cookbook-official-filter .toggle { - width: 26px; - height: 14px; -} -.cookbook-official-filter .toggle .slider:before { - width: 10px; - height: 10px; - left: 2px; - bottom: 2px; -} -.cookbook-official-filter .toggle input:checked + .slider:before { - transform: translateX(12px); -} -#gallery-editor-drafts-select { margin-left: auto; flex-shrink: 0; } -#gallery-editor-drafts-select.active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -/* Graceful exit when a project card is deleted — fade + shrink before re-render. */ -.gallery-editor-draft-card.gallery-draft-removing { - opacity: 0; - transform: scale(0.92); - transition: opacity 0.24s ease, transform 0.24s ease; - pointer-events: none; -} -/* Draft select dot uses the standard small .gallery-select-dot look (same as the - photo grid) — no oversized white-ring checkbox. */ -.gallery-editor-drafts-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); - gap: 10px; -} -.gallery-drafts-just-opened > .gallery-editor-draft-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.gallery-drafts-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.gallery-drafts-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.gallery-drafts-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.gallery-drafts-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.gallery-drafts-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.gallery-drafts-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.gallery-drafts-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.gallery-drafts-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.gallery-drafts-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.gallery-drafts-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.gallery-drafts-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.gallery-drafts-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.gallery-drafts-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.gallery-drafts-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.gallery-drafts-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.gallery-drafts-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.gallery-drafts-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.gallery-drafts-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.gallery-drafts-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.gallery-drafts-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.gallery-drafts-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } -.gallery-drafts-view-toggle { - display: inline-flex; - position: relative; - top: 0; - height: 24px; - flex: 0 0 auto; - overflow: hidden; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); -} -.gallery-drafts-view-toggle button { - width: 25px; - height: 22px; - margin: 0; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - border: 0; - border-right: 1px solid var(--border); - background: transparent; - color: color-mix(in srgb, var(--fg) 55%, transparent); - cursor: pointer; - line-height: 0; -} -.gallery-drafts-view-toggle button:last-child { border-right: 0; } -.gallery-drafts-view-toggle button:hover { color: var(--fg); } -.gallery-drafts-view-toggle button.active { - height: 22px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-thumb { - width: 44px; - height: 44px; - aspect-ratio: auto; -} -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete { - position: static; - opacity: 0.55; - background: transparent; - color: var(--fg); -} -.gallery-editor-draft-delete-trash { display: none; } -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete-trash { - display: block; - width: 18px; - height: 18px; - position: relative; - top: -2px; -} -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete:hover { - opacity: 1; - color: var(--color-error, #f44); - background: color-mix(in srgb, var(--color-error, #f44) 10%, transparent); -} -.gallery-editor-draft-card { - position: relative; - display: flex; - flex-direction: column; - gap: 4px; - padding: 6px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 70%, transparent); - cursor: pointer; - transition: border-color 0.15s, background 0.15s, transform 0.15s; - text-align: left; -} -.gallery-editor-draft-card:hover { - border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--panel) 90%, transparent); -} -.gallery-editor-draft-thumb { - width: 100%; - aspect-ratio: 1; - object-fit: cover; - border-radius: 4px; - background: var(--bg); - display: block; -} -.gallery-editor-draft-thumb-empty { - background: repeating-linear-gradient( - 45deg, - color-mix(in srgb, var(--fg) 4%, transparent), - color-mix(in srgb, var(--fg) 4%, transparent) 6px, - transparent 6px, - transparent 12px - ); -} -.gallery-editor-draft-info { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; -} -.gallery-editor-draft-name { - font-size: 12px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.gallery-editor-draft-meta { - font-size: 10px; - opacity: 0.55; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.gallery-editor-draft-delete { - position: absolute; - top: -2px; - right: 8px; - width: 22px; - height: 22px; - border-radius: 50%; - border: none; - background: rgba(0, 0, 0, 0.55); - color: #fff; - font-size: 14px; - line-height: 1; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.12s, background 0.12s; -} -.gallery-editor-draft-delete { - padding-bottom: 2px; -} -.gallery-editor-draft-delete:hover { background: var(--red); } -/* On very narrow phones, give project search its own row instead of - compressing the title and view controls into an overflowing header. */ -@media (max-width: 360px) { - .gallery-editor-drafts-header { - flex-wrap: wrap; - justify-content: flex-start; - gap: 6px; - } - .gallery-editor-drafts-title { - flex: 1 1 auto; - } - .gallery-editor-drafts-search { - order: 10; - flex: 1 1 100%; - width: 100%; - max-width: none; - } - #gallery-editor-drafts-select { - margin-left: 0; - } -} -/* Editor layout */ -.gallery-editor { - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; - height: 100%; - overflow: hidden; - /* Prevent the editor's own scroll/touch from chaining out to the - gallery modal or the page body — critical on mobile so touching - the canvas doesn't slide the whole page. */ - overscroll-behavior: contain; - touch-action: pan-y; -} -/* Pin the topbar to the top of the editor so its action buttons remain - visible if the gallery modal body itself ends up scrolling. */ -.ge-topbar { position: sticky; top: 0; z-index: 5; } - -/* Top bar */ -.ge-topbar { - display: flex; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: thin; - align-items: center; - justify-content: space-between; - padding: 6px 10px; - background: var(--panel); - border-bottom: 1px solid var(--border); - flex-shrink: 0; - gap: 8px; -} -.ge-topbar-left, .ge-topbar-right { - display: flex; - flex: 0 0 auto; - align-items: center; - gap: 4px; -} -.ge-topbar-right > * { - flex-shrink: 0; -} -.ge-topbar .dropdown[popover] { - position: fixed; - margin: 0; - bottom: auto; - max-height: calc(100vh - 80px); - overflow-y: auto; -} -.ge-draft-status { - flex: 0 1 auto; - min-width: 0; - max-width: 92px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 52%, transparent); - font-size: 10px; - line-height: 1; - transition: color 0.15s, opacity 0.15s; -} -.ge-draft-status[data-state="pending"], -.ge-draft-status[data-state="saving"] { color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); } -.ge-draft-status[data-state="saved"] { color: color-mix(in srgb, var(--color-success, #4ade80) 78%, var(--fg)); } -.ge-draft-status[data-state="error"] { color: var(--color-error, #ef4444); } -/* "ALPHA" badge — flags the editor as in-development. */ -.ge-alpha-badge { - flex-shrink: 0; - font-size: 9px; - font-weight: 700; - letter-spacing: 0.1em; - line-height: 1; - padding: 3px 6px; - margin-right: 4px; - border-radius: 4px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - text-transform: uppercase; - user-select: none; - cursor: default; -} -@media (max-width: 768px) { - /* Keep the toolbar tight on mobile — badge stays but shrinks. */ - .ge-alpha-badge { font-size: 8px; padding: 2px 5px; margin-right: 2px; } -} -.ge-diffusion-status { - font-size: 11px; - cursor: pointer; - padding: 2px 8px; - border-radius: 4px; - transition: all 0.15s; - white-space: nowrap; -} -.ge-diffusion-status.online { - color: var(--color-success, #4ade80); -} -.ge-diffusion-status.offline { - color: var(--color-error, #ef4444); - opacity: 0.7; -} -.ge-diffusion-status.offline:hover { - opacity: 1; - background: color-mix(in srgb, var(--color-error) 10%, transparent); -} -.ge-topbar-fill { - display: inline-flex; - align-items: center; - gap: 4px; - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -.ge-topbar-fill:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: var(--accent, var(--red)); -} -/* Mask-color swatch in the topbar — small label + circular swatch so - the user can pick a contrasting overlay colour from anywhere. */ -.ge-topbar-mask-color-wrap { - display: inline-flex; - align-items: center; - gap: 4px; - cursor: pointer; -} -.ge-topbar-mask-color-label { - font-size: 10px; - opacity: 0.6; - text-transform: uppercase; - letter-spacing: 0.06em; -} -.ge-topbar-mask-color { - width: 18px; - height: 18px; - flex: 0 0 18px; -} -.ge-topbar-mask-color.cp-swatch-input { - width: 18px; - height: 18px; - flex: 0 0 18px; - border-radius: 50%; - padding: 0; -} -.ge-topbar-sep { - width: 1px; - height: 16px; - background: var(--border); - margin: 0 4px; -} - -/* Editor body (toolbar + canvas + panel) */ -.ge-editor-body { - display: flex; - flex: 1; - min-height: 0; - overflow: hidden; - position: relative; -} -.gallery-editor-container { - flex: 1; - min-height: 0; - display: none; -} - -.ge-toolbar { - display: flex; - flex-direction: column; - gap: 2px; - /* Asymmetric horizontal padding shifts the whole column 4 px left — - buttons, hover backgrounds, active highlight pills, and section - separators all move together. */ - padding: 8px 8px 8px 0; - background: var(--panel); - border-right: 1px solid var(--border); - width: 64px; - flex-shrink: 0; - min-height: 0; - overflow-x: hidden; - overflow-y: auto; - scrollbar-width: none; -} -.ge-toolbar::-webkit-scrollbar { display: none; } -.ge-tool-sep { - font-size: 8px; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--color-muted); - text-align: center; - padding: 8px 0 4px; - border-top: 1px solid var(--border); - margin-top: 4px; - opacity: 0.6; - flex-shrink: 0; -} -/* All tool buttons share a fixed height so the column reads as a clean - grid. Long labels (e.g. "Remove BG") truncate with ellipsis instead of - wrapping to two lines and making one button taller than the others. */ -.ge-tool-btn { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 2px; - padding: 0 2px; - height: 42px; - flex-shrink: 0; - border: none; - background: none; - color: var(--fg); - opacity: 0.6; - cursor: pointer; - border-radius: 6px; - transition: background 0.15s, opacity 0.15s; -} -.ge-tool-btn { position: relative; } -/* Hover background is now a pseudo so we can shift it 2 px left - independently from the button's own bounds (which contain the icon - and label at their already-shifted positions). */ -.ge-tool-btn:hover { opacity: 0.85; } -.ge-tool-btn.active { - opacity: 1; - color: var(--red); - background: none; - /* Padding stays the same as inactive buttons so the icon stays - horizontally aligned with its neighbors. The taller highlight is - drawn by ::before so it can extend up/down without pushing the - button or shifting the icon. */ -} -/* Tool button contents stay above the active highlight pseudo. Also - shifted an additional 2 px left so the icon + label sit further inside - the highlight pill (which still anchors to the button bounds). */ -.ge-tool-btn > * { position: relative; z-index: 1; left: -2px; } -.ge-tool-icon { font-size: 18px; line-height: 1; } -.ge-tool-label { font-size: 10px; line-height: 1.25; } -/* AI badge — same ✦ glyph the Enhance tool uses, pinned to the top- - left of any tool button marked `ai: true`. Same color as the icon - itself (inherits foreground) so it reads as part of the tool, not - a flag. */ -.ge-tool-ai { - position: absolute !important; - top: 1px; - z-index: 2; - color: inherit; - opacity: 0.7; - pointer-events: none; - font-size: 11px; - line-height: 1; - font-weight: 700; - left: 3px !important; -} -.ge-tool-btn.is-ai:hover .ge-tool-ai { opacity: 0.95; } -.ge-tool-btn.is-ai.active .ge-tool-ai { opacity: 1; } -/* Inline ✦ marker used on AI action buttons (Generate, Remove, etc.) - so they read as AI-backed at a glance — same glyph as .ge-tool-ai - in the toolbar, just sitting inline next to the label. */ -.ge-btn-ai-mark { - display: inline-block; - font-size: 11px; - line-height: 1; - font-weight: 700; - opacity: 0.75; - margin-right: 1px; -} - -/* Per-tool clear-selection badge — shows a tiny X in the top-right of - the Lasso / Wand button when each holds a selection. Click clears - the selection without switching tools (handler stops propagation). */ -.ge-tool-clear { - position: absolute !important; - top: 3px; - right: 3px; - width: 14px; - height: 14px; - display: none; - align-items: center; - justify-content: center; - border-radius: 50%; - color: #fff; - background: var(--red); - cursor: pointer; - opacity: 0.95; - z-index: 2; - box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 70%, transparent), - 0 1px 4px color-mix(in srgb, var(--red) 50%, transparent); - transition: opacity 0.12s, transform 0.12s, box-shadow 0.12s; - left: auto !important; -} -.ge-tool-clear:hover { - opacity: 1; - transform: scale(1.15); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 80%, transparent), - 0 2px 6px color-mix(in srgb, var(--red) 60%, transparent); -} - -/* Aspect-ratio placeholder shown while a draft is loading. */ -.ge-canvas-placeholder { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border: 1px dashed color-mix(in srgb, var(--fg) 20%, transparent); - border-radius: 4px; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 1; -} -/* Canvas area (center) */ -.ge-canvas-area { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - overflow: auto; - background: var(--bg); - position: relative; - min-width: 0; - /* Stop touch interactions inside the editor from dragging the whole - page (or the gallery modal). Touches on the canvas are owned by the - editor's drawing logic — no native pan/zoom. */ - overscroll-behavior: contain; -} -.ge-ruler { - position: absolute; - z-index: 16; - touch-action: none; - user-select: none; -} -.ge-ruler-horizontal { - top: 0; - left: 18px; - right: 0; - width: calc(100% - 18px); - height: 18px; - cursor: row-resize; -} -.ge-ruler-vertical { - top: 18px; - bottom: 0; - left: 0; - width: 18px; - height: calc(100% - 18px); - cursor: col-resize; -} -.ge-ruler-corner { - position: absolute; - top: 0; - left: 0; - z-index: 17; - width: 18px; - height: 18px; - background: var(--panel); - border-right: 1px solid var(--border); - border-bottom: 1px solid var(--border); - box-sizing: border-box; -} -.ge-wand-loading { - position: absolute; - inset: 0; - z-index: 25; - display: flex; - align-items: center; - justify-content: center; - background: color-mix(in srgb, var(--bg) 30%, transparent); - pointer-events: none; -} -.ge-ai-command { - position: absolute; - left: 50%; - bottom: 14px; - z-index: 18; - width: min(430px, calc(100% - 28px)); - transform: translateX(-50%); - padding: 7px; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 94%, transparent); - box-shadow: 0 8px 22px color-mix(in srgb, #000 26%, transparent); - backdrop-filter: blur(8px); - transition: bottom 0.16s ease, opacity 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; -} -.ge-ai-command-toggle { - width: 100%; - height: 28px; - display: none; - align-items: center; - justify-content: center; - gap: 5px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--accent, var(--red)); - font: inherit; - font-size: 11px; - font-weight: 600; - cursor: pointer; -} -.ge-ai-command-toggle-caret { - opacity: 0.7; - flex-shrink: 0; -} -.ge-ai-command-head { - display: flex; - align-items: center; - min-height: 24px; - padding: 0 2px 5px; - color: var(--fg); -} -.ge-ai-command-title { - margin-left: 5px; - font-size: 11px; - font-weight: 650; -} -.ge-ai-command-scope { - margin-left: 7px; - padding-left: 7px; - border-left: 1px solid var(--border); - color: var(--fg-muted); - font-size: 9px; - line-height: 1; - text-transform: uppercase; -} -.ge-ai-command-collapsed { - bottom: -16px; - width: auto; - min-width: 102px; - padding: 2px 9px; - opacity: 0.32; - border-color: color-mix(in srgb, var(--accent, var(--red)) 18%, var(--border)); - box-shadow: 0 3px 10px color-mix(in srgb, #000 16%, transparent); -} -.ge-canvas-area:hover .ge-ai-command-collapsed, -.ge-ai-command-collapsed:hover, -.ge-ai-command-collapsed:focus-within { - bottom: 10px; - opacity: 0.94; - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 24%, transparent); -} -.ge-ai-command-form { - display: flex; - align-items: center; - gap: 6px; -} -.ge-ai-command-input { - flex: 1 1 auto; - min-width: 0; - height: 32px; - box-sizing: border-box; - padding: 0 9px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--bg) 76%, #000 24%); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.ge-ai-command-input:focus { - outline: none; - border-color: var(--accent, var(--red)); -} -.ge-ai-command-clear { - position: relative; - top: -2px; - width: 26px; - height: 32px; - flex: 0 0 26px; - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: -38px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--fg-muted); - cursor: pointer; - padding: 0; - z-index: 1; -} -.ge-ai-command-clear:hover { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.ge-ai-command-run { - position: relative; - top: -2px; - background: var(--send-btn-bg, var(--red)); - color: #fff; - border: none; - border-radius: 8px; - min-width: 32px; - width: 32px; - height: 32px !important; - padding: 0; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - transition: background 0.25s, opacity 0.12s; - overflow: hidden; -} -.ge-ai-command-run:hover { - background: var(--send-btn-hover, color-mix(in srgb, var(--red) 80%, white)); -} -.ge-ai-command-busy .ge-ai-command-run { - opacity: 0.58; - cursor: wait; -} -.ge-ai-command-busy .ge-ai-command-run svg { - display: none; -} -.ge-ai-command-close { - width: 28px; - height: 32px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border: 0; - border-radius: 8px; - background: transparent; - color: var(--fg); - opacity: 0.52; - cursor: pointer; - padding: 0; - margin-left: auto; -} -.ge-ai-command-close:hover { - opacity: 0.9; - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.ge-ai-command-suggestions { - position: absolute; - left: 7px; - right: 7px; - bottom: calc(100% - 2px); - z-index: 2; - display: flex; - flex-direction: column; - gap: 2px; - max-height: 236px; - overflow: auto; - padding: 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 97%, #000 3%); - box-shadow: 0 10px 28px color-mix(in srgb, #000 28%, transparent); -} -.ge-ai-command-suggestion { - width: 100%; - display: grid; - grid-template-columns: 38px minmax(82px, auto) minmax(0, 1fr); - align-items: center; - gap: 8px; - min-height: 28px; - padding: 4px 7px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--fg); - text-align: left; - cursor: pointer; - font: inherit; -} -.ge-ai-command-suggestion:hover, -.ge-ai-command-suggestion.active { - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); -} -.ge-ai-command-suggestion-kind { - min-width: 0; - overflow: hidden; - color: var(--accent, var(--red)); - font-size: 8px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; -} -.ge-ai-command-suggestion-main { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 11px; - font-weight: 650; -} -.ge-ai-command-suggestion-hint { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--fg-muted); - font-size: 10px; -} diff --git a/static/css/11-tasks.css b/static/css/11-tasks.css new file mode 100644 index 000000000..1ff602fa3 --- /dev/null +++ b/static/css/11-tasks.css @@ -0,0 +1,1972 @@ +/* 11-tasks - 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. + */ +/* Snapped/narrow Settings window: move the tab rail to the top. Viewport media + alone misses desktop right-half snapping, where the modal is narrow but the + browser window is not. */ +@container settings-modal (max-width: 620px) { + .settings-layout { + flex-direction: column; + } + .settings-sidebar { + width: auto; + max-height: none; + flex-direction: row; + border-right: none; + border-bottom: 1px solid var(--border); + overflow-x: auto; + overflow-y: hidden; + padding: 6px; + scrollbar-width: none; + } + .settings-sidebar::-webkit-scrollbar { + display: none; + } + .settings-sidebar-divider, + .settings-sidebar-label { + display: none; + } + .settings-nav-item { + flex: 0 0 auto; + padding: 6px 10px; + font-size: 11px; + } + .settings-panels { + padding: 12px 14px; + } +} + +/* ── Entrance Animations ── */ + +/* Welcome name — left-to-right wipe with a touch of horizontal stretch. + Fires on initial render via the .welcome-name rule; restarts on Nobody⇄ + Odysseus toggle via JS reflow trick. */ +.welcome-name { + transform-origin: left center; + animation: welcome-name-reveal 0.55s cubic-bezier(0.34, 1.32, 0.55, 1) both; +} +/* Hold the welcome-screen entrance animations until the app has finished its + initial load (fonts loaded + layout settled). Running them mid-load made the + splash jump/flicker as the page reflowed ("haywire"). Until body.welcome-ready + is set by JS, the splash sits in its final, static state (no flash, since the + non-animated state IS the resting state); adding the class then plays the + entrance once, cleanly. */ +body:not(.welcome-ready) #welcome-screen, +body:not(.welcome-ready) .welcome-name { + animation: none !important; +} +/* Hold the splash invisible (the entrance's start state) until ready, so when + the animation is released it fades/wipes in smoothly instead of flashing + from fully-visible to the animation's hidden first frame. */ +body:not(.welcome-ready) #welcome-screen { + opacity: 0; +} +@keyframes welcome-name-reveal { + from { + clip-path: inset(0 100% 0 0); + transform: scaleX(0.96); + opacity: 0.5; + } + to { + clip-path: inset(0 0 0 0); + transform: scaleX(1); + opacity: 1; + } +} + +@keyframes welcome-enter { + from { + opacity: 0; + transform: translate(-50%, -45%); + } + to { + opacity: 1; + transform: translate(-50%, -50%); + } +} + +@keyframes msg-enter { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes picker-roll-up { + from { + opacity: 0; + transform: scaleY(0.4) translateY(8px); + } + to { + opacity: 1; + transform: scaleY(1) translateY(0); + } +} +@keyframes picker-roll-down { + from { + opacity: 1; + transform: scaleY(1) translateY(0); + } + to { + opacity: 0; + transform: scaleY(0.4) translateY(8px); + } +} +@keyframes model-picker-refresh-spin { + to { transform: rotate(360deg); } +} + +@keyframes modal-enter { + from { + opacity: 0; + transform: scale(0.95) translateY(8px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} +@keyframes modal-exit { + from { + opacity: 1; + transform: scale(1) translateY(0); + } + to { + opacity: 0; + transform: scale(0.97) translateY(6px); + } +} +.modal-content.modal-closing { + animation: modal-exit 0.18s ease-in both; +} + +#cookbook-modal .modal-content.cookbook-modal-entering { + animation: cookbook-modal-enter 0.28s cubic-bezier(0.22, 1.35, 0.36, 1) both; +} +/* Mobile: real bottom-sheet slide-up — the existing 12px translate is + too subtle on a phone, the modal effectively just appeared. */ +@media (max-width: 768px) { + #cookbook-modal .modal-content.cookbook-modal-entering { + animation: cookbook-modal-enter-mobile 0.32s cubic-bezier(0.22, 1, 0.36, 1) both; + } +} +#cookbook-modal .modal-content:not(.cookbook-modal-entering):not(.modal-closing) { + animation: none; +} +#cookbook-modal .modal-content.modal-closing { + animation: modal-exit 0.18s cubic-bezier(0.4, 0, 1, 1) both; +} + +/* Per-token streaming fade — new words materialize */ +.token-new { + animation: token-fade 0.4s ease-out both; +} + +@keyframes token-fade { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Disable entrance animations when loading chat history (bulk render) */ +.chat-history.no-animate .msg { + animation: none; +} + +/* Prefer reduced motion */ +@media (prefers-reduced-motion: reduce) { + .msg, + .modal-content, + #welcome-screen, + .toast, + .token-new { + animation: none !important; + transition: none !important; + } +} + +@media (min-width: 769px) { + #tool-tasks-btn #assistant-notif-dot.sidebar-notif-dot, + #tool-tasks-btn.task-failure-pending::after, + #tool-tasks-btn.task-completion-pending::after { + transform: translateX(-2px); + } +} +.tasks-modal-content { max-width: 600px; width: min(600px, 92vw); background: var(--bg); font-size: 12px; } +.task-completed-preview-row { + cursor: default; + margin-bottom: 6px; + padding: 8px 10px 10px; + max-height: none; + overflow: visible; +} +.task-completed-preview-head { + display: flex; + align-items: center; + gap: 7px; + min-height: 20px; + font-size: 11px; +} +.task-completed-chat-preview { + padding: 0 0 4px; +} +.task-completed-chat-preview .doclib-chat-preview-messages { + max-height: 220px; + margin-top: 7px; +} +.task-completed-chat-preview .doclib-chat-bubble { + max-width: 92%; +} +.task-completed-chat-preview .doclib-chat-preview-actions { + top: 2px; + margin-top: 4px; +} +.task-completed-preview-row .task-log-time { + margin-left: 8px; +} + +/* Tasks tabs reuse the .memory-tab look. The Brain window's tab bar is + full-bleed (its underline spans the whole modal width). The Tasks bar is a + direct child of .modal-content (padding:10px), so cancel that padding with + negative side margins to span edge-to-edge, then re-inset the tabs by 10px + so they line up with the rest of the modal content — matching the Brain bar. */ +.tasks-modal-content .tasks-tabs { + margin: -2px -10px 8px; + padding: 0 10px; +} + +/* Activity log — compact by default. Click the row to expand body+actions. */ +.task-log-row { + --cat-hue: 220; + border: 1px solid var(--border); + border-radius: 8px; + padding: 4px 10px; + margin-bottom: 3px; + background: color-mix(in srgb, var(--fg) 2%, transparent); + position: relative; + cursor: pointer; + transition: padding .12s ease; +} +.task-log-row:hover { + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.task-log-row.task-log-row-error { + border-color: color-mix(in srgb, var(--red, #f87171) 35%, var(--border)); + background: color-mix(in srgb, var(--red, #f87171) 4%, transparent); +} +.task-log-row.task-log-row-error:hover { + background: color-mix(in srgb, var(--red, #f87171) 7%, transparent); +} +.task-log-row.expanded { padding: 8px 10px 6px; } +.task-log-row-head { + display: flex; + align-items: center; + gap: 8px; + font-size: 11px; + margin-bottom: 0; +} +.task-log-row.expanded .task-log-row-head { margin-bottom: 4px; } +/* Collapsed: body + footer hidden. Expanded: visible. */ +.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-body, +.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-actions, +.task-log-row:not(.expanded):not(.is-skipped) .task-log-prompt { + display: none; +} +.task-log-name { + font-weight: 600; + /* Pull saturation from the per-row hue but mix with the foreground so the + title still reads in dark mode. Lightness stays adaptive. */ + color: hsl(var(--cat-hue) 60% 60%); +} +.task-log-failed-tag { + color: var(--red, #f87171); + font-size: 10px; + font-weight: 700; + line-height: 1; + white-space: nowrap; + margin-left: -2px; +} +.task-log-task-icon { + display: inline-flex; + align-items: center; + flex: 0 0 auto; + margin-right: -3px; +} +.task-log-row.task-log-row-error .task-log-name, +.task-log-row.task-log-row-error .task-log-task-icon { + color: var(--red, #f87171); +} +.task-log-task-icon svg { + top: 0 !important; + opacity: 0.46 !important; +} +.task-log-row-head .task-ai-mark { + top: 0; + margin-left: -4px; +} +.task-log-repeat { + font-size: 10px; + font-weight: 500; + line-height: 1; + color: color-mix(in srgb, var(--fg) 62%, transparent); + border-radius: 999px; + padding: 1px 0; + white-space: nowrap; +} +@media (prefers-color-scheme: light) { + .task-log-name { color: hsl(var(--cat-hue) 65% 38%); } +} +/* Per-account prefix in fan-out results — e.g. "[Default] No recent emails" + becomes a compact accent chip + plain message. Makes multi-account activity + rows readable instead of a bracket soup. */ +/* Running / queued status line in the Activity tab — whirlpool + label. */ +.task-log-running { + display: inline-flex; + align-items: center; + gap: 0; + font-size: 11px; + opacity: 0.75; +} +.task-log-running-label { font-style: normal; } + +/* New right-side placement: "Running " sits where the timestamp + normally would, on the head row's right edge. */ +.task-log-running-inline { + display: inline-flex; + align-items: center; + gap: 0; + font-size: 11px; + opacity: 0.75; +} +.task-log-force-run { + appearance: none; + box-sizing: border-box; + border: 0; + background: color-mix(in srgb, var(--fg) 7%, transparent); + box-shadow: none; + color: inherit; + opacity: .82; + margin-left: 7px; + padding: 1px 6px 1px 4px; + height: 18px; + min-height: 16px; + max-height: 18px; + border-radius: 999px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 3px; + font-family: inherit; + font-size: 10px; + line-height: 1; + cursor: pointer; + position: relative; + top: -3px; +} +.task-log-force-run svg, +.task-log-stop svg { + display: block; + flex-shrink: 0; + transform: translateY(1px); +} +.task-log-force-run:hover { + opacity: 1; + background: color-mix(in srgb, var(--green, #50fa7b) 16%, transparent); + color: var(--green, #50fa7b); +} +.task-log-stop { + border: 0; + background: transparent; + color: inherit; + opacity: .72; + padding: 0; + margin-left: 6px; + width: 12px; + height: 12px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + position: relative; + top: -3px; +} +.task-log-stop:hover { + opacity: 1; + color: var(--red, #f87171); +} + +/* Slim single-line row for skipped (noop) runs — body/actions stripped, font + shrunk, opacity dropped. Distinguishes "task ran but had nothing to do" + from a "real" entry without flooding the feed visually. */ +.task-log-row.is-skipped { + padding: 4px 8px; + opacity: 0.45; + font-size: 11px; + background: transparent; +} +.task-log-row.is-skipped .task-log-row-head { padding: 0; } +.task-log-row.is-skipped .task-log-name { font-weight: 500; } +.task-log-row.is-skipped .task-log-skipped-reason { + flex: 1 1 auto; + margin-left: 6px; + font-style: italic; + opacity: 0.85; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.task-log-row.is-skipped:hover { opacity: 0.7; } + +.task-log-account-tag { + display: inline-block; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.02em; + padding: 1px 7px; + margin-right: 4px; + border-radius: 10px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); + vertical-align: 1px; +} +.task-log-status { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; + background: color-mix(in srgb, var(--fg) 30%, transparent); +} +.task-log-status-ok { background: #4ade80; box-shadow: 0 0 6px #4ade80, 0 0 3px #4ade80; } +.task-log-status-error { background: var(--red, #f87171); box-shadow: 0 0 6px var(--red, #f87171), 0 0 3px var(--red, #f87171); } +.task-log-status-info { background: color-mix(in srgb, var(--fg) 25%, transparent); } +.task-log-status-queued { background: #fbbf24; box-shadow: 0 0 0 2px color-mix(in srgb, #fbbf24 30%, transparent); } +.task-log-status-running { background: #60a5fa; animation: task-log-pulse 1.4s ease-in-out infinite; } +.task-log-status-skipped { background: color-mix(in srgb, var(--fg) 20%, transparent); } +.task-log-status-aborted { background: color-mix(in srgb, var(--fg) 30%, transparent); border: 1px dashed color-mix(in srgb, var(--fg) 50%, transparent); } +.task-log-row-body { + font-size: 12px; + line-height: 1.5; + color: color-mix(in srgb, var(--fg) 85%, transparent); + overflow-wrap: anywhere; + word-break: break-word; +} +.task-log-row-body p { margin: 0 0 6px 0; } +.task-log-row-body p:last-child { margin-bottom: 0; } +.task-log-row-body pre { + margin: 4px 0; + padding: 6px 8px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + border-radius: 4px; + white-space: pre-wrap; + word-break: break-word; + font-size: 11px; + max-height: 180px; + overflow: auto; +} +.task-log-row-body code { + font-size: 11px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + padding: 1px 4px; + border-radius: 3px; +} +.task-log-row-body ul, .task-log-row-body ol { + margin: 4px 0; + padding-left: 20px; +} +/* Markdown headings inside a task result — without these, ## headings + render at browser-default huge sizes and blow out the cramped row. */ +.task-log-row-body h1, +.task-log-row-body h2, +.task-log-row-body h3, +.task-log-row-body h4 { + margin: 10px 0 4px; + line-height: 1.3; + font-weight: 650; + color: var(--fg); +} +.task-log-row-body h1 { font-size: 14px; } +.task-log-row-body h2 { font-size: 13px; } +.task-log-row-body h3, +.task-log-row-body h4 { font-size: 12px; opacity: 0.9; } +.task-log-row-body h1:first-child, +.task-log-row-body h2:first-child, +.task-log-row-body h3:first-child { margin-top: 0; } +.task-log-row-body blockquote { + margin: 4px 0; + padding: 2px 0 2px 10px; + border-left: 2px solid color-mix(in srgb, var(--fg) 20%, transparent); + opacity: 0.85; +} +.task-log-row-body a { color: var(--accent, #60a5fa); text-decoration: none; } +.task-log-row-body a:hover { text-decoration: underline; } +.task-log-row-body strong { font-weight: 650; color: var(--fg); } +/* Email-summary tables — render compactly instead of default browser ugliness. */ +.task-log-row-body table { + border-collapse: collapse; + width: 100%; + margin: 6px 0; + font-size: 11px; +} +.task-log-row-body th, +.task-log-row-body td { + border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); + padding: 3px 6px; + text-align: left; + vertical-align: top; +} +.task-log-row-body th { + background: color-mix(in srgb, var(--fg) 5%, transparent); + font-weight: 600; +} +.task-log-row-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-top: 4px; +} +.task-log-open-chat, +.task-log-open-report, +.task-log-copy, +.task-log-clear-cache, +.task-log-run-again, +.task-log-force-run-action { + display: inline-flex; + align-items: center; + gap: 3px; + background: none; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + border-radius: 5px; + color: color-mix(in srgb, var(--fg) 60%, transparent); + font-family: inherit; + font-size: 10px; + padding: 1px 6px; + cursor: pointer; + transition: background .15s, color .15s, border-color .15s; + line-height: 1.4; +} +.task-log-open-chat:hover, +.task-log-open-report:hover, +.task-log-copy:hover, +.task-log-clear-cache:hover, +.task-log-run-again:hover, +.task-log-force-run-action:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--fg) 30%, transparent); + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.task-log-row-actions > .task-log-open-chat, +.task-log-row-actions > .task-log-copy { + margin-left: auto; +} +.task-log-clear-cache svg { + position: relative; + top: 0; +} +/* Activity filter chips — toggle-out model: ON by default (solid), + click to toggle OFF (dimmed + strikethrough) to hide that group. */ +.tasks-af-chip { + font-size: 11px; + padding: 3px 10px; + border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); + border-radius: 12px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + color: var(--fg); + cursor: pointer; + font-family: inherit; + transition: opacity .12s, background .12s, border-color .12s; +} +.tasks-af-chip:hover { border-color: color-mix(in srgb, var(--fg) 32%, transparent); } +.tasks-af-chip.off { + opacity: 0.4; + text-decoration: line-through; + background: transparent; +} +.tasks-af-chip.active { + border-color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + opacity: 1; +} +.tasks-af-chip-error { border-color: color-mix(in srgb, var(--red, #f87171) 45%, transparent); } +.tasks-af-chip-error:not(.off) { color: var(--red, #f87171); } + +/* "Open in Deep Research" is now a regular clickable chat link (a + `#research-` markdown anchor in the assistant's message), rendered as + an a.chat-link. Nudge it 4px right so it sits slightly inset. */ +a.chat-link[href^="#research-"] { + margin-left: 4px; +} +.task-log-row.is-long .task-log-row-body { + max-height: 8.5em; + overflow: hidden; + position: relative; + mask-image: linear-gradient(to bottom, #000 70%, transparent); + -webkit-mask-image: linear-gradient(to bottom, #000 70%, transparent); +} +.task-log-row.is-long.expanded .task-log-row-body { + max-height: none; + overflow: visible; + mask-image: none; + -webkit-mask-image: none; +} +.task-log-row-toggle { + margin-top: 6px; + background: none; + border: none; + font-family: inherit; + font-size: 11px; + cursor: pointer; + padding: 2px 0; + color: transparent; /* hide raw "Show more" text */ +} +.task-log-row:not(.expanded) .task-log-row-toggle::before { content: 'Show more'; } +.task-log-row.expanded .task-log-row-toggle::before { content: 'Show less'; } +.task-log-prompt { + margin-top: 6px; + font-size: 11px; +} +.task-log-prompt summary { + cursor: pointer; + opacity: 0.5; + user-select: none; +} +.task-log-prompt summary:hover { opacity: 0.8; } +.task-log-prompt pre { + margin: 4px 0 0; + padding: 6px 8px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border-radius: 4px; + white-space: pre-wrap; + word-break: break-word; + font-size: 11px; + opacity: 0.75; + max-height: 200px; + overflow: auto; +} + +/* Tasks is the shared theme reference for procedural cards. Keep the tint + together so Skills cannot drift when theme colors change. Hover and + selection remain owned by the shared card state rules above. */ +.task-card, +.skill-card { + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +.task-card { + border: 1px solid var(--border); + border-radius: 8px; + padding: 7px 8px; + margin-bottom: 0; + min-height: 46px; + box-sizing: border-box; + align-items: center; +} +/* When expanded, height grows to fit the detail panel. */ +.task-card.expanded { align-items: flex-start; } +.task-card .memory-item-title { + position: relative; + top: -2px; +} +/* Always show the per-card ⋮ kebab on task cards (the default opacity:0 + + hover-reveal is unreliable on touch and easy to miss on desktop). + pointer-events / z-index belt-and-braces against any sibling overlay, + margin reset undoes the global `.modal-body button { margin-top:6px }` + that was punting the hit-target down. */ +.task-card .memory-item-actions { + opacity: 0.55; + pointer-events: auto; + position: relative; + z-index: 5; +} +.task-card:hover .memory-item-actions, +.task-card .memory-item-actions:hover { opacity: 1; } +.task-card .memory-item-actions .memory-item-btn { + margin: 0 !important; + pointer-events: auto; + position: relative; + z-index: 5; + width: 28px; + min-width: 28px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; +} +/* Make the SVG inside the kebab transparent to pointer events so the click + always lands on the BUTTON itself (some browsers / nested SVG events lose + the click when hitting the inner glyph). */ +.task-card .memory-item-actions .memory-item-btn svg { pointer-events: none; } +.task-ai-mark { + flex: 0 0 auto; + color: var(--accent, var(--red)); + opacity: 0.78; +} +/* Per-card select checkbox rides up to the title line. The "All" checkbox is + #tasks-select-all (not .memory-select-cb), so it stays put. */ +.task-card .memory-select-cb { position: relative; } +.task-card.task-card-deleting { + opacity: 0.68; + pointer-events: none; + position: relative; +} +.task-card-delete-busy { + position: absolute; + right: 11px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + gap: 5px; + height: 22px; + padding: 0 8px; + border-radius: 999px; + color: var(--red); + background: color-mix(in srgb, var(--red) 12%, var(--panel, #111)); + border: 1px solid color-mix(in srgb, var(--red) 24%, transparent); + font-size: 10px; + font-weight: 600; + line-height: 1; + z-index: 7; +} +.task-card-delete-busy-spin { + width: 10px; + height: 10px; + border-radius: 999px; + border: 1.5px solid color-mix(in srgb, currentColor 28%, transparent); + border-top-color: currentColor; + animation: spin 0.8s linear infinite; +} +#tasks-pause-all-btn { position: relative; top: -2px; } +#tasks-pause-all-btn:has(input:disabled) { opacity: 0.4; } +/* Bigger ⋮ dropdown on mobile (its buttons carry inline styles → !important). */ +@media (max-width: 768px) { + .task-card .task-status-badge { + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; + } + .task-card .task-state-badge svg, + .task-card .task-card-run-btn svg { + width: 10px; + height: 10px; + } +} + +.task-card-header { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.task-card-name { + font-size: 12px; + font-weight: 600; +} + +.task-card-schedule { + font-size: 10.5px; + opacity: 0.5; + margin-left: auto; +} + +.task-card-output { + font-size: 10px; + opacity: 0.45; +} + +.task-card-prompt { + font-size: 11px; + opacity: 0.55; + margin: 4px 0; + line-height: 1.4; +} + +.task-card-meta { + font-size: 10.5px; + opacity: 0.45; + margin-bottom: 6px; +} + +.task-card-actions { + display: flex; + gap: 4px; + flex-wrap: wrap; +} + +.task-btn { + font-family: inherit; + font-size: 11px; + padding: 4px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: none; + color: var(--fg); + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s, background 0.15s; +} +.task-btn:hover { opacity: 1; } +.task-btn:disabled { opacity: 0.3; cursor: not-allowed; } + +.task-btn-primary { + background: var(--red); + color: #fff; + border-color: transparent; + opacity: 1; +} +.task-btn-primary:hover { opacity: 0.85; } + +.task-btn-danger { color: var(--red); border-color: color-mix(in srgb, var(--red) 30%, transparent); } +.task-btn-danger:hover { opacity: 1; border-color: var(--red); } + +.tasks-clock { + font-size: 10px; + opacity: 0.35; + text-align: center; + padding: 6px 0 8px; +} + +.task-preset-grid { + display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; +} +.task-preset-card { + display: flex; flex-direction: column; align-items: flex-start; gap: 2px; + padding: 12px; border: 1px solid var(--border); border-radius: 8px; + background: var(--bg); cursor: pointer; transition: all 0.15s; text-align: left; + color: var(--fg); +} +.task-preset-card:hover { + border-color: var(--accent, #cc6a3a); background: color-mix(in srgb, var(--accent, #cc6a3a) 8%, var(--bg)); +} +.task-preset-icon { font-size: 20px; margin-bottom: 2px; } +.task-preset-label { font-size: 12px; font-weight: 600; } +.task-preset-desc { font-size: 10px; opacity: 0.5; line-height: 1.3; } + +.task-form { display: flex; flex-direction: column; gap: 4px; } +.task-form-label { + font-size: 11px; + opacity: 0.55; + font-weight: 500; + display: block; + /* Consistent rhythm: 8px above each label (section gap), 4px below to the + field — same spacing for Name/Prompt/Trigger/Output/Model/Chain. */ + margin: 8px 0 4px; +} +.task-form-input { + font-family: inherit; + font-size: 11px; + padding: 6px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + transition: border-color 0.15s; + box-sizing: border-box; + width: 100%; + max-width: 100%; + min-width: 0; +} +.task-form-input:focus { + outline: none; + border-color: var(--red); +} +.task-form-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } + +.task-form-notif-toggle { + display: flex; + align-items: center; + gap: 8px; + margin: 9px 0 3px; + cursor: pointer; + color: var(--fg); +} +.task-form-notif-toggle input { + position: absolute; + opacity: 0; + pointer-events: none; +} +.task-form-notif-switch { + width: 30px; + height: 16px; + border-radius: 999px; + border: 1px solid var(--border); + background: color-mix(in srgb, var(--fg) 8%, transparent); + position: relative; + flex: 0 0 auto; + transition: background 0.15s, border-color 0.15s; +} +.task-form-notif-toggle input:checked + .task-form-notif-switch { + border-color: color-mix(in srgb, var(--accent, var(--red)) 58%, transparent); + background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); +} +.task-form-notif-copy { + display: flex; + flex-direction: column; + gap: 1px; + font-size: 11px; + font-weight: 500; +} +.task-form-notif-copy span:last-child { + font-size: 10px; + font-weight: 400; + opacity: 0.52; +} + +.task-form-toggle { + display: flex; gap: 4px; +} +.task-toggle-btn { + flex: 1; padding: 4px 8px; font-size: 10px; font-family: inherit; + border: 1px solid var(--border); border-radius: 6px; background: transparent; + color: var(--fg); opacity: 0.4; cursor: pointer; transition: all 0.15s; +} +.task-toggle-btn:hover { opacity: 0.7; border-color: var(--fg); } +.task-toggle-btn.active { + opacity: 1; border-color: var(--red); color: var(--red); + background: color-mix(in srgb, var(--red) 10%, transparent); +} + +.task-time-picker, .task-date-picker { + display: flex; + align-items: center; + gap: 4px; +} +.task-time-select, .task-date-select { + width: auto; + min-width: 48px; + font-size: 11px; +} +.task-time-sep { opacity: 0.4; font-size: 11px; } + +.task-history-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.task-runs-list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; } +.task-run-item { + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 10px; +} +.task-run-item-header { + display: flex; + align-items: center; + gap: 4px; + font-size: 11px; +} +.task-run-time { margin-left: auto; font-size: 10px; opacity: 0.45; } +.task-run-result { + font-size: 11px; + opacity: 0.6; + margin-top: 4px; + line-height: 1.4; + white-space: pre-wrap; + word-break: break-word; +} + +/* ── Settings Form Utilities ── */ + +.settings-row { + display: flex; + align-items: center; + gap: 8px; +} +.settings-col { + display: flex; + flex-direction: column; + gap: 6px; +} +.settings-label { + font-size: 12px; + min-width: 70px; +} +.settings-select { + flex: 1; + /* Allow the select to shrink below its longest option's width inside a flex + row — without this a long model name (e.g. the Vision picker) overflowed + the card off-screen on mobile. */ + min-width: 0; + max-width: 100%; + padding: 5px 8px; + font-family: inherit; + font-size: 12px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + transition: border-color 0.15s; + appearance: none; + -webkit-appearance: none; + -moz-appearance: textfield; + outline: none; +} +input.settings-select::-webkit-outer-spin-button, +input.settings-select::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +input.settings-select::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } +.settings-select:focus { + outline: none; + border-color: var(--red); +} + +/* Deep Research uses long labels and previously fixed-width fields. Keep + every control constrained by the card at intermediate desktop widths too. */ +.admin-card:has(#set-researchSearch) .settings-row { min-width: 0; } +.admin-card:has(#set-researchSearch) .settings-row > .settings-label { flex: 0 0 112px; min-width: 0; } +#set-researchSearch, +#set-researchMaxTokens, +#set-researchRunTimeout, +.settings-row div:has(> #set-researchExtractTimeout), +.settings-row div:has(> #set-researchExtractConcurrency) { + width: auto !important; + max-width: 100% !important; + min-width: 0 !important; + flex: 1 1 0 !important; +} + +@media (max-width: 768px) { + /* Deep Research settings contain desktop-sized inline widths. On mobile, + let each control claim the card width instead of escaping the admin block. */ + .settings-row:has(#set-researchSearch), + .settings-row:has(#set-researchMaxTokens), + .settings-row:has(#set-researchExtractTimeout), + .settings-row:has(#set-researchExtractConcurrency), + .settings-row:has(#set-researchRunTimeout) { + flex-wrap: wrap; + align-items: stretch; + } + .settings-row:has(#set-researchSearch) .settings-label, + .settings-row:has(#set-researchMaxTokens) .settings-label, + .settings-row:has(#set-researchExtractTimeout) .settings-label, + .settings-row:has(#set-researchExtractConcurrency) .settings-label, + .settings-row:has(#set-researchRunTimeout) .settings-label { + flex: 1 1 auto; + } + #set-researchSearch, + #set-researchMaxTokens, + #set-researchRunTimeout, + .settings-row div:has(> #set-researchExtractTimeout), + .settings-row div:has(> #set-researchExtractConcurrency) { + width: 100% !important; + max-width: 100% !important; + flex: 1 1 100% !important; + margin-left: 0 !important; + } +} + +/* Default-chat fallback chain editor. Each row mirrors the primary + endpoint/model selectors, indented under them to read as a chain. */ +.settings-fallbacks { + display: flex; + flex-direction: column; + gap: 6px; +} +.settings-fallbacks:not(:empty) { margin-top: 2px; } +.settings-fallback-row { + display: flex; + align-items: center; + gap: 6px; + padding-left: 12px; + border-left: 2px solid color-mix(in srgb, var(--fg) 12%, transparent); + max-width: 100%; + box-sizing: border-box; +} +.settings-fallback-num { + font-size: 11px; + opacity: 0.4; + min-width: 14px; + text-align: right; +} +.settings-fallback-row .settings-select { + width: 0; + min-width: 0; + min-inline-size: 0; + box-sizing: border-box; + overflow: hidden; + text-overflow: ellipsis; +} +.settings-fallback-row .settings-select:first-of-type { + flex: 0 1 128px; + width: 128px; + max-width: 34%; +} +.settings-fallback-row .settings-select:nth-of-type(2) { + flex: 1 1 0; + width: 0; + max-width: 100%; +} +details.hwfit-serve-advanced > .hwfit-serve-row, +details.hwfit-serve-advanced > .hwfit-serve-checks, +details.hwfit-serve-advanced > .hwfit-serve-cmd-wrap, +details.hwfit-serve-advanced > .hwfit-serve-extra { + margin-top: 0; + margin-bottom: 0; +} +/* Inside Advanced, tighten the per-row grid gap and the label → + select gap so rows of single-line dropdowns don't visually sit + on top of half a row of empty space. Outside Advanced (Core, + etc.) the original ~6px breathing room stays. */ +details.hwfit-serve-advanced > .hwfit-serve-row { + gap: 4px; + row-gap: 4px; +} +details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp, +details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-llamacpp { + margin-top: -20px; + position: relative; + left: 4px; +} +details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp { + column-gap: 8px; + row-gap: 2px; +} +details.hwfit-serve-advanced > .hwfit-llama-placement-row { + margin-top: -6px !important; +} +details.hwfit-serve-advanced > .hwfit-llama-memory-row { + margin-top: -20px !important; +} +.cookbook-llama-cpu-mode details.hwfit-serve-advanced > .hwfit-llama-memory-row { + margin-top: -8px !important; +} +details.hwfit-serve-advanced > .hwfit-llama-batch-row, +details.hwfit-serve-advanced > .hwfit-llama-monitor-row, +details.hwfit-serve-advanced > .hwfit-llama-checks-row { + margin-top: -18px !important; +} +details.hwfit-serve-advanced > .hwfit-diff-settings-row { + row-gap: 2px; +} +details.hwfit-serve-advanced > .hwfit-diff-checks-row, +details.hwfit-serve-advanced > .hwfit-diff-harmonize-row { + margin-top: -10px !important; +} +details.hwfit-serve-advanced > .hwfit-diff-checks-row { + row-gap: 2px; +} +details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-vram-monitor { + gap: 4px !important; + grid-template-columns: max-content minmax(0, 1fr); + white-space: nowrap; +} +details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_block_size"] { + width: calc(100% - 6px); +} +details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_kv_cache_dtype"] { + width: 60px; + min-width: 60px; +} +details.hwfit-serve-advanced > .hwfit-serve-checks { + gap: 4px; + row-gap: 4px; +} +/* Pull the vLLM/SGLang checks row, Extra args, and the trailing + model-specific (Speculative) checks row up tight against the row + above — the previous 4px gap plus per-row baseline padding left a + ~8px gap that read as too airy in the Advanced fold. */ +details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-vllm, +details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-sglang, +details.hwfit-serve-advanced > .hwfit-serve-extra { + margin-top: -8px; +} +details.hwfit-serve-advanced > .hwfit-serve-extra { + margin-top: -18px; +} +details.hwfit-serve-advanced > .hwfit-serve-row:last-of-type, +details.hwfit-serve-advanced > .hwfit-serve-checks:last-of-type { + margin-bottom: 0; +} + +.settings-fallback-remove { + flex-shrink: 0; + width: 32px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 55%, transparent); + cursor: pointer; + transition: border-color 0.12s, color 0.12s, background 0.12s; +} +.settings-fallback-remove:hover { + border-color: var(--red); + color: var(--red); + background: color-mix(in srgb, var(--red) 10%, transparent); +} +.settings-fallback-add { + align-self: flex-start; + margin-top: 2px; + font-size: 11px; + padding: 3px 9px; + border: 1px dashed var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 65%, transparent); + cursor: pointer; + transition: border-color 0.12s, color 0.12s; +} +.settings-fallback-add:hover { + border-color: var(--red); + color: var(--red); +} +.settings-input { + flex: 1; + padding: 5px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font-family: inherit; + font-size: 12px; + transition: border-color 0.15s; +} +.settings-input:focus { + outline: none; + border-color: var(--red); +} +/* ── Contacts manager (Settings → Integrations → CardDAV) ── */ +.contacts-add-row { + display: flex; + gap: 6px; + margin-bottom: 8px; + align-items: center; +} +.contacts-list { + display: flex; + flex-direction: column; + gap: 2px; + max-height: 360px; + overflow-y: auto; +} +.contact-row { + padding: 6px 4px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 35%, transparent); +} +.contact-row:last-child { border-bottom: none; } +.contact-row-edit { margin-top: 4px; } + +/* ── Sort Dropdown ── */ + +.sort-dropdown { + position: absolute; + right: 0; + top: 100%; + z-index: 1000; + min-width: 120px !important; + width: max-content; + padding: 4px !important; + margin-top: 4px; + background: var(--panel) !important; + border: 1px solid var(--border) !important; + border-radius: 8px !important; + box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; + backdrop-filter: none !important; +} +.sort-dropdown-item { + cursor: pointer; + padding: 6px 8px 6px 6px !important; + font-size: 11px !important; + border-radius: 6px !important; + border-bottom: none !important; + white-space: nowrap; + transition: background 0.1s; +} +.sort-dropdown-item:hover { + background: color-mix(in srgb, var(--accent) 10%, transparent) !important; +} +.sort-dropdown-item.ai-sort-option { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); +} +.sort-dropdown-item.ai-sort-option .auto-sort-icon { + color: var(--accent, var(--red)); + display: inline-flex; + align-items: center; +} +#auto-sort-sessions-btn { + transform: translateX(-4px); +} +.sort-dropdown-item.ai-sort-option:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent) !important; +} +.sort-dropdown-sep { + border-top: 1px solid var(--border); + margin-top: 2px; + padding-top: 6px; +} + +/* Mobile: bigger taps for the chats sort/funnel menu and the select-mode + bulk-action bar. Also drop the "Rearrange" item — it's a touch-finicky + feature better left to desktop. */ +@media (max-width: 768px) { + /* Nudge the funnel/sort button right on mobile so it doesn't hug the + chevron next to it. */ + #session-sort-btn { transform: translateX(13px); } + #session-sort-dropdown.sort-dropdown { + min-width: 200px !important; + padding: 6px !important; + } + #session-sort-dropdown .sort-dropdown-item { + padding: 12px 14px !important; + font-size: 14px !important; + } + #session-rearrange-toggle { display: none !important; } + + /* Select-mode bar — make Archive / Delete / Cancel buttons + the + Select-all toggle finger-sized. */ + .session-bulk-bar { + padding: 8px 10px; + font-size: 14px; + gap: 10px; + } + .session-bulk-btn { + padding: 10px; + min-width: 44px; + min-height: 44px; + } + .session-bulk-btn svg { width: 20px; height: 20px; } + #session-select-all-dot { font-size: 22px !important; padding: 6px; } + #session-select-all-label { font-size: 14px !important; padding: 4px; } +} + + +/* ── Admin: Built-in tools ── */ +.admin-tool-category { + border: 1px solid var(--border); + border-radius: 6px; + margin-bottom: 6px; + overflow: hidden; + transition: border-color 0.15s; +} +.admin-tool-category:hover { + border-color: color-mix(in srgb, var(--fg) 20%, var(--border)); +} +.admin-tool-cat-header { + font-size: 12px; + font-weight: 500; + padding: 8px 10px; + transition: background 0.15s; +} +.admin-tool-cat-header:hover { + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.admin-tool-cat-body { + border-top: 1px solid var(--border); + padding: 4px 0; +} +.admin-tool-cat-body.hidden { + display: none; +} +.admin-tool-row { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 10px; + padding-right: 28px; /* align toggles with header toggle (chevron 12px + gap 6px + padding) */ + transition: background 0.08s; +} +.admin-tool-row:hover { + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.admin-tool-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 1px; +} +.admin-tool-name { + font-size: 11px; + font-weight: 500; +} +.admin-tool-desc { + font-size: 10px; + opacity: 0.4; +} +.admin-tool-ctx { + font-size: 9px; + opacity: 0.3; + flex-shrink: 0; + min-width: 35px; + text-align: right; + font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; +} + +/* Cookbook serve param hints */ +.hwfit-hint { + display: inline-flex; + align-items: center; + justify-content: center; + width: 12px; + height: 12px; + border-radius: 50%; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + font-size: 8px; + font-weight: 600; + opacity: 0.35; + cursor: help; + margin-left: 2px; + vertical-align: middle; + transition: opacity 0.1s; +} +.hwfit-hint:hover { + opacity: 0.8; + background: color-mix(in srgb, var(--accent) 15%, transparent); +} +.hwfit-dl-dot { + color: var(--green); + font-size: 8px; + margin-left: 4px; + opacity: 0.7; +} +.hwfit-parser-tag { + font-size: 9px; + opacity: 0.4; + background: color-mix(in srgb, var(--fg) 8%, transparent); + padding: 1px 5px; + border-radius: 3px; + margin-left: 4px; + font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; +} + +/* Cookbook output copy — show only on hover */ +.cookbook-output-wrap .cookbook-output-copy { + opacity: 0; + transition: opacity 0.1s; +} + +/* Cached model menu button */ +.hwfit-cached-menu-btn { + background: none; + border: none; + color: var(--fg); + font-size: 16px; + cursor: pointer; + opacity: 0; + padding: 2px 4px; + border-radius: 4px; + transition: opacity 0.1s; + flex-shrink: 0; + position: relative; + top: -2px; +} +.memory-item:hover .hwfit-cached-menu-btn, +.hwfit-cached-item:hover .hwfit-cached-menu-btn { opacity: 0.4; } +.hwfit-cached-menu-btn:hover { opacity: 1 !important; } +.hwfit-cached-menu-btn svg { pointer-events: none; } +@media (max-width: 768px) { + #cookbook-modal .hwfit-cached-menu-btn { + opacity: 0.72 !important; + width: 32px; + height: 32px; + min-width: 32px; + padding: 0; + justify-content: center; + top: -4px; + } + #cookbook-modal .hwfit-cached-menu-btn:active { + opacity: 1 !important; + background: color-mix(in srgb, var(--fg) 9%, transparent); + } +} + +/* Quick run button */ +.cookbook-run-btn { + background: var(--accent, var(--red)) !important; + color: var(--panel) !important; + border-color: var(--accent, var(--red)) !important; + font-weight: 600; +} +.cookbook-run-btn:hover { + opacity: 0.9; +} +#hwfit-cache-scan { + position: relative; + top: 1px; + width: 26px; + height: 24px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +#hwfit-cache-scan.spinning svg { + animation: modelPickerRefreshSpin 0.75s linear infinite; +} +#serve-search { + height: 28px; + min-height: 28px; + font-family: inherit; + font-size: 11px; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: 9px center; + padding-left: 28px; +} +.cookbook-sort-select-wrap { + position: relative; + display: inline-flex; + flex-shrink: 0; + align-items: center; +} +.cookbook-sort-select-wrap > svg { + position: absolute; + left: 9px; + z-index: 1; + pointer-events: none; + opacity: 0.65; + transform: translateY(4px); +} + +/* Cookbook model directory tags */ +.cookbook-modeldir-tag { + font-size: 11px; + padding: 3px 8px; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + display: inline-flex; + align-items: center; + gap: 4px; + font-family: 'Berkeley Mono', 'SF Mono', monospace; +} +.cookbook-modeldir-default { + opacity: 0.5; +} +.cookbook-server-bottom-actions { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 4px; + margin-top: 5px; +} +.cookbook-server-bottom-actions > button { + height: 22px; + box-sizing: border-box; + display: inline-flex; + align-items: center; +} +/* Default-server radio-check in a Settings server title (same look as the + model-dir download target). Active = accent-tinted. */ +.cookbook-srv-default { + cursor: pointer; + opacity: 0.35; + display: inline-flex; + align-items: center; + gap: 3px; + line-height: 1; + transition: opacity 0.12s, color 0.12s; +} +.cookbook-srv-default:hover { opacity: 0.8; } +.cookbook-srv-default.active { opacity: 1; color: var(--accent, var(--red)); } +.cookbook-srv-default-label { font-size: 10px; font-weight: 600; letter-spacing: 0.02em; } +.cookbook-srv-default-icon { + display: inline-flex; + align-items: center; + line-height: 0; +} +.cookbook-srv-default-icon svg { + width: 13px; + height: 13px; +} +.cookbook-srv-default-icon, +.cookbook-srv-default-label { + position: relative; + top: 2px; +} +.email-settings-default, +.email-settings-default .cookbook-srv-default-icon, +.email-settings-default .cookbook-srv-default-label { + position: relative; + top: -2px; +} +/* Download-target toggle inside a model-dir tag */ +.cookbook-modeldir-dl { + cursor: pointer; + opacity: 0.35; + display: inline-flex; + align-items: center; + line-height: 0; + transition: opacity 0.12s, color 0.12s; +} +.cookbook-modeldir-dl:hover { opacity: 0.8; } +.cookbook-modeldir-dl.active { opacity: 1; color: var(--accent, var(--red)); } +/* The tag currently flagged as the download target — clearly highlighted so + it's obvious where downloads land. */ +.cookbook-modeldir-target { + opacity: 1; + color: var(--accent, var(--red)); + font-weight: 600; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); +} +.cookbook-modeldir-rm { + cursor: pointer; + opacity: 0.4; + font-size: 10px; +} +.cookbook-modeldir-rm:hover { + opacity: 1; +} +.cookbook-modeldir-add { + background: none; + border: 1px solid var(--border); + border-radius: 4px; + color: var(--fg); + font-size: 10px; + font-weight: 500; + height: 19.5px; + box-sizing: border-box; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 2px; + position: relative; + top: -3px; + padding: 0 9px; + transition: border-color 0.12s, background 0.12s; +} +.cookbook-modeldir-add:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); } + +/* Cookbook serve optimizations */ +.hwfit-serve-opts { + display: flex; + align-items: center; + gap: 8px; + margin: 4px 0; +} +.hwfit-apply-opts { + font-size: 11px !important; + min-width: auto !important; + white-space: nowrap; +} +.hwfit-opts-desc { + font-size: 9px; + opacity: 0.4; + font-style: italic; +} + +/* Library modal tabs */ +.lib-tabs, +.admin-tabs { + display: flex; + gap: 0; + margin-bottom: 8px; + border-bottom: 1px solid var(--border); +} +.lib-tab, +.admin-tab { + background: none; + border: none; + border-bottom: 2px solid transparent; + color: var(--fg-muted); + font-size: 12px; + font-family: inherit; + padding: 6px 14px; + cursor: pointer; + transition: color 0.1s, border-color 0.1s; +} +.lib-tab:hover, +.admin-tab:hover { color: var(--fg); } +.lib-tab.active, +.admin-tab.active { + color: var(--accent, var(--red)); + border-bottom-color: var(--accent, var(--red)); +} + +/* Cookbook tab count badge */ +.cookbook-tab-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 16px; + height: 16px; + padding: 0 4px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); + color: var(--accent, var(--red)); + font-size: 9px; + font-weight: 700; + margin-left: 4px; + line-height: 1; +} + +/* ── Generate Visual Report button (deep research) ── */ +.view-report-btn { + display: inline-flex; + align-items: center; + gap: 6px; + margin-top: 12px; + padding: 8px 16px; + border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--red) 10%, transparent); + color: var(--fg); + font-family: inherit; + font-size: 0.88em; + font-weight: 500; + transition: background 0.15s ease; + cursor: pointer; +} +.view-report-btn:hover:not(:disabled) { + background: color-mix(in srgb, var(--red) 18%, transparent); +} +.view-report-btn svg { + flex-shrink: 0; +} +.report-btn-wrap { + margin-top: 12px; + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.view-report-btn.chat-about-btn { + border-color: color-mix(in srgb, var(--fg) 22%, transparent); + background: color-mix(in srgb, var(--fg) 6%, transparent); +} +.view-report-btn.chat-about-btn:hover:not(:disabled) { + background: color-mix(in srgb, var(--fg) 12%, transparent); +} +.view-report-btn:disabled { + opacity: 0.6; + cursor: progress; +} +.report-spinner { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.88em; + color: var(--fg); + opacity: 0.7; +} +.report-spinner-text { + font-weight: 500; +} +.report-spinner-dots::after { + content: ''; + animation: report-dots 1.4s steps(4, end) infinite; +} +@keyframes report-dots { + 0% { content: ''; } + 25% { content: '.'; } + 50% { content: '..'; } + 75% { content: '...'; } + 100% { content: ''; } +} + +/* ── Continue research hint ────────────────────────── */ +.continue-research-wrap { + margin-top: 8px; +} +.continue-research-hint { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 0.78em; + color: var(--fg-dim, var(--fg)); + opacity: 0.5; + padding: 4px 0; +} +.continue-research-hint svg { + flex-shrink: 0; + opacity: 0.6; +} + +/* ── Research reconnect & timer ────────────────────── */ +.msg.research-reconnect { + border-left: 3px solid var(--red); + background: color-mix(in srgb, var(--red) 5%, transparent); +} +.research-timer { + font-size: 0.8em; + opacity: 0.6; + margin-top: 4px; + font-family: monospace; + color: var(--fg-dim, var(--fg)); +} + +/* ── Research synapse visualization ─────────────────────────────── + Live SVG graph of an in-flight deep-research run. The query is the + central node; sub-questions branch off per round; sources are leaf + nodes that pop in as they're captured. Pulses indicate activity. */ +.research-synapse { + margin: 6px 0 4px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 24%, var(--border)); + border-radius: 7px; + background: + linear-gradient(color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), + linear-gradient(90deg, color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), + radial-gradient(ellipse at 50% 48%, color-mix(in srgb, var(--accent, var(--red)) 13%, transparent) 0%, transparent 62%), + color-mix(in srgb, var(--panel) 68%, var(--bg)); + background-size: 18px 18px, 18px 18px, auto, auto; + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--fg) 5%, transparent); + overflow: hidden; +} +.research-synapse .rs-stage { + height: 400px; + position: relative; +} +.research-synapse .rs-meta-header { + position: absolute; + z-index: 4; + top: 0; + left: 0; + right: 0; + border-top: 0; + border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + padding: 5px 8px 6px; + background: color-mix(in srgb, var(--panel) 76%, transparent); +} +.research-synapse-ascii-filter > .rs-stage > svg { + display: none; +} +.research-synapse-ascii-filter .rs-ascii-overlay { + position: absolute; + z-index: 3; + inset: 28px 10px 10px; + margin: 0; + overflow: hidden; + pointer-events: none; + color: color-mix(in srgb, var(--accent, var(--red)) 76%, var(--fg)); + font: 600 10px/1.35 ui-monospace, SFMono-Regular, Consolas, monospace; + white-space: pre; + text-align: center; + opacity: 1; + text-shadow: 0 0 7px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); + mix-blend-mode: screen; +} +.research-synapse-compact .rs-meta { padding: 5px 8px 6px; font-size: 10px; } +.research-synapse-compact .rs-label-sub { font-size: 8px; } +.research-synapse .rs-stage-head { + position: absolute; + z-index: 2; + top: 7px; + left: 9px; + right: 9px; + display: flex; + align-items: center; + gap: 5px; + color: var(--fg-dim, var(--fg)); + font: 600 8px/1 ui-monospace, "JetBrains Mono", monospace; + text-transform: uppercase; + pointer-events: none; +} +.research-synapse .rs-live-dot { + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--accent, var(--red)); + box-shadow: 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 75%, transparent); + animation: rs-live 1.8s ease-in-out infinite; +} +.research-synapse .rs-phase-chip { + margin-left: auto; + padding: 3px 5px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, var(--border)); + border-radius: 3px; + background: color-mix(in srgb, var(--accent, var(--red)) 8%, var(--bg)); + color: var(--accent, var(--red)); +} +.research-synapse svg { + display: block; + width: 100%; + height: 100%; +} +.research-synapse .rs-edge { + stroke: color-mix(in srgb, var(--accent, var(--red)) 32%, var(--border)); + stroke-width: 1.35; + fill: none; + opacity: 0.72; +} +.research-synapse .rs-node { + fill: var(--bg); + stroke: var(--accent, var(--red)); + stroke-width: 1.5; + transition: all 0.3s ease; + transform-box: fill-box; + transform-origin: center; +} +.research-synapse .rs-source-index { + fill: var(--fg); + font: 650 4.5px/1 ui-monospace, "JetBrains Mono", monospace; + pointer-events: none; + opacity: 0.88; +} +.research-synapse .rs-node-expanded { animation: rs-hub-expand 0.75s ease-out; } +.research-synapse .rs-node-new { + animation: rs-pop 0.6s ease-out; +} +.research-synapse .rs-pulse { + fill: var(--accent, var(--red)); + opacity: 0; + animation: rs-pulse 2.6s ease-out infinite; + transform-box: fill-box; + transform-origin: center; +} +.research-synapse .rs-label { + fill: var(--fg); + font-size: 10px; + font-family: ui-monospace, "JetBrains Mono", monospace; + pointer-events: none; + opacity: 0.85; +} +.research-synapse .rs-label-sub { + font-size: 9px; + opacity: 0.7; +} +.research-synapse .rs-meta { + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto auto; + align-items: center; + gap: 10px; + padding: 6px 10px 8px; + font-family: ui-monospace, "JetBrains Mono", monospace; + font-size: 11px; + color: var(--fg-dim, var(--fg)); + opacity: 0.92; + border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + background: color-mix(in srgb, var(--panel) 76%, transparent); +} +.research-synapse .rs-meta .rs-status { + display: none; + color: var(--accent, var(--red)); + font-weight: 600; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.research-synapse .rs-meta b { + color: var(--fg); + font-weight: 600; +} +.research-synapse.rs-complete .rs-meta .rs-status { color: var(--color-success, #4caf50); } +.research-synapse.rs-error .rs-meta .rs-status { color: var(--red); } diff --git a/static/css/12-gallery.css b/static/css/12-gallery.css new file mode 100644 index 000000000..16f47e863 --- /dev/null +++ b/static/css/12-gallery.css @@ -0,0 +1,839 @@ +/* 12-gallery - 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. + */ +.research-synapse.rs-phase-reading .rs-phase-chip, +.research-synapse.rs-phase-analyzing .rs-phase-chip { color: var(--gold, #d4a72c); } +.research-synapse.rs-phase-writing .rs-phase-chip { color: var(--color-success, #4caf50); } + +@media (prefers-reduced-motion: reduce) { + .research-synapse .rs-live-dot, + .research-synapse .rs-pulse, + .research-synapse .rs-node-new, + .research-synapse .rs-node-expanded, + .research-synapse .rs-edge-firing { animation: none !important; } +} + +/* ── Raw findings items (inside sources-style box) ── */ +.finding-item { + display: flex; + flex-direction: column; + gap: 4px; +} +.finding-item .source-link { + border-radius: 6px 6px 0 0; +} +.finding-summary { + padding: 6px 8px 8px; + font-size: 0.82em; + line-height: 1.5; + color: var(--fg-dim, var(--fg)); + opacity: 0.85; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); + margin-bottom: 4px; + white-space: pre-wrap; + word-break: break-word; +} +.finding-item:last-child .finding-summary { + border-bottom: none; + margin-bottom: 0; +} + +/* ═══════════════════════════════════════════════════════════ + Gallery Editor + ═══════════════════════════════════════════════════════════ */ + +/* Tab bar */ +.gallery-tabs { + display: flex; + gap: 0; + border-bottom: 1px solid var(--border); + padding: 0 16px; + background: var(--panel); +} +.gallery-tab { + padding: 8px 18px; + background: none; + border: none; + border-bottom: 2px solid transparent; + color: var(--fg); + opacity: 0.6; + cursor: pointer; + font-size: 13px; + font-weight: 500; + transition: opacity 0.15s, border-color 0.15s; +} +.gallery-tab:hover { opacity: 0.85; } +.gallery-tab.active { + opacity: 1; + border-bottom-color: var(--red); +} + +/* Narrow modal tab strips should stay on one row. Resized docked windows can + be much narrower than the viewport, so this cannot live only in mobile media + queries. */ +.cookbook-tabs, +.memory-tabs, +.admin-tabs, +.lib-tabs, +.gallery-tabs, +.preset-tabs { + flex-wrap: nowrap !important; + overflow-x: auto !important; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + overscroll-behavior-x: contain; + scrollbar-width: none; +} +.cookbook-tabs::-webkit-scrollbar, +.memory-tabs::-webkit-scrollbar, +.admin-tabs::-webkit-scrollbar, +.lib-tabs::-webkit-scrollbar, +.gallery-tabs::-webkit-scrollbar, +.preset-tabs::-webkit-scrollbar { + display: none; +} +.cookbook-tabs > *, +.memory-tabs > *, +.admin-tabs > *, +.lib-tabs > *, +.gallery-tabs > *, +.preset-tabs > * { + flex: 0 0 auto; +} +.cookbook-tab, +.memory-tab, +.admin-tab, +.lib-tab, +.gallery-tab, +.preset-tab { + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; + line-height: 1; +} +.gallery-tab { + gap: 6px; +} + +@container (max-width: 360px) { + .cookbook-tab:has(svg), + .memory-tab:has(svg), + .admin-tab:has(svg), + .lib-tab:has(svg), + .gallery-tab:has(svg), + .preset-tab:has(svg) { + width: 34px; + min-width: 34px; + padding-left: 0; + padding-right: 0; + font-size: 0; + } + + .cookbook-tab:has(svg) svg, + .memory-tab:has(svg) svg, + .admin-tab:has(svg) svg, + .lib-tab:has(svg) svg, + .gallery-tab:has(svg) svg, + .preset-tab:has(svg) svg { + width: 14px; + height: 14px; + margin-right: 0 !important; + vertical-align: middle !important; + } + + .memory-tab:has(svg) .memory-count, + .gallery-tab:has(svg) .gallery-tab-label, + .gallery-tab:has(svg) .gallery-tab-close, + .cookbook-tab:has(svg) .cookbook-tab-count, + .preset-tab:has(svg) .preset-count { + display: none !important; + } +} +/* Icon + label layout inside each tab. */ +.gallery-tab { + display: inline-flex; + align-items: center; + gap: 6px; +} +.gallery-tab-icon { + display: inline-flex; + align-items: center; + opacity: 0.85; +} +.gallery-tab.active .gallery-tab-icon { opacity: 1; } +/* Close × on the Edit tab — appears on hover. */ +.gallery-tab-close { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + margin-left: 2px; + border-radius: 4px; + opacity: 0; + transition: opacity 0.12s, background 0.12s; + /* Use a slightly larger glyph at a larger line-height so the × sits + visually centered — the U+00D7 character has uneven em-box metrics. */ + font-size: 16px; + line-height: 16px; + font-weight: 500; + cursor: pointer; + padding-bottom: 2px; + box-sizing: border-box; +} +.gallery-tab:hover .gallery-tab-close, +.gallery-tab.active .gallery-tab-close { opacity: 0.65; } +/* Hide the close × entirely on the Edit tab when there's no edit open. */ +.gallery-tab[data-tab="editor"]:not(.has-edit) .gallery-tab-close { display: none !important; } +.gallery-tab-close:hover { opacity: 1 !important; background: color-mix(in srgb, var(--red) 25%, transparent); color: var(--red); } +/* Inline rename input shown when the Edit tab is double-clicked. */ +.gallery-tab-rename-input { + background: var(--bg); + border: 1px solid var(--red); + border-radius: 3px; + color: var(--fg); + font: inherit; + font-size: 13px; + padding: 1px 4px; + width: 140px; + outline: none; +} + +/* Albums tab — grid of album cards with cover thumbnails. */ +.gallery-albums-container { + padding: 12px 4px; + max-height: calc(92vh - 120px); + overflow-y: auto; + border: 2px dashed transparent; + border-radius: 8px; + transition: border-color 0.15s, background 0.15s; +} +.gallery-settings-container { + padding: 8px 4px; + max-height: calc(92vh - 120px); + overflow-y: auto; +} +.gallery-albums-container.gallery-dragover { + border-color: var(--red); + background: color-mix(in srgb, var(--red) 5%, transparent); +} +.gallery-albums-empty { + display: flex; flex-direction: column; align-items: center; gap: 12px; + padding: 48px 16px; + opacity: 0.7; + font-size: 13px; +} +.gallery-albums-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 12px; +} +.gallery-album-card { + position: relative; + display: flex; + flex-direction: column; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + overflow: hidden; + cursor: pointer; + transition: transform 0.12s, border-color 0.12s, box-shadow 0.12s; +} +.gallery-album-card:hover { + transform: translateY(-2px); + border-color: var(--red); + box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 12%, transparent); +} +.gallery-album-menu-btn { + position: absolute; + top: 6px; right: 6px; + width: 24px; height: 24px; + display: flex; align-items: center; justify-content: center; + background: color-mix(in srgb, var(--bg) 70%, transparent); + color: var(--fg); + border: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + border-radius: 6px; + cursor: pointer; + opacity: 0; + transition: opacity 0.12s, background 0.12s; + z-index: 2; + backdrop-filter: blur(4px); + /* Plain "…" text — the previous SVG was rendering as a flat black + block on some themes when its currentColor didn't contrast. */ + font-size: 16px; + line-height: 1; + font-weight: 600; + padding: 0 0 4px; +} +.gallery-album-card:hover .gallery-album-menu-btn, +.gallery-album-menu-btn:focus-visible { opacity: 1; } +.gallery-album-menu-btn:hover { background: var(--bg); } +.gallery-album-menu-pop { + position: absolute; + top: 34px; right: 6px; + min-width: 140px; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); + display: flex; flex-direction: column; + padding: 4px; + z-index: 3; +} +.gallery-album-menu-pop[hidden] { display: none; } +.gallery-album-cover { + aspect-ratio: 4 / 3; + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + display: flex; align-items: center; justify-content: center; + overflow: hidden; +} +.gallery-album-placeholder { + opacity: 0.4; + font-size: 40px; + display: flex; align-items: center; justify-content: center; + width: 100%; height: 100%; +} +.gallery-album-info { + padding: 8px 10px; + display: flex; flex-direction: column; gap: 2px; +} +.gallery-album-name { + font-size: 13px; + font-weight: 500; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.gallery-album-count { + font-size: 11px; + opacity: 0.55; +} +.gallery-album-card-add { + border-style: dashed; + opacity: 0.7; +} +.gallery-album-card-add:hover { opacity: 1; } +.gallery-album-action-tile { + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); +} + +/* Edit-tab empty state — shown before any image is loaded. */ +.gallery-editor-landing { + display: flex; flex-direction: column; align-items: center; justify-content: center; + gap: 10px; + padding: 28px 16px; + flex: 1; + text-align: center; + color: var(--fg); +} +.gallery-editor-landing h3 { + margin: 0; + font-size: 16px; + font-weight: 600; +} +.gallery-editor-heading { + display: inline-flex; + align-items: center; + gap: 7px; +} +.gallery-editor-heading > svg { opacity: 0.6; flex: 0 0 auto; } +/* "vision model" link in the AI-tagging description → opens Settings. */ +.ge-vision-link { + color: var(--accent, var(--red)); + text-decoration: underline; + text-underline-offset: 2px; + cursor: pointer; +} +.ge-vision-link:hover { opacity: 0.8; } +.ge-alpha-tag { + font-size: 9px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + vertical-align: middle; + padding: 1px 5px; + border-radius: 4px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + position: relative; + top: -1px; +} +.gallery-editor-landing-actions { + display: flex; gap: 10px; + margin-top: 8px; +} +/* Bigger primary action buttons specifically inside the editor landing — + the gallery-wide .gallery-select-btn is a compact toolbar style which is + too small for the empty-state hero. */ +.gallery-editor-landing-actions .gallery-select-btn { + padding: 7px 22px 17px; + font-size: 13px; + opacity: 0.85; +} +/* Visible template picker: every canvas ratio is inspectable without opening + a native menu, and its SVG outline previews the resulting canvas shape. */ +.gallery-editor-template-picker { + width: min(760px, 96%); + margin-top: 10px; +} +.gallery-editor-template-picker h4 { + margin: 0 0 7px; + font-size: 11px; + opacity: 0.55; + text-transform: uppercase; + letter-spacing: 0.06em; +} +.gallery-editor-template-options { + display: grid; + grid-template-columns: repeat(4, minmax(105px, 1fr)); + gap: 6px; +} +.gallery-editor-template-option { + height: 72px; + min-width: 0; + padding: 6px 7px; + display: grid; + grid-template-rows: 28px auto auto; + justify-items: center; + align-items: center; + background: var(--panel); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 5px; + font: inherit; + font-size: 11px; + cursor: pointer; + overflow: hidden; +} +.gallery-editor-template-option svg { color: var(--accent, var(--red)); } +.gallery-editor-template-option:hover, +.gallery-editor-template-option:focus-visible { + outline: none; + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 7%, var(--panel)); +} +.gallery-editor-template-option span { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 600; +} +.gallery-editor-template-option small { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 9px; + opacity: 0.52; +} +@media (max-width: 620px) { + .gallery-editor-landing { + justify-content: flex-start; + gap: 7px; + padding: 18px 12px; + overflow-y: auto; + } + .gallery-editor-landing-actions { + width: 100%; + gap: 6px; + justify-content: center; + } + .gallery-editor-landing-actions .gallery-select-btn { + flex: 1 1 0; + min-width: 0; + max-width: 180px; + padding: 6px 8px 10px; + font-size: 11px; + } + .gallery-editor-template-picker { + width: 100%; + margin-top: 4px; + } + .gallery-editor-template-options { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + /* The common square, widescreen, portrait, and Instagram presets are + enough to start on a phone. Keep the print and 4K presets on desktop. */ + .gallery-editor-template-option:nth-child(n + 5) { + display: none; + } +} +/* Saved-drafts grid on the editor landing. Surfaces every persisted + in-progress project so the user can resume without re-opening the + original photo. Each card: thumbnail + name + last-saved hint + ×. */ +.gallery-editor-drafts { + width: min(720px, 92%); + margin: 28px auto 0; /* centered */ + padding-top: 18px; + border-top: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 10px; +} +.gallery-editor-drafts { + position: relative; +} +.gallery-editor-drafts-loading { + position: absolute; + inset: 30px 0 0 0; + z-index: 4; + display: flex; + align-items: center; + justify-content: center; + background: var(--panel); + border-radius: 6px; + min-height: 80px; +} +.gallery-editor-drafts-header { + display: flex; + align-items: center; + justify-content: center; /* center the title / search / select */ + gap: 8px; + margin-bottom: 8px; +} +.gallery-editor-drafts-title { + margin: 0; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.06em; + opacity: 0.55; + font-weight: 600; + text-align: left; + flex-shrink: 0; +} +.gallery-editor-drafts-search { + flex: 1 1 auto; + min-width: 0; + max-width: none; + height: 26px; /* same thickness as the Select button */ + box-sizing: border-box; + padding: 0 8px; + font-size: 12px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; +} +.gallery-editor-drafts-search:focus { outline: none; border-color: var(--red); } +/* The shared .gallery-select-btn has a -4px top margin + tall asymmetric padding + meant for the main gallery toolbar; in this header it makes the button a + different height / offset from the search box. Normalize so it lines up. */ +.gallery-editor-drafts-header .gallery-select-btn { + margin-top: 0 !important; + height: 26px; /* match the search bar */ + box-sizing: border-box; + padding: 0 10px; + font-size: 12px; + line-height: 1; + flex-shrink: 0; +} + +.cookbook-official-filter { + display: inline-flex; + align-items: center; + gap: 4px; + flex: 0 1 auto; + min-width: 0; + max-width: 100%; + color: var(--fg); + font-size: 10px; + opacity: 0.72; + white-space: nowrap; + position: relative; + top: 1px; +} +.cookbook-official-filter > span:first-child { + position: relative; + top: 1px; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: 9px; +} +.cookbook-official-filter .toggle { + width: 26px; + height: 14px; +} +.cookbook-official-filter .toggle .slider:before { + width: 10px; + height: 10px; + left: 2px; + bottom: 2px; +} +.cookbook-official-filter .toggle input:checked + .slider:before { + transform: translateX(12px); +} +#gallery-editor-drafts-select { margin-left: auto; flex-shrink: 0; } +#gallery-editor-drafts-select.active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); +} +/* Graceful exit when a project card is deleted — fade + shrink before re-render. */ +.gallery-editor-draft-card.gallery-draft-removing { + opacity: 0; + transform: scale(0.92); + transition: opacity 0.24s ease, transform 0.24s ease; + pointer-events: none; +} +/* Draft select dot uses the standard small .gallery-select-dot look (same as the + photo grid) — no oversized white-ring checkbox. */ +.gallery-editor-drafts-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 10px; +} +.gallery-drafts-just-opened > .gallery-editor-draft-card { + animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; +} +.gallery-drafts-just-opened > :nth-child(1) { animation-delay: 0.02s; } +.gallery-drafts-just-opened > :nth-child(2) { animation-delay: 0.04s; } +.gallery-drafts-just-opened > :nth-child(3) { animation-delay: 0.06s; } +.gallery-drafts-just-opened > :nth-child(4) { animation-delay: 0.08s; } +.gallery-drafts-just-opened > :nth-child(5) { animation-delay: 0.10s; } +.gallery-drafts-just-opened > :nth-child(6) { animation-delay: 0.12s; } +.gallery-drafts-just-opened > :nth-child(7) { animation-delay: 0.14s; } +.gallery-drafts-just-opened > :nth-child(8) { animation-delay: 0.16s; } +.gallery-drafts-just-opened > :nth-child(9) { animation-delay: 0.18s; } +.gallery-drafts-just-opened > :nth-child(10) { animation-delay: 0.20s; } +.gallery-drafts-just-opened > :nth-child(11) { animation-delay: 0.22s; } +.gallery-drafts-just-opened > :nth-child(12) { animation-delay: 0.24s; } +.gallery-drafts-just-opened > :nth-child(13) { animation-delay: 0.26s; } +.gallery-drafts-just-opened > :nth-child(14) { animation-delay: 0.28s; } +.gallery-drafts-just-opened > :nth-child(15) { animation-delay: 0.30s; } +.gallery-drafts-just-opened > :nth-child(16) { animation-delay: 0.32s; } +.gallery-drafts-just-opened > :nth-child(17) { animation-delay: 0.34s; } +.gallery-drafts-just-opened > :nth-child(18) { animation-delay: 0.36s; } +.gallery-drafts-just-opened > :nth-child(19) { animation-delay: 0.38s; } +.gallery-drafts-just-opened > :nth-child(20) { animation-delay: 0.40s; } +.gallery-drafts-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } +.gallery-drafts-view-toggle { + display: inline-flex; + position: relative; + top: 0; + height: 24px; + flex: 0 0 auto; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); +} +.gallery-drafts-view-toggle button { + width: 25px; + height: 22px; + margin: 0; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + border: 0; + border-right: 1px solid var(--border); + background: transparent; + color: color-mix(in srgb, var(--fg) 55%, transparent); + cursor: pointer; + line-height: 0; +} +.gallery-drafts-view-toggle button:last-child { border-right: 0; } +.gallery-drafts-view-toggle button:hover { color: var(--fg); } +.gallery-drafts-view-toggle button.active { + height: 22px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-thumb { + width: 44px; + height: 44px; + aspect-ratio: auto; +} +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete { + position: static; + opacity: 0.55; + background: transparent; + color: var(--fg); +} +.gallery-editor-draft-delete-trash { display: none; } +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete-trash { + display: block; + width: 18px; + height: 18px; + position: relative; + top: -2px; +} +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete:hover { + opacity: 1; + color: var(--color-error, #f44); + background: color-mix(in srgb, var(--color-error, #f44) 10%, transparent); +} +.gallery-editor-draft-card { + position: relative; + display: flex; + flex-direction: column; + gap: 4px; + padding: 6px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 70%, transparent); + cursor: pointer; + transition: border-color 0.15s, background 0.15s, transform 0.15s; + text-align: left; +} +.gallery-editor-draft-card:hover { + border-color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--panel) 90%, transparent); +} +.gallery-editor-draft-thumb { + width: 100%; + aspect-ratio: 1; + object-fit: cover; + border-radius: 4px; + background: var(--bg); + display: block; +} +.gallery-editor-draft-thumb-empty { + background: repeating-linear-gradient( + 45deg, + color-mix(in srgb, var(--fg) 4%, transparent), + color-mix(in srgb, var(--fg) 4%, transparent) 6px, + transparent 6px, + transparent 12px + ); +} +.gallery-editor-draft-info { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} +.gallery-editor-draft-name { + font-size: 12px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.gallery-editor-draft-meta { + font-size: 10px; + opacity: 0.55; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.gallery-editor-draft-delete { + position: absolute; + top: -2px; + right: 8px; + width: 22px; + height: 22px; + border-radius: 50%; + border: none; + background: rgba(0, 0, 0, 0.55); + color: #fff; + font-size: 14px; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: opacity 0.12s, background 0.12s; +} +.gallery-editor-draft-delete { + padding-bottom: 2px; +} +.gallery-editor-draft-delete:hover { background: var(--red); } +/* On very narrow phones, give project search its own row instead of + compressing the title and view controls into an overflowing header. */ +@media (max-width: 360px) { + .gallery-editor-drafts-header { + flex-wrap: wrap; + justify-content: flex-start; + gap: 6px; + } + .gallery-editor-drafts-title { + flex: 1 1 auto; + } + .gallery-editor-drafts-search { + order: 10; + flex: 1 1 100%; + width: 100%; + max-width: none; + } + #gallery-editor-drafts-select { + margin-left: 0; + } +} +/* Editor layout */ +.gallery-editor { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + height: 100%; + overflow: hidden; + /* Prevent the editor's own scroll/touch from chaining out to the + gallery modal or the page body — critical on mobile so touching + the canvas doesn't slide the whole page. */ + overscroll-behavior: contain; + touch-action: pan-y; +} +/* Pin the topbar to the top of the editor so its action buttons remain + visible if the gallery modal body itself ends up scrolling. */ +.ge-topbar { position: sticky; top: 0; z-index: 5; } + +/* Top bar */ +.ge-topbar { + display: flex; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: thin; + align-items: center; + justify-content: space-between; + padding: 6px 10px; + background: var(--panel); + border-bottom: 1px solid var(--border); + flex-shrink: 0; + gap: 8px; +} +.ge-topbar-left, .ge-topbar-right { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: 4px; +} +.ge-topbar-right > * { + flex-shrink: 0; +} +.ge-topbar .dropdown[popover] { + position: fixed; + margin: 0; + bottom: auto; + max-height: calc(100vh - 80px); + overflow-y: auto; +} +.ge-draft-status { + flex: 0 1 auto; + min-width: 0; + max-width: 92px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: color-mix(in srgb, var(--fg) 52%, transparent); + font-size: 10px; + line-height: 1; + transition: color 0.15s, opacity 0.15s; +} diff --git a/static/css/style-part-06.css b/static/css/13-image-editor.css similarity index 71% rename from static/css/style-part-06.css rename to static/css/13-image-editor.css index a781aeda8..d759ed7d3 100644 --- a/static/css/style-part-06.css +++ b/static/css/13-image-editor.css @@ -1,14 +1,551 @@ -/* Fragment 7/9 of the former static/style.css. +/* 13-image-editor - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ +.ge-draft-status[data-state="pending"], +.ge-draft-status[data-state="saving"] { color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); } +.ge-draft-status[data-state="saved"] { color: color-mix(in srgb, var(--color-success, #4ade80) 78%, var(--fg)); } +.ge-draft-status[data-state="error"] { color: var(--color-error, #ef4444); } +/* "ALPHA" badge — flags the editor as in-development. */ +.ge-alpha-badge { + flex-shrink: 0; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.1em; + line-height: 1; + padding: 3px 6px; + margin-right: 4px; + border-radius: 4px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + text-transform: uppercase; + user-select: none; + cursor: default; +} +@media (max-width: 768px) { + /* Keep the toolbar tight on mobile — badge stays but shrinks. */ + .ge-alpha-badge { font-size: 8px; padding: 2px 5px; margin-right: 2px; } +} +.ge-diffusion-status { + font-size: 11px; + cursor: pointer; + padding: 2px 8px; + border-radius: 4px; + transition: all 0.15s; + white-space: nowrap; +} +.ge-diffusion-status.online { + color: var(--color-success, #4ade80); +} +.ge-diffusion-status.offline { + color: var(--color-error, #ef4444); + opacity: 0.7; +} +.ge-diffusion-status.offline:hover { + opacity: 1; + background: color-mix(in srgb, var(--color-error) 10%, transparent); +} +.ge-topbar-fill { + display: inline-flex; + align-items: center; + gap: 4px; + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +.ge-topbar-fill:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); + border-color: var(--accent, var(--red)); +} +/* Mask-color swatch in the topbar — small label + circular swatch so + the user can pick a contrasting overlay colour from anywhere. */ +.ge-topbar-mask-color-wrap { + display: inline-flex; + align-items: center; + gap: 4px; + cursor: pointer; +} +.ge-topbar-mask-color-label { + font-size: 10px; + opacity: 0.6; + text-transform: uppercase; + letter-spacing: 0.06em; +} +.ge-topbar-mask-color { + width: 18px; + height: 18px; + flex: 0 0 18px; +} +.ge-topbar-mask-color.cp-swatch-input { + width: 18px; + height: 18px; + flex: 0 0 18px; + border-radius: 50%; + padding: 0; +} +.ge-topbar-sep { + width: 1px; + height: 16px; + background: var(--border); + margin: 0 4px; +} + +/* Editor body (toolbar + canvas + panel) */ +.ge-editor-body { + display: flex; + flex: 1; + min-height: 0; + overflow: hidden; + position: relative; +} +.gallery-editor-container { + flex: 1; + min-height: 0; + display: none; +} + +.ge-toolbar { + display: flex; + flex-direction: column; + gap: 2px; + /* Asymmetric horizontal padding shifts the whole column 4 px left — + buttons, hover backgrounds, active highlight pills, and section + separators all move together. */ + padding: 8px 8px 8px 0; + background: var(--panel); + border-right: 1px solid var(--border); + width: 64px; + flex-shrink: 0; + min-height: 0; + overflow-x: hidden; + overflow-y: auto; + scrollbar-width: none; +} +.ge-toolbar::-webkit-scrollbar { display: none; } +.ge-tool-sep { + font-size: 8px; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-muted); + text-align: center; + padding: 8px 0 4px; + border-top: 1px solid var(--border); + margin-top: 4px; + opacity: 0.6; + flex-shrink: 0; +} +/* All tool buttons share a fixed height so the column reads as a clean + grid. Long labels (e.g. "Remove BG") truncate with ellipsis instead of + wrapping to two lines and making one button taller than the others. */ +.ge-tool-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + padding: 0 2px; + height: 42px; + flex-shrink: 0; + border: none; + background: none; + color: var(--fg); + opacity: 0.6; + cursor: pointer; + border-radius: 6px; + transition: background 0.15s, opacity 0.15s; +} +.ge-tool-btn { position: relative; } +/* Hover background is now a pseudo so we can shift it 2 px left + independently from the button's own bounds (which contain the icon + and label at their already-shifted positions). */ +.ge-tool-btn:hover { opacity: 0.85; } +.ge-tool-btn.active { + opacity: 1; + color: var(--red); + background: none; + /* Padding stays the same as inactive buttons so the icon stays + horizontally aligned with its neighbors. The taller highlight is + drawn by ::before so it can extend up/down without pushing the + button or shifting the icon. */ +} +/* Tool button contents stay above the active highlight pseudo. Also + shifted an additional 2 px left so the icon + label sit further inside + the highlight pill (which still anchors to the button bounds). */ +.ge-tool-btn > * { position: relative; z-index: 1; left: -2px; } +.ge-tool-icon { font-size: 18px; line-height: 1; } +.ge-tool-label { font-size: 10px; line-height: 1.25; } +/* AI badge — same ✦ glyph the Enhance tool uses, pinned to the top- + left of any tool button marked `ai: true`. Same color as the icon + itself (inherits foreground) so it reads as part of the tool, not + a flag. */ +.ge-tool-ai { + position: absolute !important; + top: 1px; + z-index: 2; + color: inherit; + opacity: 0.7; + pointer-events: none; + font-size: 11px; + line-height: 1; + font-weight: 700; + left: 3px !important; +} +.ge-tool-btn.is-ai:hover .ge-tool-ai { opacity: 0.95; } +.ge-tool-btn.is-ai.active .ge-tool-ai { opacity: 1; } +/* Inline ✦ marker used on AI action buttons (Generate, Remove, etc.) + so they read as AI-backed at a glance — same glyph as .ge-tool-ai + in the toolbar, just sitting inline next to the label. */ +.ge-btn-ai-mark { + display: inline-block; + font-size: 11px; + line-height: 1; + font-weight: 700; + opacity: 0.75; + margin-right: 1px; +} + +/* Per-tool clear-selection badge — shows a tiny X in the top-right of + the Lasso / Wand button when each holds a selection. Click clears + the selection without switching tools (handler stops propagation). */ +.ge-tool-clear { + position: absolute !important; + top: 3px; + right: 3px; + width: 14px; + height: 14px; + display: none; + align-items: center; + justify-content: center; + border-radius: 50%; + color: #fff; + background: var(--red); + cursor: pointer; + opacity: 0.95; + z-index: 2; + box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 70%, transparent), + 0 1px 4px color-mix(in srgb, var(--red) 50%, transparent); + transition: opacity 0.12s, transform 0.12s, box-shadow 0.12s; + left: auto !important; +} +.ge-tool-clear:hover { + opacity: 1; + transform: scale(1.15); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 80%, transparent), + 0 2px 6px color-mix(in srgb, var(--red) 60%, transparent); +} + +/* Aspect-ratio placeholder shown while a draft is loading. */ +.ge-canvas-placeholder { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border: 1px dashed color-mix(in srgb, var(--fg) 20%, transparent); + border-radius: 4px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1; +} +/* Canvas area (center) */ +.ge-canvas-area { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + overflow: auto; + background: var(--bg); + position: relative; + min-width: 0; + /* Stop touch interactions inside the editor from dragging the whole + page (or the gallery modal). Touches on the canvas are owned by the + editor's drawing logic — no native pan/zoom. */ + overscroll-behavior: contain; +} +.ge-ruler { + position: absolute; + z-index: 16; + touch-action: none; + user-select: none; +} +.ge-ruler-horizontal { + top: 0; + left: 18px; + right: 0; + width: calc(100% - 18px); + height: 18px; + cursor: row-resize; +} +.ge-ruler-vertical { + top: 18px; + bottom: 0; + left: 0; + width: 18px; + height: calc(100% - 18px); + cursor: col-resize; +} +.ge-ruler-corner { + position: absolute; + top: 0; + left: 0; + z-index: 17; + width: 18px; + height: 18px; + background: var(--panel); + border-right: 1px solid var(--border); + border-bottom: 1px solid var(--border); + box-sizing: border-box; +} +.ge-wand-loading { + position: absolute; + inset: 0; + z-index: 25; + display: flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--bg) 30%, transparent); + pointer-events: none; +} +.ge-ai-command { + position: absolute; + left: 50%; + bottom: 14px; + z-index: 18; + width: min(430px, calc(100% - 28px)); + transform: translateX(-50%); + padding: 7px; + box-sizing: border-box; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--panel, var(--bg)) 94%, transparent); + box-shadow: 0 8px 22px color-mix(in srgb, #000 26%, transparent); + backdrop-filter: blur(8px); + transition: bottom 0.16s ease, opacity 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; +} +.ge-ai-command-toggle { + width: 100%; + height: 28px; + display: none; + align-items: center; + justify-content: center; + gap: 5px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--accent, var(--red)); + font: inherit; + font-size: 11px; + font-weight: 600; + cursor: pointer; +} +.ge-ai-command-toggle-caret { + opacity: 0.7; + flex-shrink: 0; +} +.ge-ai-command-head { + display: flex; + align-items: center; + min-height: 24px; + padding: 0 2px 5px; + color: var(--fg); +} +.ge-ai-command-title { + margin-left: 5px; + font-size: 11px; + font-weight: 650; +} +.ge-ai-command-scope { + margin-left: 7px; + padding-left: 7px; + border-left: 1px solid var(--border); + color: var(--fg-muted); + font-size: 9px; + line-height: 1; + text-transform: uppercase; +} +.ge-ai-command-collapsed { + bottom: -16px; + width: auto; + min-width: 102px; + padding: 2px 9px; + opacity: 0.32; + border-color: color-mix(in srgb, var(--accent, var(--red)) 18%, var(--border)); + box-shadow: 0 3px 10px color-mix(in srgb, #000 16%, transparent); +} +.ge-canvas-area:hover .ge-ai-command-collapsed, +.ge-ai-command-collapsed:hover, +.ge-ai-command-collapsed:focus-within { + bottom: 10px; + opacity: 0.94; + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 24%, transparent); +} +.ge-ai-command-form { + display: flex; + align-items: center; + gap: 6px; +} +.ge-ai-command-input { + flex: 1 1 auto; + min-width: 0; + height: 32px; + box-sizing: border-box; + padding: 0 9px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--bg) 76%, #000 24%); + color: var(--fg); + font: inherit; + font-size: 12px; +} +.ge-ai-command-input:focus { + outline: none; + border-color: var(--accent, var(--red)); +} +.ge-ai-command-clear { + position: relative; + top: -2px; + width: 26px; + height: 32px; + flex: 0 0 26px; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: -38px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--fg-muted); + cursor: pointer; + padding: 0; + z-index: 1; +} +.ge-ai-command-clear:hover { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.ge-ai-command-run { + position: relative; + top: -2px; + background: var(--send-btn-bg, var(--red)); + color: #fff; + border: none; + border-radius: 8px; + min-width: 32px; + width: 32px; + height: 32px !important; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + transition: background 0.25s, opacity 0.12s; + overflow: hidden; +} +.ge-ai-command-run:hover { + background: var(--send-btn-hover, color-mix(in srgb, var(--red) 80%, white)); +} +.ge-ai-command-busy .ge-ai-command-run { + opacity: 0.58; + cursor: wait; +} +.ge-ai-command-busy .ge-ai-command-run svg { + display: none; +} +.ge-ai-command-close { + width: 28px; + height: 32px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border: 0; + border-radius: 8px; + background: transparent; + color: var(--fg); + opacity: 0.52; + cursor: pointer; + padding: 0; + margin-left: auto; +} +.ge-ai-command-close:hover { + opacity: 0.9; + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.ge-ai-command-suggestions { + position: absolute; + left: 7px; + right: 7px; + bottom: calc(100% - 2px); + z-index: 2; + display: flex; + flex-direction: column; + gap: 2px; + max-height: 236px; + overflow: auto; + padding: 5px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--panel, var(--bg)) 97%, #000 3%); + box-shadow: 0 10px 28px color-mix(in srgb, #000 28%, transparent); +} +.ge-ai-command-suggestion { + width: 100%; + display: grid; + grid-template-columns: 38px minmax(82px, auto) minmax(0, 1fr); + align-items: center; + gap: 8px; + min-height: 28px; + padding: 4px 7px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--fg); + text-align: left; + cursor: pointer; + font: inherit; +} +.ge-ai-command-suggestion:hover, +.ge-ai-command-suggestion.active { + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); +} +.ge-ai-command-suggestion-kind { + min-width: 0; + overflow: hidden; + color: var(--accent, var(--red)); + font-size: 8px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} +.ge-ai-command-suggestion-main { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 11px; + font-weight: 650; +} +.ge-ai-command-suggestion-hint { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--fg-muted); + font-size: 10px; +} .ge-ai-command-status { margin-top: 4px; font-size: 10px; @@ -4284,1728 +4821,3 @@ button .spinner-whirlpool { #group-model-picker .memory-item:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); } -#group-model-picker input[type="checkbox"] { - accent-color: var(--accent, var(--red)); -} -#group-model-picker input[type="radio"] { - accent-color: var(--accent, var(--red)); -} -#group-model-picker .btn-primary { - background: var(--accent, var(--red)); - color: #fff; - border: none; - border-radius: 6px; - cursor: pointer; - font-weight: 500; -} -#group-model-picker .btn-primary:disabled { - opacity: 0.4; - cursor: not-allowed; -} -#group-model-picker .btn-primary:hover:not(:disabled) { - filter: brightness(1.1); -} - -/* ── Email document type ── */ -.doc-email-header { - display: flex; - flex-direction: column; - gap: 6px; - padding: 10px 12px; - border-bottom: 1px solid var(--border); - background: var(--bg); - flex-shrink: 0; -} -.email-field { - display: flex; - align-items: center; - gap: 8px; -} -.email-field label { - font-size: 11px; - font-weight: 600; - color: var(--fg); - opacity: 0.5; - min-width: 50px; - text-align: right; -} -.email-field input { - flex: 1; - background: transparent; - border: 1px solid var(--border); - border-radius: 4px; - padding: 5px 8px; - font-size: 13px; - font-family: inherit; - color: var(--fg); - outline: none; -} -.email-field input:focus { - border-color: var(--accent, #4a9eff); -} -.email-actions { - display: flex; - gap: 8px; - justify-content: flex-end; - margin-top: 2px; -} -.email-draft-btn { - background: transparent; - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - padding: 6px 14px; - font-size: 12px; - cursor: pointer; - font-family: inherit; -} -.email-draft-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } -.email-draft-btn:disabled { opacity: 0.5; cursor: not-allowed; } - -/* ── Email inbox list ── */ -.email-body { border-top: 1px solid var(--border); } -.email-folder-bar { padding: 4px 8px; border-bottom: 1px solid var(--border); } -.email-folder-select { - background: transparent; border: 1px solid var(--border); border-radius: 4px; - color: var(--fg); font-size: 11px; padding: 2px 6px; cursor: pointer; width: 100%; height: 26px; -} -.email-item { - display: flex; align-items: flex-start; gap: 8px; padding: 8px 12px; - cursor: pointer; border-bottom: 1px solid var(--border); position: relative; transition: background 0.1s; -} -.email-item:hover { background: var(--hover-bg, rgba(255,255,255,0.03)); } -.email-item-spam:hover { opacity: 0.75; } -.email-tag-spam { - display: inline-flex; align-items: center; gap: 4px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); - color: var(--accent, var(--red)); - border-radius: 10px; padding: 1px 6px; font-size: 9px; font-weight: 600; - text-transform: lowercase; letter-spacing: 0.3px; -} -.email-spam-unflag { - background: none; border: none; color: inherit; cursor: pointer; - padding: 0 2px; font-size: 10px; line-height: 1; opacity: 0.6; -} -.email-spam-unflag:hover { opacity: 1; color: var(--fg); } -/* Unread state is now a subtle icon, not full styling */ -.email-done-check { - display: inline-flex; align-items: center; justify-content: center; - width: 16px; height: 16px; border-radius: 50%; background: transparent; - border: 1.5px solid var(--border); color: transparent; - flex-shrink: 0; cursor: pointer; transition: all 0.15s; - margin-right: 4px; -} -.email-done-check:hover { border-color: var(--accent, #4a9eff); } -.email-done-check.active { background: var(--accent, #4a9eff); border-color: var(--accent, #4a9eff); color: #fff; } -.email-done-check.active svg { display: block; } -.email-done-check svg { display: none; } - - - - -.email-delete-busy > :not(.email-delete-overlay) { - opacity: 0.42; - transition: opacity 0.12s ease; -} - -.email-delete-overlay { - position: absolute; - inset: 0; - z-index: 8; - display: flex; - align-items: center; - justify-content: center; - border-radius: inherit; - background: color-mix(in srgb, var(--panel, var(--bg)) 82%, transparent); - pointer-events: none; -} - -.email-delete-overlay .spinner-whirlpool, -.email-delete-overlay .ai-spinner-whirlpool { - margin: 0 !important; -} - -.doclib-card.email-card-removing { - pointer-events: none !important; - max-height: 0 !important; - padding-top: 0 !important; - padding-bottom: 0 !important; - margin-top: 0 !important; - margin-bottom: 0 !important; - border-color: transparent !important; - opacity: 0; - transform: translateX(-10px) scale(0.985); - transition: - opacity 0.18s ease, - transform 0.22s ease, - max-height 0.23s ease, - padding 0.23s ease, - margin 0.23s ease, - border-color 0.18s ease; -} - -@media (prefers-reduced-motion: reduce) { - .doclib-card.email-card-removing { - transition: opacity 0.08s ease; - transform: none; - } -} - -/* Library card done check — always visible, subtle icon next to title */ -.email-card-done { - display: inline-flex; align-items: center; justify-content: center; - flex-shrink: 0; cursor: pointer; - transition: opacity 0.15s, color 0.15s; - opacity: 0.15; color: var(--fg); -} -.email-card-titlerow .memory-item-title { - flex: 1 1 auto; - min-width: 0; -} -.email-card-titlerow { position: relative; min-width: 0; } -.email-card-status { - display: inline-flex; - align-items: center; - justify-content: flex-end; - gap: 7px; - flex: 0 0 auto; - min-width: max-content; - margin-left: auto; - white-space: nowrap; -} -.email-card-status > * { - flex: 0 0 auto; -} -.email-card-status + .email-card-nav-arrows { - margin-left: 6px; -} -.email-card-status .email-card-nav-arrows { - margin-left: 2px; -} -.email-card-attachment { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 13px; - width: 13px; - height: 18px; -} -/* Hover preview: bright accent when unchecked so the user sees a check coming. - Once active, keep the exact same color on hover so the done state does not - visually flip while the pointer is still over it. */ -.email-card-done:not(.active):hover { - opacity: 0.75 !important; - color: var(--accent-primary, var(--red)); -} -.email-card-done.active { opacity: 0.95; color: var(--accent-primary, var(--red)); } -.email-card-done.active:hover { - opacity: 0.95 !important; - color: var(--accent-primary, var(--red)) !important; -} -.email-card-favorite { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 18px; - width: 18px; - height: 18px; - margin: 0; - padding: 0; - border: 0; - background: transparent; - color: var(--fg); - cursor: pointer; - position: relative; - top: -3px; - opacity: 0.18; - transition: opacity 0.15s, color 0.15s, transform 0.15s; -} -.email-card-favorite:hover, -.email-card-favorite:focus-visible { - opacity: 0.8; - color: var(--accent-primary, var(--red)); -} -.email-card-favorite.active { - opacity: 0.9; - color: var(--accent-primary, var(--red)); -} -@keyframes check-pop { - 0% { transform: scale(1); } - 40% { transform: scale(1.7) rotate(-10deg); } - 70% { transform: scale(1.1) rotate(4deg); } - 100% { transform: scale(1) rotate(0); } -} -@keyframes check-unpop { - 0% { transform: scale(1); opacity: 0.95; } - 35% { transform: scale(0.45) rotate(8deg); opacity: 0.05; } - 65% { transform: scale(0.9); opacity: 0.2; } - 100% { transform: scale(1); } -} - -/* Expanded email preview in library — override .memory-item row layout */ -.doclib-card.email-card-expanded.memory-item { - cursor: default !important; - background: var(--bg) !important; - border: 1px solid var(--border) !important; - display: flex !important; - flex-direction: column !important; - align-items: stretch !important; - /* Fill the available grid height instead of the old hardcoded 70vh — - the modal already caps height (90dvh on mobile, auto on desktop), so - the card just needs to flex into whatever remains. */ - flex: 1 1 auto !important; - height: 100% !important; - min-height: 0 !important; - max-height: none !important; - padding: 0 !important; - overflow: hidden !important; - gap: 0 !important; -} -.doclib-card.email-card-expanded.memory-item > div:first-child { - /* The summary content (subject, date) - acts as the title bar. Left padding - trimmed 14→6px to shift the title 8px left total. */ - padding: 2px 4px 10px !important; /* top trimmed to lift the header text up */ - margin-top: -14px !important; /* pull the whole bar up into the freed space */ - border-bottom: 1px solid var(--border); - flex: 0 0 auto !important; - width: 100%; -} -/* Keep the per-card '...' actions menu visible while the email card is - expanded — we used to hide it and surface a duplicate menu button in - the expanded header, but that was redundant. The bottom menu now - carries the actions in both states. */ -/* When expanded inline, show the subject a touch larger than the 12px card - title (close to the new-tab window header) without being oversized. */ -.doclib-card.email-card-expanded .memory-item-title { - font-size: 14px; - font-weight: 600; -} -/* The sender name is redundant once expanded (the From: row shows it just - below), so drop it and keep only the date — which then sits left-aligned - directly under the subject. Nudge the date 1px right to line up exactly with - the subject text, and up closer to it. */ -.doclib-card.email-card-expanded .email-meta-sender, -.doclib-card.email-card-expanded .email-meta-sep { - display: none; -} -.doclib-card.email-card-expanded .memory-item-meta { - margin-top: -9px !important; -} -.doclib-card.email-card-expanded .email-meta-date { - margin-left: 3px; -} -.email-card-reader { - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; - font-size: 12px; -} -.email-card-reader-loading { - background: color-mix(in srgb, var(--bg) 86%, #000 14%); - border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); - min-height: 240px; -} -.email-card-reader-error { - background: color-mix(in srgb, var(--bg) 86%, #000 14%); - border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); -} -.email-reader-load-error { - display: flex; - min-height: 180px; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 9px; - padding: 24px 16px; - text-align: center; - color: var(--fg); -} -.email-reader-load-error-title { - font-size: 13px; - font-weight: 700; -} -.email-reader-load-error-msg { - max-width: min(460px, 92%); - color: color-mix(in srgb, var(--fg) 62%, transparent); - font-size: 11px; - line-height: 1.35; - overflow-wrap: anywhere; -} -.email-skeleton-line, -.email-skeleton-dot { - display: block; - border-radius: 999px; - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--fg) 12%, transparent) 0%, - color-mix(in srgb, var(--fg) 22%, transparent) 45%, - color-mix(in srgb, var(--fg) 12%, transparent) 90% - ); - background-size: 220% 100%; - animation: email-skeleton-shimmer 1.35s ease-in-out infinite; -} -.email-skeleton-dot { - width: 9px; - height: 9px; - flex: 0 0 auto; -} -.email-list-skeleton { - position: absolute; - inset: 0; - padding: 10px 7px; - display: flex; - flex-direction: column; - gap: 8px; - pointer-events: none; -} -.email-skeleton-row { - display: flex; - align-items: center; - gap: 10px; - min-height: 48px; - padding: 8px 10px; - border: 1px solid color-mix(in srgb, var(--border) 76%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 70%, transparent); -} -.email-skeleton-lines { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 7px; -} -.email-reader-skeleton { - display: flex; - flex-direction: column; - min-height: 100%; - flex: 1; -} -.email-reader-skeleton-header { - min-height: 60px; - padding: 12px 14px; - display: flex; - align-items: flex-start; - gap: 8px; - border-bottom: 1px solid var(--border); - background: color-mix(in srgb, var(--panel) 72%, transparent); -} -.email-reader-skeleton-header .chip { width: 132px; height: 18px; } -.email-reader-skeleton-header .chip.short { width: 78px; } -.email-reader-skeleton-header .action { - width: 38px; - height: 28px; - margin-left: auto; - border-radius: 6px; -} -.email-reader-skeleton-header .action + .action { margin-left: 0; } -.email-reader-skeleton-atts { - display: flex; - gap: 7px; - padding: 8px 14px; - border-bottom: 1px solid var(--border); -} -.email-reader-skeleton-atts .attachment { width: 126px; height: 20px; border-radius: 6px; } -.email-reader-skeleton-atts .attachment.short { width: 88px; } -.email-reader-skeleton-body { - display: flex; - flex-direction: column; - gap: 11px; - padding: 16px 14px; - min-height: 340px; - flex: 1; -} -.email-reader-skeleton-body .body { width: 88%; height: 9px; border-radius: 6px; } -.email-reader-skeleton-body .body.wide { width: 100%; } -.email-reader-skeleton-body .body.medium { width: 94%; } -.email-reader-skeleton-body .body.short { width: 62%; } -.email-reader-skeleton-body .body.gap { - margin-top: 12px; - width: 78%; -} -.email-search-progress-row { - display: flex; - align-items: center; - justify-content: center; - gap: 7px; - min-height: 30px; - padding: 6px 10px; - font-size: 11px; - opacity: 0.65; -} -.email-search-progress-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--accent-primary, var(--red)); - box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); - animation: email-search-progress-pulse 1.2s ease-in-out infinite; -} -@media (prefers-reduced-motion: reduce) { - .email-skeleton-line, - .email-skeleton-dot, - .email-inline-image-skeleton, - .email-inline-image-placeholder.is-loading .email-inline-image-icon, - .email-inline-image-frame.is-loading, - .email-search-progress-dot { - animation: none; - } -} -/* Per-email unread dot in the expanded reader's title row. Background color - stays inline (per-sender hue from _senderColor), but the dot gets a soft - breathing glow + a 4px vertical nudge so it reads as centered against - the row instead of riding the baseline. */ -.email-card-unread-dot { - position: relative; - top: 0; - animation: email-card-unread-breathe 2.2s ease-in-out infinite; -} -.email-reader-header { - display: flex; - flex-direction: row; - /* Top-align so when the user expands To/Cc, the action cluster - stays on the From line above instead of drifting to the middle. */ - align-items: flex-start; - gap: 12px; - /* Taller header so the 44px-tall absolute-positioned action - cluster inside the From row has vertical breathing room. */ - padding: 8px 14px; - min-height: 60px; - border-bottom: 1px solid var(--border); - background: var(--bg); - flex-shrink: 0; - /* Containing block for the absolute action cluster. */ - position: relative; - /* Container so the action-cluster wrap breakpoints below react to - the reader's own width instead of the page viewport. */ - container-type: inline-size; - container-name: emailreader; -} -.email-reader-header > .email-reader-actions { - margin-left: auto; - flex-shrink: 0; -} -/* Meta block stacks the From / To / Cc rows; each row keeps label - tight to its chip(s) on the same line. The recipient-chips inside - scroll horizontally so long lists slide under the floating action - cluster on the right. */ -.email-reader-meta { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; - /* Positioning context for the absolute action cluster. */ - position: relative; -} -.email-reader-meta .recipient-chips { - display: inline-flex; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - gap: 4px; - max-width: 100%; - scrollbar-width: none; - -ms-overflow-style: none; -} -.email-reader-meta .recipient-chips::-webkit-scrollbar { display: none; } -.email-reader-meta-row { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.email-reader-meta-row.email-reader-meta-from { - position: relative; - /* Never wrap "From:" off the chip onto its own line — the chip - span has overflow-x scroll built in, so it shrinks/scrolls - instead of jumping to a new row. */ - flex-wrap: nowrap; - min-width: 0; - /* Reserve space on the right for the 1-row cluster in wide mode. */ - padding-right: 320px; -} -.email-reader-meta > .email-reader-meta-details { - min-width: 0; - padding-right: 320px; -} -.email-reader-meta > .email-reader-actions-inline { - position: absolute; - top: 1px; - right: 0; - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: flex-start; - justify-content: flex-end; - gap: 4px; - z-index: 2; - /* Sit visually above the From baseline like the lifted overlay. */ - margin-top: -9px; -} -/* Recipient chip span keeps at least ~4 characters visible (60px) - before the overflow-scroll kicks in, so the sender / first - recipient name is always partly readable. */ -.email-reader-meta .recipient-chips { - min-width: 60px; -} -/* Medium reader widths: cap the cluster at 3 icons per row so it - never wraps past 2 rows. min-width forces the cluster to reserve - enough room for 3 icons in a row — without it, grid col 2 could - shrink below the cap and the cluster fanned out into 4+ short - rows. */ -@container emailreader (max-width: 450px) { - .email-reader-meta > .email-reader-actions-inline { - /* 3 icons (48px each) + 2 gaps (4px each) = 152px exact. Adding - 6px slack so subpixel rounding can't tip a row from 3 to 2 - icons and fan the cluster into a 3rd row. */ - min-width: 158px; - max-width: 158px; - /* Solid background so the email body content behind the cluster - doesn't peek through the gaps between icons or the slack on - the right edge when the cluster's wrapped to 2 rows. */ - background: var(--bg); - } - .email-reader-meta > .email-reader-actions-inline .memory-toolbar-btn.reader-icon-btn { - width: 48px; - min-width: 48px; - max-width: 48px; - height: 40px; - padding-left: 0; - padding-right: 0; - gap: 2px; - } - .email-reader-meta > .email-reader-actions-inline .reader-btn-label { - display: block; - max-width: 44px; - overflow: hidden; - text-overflow: ellipsis; - font-size: 8px; - line-height: 1; - } - /* Tighten the right-side padding now that the cluster is 158px - wide (instead of the ~308 wide 1-row layout). */ - .email-reader-meta-row.email-reader-meta-from, - .email-reader-meta > .email-reader-meta-details { - padding-right: 170px; - } - /* Cluster wrapped to 2 rows (44px + 4 gap + 44px = 92px) sits - absolute above the From baseline. Set min-height on the meta - (a descendant of the container, more reliable than targeting - the container itself) so the header grows enough to contain - the cluster + 4px of breathing room between the cluster's - bottom and the header bottom. */ - .email-reader-meta { - min-height: 96px; - } -} -/* Very narrow reader: once the chip span has shrunk to its ~4 - character min, the cluster snaps to absolute overlay with a soft - shadow so it visually floats above the truncated chip. */ -@container emailreader (max-width: 380px) { - /* Pane too narrow even for 158px of right-side reservation — kill - the padding so chips use the full width, and let the cluster - overlay via z-index + shadow. */ - .email-reader-meta-row.email-reader-meta-from, - .email-reader-meta > .email-reader-meta-details { - padding-right: 0; - } - .email-reader-meta > .email-reader-actions-inline { - /* Keep the same -7px lift as the medium-width state so the - cluster doesn't slide ~4px DOWN when it transitions into - overlay mode. */ - /* margin-top stays inherited at -7px from the base rule. */ - /* 158px content + 22px padding-left so the inner row still fits - 3 icons cleanly. */ - min-width: 180px; - max-width: 180px; - background: linear-gradient(to right, transparent 0, var(--bg) 18px); - padding-left: 22px; - /* Soft shadow on the left edge so the overlap reads as the - cluster "floating over" the chip. */ - box-shadow: -6px 0 12px -6px rgba(0, 0, 0, 0.25); - } -} -/* Mobile: drop the box-shadow AND the left-edge gradient fade — - either can read as a shadow under the icons on phone screens. - Also lift the cluster 1px more. !important needed because the - @container query overlay rule has the same selector specificity. */ -@media (max-width: 768px) { - .memory-toolbar-btn[data-mode="ai-reply-fast"] { - height: 30px !important; - min-height: 30px !important; - } - .email-reader-meta > .email-reader-actions-inline { - box-shadow: none !important; - background: none !important; - padding-left: 0 !important; - margin-top: -10px; - } -} -/* Docked panes used to flow the cluster under the meta; now they - share the same absolute-top-right + wrap behaviour as undocked. - No docked-specific overrides — the container queries below pick - the right cluster size and overlay state from the reader width. */ -/* Gmail-style chevron — collapsed view shows only From; the chevron - reveals the To/Cc details inline below. */ -.email-reader-meta-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - margin-left: -3px; - padding: 0; - border: none; - background: transparent; - color: inherit; - opacity: 0.55; - cursor: pointer; - border-radius: 4px; - flex-shrink: 0; -} -.email-reader-meta-toggle:hover { opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent); } -.email-reader-meta-toggle svg { transition: transform 0.15s ease; } -.email-reader-meta-toggle.open svg { transform: rotate(180deg); } -.email-reader-meta-toggle.email-reader-cc-toggle { - width: auto; - min-width: 18px; - gap: 3px; - margin-left: 1px; - padding: 0 5px; - color: var(--accent, var(--red)); - font-family: inherit; - font-size: 9px; - font-weight: 600; - line-height: 1; -} -.email-reader-meta-details { - display: flex; - flex-direction: column; - gap: 6px; -} -/* When expanded, To/Cc details live in the header flow. A floating popover - can overlap the folded attachment strip directly beneath the header. */ -.email-reader-meta > .email-reader-meta-details:not([hidden]) { - position: relative; - top: auto; - left: auto; - right: auto; - margin-top: 4px; - background: transparent; - border: 0; - border-radius: 0; - padding: 0; - box-shadow: none; - z-index: 1; -} -/* Reserve a fixed label width so From: / To: / Cc: all align and - their chips start at the same x. */ -.email-reader-meta-row strong { opacity: 0.5; font-weight: 600; flex-shrink: 0; min-width: 36px; } -.email-reader-meta-row > span { - white-space: nowrap; - min-width: 0; -} -/* Recipient chips */ -.recipient-chips { - display: inline-flex !important; flex-wrap: wrap; gap: 4px; - white-space: normal !important; overflow: visible !important; - text-overflow: clip !important; -} -.recipient-chip { - display: inline-flex; align-items: center; - gap: 5px; - padding: 1px 8px; font-size: 10px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--border); - border-radius: 10px; - color: var(--fg); - white-space: nowrap; - cursor: pointer; - transition: background 0.15s, border-color 0.15s, max-width 0.2s; - max-width: 220px; - overflow: hidden; - text-overflow: ellipsis; -} -.recipient-chip-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} -.recipient-chip-copy { - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - top: -2px; - width: 14px; - height: 14px; - padding: 0; - border: none; - background: none; - color: inherit; - opacity: 0.55; - cursor: pointer; - flex: 0 0 auto; -} -.recipient-chip-copy:hover, -.recipient-chip-copy.copied { - opacity: 1; - color: var(--accent-primary, var(--red)); -} -.recipient-chip-copy[hidden] { - display: none !important; -} -.recipient-chip:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); -} -.recipient-chip.expanded { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); - border-color: var(--accent-primary, var(--red)); - max-width: 500px; -} -.recipient-chip.popover-open { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); - border-color: var(--accent-primary, var(--red)); -} -.recipient-chip-popover { - position: fixed; - z-index: 12050; - display: flex; - align-items: center; - gap: 8px; - max-width: min(420px, calc(100vw - 20px)); - padding: 8px 10px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--bg) 96%, var(--fg) 4%); - color: var(--fg); - box-shadow: 0 14px 34px rgba(0, 0, 0, 0.42); - font-size: 12px; - line-height: 1.35; - pointer-events: auto; -} -.recipient-chip-popover-main { - min-width: 0; -} -.recipient-chip-popover-name { - font-weight: 650; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.recipient-chip-popover-detail { - overflow-wrap: anywhere; - opacity: 0.86; -} -.recipient-chip-popover-copy { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 26px; - height: 26px; - padding: 0; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: inherit; - cursor: pointer; -} -.recipient-chip-popover-copy:hover, -.recipient-chip-popover-copy.copied { - color: var(--accent-primary, var(--red)); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, transparent); -} -@container docpane (max-width: 460px) { - .email-reader-header { - flex-direction: column; - gap: 6px; - } - .email-reader-header > .email-reader-actions { - /* On narrow widths fall back to in-flow so it doesn't overlap - the meta entirely. */ - position: static; - align-self: flex-end; - background: none; - padding-left: 0; - } - .email-reader-meta-row:not(.email-reader-meta-from) { - padding-right: 0; - } - .email-reader-meta-row strong { - min-width: 0; - } - .recipient-chip { - max-width: 100%; - } -} -/* Mobile: long recipient lists (To/Cc with many addresses) shouldn't wrap to - N rows and push the body down. Keep them on one row, horizontally scrollable, - no scrollbar chrome. */ -@media (max-width: 768px) { - .recipient-chips { - flex-wrap: nowrap !important; - overflow-x: auto !important; - overflow-y: hidden !important; - scrollbar-width: none; - -webkit-overflow-scrolling: touch; - } - .recipient-chips::-webkit-scrollbar { display: none; } - .recipient-chip { flex-shrink: 0; } -} -.email-reader-actions { - display: flex; flex-direction: column; gap: 4px; align-items: flex-end; - flex-shrink: 0; - margin-top: -4px; -} -/* Two stacked rows: Reply/Reply-all/Forward on the From line, with - Summary/AI-reply/More on a row below. */ -.email-reader-actions-row { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: center; - justify-content: flex-end; - gap: 4px; -} -.email-reader-atts { - display: flex; flex-wrap: wrap; gap: 6px; - padding: 8px 14px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} -.email-reader-body { - font-size: 12px; - line-height: 1.55; - white-space: pre-wrap; - word-wrap: break-word; - overflow-wrap: anywhere; - flex: 1; - overflow-y: auto; - padding: 14px; - min-height: 0; - background: var(--panel); - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--border) 65%, transparent); - /* Use the OS colored emoji font for incoming mail content */ - font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"; - font-variant-emoji: emoji; -} -.email-reader-body.html-body img { max-width: 100%; height: auto; } -.email-reader-body.html-body [style*="max-width:600px"], -.email-reader-body.html-body [style*="max-width: 600px"], -.email-reader-body.html-body [style*="width:600px"], -.email-reader-body.html-body [style*="width: 600px"], -.email-reader-body.html-body table[width="600"] { - max-width: none !important; - width: 100% !important; - margin-left: 0 !important; - margin-right: 0 !important; -} -.email-reader-body.html-body [align="center"] { - margin-left: 0 !important; - margin-right: 0 !important; -} -.email-reader-body .email-inline-image-placeholder { - display: inline-flex; - align-items: center; - gap: 9px; - max-width: min(100%, 360px); - min-height: 58px; - margin: 6px 0; - padding: 8px 10px; - border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 72%, transparent); - color: var(--fg); - vertical-align: middle; -} -.email-inline-image-skeleton { - width: 58px; - height: 42px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 6px; - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--fg) 7%, transparent) 0%, - color-mix(in srgb, var(--fg) 13%, transparent) 45%, - color-mix(in srgb, var(--fg) 7%, transparent) 90% - ); - background-size: 220% 100%; - animation: email-skeleton-shimmer 1.35s ease-in-out infinite; -} -.email-inline-image-icon { - opacity: 0.52; - display: inline-flex; -} -.email-inline-image-placeholder.is-loading .email-inline-image-icon svg { - display: none; -} -.email-inline-image-info { - display: inline-flex; - flex-direction: column; - min-width: 0; - line-height: 1.25; -} -.email-inline-image-title { - font-size: 11px; - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-inline-image-sub { - font-size: 10px; - opacity: 0.58; -} -.email-inline-image-actions { - display: inline-flex; - align-items: center; - gap: 5px; - margin-left: auto; - flex: 0 0 auto; -} -.email-inline-image-btn { - display: inline-flex; - align-items: center; - justify-content: center; - transform: translateY(-2px); - height: 24px; - line-height: 1; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: inherit; - font: inherit; - font-size: 10px; - text-decoration: none; - cursor: pointer; -} -.email-inline-image-btn:hover { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); - color: var(--accent-primary, var(--red)); -} -.email-inline-image-download-btn { - width: 24px; - min-width: 24px; - padding: 0; -} -.email-inline-image-loaded { - max-width: 100%; - height: auto; - border-radius: 6px; -} -.email-inline-image-frame { - display: inline-flex; - max-width: 100%; - min-width: min(360px, 100%); - min-height: 90px; - margin: 6px 0; - align-items: center; - justify-content: center; - border-radius: 8px; - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--fg) 7%, transparent) 0%, - color-mix(in srgb, var(--fg) 13%, transparent) 45%, - color-mix(in srgb, var(--fg) 7%, transparent) 90% - ); - background-size: 220% 100%; -} -.email-inline-image-frame .email-inline-image-loaded { - opacity: 0; - transition: opacity 0.15s ease; -} -.email-inline-image-load-all { - display: flex; - align-items: center; - gap: 8px; - width: 100%; - max-width: 100%; - margin: -4px 0 6px; - padding: 7px 9px; - border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 72%, transparent); - font-size: 11px; -} -.email-inline-image-load-all span { - opacity: 0.68; - margin-right: auto; -} -.email-inline-image-load-all button { - transform: translateY(-4px); - height: 24px; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: inherit; - font: inherit; - font-size: 10px; - cursor: pointer; -} -.email-inline-image-load-all button:hover { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); - color: var(--accent-primary, var(--red)); -} -@media (max-width: 520px) { - .email-reader-body .email-inline-image-placeholder { - display: flex; - width: 100%; - } - .email-inline-image-skeleton { - width: 48px; - } - .email-inline-image-actions { - flex-direction: column; - align-items: stretch; - } -} -.email-reader-body.html-body table { max-width: 100%; border-collapse: collapse; } -/* Gmail Drive chips arrive with inline width/height/border/padding from - Gmail's composer (border:1px solid #ddd, white-ish bg assumed). On a - dark theme that reads as a near-invisible white box. Override with a - theme-aware chip surface so the filename + icon stay visible. */ -.email-reader-body .gmail_chip, -.email-reader-body .gmail_drive_chip { - width: auto !important; - max-width: 100% !important; - height: auto !important; - max-height: none !important; - display: inline-flex !important; - align-items: center !important; - gap: 6px; - padding: 6px 10px !important; - margin: 6px 0 !important; - border: 1px solid var(--border) !important; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.email-reader-body .gmail_chip:hover, -.email-reader-body .gmail_drive_chip:hover { - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)) !important; - background: color-mix(in srgb, var(--accent, var(--red)) 6%, transparent); -} -.email-reader-body .gmail_chip a, -.email-reader-body .gmail_drive_chip a { - color: var(--accent, var(--red)) !important; - display: inline-flex !important; - align-items: center; - gap: 6px; - max-width: 100% !important; -} -.email-reader-body .gmail_chip a > span, -.email-reader-body .gmail_drive_chip a > span { - color: var(--fg) !important; - font-size: 12px; -} -.email-reader-body .gmail_chip img, -.email-reader-body .gmail_drive_chip img { - width: 16px !important; - height: 16px !important; - padding-right: 0 !important; - filter: none; -} - -/* Force any phone/sms/date/address auto-link to inherit the body color so - numbers don't render as bright-blue browser-default text inside email - content. format-detection in turns off detection on iOS, but some - mail clients ship pre-wrapped tel: links — those still need taming. */ -.email-reader-body a[href^="tel:"], -.email-reader-body a[href^="sms:"], -.email-reader-body a[x-apple-data-detectors], -.email-bubble-body a[href^="tel:"], -.email-bubble-body a[href^="sms:"], -.email-bubble-body a[x-apple-data-detectors], -.email-thread-turn-body a[href^="tel:"], -.email-thread-turn-body a[href^="sms:"], -.email-thread-turn-body a[x-apple-data-detectors] { - color: inherit !important; - text-decoration: none !important; - pointer-events: auto; -} -.email-reader-body a, -.email-bubble-body a, -.email-thread-turn-body a { - color: var(--accent-primary, var(--red)); - text-decoration: underline; - overflow-wrap: anywhere; - word-break: normal; - cursor: pointer; -} -.email-reader-body a:hover, -.email-bubble-body a:hover, -.email-thread-turn-body a:hover { opacity: 0.8; } -/* Summary block — same band chrome as Attachments / Signature / Earlier- - thread (no accent tint, no rounded corners, no leading star icon). The - only difference is the label, so all collapsible sections in the email - reader look uniform. */ -.email-summary-panel { - margin: 0 0 12px 0; - padding: 10px 12px; - background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); - border-radius: 6px; -} -.email-summary-header { - display: flex; align-items: center; gap: 5px; - font-size: 10px; font-weight: 600; text-transform: uppercase; - color: var(--accent-primary, var(--red)); opacity: 0.85; - margin-bottom: 6px; letter-spacing: 0.4px; -} -.email-summary-content { - font-size: 12px; line-height: 1.5; color: var(--fg); white-space: pre-wrap; -} -/* Click-to-fold: tap the summary header to collapse/expand. The chevron - flips when collapsed, the content hides + the panel's bottom margin - tightens so a folded summary doesn't take up vertical space. */ -.email-summary-toggle { cursor: pointer; user-select: none; } -.email-summary-panel.collapsed { padding-bottom: 6px; margin-bottom: 6px; } -.email-translation-loading { - display: flex; - align-items: center; - justify-content: center; - min-height: 26px; -} -.email-translation-busy { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 7px; -} -.email-translation-spinner { - display: inline-flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; -} -.email-translation-loading-text { - opacity: 0.68; - font-size: 12px; -} -.email-translation-panel .email-translation-icon { - color: var(--accent-primary, var(--red)); -} - -/* Foldable attachments — same fold-on-click UX as the summary panel. */ -.email-reader-atts-wrap { - display: flex; flex-direction: column; - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} -.email-reader-atts-header { - display: flex; align-items: center; gap: 5px; - padding: 6px 14px; - font-size: 10px; font-weight: 600; text-transform: uppercase; - color: var(--fg); opacity: 0.7; - cursor: pointer; user-select: none; - letter-spacing: 0.4px; -} -.email-attachments-download-all { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - height: 22px; - padding: 0 8px; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); - color: var(--accent-primary, var(--red)); - font: inherit; - font-size: 10px; - font-weight: 700; - text-transform: none; - letter-spacing: 0; - cursor: pointer; - flex: 0 0 auto; -} -.email-attachments-download-all:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 60%, transparent); -} -.email-attachments-download-all .spinner-whirlpool, -.email-attachments-download-all .ai-spinner-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 !important; -} -.email-reader-atts-wrap > .email-reader-atts { - border-bottom: none !important; -} -.email-reader-atts-hidden-note { - padding: 0 14px 6px; - font-size: 10px; - color: var(--fg-muted); - opacity: 0.65; -} - -/* Quote fold = neutral full-width band (matches attachments header). */ -.email-quote-fold { - margin: 0 -14px; - padding: 0; - border: 0; - border-top: 1px solid var(--border) !important; - border-radius: 0; - outline: 0 !important; - box-shadow: none !important; - /* Neutralise any rich-mail HTML that injects a yellow / colored bg - or border onto blockquote ancestors. */ - background: transparent !important; -} -/* Same neutralisation for any element nested inside the fold (the inner - blockquote, paragraphs, divs from the original message). Yellow outlines - in Outlook-style mails almost always come from inline border-color or - box-shadow on these. */ -.email-quote-fold *, -.email-quote-fold *::before, -.email-quote-fold *::after { - outline-color: transparent !important; - box-shadow: none !important; -} -.email-quote-fold blockquote, -.email-quote-fold blockquote[type="cite"], -.email-quote-fold table, -.email-quote-fold div { - border-color: var(--border) !important; - /* Override the .msg blockquote rule that sets a colored left border via - var(--hl-function) — that's where the "yellow" outline was coming from. */ - border-left-color: var(--border) !important; - border-radius: 0 !important; - background: transparent !important; -} -/* JS marks the last .email-quote-fold in each email body with this class, - so it's the only one that gets rounded bottom corners. Avoids :has() - browser-support quirks. */ -.email-quote-fold.last-fold { - border-radius: 0 0 8px 8px; - overflow: hidden; -} -/* ── Threaded reply turns (recursive parser) ── */ -.email-thread-turn-body { - padding: 8px 14px 4px; - font-size: 12px; - line-height: 1.45; -} -/* Nested turns indent slightly + get their own card outline so the - reply hierarchy is obvious at a glance. */ -.email-thread-turn-body .email-thread-turn { - margin: 8px 0 4px; - margin-left: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -/* The original blockquote's left stripe is redundant when each turn is - already a card — drop it inside thread bodies. */ -.email-thread-turn-body blockquote { - border-left: none !important; - margin: 0 !important; - padding: 0 !important; - background: transparent !important; -} -/* Flatten the inner blockquote: no rounded corners, no yellow tint - inherited from rich-mail HTML. Subtle left border + neutral bg. */ -.email-quote-fold blockquote { - margin: 0; - padding: 8px 12px; - border: 0; - border-left: 2px solid var(--border); - border-radius: 0 !important; - background: transparent !important; - color: inherit; -} -/* "From … · Date …" chip appended to the summary line */ -.email-fold-summary-meta { - margin-left: 6px; - font-size: 10px; - font-weight: 500; - text-transform: none; - letter-spacing: 0; - opacity: 0.55; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -/* Sender name is the primary affordance — slightly larger and not all-caps so - "From: Sam" feels like a click target on the person, not a section label. */ -.email-fold-summary-name { - font-size: 11px; - font-weight: 600; - text-transform: none; - letter-spacing: 0.1px; - color: var(--fg); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.email-quote-fold .email-fold-summary { - display: flex; align-items: center; gap: 5px; - padding: 6px 14px; - cursor: pointer; list-style: none; - font-size: 10px; font-weight: 600; text-transform: uppercase; - letter-spacing: 0.4px; - /* Force neutral page colors regardless of any rich-mail HTML wrapping us */ - color: var(--fg) !important; - background: transparent !important; - background-color: transparent !important; - text-shadow: none !important; - font-style: normal !important; - opacity: 0.7; - user-select: none; - border-radius: 0; -} -.email-quote-fold .email-fold-summary > * { - /* Prevent inline-color attrs on summary children (icon SVGs, meta span) - from picking up a forced color from ancestor email HTML. */ - color: inherit !important; - background: transparent !important; -} -.email-quote-fold .email-fold-summary { list-style: none; list-style-type: none; } -.email-quote-fold[open] > *:not(summary) { - padding: 8px 14px 12px 14px; - border-top: 1px solid var(--border); -} - -/* Signature fold = clean full-width band like the attachments header. No - accent overlay, no rounded corners — neutral chrome so it doesn't look - like the AI Summary panel. */ -.email-sig-fold { - margin: 8px 0 0; - padding: 0; - border: 0; - border-top: 1px dashed color-mix(in srgb, var(--fg) 18%, transparent); - background: transparent; - border-radius: 0; -} -.email-sig-fold .email-fold-summary { - display: flex; align-items: center; gap: 5px; - padding: 4px 0; - cursor: pointer; - list-style: none; list-style-type: none; - font-size: 9.5px; font-weight: 600; text-transform: uppercase; - letter-spacing: 0.4px; - color: var(--fg); opacity: 0.55; - user-select: none; -} -.email-sig-fold[open] > *:not(summary) { - padding: 6px 0 0; - border-top: 0; - font-size: 11px; line-height: 1.45; - color: color-mix(in srgb, var(--fg) 75%, transparent); - white-space: pre-wrap; -} - -/* ── Chat-bubble layout for email threads ── - Each parsed turn becomes a bubble. The active account's outgoing - replies align right (mine); everyone else aligns left (theirs). - Bubbles read top→bottom oldest→newest, like a chat. */ -.email-bubbles { - display: flex; - flex-direction: column; - gap: 10px; - padding: 4px 0 8px; - margin: 0 -2px; -} -.email-bubble-row { - display: flex; - align-items: flex-end; - gap: 8px; - width: 100%; - min-width: 0; -} -.email-bubble { - flex: 0 1 auto; - max-width: 90%; - min-width: 0; - padding: 8px 12px; - border: 1px solid var(--border); - background: var(--panel); - font-size: 12.5px; - line-height: 1.5; - word-wrap: break-word; - overflow-wrap: anywhere; - overflow: hidden; -} -.email-bubble-mine .email-bubble { - background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 10%, var(--bg)); - border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 35%, var(--border)); - border-radius: 14px 14px 4px 14px; -} -.email-bubble-theirs .email-bubble { - background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 5%, var(--panel)); - border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 25%, var(--border)); - border-radius: 14px 14px 14px 4px; -} -.email-bubble-head { - display: flex; - align-items: baseline; - gap: 8px; - font-size: 10.5px; - margin-bottom: 4px; - opacity: 0.7; - min-width: 0; -} -.email-bubble-author { - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 50ch; -} -.email-bubble-date { - opacity: 0.7; - font-weight: 400; - white-space: nowrap; -} -.email-bubble-body { - white-space: normal; - /* Reader-controlled typography — override sender's inline styles AND - attribute-based sizing (, , , h1..h6). Weight - and italics still pass through for emphasis. */ - font-family: inherit; - font-size: var(--email-body-size, 13.5px); - line-height: 1.5; -} -/* Heading tags carry browser-default font-size multipliers that bypass - `font-size: inherit` when an inline style was set; pin them too. */ -.email-bubble-body h1, -.email-bubble-body h2, -.email-bubble-body h3, -.email-bubble-body h4, -.email-bubble-body h5, -.email-bubble-body h6, -.email-bubble-body big, -.email-bubble-body small, -.email-bubble-body font { - font-size: inherit !important; - line-height: inherit !important; -} -/* Sender HTML often wraps content in a fixed-width - or . Override so content fills the reader's available - width — otherwise resizing the email window doesn't reflow anything. */ -.email-bubble-body, -.email-bubble-body * { - max-width: 100% !important; -} -.email-bubble-body div[style*="width"], -.email-bubble-body table[width], -.email-bubble-body table { - width: 100% !important; - box-sizing: border-box !important; -} -.email-bubble-body blockquote { - border-left: 2px solid var(--border) !important; - margin: 4px 0 !important; - padding: 4px 10px !important; - background: transparent !important; - border-radius: 0 !important; - color: inherit; -} -.email-bubble-avatar { - flex-shrink: 0; - width: 26px; - height: 26px; - border-radius: 50%; - /* Per-sender color set inline (`style="background:hsl(...)"`); this - fallback only kicks in when JS hasn't assigned one. */ - background: color-mix(in srgb, var(--fg) 14%, transparent); - color: #fff; - text-shadow: 0 1px 0 rgba(0, 0, 0, 0.22); - display: flex; - align-items: center; - justify-content: center; - font-size: 9.5px; - font-weight: 600; - letter-spacing: 0.3px; - user-select: none; -} -/* Sig folds inside a bubble: tone down — no full-bleed band, since the - bubble already provides the chrome. */ -.email-bubble-body .email-sig-fold { - margin: 8px 0 0; - border-top: 1px dashed var(--border); -} -.email-bubble-body .email-sig-fold[open] > *:not(summary), -.email-bubble-body .email-quote-fold[open] > *:not(summary) { - padding: 6px 0 0; - border-top: 0; -} -@media (max-width: 600px) { - .email-bubble { max-width: 96%; } - .email-bubble-avatar { display: none; } -} - -/* ── Schedule Send modal ── */ -.schedule-send-body { - display: flex; - flex-direction: column; - gap: 6px; - padding: 16px 18px 12px; -} -.schedule-send-label { - font-size: 11px; - font-weight: 500; - letter-spacing: 0.3px; - text-transform: uppercase; - color: var(--fg); - opacity: 0.55; - margin-top: 6px; -} -.schedule-send-label:first-child { margin-top: 0; } -.schedule-send-presets { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 6px; - margin-bottom: 4px; -} -.schedule-send-presets .memory-toolbar-btn { - padding: 5px 10px 11px; - font-size: 12px; - justify-content: center; - line-height: 1; -} -.schedule-send-confirm svg { - width: 12px; - height: 12px; - vertical-align: -1px; - margin-right: 6px; -} -.schedule-send-datetime { - font-size: 13px; - padding: 8px 10px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - outline: none; - font-family: inherit; - width: 100%; - box-sizing: border-box; -} -.schedule-send-datetime:focus { - border-color: var(--hl-function); -} -.schedule-send-footer { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 8px; - padding: 10px 18px 14px; - border-top: 1px solid var(--border); - margin-top: 4px; -} -.schedule-send-confirm { - background: var(--accent-primary, var(--red)) !important; - color: #fff !important; - border-color: var(--accent-primary, var(--red)) !important; -} -.schedule-send-confirm:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 85%, white) !important; -} - -/* Senders embed inline colors in their HTML that clash with the app's - theme (black text in dark mode, white backgrounds, yellow highlights, - etc.). Force theme colors on every descendant of the rendered body so - emails inherit the user's chosen palette regardless of what the - sender's mail client emits. */ -.email-reader-body *, -.email-bubble-body * { - color: var(--fg) !important; - -webkit-text-fill-color: currentColor !important; - background-color: transparent !important; - background-image: none !important; -} -.email-reader-body a, -.email-bubble-body a, -.email-thread-turn-body a { - color: var(--hl-function) !important; -} -.email-reader-body a[href^="tel:"], -.email-reader-body a[href^="sms:"], -.email-reader-body a[href^="x-apple-data-detectors:"], -.email-reader-body a[x-apple-data-detectors], -.email-bubble-body a[href^="tel:"], -.email-bubble-body a[href^="sms:"], -.email-bubble-body a[href^="x-apple-data-detectors:"], -.email-bubble-body a[x-apple-data-detectors], -.email-thread-turn-body a[href^="tel:"], -.email-thread-turn-body a[href^="sms:"], -.email-thread-turn-body a[href^="x-apple-data-detectors:"], -.email-thread-turn-body a[x-apple-data-detectors] { - color: inherit !important; - -webkit-text-fill-color: currentColor !important; - text-decoration: none !important; - background-color: transparent !important; - background-image: none !important; - -webkit-background-clip: border-box !important; - background-clip: border-box !important; -} - -/* Prefer local/system emoji fonts; avoid remote font providers. */ -.doc-editor-textarea, .doc-editor-highlight, #doc-editor-code, -.doc-md-preview, .doc-csv-preview { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', monospace; - font-variant-emoji: text; -} -/* Preview panes claim the editor's space so the action footer stays pinned - at the bottom of the pane even when the rendered content is short - (shorter table, single-paragraph markdown, etc). */ -.doc-csv-preview, -.doc-md-preview { - flex: 1 1 auto; - min-height: 0; - overflow: auto; -} - -/* Email documents override: use colored system emoji in compose. */ -.doc-editor-textarea.email-mode, -#doc-editor-code.email-mode, -#doc-editor-code.email-mode .doc-editor-highlight, -#doc-editor-code.email-mode .doc-editor-textarea { - font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla" !important; - font-variant-emoji: emoji; -} - -/* Single-layer rendering for ALL documents (not just email): the highlight - overlay ( with hljs spans) and the transparent textarea use two - different rendering paths internally — the textarea uses the browser's - native form line-breaker, the overlay uses CSS line-breaking — and they - can never be guaranteed to wrap byte-identical no matter how many CSS - properties are pinned. Pick one source of truth: make the textarea its - own visible text, hide the overlay. Trade-off: no syntax highlighting in - the live editor (rendered markdown preview / chat output still has it). - The caret is now glued to the typed text by definition since both are - rendered by the same element. */ -.doc-editor-highlight, -#doc-editor-code.email-mode .doc-editor-highlight { - display: none !important; -} -/* Find bar is open: float the highlight overlay ON TOP of the - textarea with transparent background + transparent text, so only - the solid spans are visible. pointer-events - off so clicks still go through to the textarea below. */ -body.doc-find-active .doc-editor-highlight, -body.doc-find-active #doc-editor-code.email-mode .doc-editor-highlight { - display: block !important; - z-index: 5 !important; - background: transparent !important; - pointer-events: none; -} -body.doc-find-active .doc-editor-highlight, -body.doc-find-active .doc-editor-highlight * { - color: transparent !important; - background: transparent !important; -} diff --git a/static/css/14-email.css b/static/css/14-email.css new file mode 100644 index 000000000..56d1df6df --- /dev/null +++ b/static/css/14-email.css @@ -0,0 +1,4729 @@ +/* 14-email - 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. + */ +#group-model-picker input[type="checkbox"] { + accent-color: var(--accent, var(--red)); +} +#group-model-picker input[type="radio"] { + accent-color: var(--accent, var(--red)); +} +#group-model-picker .btn-primary { + background: var(--accent, var(--red)); + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 500; +} +#group-model-picker .btn-primary:disabled { + opacity: 0.4; + cursor: not-allowed; +} +#group-model-picker .btn-primary:hover:not(:disabled) { + filter: brightness(1.1); +} + +/* ── Email document type ── */ +.doc-email-header { + display: flex; + flex-direction: column; + gap: 6px; + padding: 10px 12px; + border-bottom: 1px solid var(--border); + background: var(--bg); + flex-shrink: 0; +} +.email-field { + display: flex; + align-items: center; + gap: 8px; +} +.email-field label { + font-size: 11px; + font-weight: 600; + color: var(--fg); + opacity: 0.5; + min-width: 50px; + text-align: right; +} +.email-field input { + flex: 1; + background: transparent; + border: 1px solid var(--border); + border-radius: 4px; + padding: 5px 8px; + font-size: 13px; + font-family: inherit; + color: var(--fg); + outline: none; +} +.email-field input:focus { + border-color: var(--accent, #4a9eff); +} +.email-actions { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 2px; +} +.email-draft-btn { + background: transparent; + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 14px; + font-size: 12px; + cursor: pointer; + font-family: inherit; +} +.email-draft-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } +.email-draft-btn:disabled { opacity: 0.5; cursor: not-allowed; } + +/* ── Email inbox list ── */ +.email-body { border-top: 1px solid var(--border); } +.email-folder-bar { padding: 4px 8px; border-bottom: 1px solid var(--border); } +.email-folder-select { + background: transparent; border: 1px solid var(--border); border-radius: 4px; + color: var(--fg); font-size: 11px; padding: 2px 6px; cursor: pointer; width: 100%; height: 26px; +} +.email-item { + display: flex; align-items: flex-start; gap: 8px; padding: 8px 12px; + cursor: pointer; border-bottom: 1px solid var(--border); position: relative; transition: background 0.1s; +} +.email-item:hover { background: var(--hover-bg, rgba(255,255,255,0.03)); } +.email-item-spam:hover { opacity: 0.75; } +.email-tag-spam { + display: inline-flex; align-items: center; gap: 4px; + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); + color: var(--accent, var(--red)); + border-radius: 10px; padding: 1px 6px; font-size: 9px; font-weight: 600; + text-transform: lowercase; letter-spacing: 0.3px; +} +.email-spam-unflag { + background: none; border: none; color: inherit; cursor: pointer; + padding: 0 2px; font-size: 10px; line-height: 1; opacity: 0.6; +} +.email-spam-unflag:hover { opacity: 1; color: var(--fg); } +/* Unread state is now a subtle icon, not full styling */ +.email-done-check { + display: inline-flex; align-items: center; justify-content: center; + width: 16px; height: 16px; border-radius: 50%; background: transparent; + border: 1.5px solid var(--border); color: transparent; + flex-shrink: 0; cursor: pointer; transition: all 0.15s; + margin-right: 4px; +} +.email-done-check:hover { border-color: var(--accent, #4a9eff); } +.email-done-check.active { background: var(--accent, #4a9eff); border-color: var(--accent, #4a9eff); color: #fff; } +.email-done-check.active svg { display: block; } +.email-done-check svg { display: none; } + + + + +.email-delete-busy > :not(.email-delete-overlay) { + opacity: 0.42; + transition: opacity 0.12s ease; +} + +.email-delete-overlay { + position: absolute; + inset: 0; + z-index: 8; + display: flex; + align-items: center; + justify-content: center; + border-radius: inherit; + background: color-mix(in srgb, var(--panel, var(--bg)) 82%, transparent); + pointer-events: none; +} + +.email-delete-overlay .spinner-whirlpool, +.email-delete-overlay .ai-spinner-whirlpool { + margin: 0 !important; +} + +.doclib-card.email-card-removing { + pointer-events: none !important; + max-height: 0 !important; + padding-top: 0 !important; + padding-bottom: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + border-color: transparent !important; + opacity: 0; + transform: translateX(-10px) scale(0.985); + transition: + opacity 0.18s ease, + transform 0.22s ease, + max-height 0.23s ease, + padding 0.23s ease, + margin 0.23s ease, + border-color 0.18s ease; +} + +@media (prefers-reduced-motion: reduce) { + .doclib-card.email-card-removing { + transition: opacity 0.08s ease; + transform: none; + } +} + +/* Library card done check — always visible, subtle icon next to title */ +.email-card-done { + display: inline-flex; align-items: center; justify-content: center; + flex-shrink: 0; cursor: pointer; + transition: opacity 0.15s, color 0.15s; + opacity: 0.15; color: var(--fg); +} +.email-card-titlerow .memory-item-title { + flex: 1 1 auto; + min-width: 0; +} +.email-card-titlerow { position: relative; min-width: 0; } +.email-card-status { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 7px; + flex: 0 0 auto; + min-width: max-content; + margin-left: auto; + white-space: nowrap; +} +.email-card-status > * { + flex: 0 0 auto; +} +.email-card-status + .email-card-nav-arrows { + margin-left: 6px; +} +.email-card-status .email-card-nav-arrows { + margin-left: 2px; +} +.email-card-attachment { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 13px; + width: 13px; + height: 18px; +} +/* Hover preview: bright accent when unchecked so the user sees a check coming. + Once active, keep the exact same color on hover so the done state does not + visually flip while the pointer is still over it. */ +.email-card-done:not(.active):hover { + opacity: 0.75 !important; + color: var(--accent-primary, var(--red)); +} +.email-card-done.active { opacity: 0.95; color: var(--accent-primary, var(--red)); } +.email-card-done.active:hover { + opacity: 0.95 !important; + color: var(--accent-primary, var(--red)) !important; +} +.email-card-favorite { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 18px; + width: 18px; + height: 18px; + margin: 0; + padding: 0; + border: 0; + background: transparent; + color: var(--fg); + cursor: pointer; + position: relative; + top: -3px; + opacity: 0.18; + transition: opacity 0.15s, color 0.15s, transform 0.15s; +} +.email-card-favorite:hover, +.email-card-favorite:focus-visible { + opacity: 0.8; + color: var(--accent-primary, var(--red)); +} +.email-card-favorite.active { + opacity: 0.9; + color: var(--accent-primary, var(--red)); +} +@keyframes check-pop { + 0% { transform: scale(1); } + 40% { transform: scale(1.7) rotate(-10deg); } + 70% { transform: scale(1.1) rotate(4deg); } + 100% { transform: scale(1) rotate(0); } +} +@keyframes check-unpop { + 0% { transform: scale(1); opacity: 0.95; } + 35% { transform: scale(0.45) rotate(8deg); opacity: 0.05; } + 65% { transform: scale(0.9); opacity: 0.2; } + 100% { transform: scale(1); } +} + +/* Expanded email preview in library — override .memory-item row layout */ +.doclib-card.email-card-expanded.memory-item { + cursor: default !important; + background: var(--bg) !important; + border: 1px solid var(--border) !important; + display: flex !important; + flex-direction: column !important; + align-items: stretch !important; + /* Fill the available grid height instead of the old hardcoded 70vh — + the modal already caps height (90dvh on mobile, auto on desktop), so + the card just needs to flex into whatever remains. */ + flex: 1 1 auto !important; + height: 100% !important; + min-height: 0 !important; + max-height: none !important; + padding: 0 !important; + overflow: hidden !important; + gap: 0 !important; +} +.doclib-card.email-card-expanded.memory-item > div:first-child { + /* The summary content (subject, date) - acts as the title bar. Left padding + trimmed 14→6px to shift the title 8px left total. */ + padding: 2px 4px 10px !important; /* top trimmed to lift the header text up */ + margin-top: -14px !important; /* pull the whole bar up into the freed space */ + border-bottom: 1px solid var(--border); + flex: 0 0 auto !important; + width: 100%; +} +/* Keep the per-card '...' actions menu visible while the email card is + expanded — we used to hide it and surface a duplicate menu button in + the expanded header, but that was redundant. The bottom menu now + carries the actions in both states. */ +/* When expanded inline, show the subject a touch larger than the 12px card + title (close to the new-tab window header) without being oversized. */ +.doclib-card.email-card-expanded .memory-item-title { + font-size: 14px; + font-weight: 600; +} +/* The sender name is redundant once expanded (the From: row shows it just + below), so drop it and keep only the date — which then sits left-aligned + directly under the subject. Nudge the date 1px right to line up exactly with + the subject text, and up closer to it. */ +.doclib-card.email-card-expanded .email-meta-sender, +.doclib-card.email-card-expanded .email-meta-sep { + display: none; +} +.doclib-card.email-card-expanded .memory-item-meta { + margin-top: -9px !important; +} +.doclib-card.email-card-expanded .email-meta-date { + margin-left: 3px; +} +.email-card-reader { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + font-size: 12px; +} +.email-card-reader-loading { + background: color-mix(in srgb, var(--bg) 86%, #000 14%); + border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); + min-height: 240px; +} +.email-card-reader-error { + background: color-mix(in srgb, var(--bg) 86%, #000 14%); + border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); +} +.email-reader-load-error { + display: flex; + min-height: 180px; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 9px; + padding: 24px 16px; + text-align: center; + color: var(--fg); +} +.email-reader-load-error-title { + font-size: 13px; + font-weight: 700; +} +.email-reader-load-error-msg { + max-width: min(460px, 92%); + color: color-mix(in srgb, var(--fg) 62%, transparent); + font-size: 11px; + line-height: 1.35; + overflow-wrap: anywhere; +} +.email-skeleton-line, +.email-skeleton-dot { + display: block; + border-radius: 999px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 12%, transparent) 0%, + color-mix(in srgb, var(--fg) 22%, transparent) 45%, + color-mix(in srgb, var(--fg) 12%, transparent) 90% + ); + background-size: 220% 100%; + animation: email-skeleton-shimmer 1.35s ease-in-out infinite; +} +.email-skeleton-dot { + width: 9px; + height: 9px; + flex: 0 0 auto; +} +.email-list-skeleton { + position: absolute; + inset: 0; + padding: 10px 7px; + display: flex; + flex-direction: column; + gap: 8px; + pointer-events: none; +} +.email-skeleton-row { + display: flex; + align-items: center; + gap: 10px; + min-height: 48px; + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--border) 76%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 70%, transparent); +} +.email-skeleton-lines { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 7px; +} +.email-reader-skeleton { + display: flex; + flex-direction: column; + min-height: 100%; + flex: 1; +} +.email-reader-skeleton-header { + min-height: 60px; + padding: 12px 14px; + display: flex; + align-items: flex-start; + gap: 8px; + border-bottom: 1px solid var(--border); + background: color-mix(in srgb, var(--panel) 72%, transparent); +} +.email-reader-skeleton-header .chip { width: 132px; height: 18px; } +.email-reader-skeleton-header .chip.short { width: 78px; } +.email-reader-skeleton-header .action { + width: 38px; + height: 28px; + margin-left: auto; + border-radius: 6px; +} +.email-reader-skeleton-header .action + .action { margin-left: 0; } +.email-reader-skeleton-atts { + display: flex; + gap: 7px; + padding: 8px 14px; + border-bottom: 1px solid var(--border); +} +.email-reader-skeleton-atts .attachment { width: 126px; height: 20px; border-radius: 6px; } +.email-reader-skeleton-atts .attachment.short { width: 88px; } +.email-reader-skeleton-body { + display: flex; + flex-direction: column; + gap: 11px; + padding: 16px 14px; + min-height: 340px; + flex: 1; +} +.email-reader-skeleton-body .body { width: 88%; height: 9px; border-radius: 6px; } +.email-reader-skeleton-body .body.wide { width: 100%; } +.email-reader-skeleton-body .body.medium { width: 94%; } +.email-reader-skeleton-body .body.short { width: 62%; } +.email-reader-skeleton-body .body.gap { + margin-top: 12px; + width: 78%; +} +.email-search-progress-row { + display: flex; + align-items: center; + justify-content: center; + gap: 7px; + min-height: 30px; + padding: 6px 10px; + font-size: 11px; + opacity: 0.65; +} +.email-search-progress-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent-primary, var(--red)); + box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); + animation: email-search-progress-pulse 1.2s ease-in-out infinite; +} +@media (prefers-reduced-motion: reduce) { + .email-skeleton-line, + .email-skeleton-dot, + .email-inline-image-skeleton, + .email-inline-image-placeholder.is-loading .email-inline-image-icon, + .email-inline-image-frame.is-loading, + .email-search-progress-dot { + animation: none; + } +} +/* Per-email unread dot in the expanded reader's title row. Background color + stays inline (per-sender hue from _senderColor), but the dot gets a soft + breathing glow + a 4px vertical nudge so it reads as centered against + the row instead of riding the baseline. */ +.email-card-unread-dot { + position: relative; + top: 0; + animation: email-card-unread-breathe 2.2s ease-in-out infinite; +} +.email-reader-header { + display: flex; + flex-direction: row; + /* Top-align so when the user expands To/Cc, the action cluster + stays on the From line above instead of drifting to the middle. */ + align-items: flex-start; + gap: 12px; + /* Taller header so the 44px-tall absolute-positioned action + cluster inside the From row has vertical breathing room. */ + padding: 8px 14px; + min-height: 60px; + border-bottom: 1px solid var(--border); + background: var(--bg); + flex-shrink: 0; + /* Containing block for the absolute action cluster. */ + position: relative; + /* Container so the action-cluster wrap breakpoints below react to + the reader's own width instead of the page viewport. */ + container-type: inline-size; + container-name: emailreader; +} +.email-reader-header > .email-reader-actions { + margin-left: auto; + flex-shrink: 0; +} +/* Meta block stacks the From / To / Cc rows; each row keeps label + tight to its chip(s) on the same line. The recipient-chips inside + scroll horizontally so long lists slide under the floating action + cluster on the right. */ +.email-reader-meta { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + /* Positioning context for the absolute action cluster. */ + position: relative; +} +.email-reader-meta .recipient-chips { + display: inline-flex; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + gap: 4px; + max-width: 100%; + scrollbar-width: none; + -ms-overflow-style: none; +} +.email-reader-meta .recipient-chips::-webkit-scrollbar { display: none; } +.email-reader-meta-row { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.email-reader-meta-row.email-reader-meta-from { + position: relative; + /* Never wrap "From:" off the chip onto its own line — the chip + span has overflow-x scroll built in, so it shrinks/scrolls + instead of jumping to a new row. */ + flex-wrap: nowrap; + min-width: 0; + /* Reserve space on the right for the 1-row cluster in wide mode. */ + padding-right: 320px; +} +.email-reader-meta > .email-reader-meta-details { + min-width: 0; + padding-right: 320px; +} +.email-reader-meta > .email-reader-actions-inline { + position: absolute; + top: 1px; + right: 0; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + justify-content: flex-end; + gap: 4px; + z-index: 2; + /* Sit visually above the From baseline like the lifted overlay. */ + margin-top: -9px; +} +/* Recipient chip span keeps at least ~4 characters visible (60px) + before the overflow-scroll kicks in, so the sender / first + recipient name is always partly readable. */ +.email-reader-meta .recipient-chips { + min-width: 60px; +} +/* Medium reader widths: cap the cluster at 3 icons per row so it + never wraps past 2 rows. min-width forces the cluster to reserve + enough room for 3 icons in a row — without it, grid col 2 could + shrink below the cap and the cluster fanned out into 4+ short + rows. */ +@container emailreader (max-width: 450px) { + .email-reader-meta > .email-reader-actions-inline { + /* 3 icons (48px each) + 2 gaps (4px each) = 152px exact. Adding + 6px slack so subpixel rounding can't tip a row from 3 to 2 + icons and fan the cluster into a 3rd row. */ + min-width: 158px; + max-width: 158px; + /* Solid background so the email body content behind the cluster + doesn't peek through the gaps between icons or the slack on + the right edge when the cluster's wrapped to 2 rows. */ + background: var(--bg); + } + .email-reader-meta > .email-reader-actions-inline .memory-toolbar-btn.reader-icon-btn { + width: 48px; + min-width: 48px; + max-width: 48px; + height: 40px; + padding-left: 0; + padding-right: 0; + gap: 2px; + } + .email-reader-meta > .email-reader-actions-inline .reader-btn-label { + display: block; + max-width: 44px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 8px; + line-height: 1; + } + /* Tighten the right-side padding now that the cluster is 158px + wide (instead of the ~308 wide 1-row layout). */ + .email-reader-meta-row.email-reader-meta-from, + .email-reader-meta > .email-reader-meta-details { + padding-right: 170px; + } + /* Cluster wrapped to 2 rows (44px + 4 gap + 44px = 92px) sits + absolute above the From baseline. Set min-height on the meta + (a descendant of the container, more reliable than targeting + the container itself) so the header grows enough to contain + the cluster + 4px of breathing room between the cluster's + bottom and the header bottom. */ + .email-reader-meta { + min-height: 96px; + } +} +/* Very narrow reader: once the chip span has shrunk to its ~4 + character min, the cluster snaps to absolute overlay with a soft + shadow so it visually floats above the truncated chip. */ +@container emailreader (max-width: 380px) { + /* Pane too narrow even for 158px of right-side reservation — kill + the padding so chips use the full width, and let the cluster + overlay via z-index + shadow. */ + .email-reader-meta-row.email-reader-meta-from, + .email-reader-meta > .email-reader-meta-details { + padding-right: 0; + } + .email-reader-meta > .email-reader-actions-inline { + /* Keep the same -7px lift as the medium-width state so the + cluster doesn't slide ~4px DOWN when it transitions into + overlay mode. */ + /* margin-top stays inherited at -7px from the base rule. */ + /* 158px content + 22px padding-left so the inner row still fits + 3 icons cleanly. */ + min-width: 180px; + max-width: 180px; + background: linear-gradient(to right, transparent 0, var(--bg) 18px); + padding-left: 22px; + /* Soft shadow on the left edge so the overlap reads as the + cluster "floating over" the chip. */ + box-shadow: -6px 0 12px -6px rgba(0, 0, 0, 0.25); + } +} +/* Mobile: drop the box-shadow AND the left-edge gradient fade — + either can read as a shadow under the icons on phone screens. + Also lift the cluster 1px more. !important needed because the + @container query overlay rule has the same selector specificity. */ +@media (max-width: 768px) { + .memory-toolbar-btn[data-mode="ai-reply-fast"] { + height: 30px !important; + min-height: 30px !important; + } + .email-reader-meta > .email-reader-actions-inline { + box-shadow: none !important; + background: none !important; + padding-left: 0 !important; + margin-top: -10px; + } +} +/* Docked panes used to flow the cluster under the meta; now they + share the same absolute-top-right + wrap behaviour as undocked. + No docked-specific overrides — the container queries below pick + the right cluster size and overlay state from the reader width. */ +/* Gmail-style chevron — collapsed view shows only From; the chevron + reveals the To/Cc details inline below. */ +.email-reader-meta-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + margin-left: -3px; + padding: 0; + border: none; + background: transparent; + color: inherit; + opacity: 0.55; + cursor: pointer; + border-radius: 4px; + flex-shrink: 0; +} +.email-reader-meta-toggle:hover { opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent); } +.email-reader-meta-toggle svg { transition: transform 0.15s ease; } +.email-reader-meta-toggle.open svg { transform: rotate(180deg); } +.email-reader-meta-toggle.email-reader-cc-toggle { + width: auto; + min-width: 18px; + gap: 3px; + margin-left: 1px; + padding: 0 5px; + color: var(--accent, var(--red)); + font-family: inherit; + font-size: 9px; + font-weight: 600; + line-height: 1; +} +.email-reader-meta-details { + display: flex; + flex-direction: column; + gap: 6px; +} +/* When expanded, To/Cc details live in the header flow. A floating popover + can overlap the folded attachment strip directly beneath the header. */ +.email-reader-meta > .email-reader-meta-details:not([hidden]) { + position: relative; + top: auto; + left: auto; + right: auto; + margin-top: 4px; + background: transparent; + border: 0; + border-radius: 0; + padding: 0; + box-shadow: none; + z-index: 1; +} +/* Reserve a fixed label width so From: / To: / Cc: all align and + their chips start at the same x. */ +.email-reader-meta-row strong { opacity: 0.5; font-weight: 600; flex-shrink: 0; min-width: 36px; } +.email-reader-meta-row > span { + white-space: nowrap; + min-width: 0; +} +/* Recipient chips */ +.recipient-chips { + display: inline-flex !important; flex-wrap: wrap; gap: 4px; + white-space: normal !important; overflow: visible !important; + text-overflow: clip !important; +} +.recipient-chip { + display: inline-flex; align-items: center; + gap: 5px; + padding: 1px 8px; font-size: 10px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid var(--border); + border-radius: 10px; + color: var(--fg); + white-space: nowrap; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, max-width 0.2s; + max-width: 220px; + overflow: hidden; + text-overflow: ellipsis; +} +.recipient-chip-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} +.recipient-chip-copy { + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + top: -2px; + width: 14px; + height: 14px; + padding: 0; + border: none; + background: none; + color: inherit; + opacity: 0.55; + cursor: pointer; + flex: 0 0 auto; +} +.recipient-chip-copy:hover, +.recipient-chip-copy.copied { + opacity: 1; + color: var(--accent-primary, var(--red)); +} +.recipient-chip-copy[hidden] { + display: none !important; +} +.recipient-chip:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); +} +.recipient-chip.expanded { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); + border-color: var(--accent-primary, var(--red)); + max-width: 500px; +} +.recipient-chip.popover-open { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); + border-color: var(--accent-primary, var(--red)); +} +.recipient-chip-popover { + position: fixed; + z-index: 12050; + display: flex; + align-items: center; + gap: 8px; + max-width: min(420px, calc(100vw - 20px)); + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--bg) 96%, var(--fg) 4%); + color: var(--fg); + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.42); + font-size: 12px; + line-height: 1.35; + pointer-events: auto; +} +.recipient-chip-popover-main { + min-width: 0; +} +.recipient-chip-popover-name { + font-weight: 650; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.recipient-chip-popover-detail { + overflow-wrap: anywhere; + opacity: 0.86; +} +.recipient-chip-popover-copy { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 26px; + height: 26px; + padding: 0; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + color: inherit; + cursor: pointer; +} +.recipient-chip-popover-copy:hover, +.recipient-chip-popover-copy.copied { + color: var(--accent-primary, var(--red)); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, transparent); +} +@container docpane (max-width: 460px) { + .email-reader-header { + flex-direction: column; + gap: 6px; + } + .email-reader-header > .email-reader-actions { + /* On narrow widths fall back to in-flow so it doesn't overlap + the meta entirely. */ + position: static; + align-self: flex-end; + background: none; + padding-left: 0; + } + .email-reader-meta-row:not(.email-reader-meta-from) { + padding-right: 0; + } + .email-reader-meta-row strong { + min-width: 0; + } + .recipient-chip { + max-width: 100%; + } +} +/* Mobile: long recipient lists (To/Cc with many addresses) shouldn't wrap to + N rows and push the body down. Keep them on one row, horizontally scrollable, + no scrollbar chrome. */ +@media (max-width: 768px) { + .recipient-chips { + flex-wrap: nowrap !important; + overflow-x: auto !important; + overflow-y: hidden !important; + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + } + .recipient-chips::-webkit-scrollbar { display: none; } + .recipient-chip { flex-shrink: 0; } +} +.email-reader-actions { + display: flex; flex-direction: column; gap: 4px; align-items: flex-end; + flex-shrink: 0; + margin-top: -4px; +} +/* Two stacked rows: Reply/Reply-all/Forward on the From line, with + Summary/AI-reply/More on a row below. */ +.email-reader-actions-row { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: flex-end; + gap: 4px; +} +.email-reader-atts { + display: flex; flex-wrap: wrap; gap: 6px; + padding: 8px 14px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.email-reader-body { + font-size: 12px; + line-height: 1.55; + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: anywhere; + flex: 1; + overflow-y: auto; + padding: 14px; + min-height: 0; + background: var(--panel); + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--border) 65%, transparent); + /* Use the OS colored emoji font for incoming mail content */ + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"; + font-variant-emoji: emoji; +} +.email-reader-body.html-body img { max-width: 100%; height: auto; } +.email-reader-body.html-body [style*="max-width:600px"], +.email-reader-body.html-body [style*="max-width: 600px"], +.email-reader-body.html-body [style*="width:600px"], +.email-reader-body.html-body [style*="width: 600px"], +.email-reader-body.html-body table[width="600"] { + max-width: none !important; + width: 100% !important; + margin-left: 0 !important; + margin-right: 0 !important; +} +.email-reader-body.html-body [align="center"] { + margin-left: 0 !important; + margin-right: 0 !important; +} +.email-reader-body .email-inline-image-placeholder { + display: inline-flex; + align-items: center; + gap: 9px; + max-width: min(100%, 360px); + min-height: 58px; + margin: 6px 0; + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 72%, transparent); + color: var(--fg); + vertical-align: middle; +} +.email-inline-image-skeleton { + width: 58px; + height: 42px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 6px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 7%, transparent) 0%, + color-mix(in srgb, var(--fg) 13%, transparent) 45%, + color-mix(in srgb, var(--fg) 7%, transparent) 90% + ); + background-size: 220% 100%; + animation: email-skeleton-shimmer 1.35s ease-in-out infinite; +} +.email-inline-image-icon { + opacity: 0.52; + display: inline-flex; +} +.email-inline-image-placeholder.is-loading .email-inline-image-icon svg { + display: none; +} +.email-inline-image-info { + display: inline-flex; + flex-direction: column; + min-width: 0; + line-height: 1.25; +} +.email-inline-image-title { + font-size: 11px; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-inline-image-sub { + font-size: 10px; + opacity: 0.58; +} +.email-inline-image-actions { + display: inline-flex; + align-items: center; + gap: 5px; + margin-left: auto; + flex: 0 0 auto; +} +.email-inline-image-btn { + display: inline-flex; + align-items: center; + justify-content: center; + transform: translateY(-2px); + height: 24px; + line-height: 1; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: inherit; + font: inherit; + font-size: 10px; + text-decoration: none; + cursor: pointer; +} +.email-inline-image-btn:hover { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); + color: var(--accent-primary, var(--red)); +} +.email-inline-image-download-btn { + width: 24px; + min-width: 24px; + padding: 0; +} +.email-inline-image-loaded { + max-width: 100%; + height: auto; + border-radius: 6px; +} +.email-inline-image-frame { + display: inline-flex; + max-width: 100%; + min-width: min(360px, 100%); + min-height: 90px; + margin: 6px 0; + align-items: center; + justify-content: center; + border-radius: 8px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 7%, transparent) 0%, + color-mix(in srgb, var(--fg) 13%, transparent) 45%, + color-mix(in srgb, var(--fg) 7%, transparent) 90% + ); + background-size: 220% 100%; +} +.email-inline-image-frame .email-inline-image-loaded { + opacity: 0; + transition: opacity 0.15s ease; +} +.email-inline-image-load-all { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + max-width: 100%; + margin: -4px 0 6px; + padding: 7px 9px; + border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 72%, transparent); + font-size: 11px; +} +.email-inline-image-load-all span { + opacity: 0.68; + margin-right: auto; +} +.email-inline-image-load-all button { + transform: translateY(-4px); + height: 24px; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: inherit; + font: inherit; + font-size: 10px; + cursor: pointer; +} +.email-inline-image-load-all button:hover { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); + color: var(--accent-primary, var(--red)); +} +@media (max-width: 520px) { + .email-reader-body .email-inline-image-placeholder { + display: flex; + width: 100%; + } + .email-inline-image-skeleton { + width: 48px; + } + .email-inline-image-actions { + flex-direction: column; + align-items: stretch; + } +} +.email-reader-body.html-body table { max-width: 100%; border-collapse: collapse; } +/* Gmail Drive chips arrive with inline width/height/border/padding from + Gmail's composer (border:1px solid #ddd, white-ish bg assumed). On a + dark theme that reads as a near-invisible white box. Override with a + theme-aware chip surface so the filename + icon stay visible. */ +.email-reader-body .gmail_chip, +.email-reader-body .gmail_drive_chip { + width: auto !important; + max-width: 100% !important; + height: auto !important; + max-height: none !important; + display: inline-flex !important; + align-items: center !important; + gap: 6px; + padding: 6px 10px !important; + margin: 6px 0 !important; + border: 1px solid var(--border) !important; + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.email-reader-body .gmail_chip:hover, +.email-reader-body .gmail_drive_chip:hover { + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)) !important; + background: color-mix(in srgb, var(--accent, var(--red)) 6%, transparent); +} +.email-reader-body .gmail_chip a, +.email-reader-body .gmail_drive_chip a { + color: var(--accent, var(--red)) !important; + display: inline-flex !important; + align-items: center; + gap: 6px; + max-width: 100% !important; +} +.email-reader-body .gmail_chip a > span, +.email-reader-body .gmail_drive_chip a > span { + color: var(--fg) !important; + font-size: 12px; +} +.email-reader-body .gmail_chip img, +.email-reader-body .gmail_drive_chip img { + width: 16px !important; + height: 16px !important; + padding-right: 0 !important; + filter: none; +} + +/* Force any phone/sms/date/address auto-link to inherit the body color so + numbers don't render as bright-blue browser-default text inside email + content. format-detection in turns off detection on iOS, but some + mail clients ship pre-wrapped tel: links — those still need taming. */ +.email-reader-body a[href^="tel:"], +.email-reader-body a[href^="sms:"], +.email-reader-body a[x-apple-data-detectors], +.email-bubble-body a[href^="tel:"], +.email-bubble-body a[href^="sms:"], +.email-bubble-body a[x-apple-data-detectors], +.email-thread-turn-body a[href^="tel:"], +.email-thread-turn-body a[href^="sms:"], +.email-thread-turn-body a[x-apple-data-detectors] { + color: inherit !important; + text-decoration: none !important; + pointer-events: auto; +} +.email-reader-body a, +.email-bubble-body a, +.email-thread-turn-body a { + color: var(--accent-primary, var(--red)); + text-decoration: underline; + overflow-wrap: anywhere; + word-break: normal; + cursor: pointer; +} +.email-reader-body a:hover, +.email-bubble-body a:hover, +.email-thread-turn-body a:hover { opacity: 0.8; } +/* Summary block — same band chrome as Attachments / Signature / Earlier- + thread (no accent tint, no rounded corners, no leading star icon). The + only difference is the label, so all collapsible sections in the email + reader look uniform. */ +.email-summary-panel { + margin: 0 0 12px 0; + padding: 10px 12px; + background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); + border-radius: 6px; +} +.email-summary-header { + display: flex; align-items: center; gap: 5px; + font-size: 10px; font-weight: 600; text-transform: uppercase; + color: var(--accent-primary, var(--red)); opacity: 0.85; + margin-bottom: 6px; letter-spacing: 0.4px; +} +.email-summary-content { + font-size: 12px; line-height: 1.5; color: var(--fg); white-space: pre-wrap; +} +/* Click-to-fold: tap the summary header to collapse/expand. The chevron + flips when collapsed, the content hides + the panel's bottom margin + tightens so a folded summary doesn't take up vertical space. */ +.email-summary-toggle { cursor: pointer; user-select: none; } +.email-summary-panel.collapsed { padding-bottom: 6px; margin-bottom: 6px; } +.email-translation-loading { + display: flex; + align-items: center; + justify-content: center; + min-height: 26px; +} +.email-translation-busy { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; +} +.email-translation-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; +} +.email-translation-loading-text { + opacity: 0.68; + font-size: 12px; +} +.email-translation-panel .email-translation-icon { + color: var(--accent-primary, var(--red)); +} + +/* Foldable attachments — same fold-on-click UX as the summary panel. */ +.email-reader-atts-wrap { + display: flex; flex-direction: column; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.email-reader-atts-header { + display: flex; align-items: center; gap: 5px; + padding: 6px 14px; + font-size: 10px; font-weight: 600; text-transform: uppercase; + color: var(--fg); opacity: 0.7; + cursor: pointer; user-select: none; + letter-spacing: 0.4px; +} +.email-attachments-download-all { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + height: 22px; + padding: 0 8px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); + color: var(--accent-primary, var(--red)); + font: inherit; + font-size: 10px; + font-weight: 700; + text-transform: none; + letter-spacing: 0; + cursor: pointer; + flex: 0 0 auto; +} +.email-attachments-download-all:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 60%, transparent); +} +.email-attachments-download-all .spinner-whirlpool, +.email-attachments-download-all .ai-spinner-whirlpool { + width: 12px !important; + height: 12px !important; + margin: 0 !important; +} +.email-reader-atts-wrap > .email-reader-atts { + border-bottom: none !important; +} +.email-reader-atts-hidden-note { + padding: 0 14px 6px; + font-size: 10px; + color: var(--fg-muted); + opacity: 0.65; +} + +/* Quote fold = neutral full-width band (matches attachments header). */ +.email-quote-fold { + margin: 0 -14px; + padding: 0; + border: 0; + border-top: 1px solid var(--border) !important; + border-radius: 0; + outline: 0 !important; + box-shadow: none !important; + /* Neutralise any rich-mail HTML that injects a yellow / colored bg + or border onto blockquote ancestors. */ + background: transparent !important; +} +/* Same neutralisation for any element nested inside the fold (the inner + blockquote, paragraphs, divs from the original message). Yellow outlines + in Outlook-style mails almost always come from inline border-color or + box-shadow on these. */ +.email-quote-fold *, +.email-quote-fold *::before, +.email-quote-fold *::after { + outline-color: transparent !important; + box-shadow: none !important; +} +.email-quote-fold blockquote, +.email-quote-fold blockquote[type="cite"], +.email-quote-fold table, +.email-quote-fold div { + border-color: var(--border) !important; + /* Override the .msg blockquote rule that sets a colored left border via + var(--hl-function) — that's where the "yellow" outline was coming from. */ + border-left-color: var(--border) !important; + border-radius: 0 !important; + background: transparent !important; +} +/* JS marks the last .email-quote-fold in each email body with this class, + so it's the only one that gets rounded bottom corners. Avoids :has() + browser-support quirks. */ +.email-quote-fold.last-fold { + border-radius: 0 0 8px 8px; + overflow: hidden; +} +/* ── Threaded reply turns (recursive parser) ── */ +.email-thread-turn-body { + padding: 8px 14px 4px; + font-size: 12px; + line-height: 1.45; +} +/* Nested turns indent slightly + get their own card outline so the + reply hierarchy is obvious at a glance. */ +.email-thread-turn-body .email-thread-turn { + margin: 8px 0 4px; + margin-left: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +/* The original blockquote's left stripe is redundant when each turn is + already a card — drop it inside thread bodies. */ +.email-thread-turn-body blockquote { + border-left: none !important; + margin: 0 !important; + padding: 0 !important; + background: transparent !important; +} +/* Flatten the inner blockquote: no rounded corners, no yellow tint + inherited from rich-mail HTML. Subtle left border + neutral bg. */ +.email-quote-fold blockquote { + margin: 0; + padding: 8px 12px; + border: 0; + border-left: 2px solid var(--border); + border-radius: 0 !important; + background: transparent !important; + color: inherit; +} +/* "From … · Date …" chip appended to the summary line */ +.email-fold-summary-meta { + margin-left: 6px; + font-size: 10px; + font-weight: 500; + text-transform: none; + letter-spacing: 0; + opacity: 0.55; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +/* Sender name is the primary affordance — slightly larger and not all-caps so + "From: Sam" feels like a click target on the person, not a section label. */ +.email-fold-summary-name { + font-size: 11px; + font-weight: 600; + text-transform: none; + letter-spacing: 0.1px; + color: var(--fg); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.email-quote-fold .email-fold-summary { + display: flex; align-items: center; gap: 5px; + padding: 6px 14px; + cursor: pointer; list-style: none; + font-size: 10px; font-weight: 600; text-transform: uppercase; + letter-spacing: 0.4px; + /* Force neutral page colors regardless of any rich-mail HTML wrapping us */ + color: var(--fg) !important; + background: transparent !important; + background-color: transparent !important; + text-shadow: none !important; + font-style: normal !important; + opacity: 0.7; + user-select: none; + border-radius: 0; +} +.email-quote-fold .email-fold-summary > * { + /* Prevent inline-color attrs on summary children (icon SVGs, meta span) + from picking up a forced color from ancestor email HTML. */ + color: inherit !important; + background: transparent !important; +} +.email-quote-fold .email-fold-summary { list-style: none; list-style-type: none; } +.email-quote-fold[open] > *:not(summary) { + padding: 8px 14px 12px 14px; + border-top: 1px solid var(--border); +} + +/* Signature fold = clean full-width band like the attachments header. No + accent overlay, no rounded corners — neutral chrome so it doesn't look + like the AI Summary panel. */ +.email-sig-fold { + margin: 8px 0 0; + padding: 0; + border: 0; + border-top: 1px dashed color-mix(in srgb, var(--fg) 18%, transparent); + background: transparent; + border-radius: 0; +} +.email-sig-fold .email-fold-summary { + display: flex; align-items: center; gap: 5px; + padding: 4px 0; + cursor: pointer; + list-style: none; list-style-type: none; + font-size: 9.5px; font-weight: 600; text-transform: uppercase; + letter-spacing: 0.4px; + color: var(--fg); opacity: 0.55; + user-select: none; +} +.email-sig-fold[open] > *:not(summary) { + padding: 6px 0 0; + border-top: 0; + font-size: 11px; line-height: 1.45; + color: color-mix(in srgb, var(--fg) 75%, transparent); + white-space: pre-wrap; +} + +/* ── Chat-bubble layout for email threads ── + Each parsed turn becomes a bubble. The active account's outgoing + replies align right (mine); everyone else aligns left (theirs). + Bubbles read top→bottom oldest→newest, like a chat. */ +.email-bubbles { + display: flex; + flex-direction: column; + gap: 10px; + padding: 4px 0 8px; + margin: 0 -2px; +} +.email-bubble-row { + display: flex; + align-items: flex-end; + gap: 8px; + width: 100%; + min-width: 0; +} +.email-bubble { + flex: 0 1 auto; + max-width: 90%; + min-width: 0; + padding: 8px 12px; + border: 1px solid var(--border); + background: var(--panel); + font-size: 12.5px; + line-height: 1.5; + word-wrap: break-word; + overflow-wrap: anywhere; + overflow: hidden; +} +.email-bubble-mine .email-bubble { + background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 10%, var(--bg)); + border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 35%, var(--border)); + border-radius: 14px 14px 4px 14px; +} +.email-bubble-theirs .email-bubble { + background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 5%, var(--panel)); + border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 25%, var(--border)); + border-radius: 14px 14px 14px 4px; +} +.email-bubble-head { + display: flex; + align-items: baseline; + gap: 8px; + font-size: 10.5px; + margin-bottom: 4px; + opacity: 0.7; + min-width: 0; +} +.email-bubble-author { + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 50ch; +} +.email-bubble-date { + opacity: 0.7; + font-weight: 400; + white-space: nowrap; +} +.email-bubble-body { + white-space: normal; + /* Reader-controlled typography — override sender's inline styles AND + attribute-based sizing (, , , h1..h6). Weight + and italics still pass through for emphasis. */ + font-family: inherit; + font-size: var(--email-body-size, 13.5px); + line-height: 1.5; +} +/* Heading tags carry browser-default font-size multipliers that bypass + `font-size: inherit` when an inline style was set; pin them too. */ +.email-bubble-body h1, +.email-bubble-body h2, +.email-bubble-body h3, +.email-bubble-body h4, +.email-bubble-body h5, +.email-bubble-body h6, +.email-bubble-body big, +.email-bubble-body small, +.email-bubble-body font { + font-size: inherit !important; + line-height: inherit !important; +} +/* Sender HTML often wraps content in a fixed-width + or . Override so content fills the reader's available + width — otherwise resizing the email window doesn't reflow anything. */ +.email-bubble-body, +.email-bubble-body * { + max-width: 100% !important; +} +.email-bubble-body div[style*="width"], +.email-bubble-body table[width], +.email-bubble-body table { + width: 100% !important; + box-sizing: border-box !important; +} +.email-bubble-body blockquote { + border-left: 2px solid var(--border) !important; + margin: 4px 0 !important; + padding: 4px 10px !important; + background: transparent !important; + border-radius: 0 !important; + color: inherit; +} +.email-bubble-avatar { + flex-shrink: 0; + width: 26px; + height: 26px; + border-radius: 50%; + /* Per-sender color set inline (`style="background:hsl(...)"`); this + fallback only kicks in when JS hasn't assigned one. */ + background: color-mix(in srgb, var(--fg) 14%, transparent); + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.22); + display: flex; + align-items: center; + justify-content: center; + font-size: 9.5px; + font-weight: 600; + letter-spacing: 0.3px; + user-select: none; +} +/* Sig folds inside a bubble: tone down — no full-bleed band, since the + bubble already provides the chrome. */ +.email-bubble-body .email-sig-fold { + margin: 8px 0 0; + border-top: 1px dashed var(--border); +} +.email-bubble-body .email-sig-fold[open] > *:not(summary), +.email-bubble-body .email-quote-fold[open] > *:not(summary) { + padding: 6px 0 0; + border-top: 0; +} +@media (max-width: 600px) { + .email-bubble { max-width: 96%; } + .email-bubble-avatar { display: none; } +} + +/* ── Schedule Send modal ── */ +.schedule-send-body { + display: flex; + flex-direction: column; + gap: 6px; + padding: 16px 18px 12px; +} +.schedule-send-label { + font-size: 11px; + font-weight: 500; + letter-spacing: 0.3px; + text-transform: uppercase; + color: var(--fg); + opacity: 0.55; + margin-top: 6px; +} +.schedule-send-label:first-child { margin-top: 0; } +.schedule-send-presets { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; + margin-bottom: 4px; +} +.schedule-send-presets .memory-toolbar-btn { + padding: 5px 10px 11px; + font-size: 12px; + justify-content: center; + line-height: 1; +} +.schedule-send-confirm svg { + width: 12px; + height: 12px; + vertical-align: -1px; + margin-right: 6px; +} +.schedule-send-datetime { + font-size: 13px; + padding: 8px 10px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + outline: none; + font-family: inherit; + width: 100%; + box-sizing: border-box; +} +.schedule-send-datetime:focus { + border-color: var(--hl-function); +} +.schedule-send-footer { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 8px; + padding: 10px 18px 14px; + border-top: 1px solid var(--border); + margin-top: 4px; +} +.schedule-send-confirm { + background: var(--accent-primary, var(--red)) !important; + color: #fff !important; + border-color: var(--accent-primary, var(--red)) !important; +} +.schedule-send-confirm:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 85%, white) !important; +} + +/* Senders embed inline colors in their HTML that clash with the app's + theme (black text in dark mode, white backgrounds, yellow highlights, + etc.). Force theme colors on every descendant of the rendered body so + emails inherit the user's chosen palette regardless of what the + sender's mail client emits. */ +.email-reader-body *, +.email-bubble-body * { + color: var(--fg) !important; + -webkit-text-fill-color: currentColor !important; + background-color: transparent !important; + background-image: none !important; +} +.email-reader-body a, +.email-bubble-body a, +.email-thread-turn-body a { + color: var(--hl-function) !important; +} +.email-reader-body a[href^="tel:"], +.email-reader-body a[href^="sms:"], +.email-reader-body a[href^="x-apple-data-detectors:"], +.email-reader-body a[x-apple-data-detectors], +.email-bubble-body a[href^="tel:"], +.email-bubble-body a[href^="sms:"], +.email-bubble-body a[href^="x-apple-data-detectors:"], +.email-bubble-body a[x-apple-data-detectors], +.email-thread-turn-body a[href^="tel:"], +.email-thread-turn-body a[href^="sms:"], +.email-thread-turn-body a[href^="x-apple-data-detectors:"], +.email-thread-turn-body a[x-apple-data-detectors] { + color: inherit !important; + -webkit-text-fill-color: currentColor !important; + text-decoration: none !important; + background-color: transparent !important; + background-image: none !important; + -webkit-background-clip: border-box !important; + background-clip: border-box !important; +} + +/* Prefer local/system emoji fonts; avoid remote font providers. */ +.doc-editor-textarea, .doc-editor-highlight, #doc-editor-code, +.doc-md-preview, .doc-csv-preview { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', monospace; + font-variant-emoji: text; +} +/* Preview panes claim the editor's space so the action footer stays pinned + at the bottom of the pane even when the rendered content is short + (shorter table, single-paragraph markdown, etc). */ +.doc-csv-preview, +.doc-md-preview { + flex: 1 1 auto; + min-height: 0; + overflow: auto; +} + +/* Email documents override: use colored system emoji in compose. */ +.doc-editor-textarea.email-mode, +#doc-editor-code.email-mode, +#doc-editor-code.email-mode .doc-editor-highlight, +#doc-editor-code.email-mode .doc-editor-textarea { + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla" !important; + font-variant-emoji: emoji; +} + +/* Single-layer rendering for ALL documents (not just email): the highlight + overlay ( with hljs spans) and the transparent textarea use two + different rendering paths internally — the textarea uses the browser's + native form line-breaker, the overlay uses CSS line-breaking — and they + can never be guaranteed to wrap byte-identical no matter how many CSS + properties are pinned. Pick one source of truth: make the textarea its + own visible text, hide the overlay. Trade-off: no syntax highlighting in + the live editor (rendered markdown preview / chat output still has it). + The caret is now glued to the typed text by definition since both are + rendered by the same element. */ +.doc-editor-highlight, +#doc-editor-code.email-mode .doc-editor-highlight { + display: none !important; +} +/* Find bar is open: float the highlight overlay ON TOP of the + textarea with transparent background + transparent text, so only + the solid spans are visible. pointer-events + off so clicks still go through to the textarea below. */ +body.doc-find-active .doc-editor-highlight, +body.doc-find-active #doc-editor-code.email-mode .doc-editor-highlight { + display: block !important; + z-index: 5 !important; + background: transparent !important; + pointer-events: none; +} +body.doc-find-active .doc-editor-highlight, +body.doc-find-active .doc-editor-highlight * { + color: transparent !important; + background: transparent !important; +} +.doc-editor-textarea, +#doc-editor-code.email-mode .doc-editor-textarea { + color: var(--fg) !important; + caret-color: var(--fg) !important; + z-index: 1; +} + +/* Emoji picker (monochrome icon picker) */ +.emoji-picker-btn { + background: none; border: none; color: var(--fg); opacity: 0.35; + padding: 4px 7px; cursor: pointer; min-height: 28px; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.1s; +} +.emoji-picker-btn:hover { opacity: 1; } +.emoji-picker { + width: min(300px, 92vw); max-height: min(300px, 55vh); + background: var(--panel); border: 1px solid var(--border); border-radius: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.3); + display: flex; flex-direction: column; overflow: hidden; +} +.emoji-picker-search { + padding: 8px 12px; background: var(--panel); border: none; + border-bottom: 1px solid var(--border); color: var(--fg); + font-size: 12px; font-family: inherit; outline: none; +} +.emoji-picker-groups { overflow-y: auto; padding: 4px 0; flex: 1; } +.emoji-picker-group { padding: 0 6px 4px 6px; } +.emoji-picker-group-name { + font-size: 10px; font-weight: 600; opacity: 0.5; + text-transform: uppercase; letter-spacing: 0.5px; + padding: 4px 4px 2px 4px; +} +.emoji-picker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; } +.emoji-picker-item { + background: none; border: none; cursor: pointer; + padding: 4px; border-radius: 4px; + display: flex; align-items: center; justify-content: center; + color: var(--fg); transition: background 0.1s; +} +.emoji-picker-item:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } +.emoji-picker-item svg { width: 16px; height: 16px; } + +.email-avatar { + width: 28px; height: 28px; border-radius: 50%; background: var(--accent, #4a9eff); + color: #fff; display: flex; align-items: center; justify-content: center; + font-size: 12px; font-weight: 600; flex-shrink: 0; margin-top: 1px; +} +.email-item-content { flex: 1; min-width: 0; overflow: hidden; } +.email-item-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } +.email-sender { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.email-filter-chip { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; + margin: 4px 8px 6px; + padding: 4px 8px; + border-radius: 12px; + font-size: 11px; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + color: var(--fg); +} +.email-filter-chip-clear { + background: none; + border: none; + color: inherit; + cursor: pointer; + font-size: 14px; + line-height: 1; + padding: 0 2px; + opacity: 0.6; +} +.email-date { font-size: 10px; opacity: 0.5; white-space: nowrap; flex-shrink: 0; } +.email-subject { font-size: 11px; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } +.email-sent-chip, +.email-folder-chip { + display: inline-flex; + align-items: center; + height: 14px; + padding: 0 5px; + margin-right: 5px; + border-radius: 999px; + font-size: 8px; + line-height: 1; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + vertical-align: 1px; +} +.email-folder-chip { + position: relative; + top: 4px; + color: color-mix(in srgb, var(--fg) 75%, transparent); + background: rgba(127, 127, 127, 0.13); + border-color: rgba(127, 127, 127, 0.24); +} +.email-meta-date-group { + display: inline-flex; + align-items: center; + gap: 5px; + white-space: nowrap; +} +.email-meta-date-group .email-folder-chip { + margin-right: 0; +} +.email-card-expanded .email-sent-chip, +.email-card-reader .email-sent-chip { + position: relative; + top: 4px; +} +.email-tags { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 3px; margin-left: 6px; vertical-align: middle; position: relative; top: -2px; } +.email-card-tags { + flex: 0 0 auto; + min-width: 0; + max-width: 48%; + position: relative; + top: -4px; +} +.email-card-expanded .email-tags, +.email-card-reader .email-tags { top: -4px; } +.doclib-card-expanded .email-card-tags { top: -4px; } +.email-card-expanded .email-card-titlerow .email-tag, +.email-card-expanded .email-card-titlerow .email-tags-more { + transform: translateY(-2px); +} +@media (max-width: 768px) { + .email-card-expanded .email-card-titlerow .email-tag, + .email-card-expanded .email-card-titlerow .email-tags-more { + transform: translateY(2px); + } +} +.email-card-tags.email-tags-expanded { + position: absolute; + z-index: 4; + right: calc(var(--email-status-width, 44px) + 6px); + top: -5px; + max-width: min(58%, 260px); + padding: 4px 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent); +} +.email-tags-more { + appearance: none; + border: 0; + box-sizing: border-box; + border-radius: 999px; + background: rgba(127, 127, 127, 0.16); + color: var(--fg); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 1px; + height: 13px !important; + min-height: 0 !important; + min-width: 23px; + padding: 1px 4px; + font-family: inherit; + font-size: 9px; + line-height: 1; + margin: 0; + opacity: 0.68; + position: relative; + top: 0; + vertical-align: middle; +} +.email-tags-more svg { display: block; flex: 0 0 auto; } +.email-tags-expanded .email-tags-more svg { transform: rotate(180deg); } +.email-tags-toggle-inline { + position: absolute; + right: 94px; + top: 50%; + transform: translateY(calc(-50% - 5px)); + height: 20px !important; + min-width: 34px; + padding: 2px 6px !important; + font-size: 10px !important; + line-height: 1 !important; + opacity: 0.62; + border-radius: 999px !important; +} +.email-tags-toggle-inline.active { + opacity: 1; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 36%, transparent); +} +.email-tags-toggle-inline.email-tags-empty { + color: var(--warning, #d6b35a); + background: color-mix(in srgb, var(--warning, #d6b35a) 12%, transparent); + border-color: color-mix(in srgb, var(--warning, #d6b35a) 34%, transparent); +} +.email-tag { + display: inline-block; + font-size: 9px; + line-height: 1; + padding: 2px 5px; + border-radius: 8px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.3px; + background: rgba(127, 127, 127, 0.18); + color: var(--fg); +} +.email-tag-clickable { + appearance: none; + border: 0; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + font-family: inherit; + font-size: 9px; + font-weight: 500; + height: 13px !important; + min-height: 0 !important; + line-height: 1; + margin: 0; + padding: 2px 5px !important; + text-transform: uppercase; + vertical-align: middle; + flex: 0 0 auto; +} +.email-tag-work { background: rgba(96, 165, 250, 0.22); color: #60a5fa; } +.email-tag-personal { background: rgba(74, 222, 128, 0.22); color: #4ade80; } +.email-tag-finance { background: rgba(250, 204, 21, 0.22); color: #facc15; } +.email-tag-bills { background: rgba(251, 146, 60, 0.22); color: #fb923c; } +.email-tag-receipt { background: rgba(251, 146, 60, 0.22); color: #fb923c; } +.email-tag-travel { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } +.email-tag-newsletter { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } +.email-tag-promo, +.email-tag-marketing { background: rgba(244, 114, 182, 0.22); color: #f472b6; } +.email-tag-notification { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } +.email-tag-security { background: rgba(248, 113, 113, 0.22); color: #f87171; } +.email-tag-urgent { background: color-mix(in srgb, var(--color-error, #e06c75) 25%, transparent); color: var(--color-error, #e06c75); font-weight: 600; } +.email-tag-reply-soon { background: rgba(240, 173, 78, 0.22); color: #f0ad4e; } +.email-tag-social { background: rgba(56, 189, 248, 0.22); color: #38bdf8; } +.email-tag-shopping { background: rgba(236, 72, 153, 0.22); color: #ec4899; } +.email-tag-calendar { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } +.email-menu-wrap { position: relative; flex-shrink: 0; } +.email-menu-btn { + background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; + padding: 4px; transition: opacity 0.15s; + position: relative; top: 0px; +} +.email-dropdown { + position: absolute; right: 0; top: 100%; z-index: 9999; + min-width: 120px; background: var(--bg); border: 1px solid var(--border); + border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); padding: 4px 0; +} +.email-loading { padding: 16px; text-align: center; font-size: 12px; opacity: 0.5; } +.email-load-more { padding: 8px; text-align: center; } +.email-load-more-btn { + background: transparent; border: 1px solid var(--border); border-radius: 4px; + color: var(--fg); padding: 4px 12px; font-size: 11px; cursor: pointer; +} +.email-load-more-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } +.email-spinner { + display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); + border-top-color: #fff; border-radius: 50%; animation: email-spin 0.6s linear infinite; + vertical-align: middle; margin-right: 4px; +} + +/* Email section doesn't collapse — hide the auto-injected chevron */ +#email-section .section-collapse-btn { display: none !important; } + +#email-section:hover #email-compose-btn { + opacity: 0.7 !important; +} +#email-compose-btn:hover { + opacity: 1 !important; + background: none !important; +} +#email-compose-btn svg { + width: 11px; + height: 11px; + transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); +} +#email-compose-btn .list-item-plus-label { + top: calc(50% - 0.5px); + color: var(--fg); + font-size: 10px; +} +#email-compose-btn:hover svg { + transform: rotate(180deg) scale(1.15); +} + +/* Library row "+" — identical to the email compose "+": hidden until the row + is hovered, no accent box, and the same rotate-in animation. */ +#library-new-doc-btn, +#notes-new-note-btn { + opacity: 0.4 !important; + transition: opacity 0.15s ease; +} +#tool-library-btn:hover #library-new-doc-btn, +#tool-notes-btn:hover #notes-new-note-btn { + opacity: 0.7 !important; +} +#library-new-doc-btn:hover, +#notes-new-note-btn:hover { + opacity: 1 !important; + background: none !important; +} +#library-new-doc-btn svg, +#notes-new-note-btn svg { + transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); +} +#library-new-doc-btn .list-item-plus-label, +#notes-new-note-btn .list-item-plus-label { + top: calc(50% + 1px); +} +#library-new-doc-btn:hover svg, +#notes-new-note-btn:hover svg { + transform: rotate(180deg) scale(1.15); +} + + +/* MD toolbar attach button — matches other toolbar buttons */ +.md-toolbar-attach-btn { + background: none; border: none; color: var(--fg); opacity: 0.35; + padding: 4px 7px; cursor: pointer; min-height: 28px; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.1s; +} +.md-toolbar-attach-btn:hover { opacity: 1; } + +/* Email reader icon buttons — vertical icon + label stack. */ +.memory-toolbar-btn.reader-icon-btn { + width: auto; + height: 44px; + /* padding: 4px 2px; */ + position: relative; + top: 1px; + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + flex: 0 0 auto; +} +.memory-toolbar-btn.reader-icon-btn svg { width: 18px; height: 18px; } +.memory-toolbar-btn.reader-icon-btn[data-act="reply"] svg { + color: inherit; + stroke: currentColor; +} +.memory-toolbar-btn.reader-icon-btn.active { + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + color: var(--accent, var(--red)); +} +.memory-toolbar-btn.reader-icon-btn[data-act="more"] { + color: inherit; +} +.memory-toolbar-btn.reader-icon-btn[data-act="more"] svg { fill: currentColor; } +.memory-toolbar-btn.reader-icon-btn.reader-more-active { + background: color-mix(in srgb, var(--fg) 12%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); + color: inherit; +} +/* Cookbook Serve dropdown triggers (the cached-model kebab + the saved-config + arrow) — accent-colored to signal they open a menu, with a filled tint when + their dropdown is open. Matches the email reader More button pattern. */ +.hwfit-cached-menu-btn, +.cookbook-saved-arrow { + color: var(--accent, var(--red)); + /* Base .memory-item-btn is flex with align-items but no justify-content, + so the kebab SVG sat left-aligned inside the button instead of being + horizontally centered. Pin it center. */ + justify-content: center; +} +.hwfit-cached-menu-btn.cookbook-menu-active, +.cookbook-saved-arrow.cookbook-menu-active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); + color: var(--accent, var(--red)); +} + +/* Card action menus share one hit target and baseline. Keep this late in the + cascade because modal buttons and Cookbook Serve add their own offsets. */ +.memory-item-actions > .memory-item-btn, +.memory-item-actions .hwfit-cached-menu-btn { + box-sizing: border-box; + width: 28px; + min-width: 28px; + height: 22px; + min-height: 22px; + margin: 0 !important; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-muted); + font-size: 11px; + display: inline-flex; + align-items: center; + align-self: center; + justify-content: center; + position: relative; + top: 0 !important; + transform: none !important; + line-height: 1; + transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s; +} +.memory-item-actions .hwfit-cached-menu-btn:hover { + opacity: 1 !important; + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); + color: var(--fg); +} +.memory-item-actions .hwfit-cached-menu-btn { + transform: translateY(2px) !important; +} + +@media (min-width: 769px) { + .task-card .memory-item-actions { + top: 6px; + } + .task-card .memory-item-actions > .memory-item-btn { + transform: none !important; + } +} + +/* All kebab menus use the same muted tone as the expand/collapse chevron. */ +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +) { + color: var(--color-muted, var(--fg)) !important; +} +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +) svg { + color: inherit; + fill: currentColor; +} +/* Card kebabs are lightweight menu affordances, not bordered buttons. The + old base hover rule gave only some library cards a boxed outline while + chats, tasks, and cookbook stayed borderless. */ +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +):hover { + background: none !important; + border-color: transparent !important; + color: var(--fg) !important; + opacity: 1; +} +/* Skills used a separate, larger kebab with a different hover treatment. + Match every other card action trigger. */ +.skill-kebab-btn { + box-sizing: border-box; + width: 28px; + min-width: 28px; + height: 22px; + min-height: 22px; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-muted) !important; + opacity: 0.55; + top: -2px; +} +@media (max-width: 768px) { + .memory-item-actions > .memory-item-btn, + .memory-item-actions .hwfit-cached-menu-btn { + width: 40px; + min-width: 40px; + height: 40px; + min-height: 40px; + } + /* Card menus all need the same comfortable tap target. These components + previously escaped the shared action-button sizing through more-specific + desktop selectors. */ + .memory-menu-btn, + .skill-kebab-btn, + .task-card .memory-item-actions .memory-item-btn { + width: 40px !important; + min-width: 40px !important; + height: 40px !important; + min-height: 40px !important; + } + .memory-menu-btn { opacity: 0.7; } + /* Document cards seat their actions on a slightly higher title baseline + than chats/research cards. Bring only that kebab down 4px. */ + #doclib-modal .doclib-card[data-doc-id] > .doclib-document-header .memory-item-actions .memory-item-btn { + transform: translateY(-2px) !important; + } +} +/* Mobile: center the kebab/menu dropdowns in the viewport so they don't + slide left/right as the window width changes (they were positioned via + inline `right: ...px` relative to the kebab button). Stable position is + easier to land on by touch. The inline `top:` set by the menu code is + preserved; we only override the horizontal axis. */ +@media (max-width: 768px) { + .email-card-dropdown, + .cookbook-saved-menu, + .cookbook-dep-menu { + left: 50% !important; + right: auto !important; + transform: translateX(-50%); + } +} +/* Mobile-only Cancel item appended to dropdowns (kebab/More menus). On + desktop outside-click closes them cleanly; on touch that's fiddly, so + give an explicit Cancel row. Visually separated from the action items + above by a top divider + slight muted color. */ +.dropdown-cancel-mobile { display: none; } +@media (max-width: 768px) { + .dropdown-cancel-mobile { + display: flex; + border-top: 1px solid var(--border); + margin-top: 4px; + padding-top: 8px; + opacity: 0.85; + } +} +/* Launch-command Copy button morphs into Cancel once Launch has been clicked. */ +.hwfit-serve-copy.hwfit-serve-cancel { + border-color: var(--color-danger, #e06c75) !important; + color: var(--color-danger, #e06c75) !important; +} +.hwfit-serve-copy.hwfit-serve-cancel:hover { + background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); +} + +/* One visual contract for card kebab menus. These menus are built by several + modules, and native button defaults previously made identical rows render + with different fonts, heights, and hover movement. */ +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) { + box-sizing: border-box; + padding: 4px !important; + color: var(--fg); + background: var(--panel, var(--bg)) !important; + border: 1px solid var(--border) !important; + border-radius: 8px !important; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important; + font-family: inherit !important; + font-size: 11px !important; + line-height: 1.2; +} +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) > .dropdown-item-compact { + box-sizing: border-box; + width: 100%; + min-height: 28px; + padding: 6px 8px; + gap: 10px; + border: 0; + border-radius: 6px; + color: var(--fg); + background: transparent; + font: inherit !important; + line-height: 1.2; + text-align: left; + white-space: nowrap; +} +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) > .dropdown-item-compact:hover:not(:disabled) { + color: var(--fg); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + transform: none; +} +@media (max-width: 768px) { + :is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu + ) > .dropdown-item-compact { + min-height: 44px; + padding: 12px !important; + gap: 10px !important; + font-size: 14px !important; + } +} +/* Email modal title unread badge — small accent pill next to "Email". */ +.email-lib-unread-badge { + display: inline-block; + font-size: 0.55em; + font-weight: 700; + background: var(--accent-primary, var(--red)); + color: var(--bg, #1a1a1a); + border-radius: 9px; + padding: 1px 7px; + margin-left: 8px; + vertical-align: 2px; + line-height: 1.3; + letter-spacing: 0.02em; + cursor: pointer; +} +.email-lib-unread-badge.email-lib-away-badge { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); + padding: 0 7px; +} +.email-lib-auto-reply-badge { + display: inline-block; + margin-left: 7px; + padding: 1px 7px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border)); + border-radius: 9px; + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + color: var(--accent, var(--red)); + font-size: 0.55em; + font-weight: 700; + line-height: 1.3; + letter-spacing: 0.01em; + vertical-align: 2px; + cursor: pointer; +} +.email-lib-auto-reply-status-dot { + width: 6px; + height: 6px; + margin-left: 6px; + border-radius: 50%; + background: var(--accent, var(--red)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), + 0 0 8px color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); + vertical-align: 2px; +} +.modal-header h4 > .email-lib-auto-reply-badge { + font-size: 0.65em; + white-space: nowrap; + vertical-align: 1px; +} +#email-lib-modal.email-lib-unread-active .doclib-modal-content { + box-shadow: + 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent), + 0 0 18px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), + var(--shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.35)); +} +#email-lib-modal .email-lib-header-actions .minimize-btn { + position: relative; + left: 6px; +} +/* Per-card prev/next nav — only visible on the currently expanded card, + sits at the right of the title row next to the done check. */ +.email-card-nav-arrows { + display: none; + margin-left: auto; + gap: 2px; + flex-shrink: 0; + align-items: center; + position: relative; + top: -3px; + left: 19px; +} +.doclib-card.email-card-expanded .email-card-nav-arrows { + display: inline-flex; +} +.email-card-header-menu { + display: none; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 28px; + height: 28px; + padding: 0; + border: none; + border-radius: 6px; + background: none; + color: var(--fg); + opacity: 0.65; + cursor: pointer; + position: relative; + top: -3px; + transition: opacity 0.12s, background 0.12s; +} +.doclib-card.email-card-expanded .email-card-header-menu { + display: inline-flex; +} +.email-card-header-menu:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.email-card-nav-btn { + background: none; + border: none; + color: var(--fg); + opacity: 0.65; + cursor: pointer; + padding: 6px 8px; + border-radius: 6px; + display: inline-flex; + align-items: center; + justify-content: center; + transition: opacity 0.12s, background 0.12s; +} +.email-card-nav-btn:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} + +/* ── Email document editor ── */ +.doc-email-header { + display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; + border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; +} +.doc-email-fields { + display: flex; + flex-direction: column; + gap: 6px; + min-height: 0; +} +.doc-email-collapse-btn { + width: 100%; + min-height: 24px; + display: none; + align-items: center; + gap: 7px; + padding: 2px 4px 3px; + border: none; + background: transparent; + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; + opacity: 0.72; + text-align: left; +} +.doc-email-collapse-btn:hover { opacity: 1; color: var(--accent, var(--red)); } +.doc-email-collapse-btn svg { + flex-shrink: 0; + opacity: 0.65; + transition: transform 0.14s ease; +} +.doc-email-collapse-summary { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + opacity: 0.74; +} +.doc-email-header.doc-email-header-collapsed .doc-email-collapse-btn svg { + transform: rotate(180deg); +} +.email-field { display: flex; align-items: center; gap: 8px; position: relative; } +.email-field label { font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.5; min-width: 50px; text-align: right; flex-shrink: 0; } +.email-field input { + flex: 1; width: 100%; background: transparent; border: 1px solid var(--border); border-radius: 4px; + padding: 5px 8px; font-size: 13px; font-family: inherit; color: var(--fg); outline: none; + min-width: 0; +} +.email-field input:focus { border-color: var(--accent, #4a9eff); } +@container docpane (max-width: 460px) { + .doc-email-header .email-field { + display: grid; + grid-template-columns: 1fr; + gap: 3px; + align-items: stretch; + } + .doc-email-header .email-field label { + min-width: 0; + text-align: left; + } + .doc-email-header .email-field input { + width: 100%; + } +} +/* Cc toggle and attach button are absolute so they don't steal width + from the To input. Anchored to the input's vertical center + 4px + nudge so the label sits where the user expects it. */ +.email-field .email-cc-toggle { + position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); + z-index: 2; +} +/* Accent-tinted prefix label (To / Cc / Bcc / Subject) sitting at + the left edge of each input. Replaces both the old tag + and the all-placeholder accent. */ +.email-field .email-field-prefix { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + font-size: 11px; + font-weight: 600; + color: var(--accent-primary, var(--red)); + pointer-events: none; + z-index: 1; + line-height: 1; +} +.email-field input { padding-left: 44px; } +.email-field #doc-email-subject { padding-left: 64px; } +/* X close button inside Cc / Bcc fields. */ +.email-field .email-cc-close { + position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); + background: none; border: none; color: var(--fg); + opacity: 0.4; cursor: pointer; + width: 22px; height: 22px; + display: inline-flex; align-items: center; justify-content: center; + border-radius: 4px; + padding: 0; + z-index: 2; +} +.email-field .email-cc-close:hover { + opacity: 1; + color: var(--accent, var(--red)); + background: color-mix(in srgb, currentColor 10%, transparent); +} +.email-field input { padding-right: 60px; } +.email-field #doc-email-cc, .email-field #doc-email-bcc { padding-right: 32px; } +.email-field #doc-email-subject { padding-right: 8px; } + +.doc-email-actions { + display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; + border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; + align-items: center; + position: relative; + z-index: 30; + pointer-events: auto; +} +#doc-actions-footer .email-send-split { + margin-left: auto; +} +.doc-save-state-icon { display: none; flex: 0 0 auto; } +.doc-save-button[data-save-state="dirty"] { color: var(--accent, var(--red)); } +.doc-save-button[data-save-state="saving"] { color: var(--fg-muted, var(--fg)); } +.doc-save-button[data-save-state="saved"] { color: var(--fg); } +.doc-save-button[data-save-state="error"] { color: var(--color-danger, #e06c75); } +.doc-stats-wrap { + position: relative; + margin-left: 0; + flex: 0 0 auto; +} +.doc-stats-btn { + height: 28px; + border: 0; + border-radius: 4px; + padding: 0 7px; + background: transparent; + color: var(--fg-muted, var(--fg)); + font: inherit; + font-size: 10px; + cursor: pointer; + white-space: nowrap; +} +.doc-stats-btn:hover, +.doc-stats-btn[aria-expanded="true"] { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 7%, transparent); +} +.doc-stats-btn.doc-stats-selected { + color: var(--accent, var(--red)); + font-weight: 600; +} +.doc-stats-popover { + position: fixed; + right: auto; + bottom: auto; + width: 190px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); + color: var(--fg); + font-size: 11px; +} +.doc-stats-scope { + padding-bottom: 6px; + margin-bottom: 5px; + border-bottom: 1px solid var(--border); + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + text-transform: uppercase; +} +.doc-stats-row { + min-height: 22px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; +} +/* WYSIWYG email body — what the recipient sees, edited in place. */ +.doc-email-richbody { + flex: 1; + min-height: 0; + overflow-y: auto; + padding: 12px 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.6; + color: var(--fg); + background: var(--bg); + outline: none; + white-space: pre-wrap; + text-align: left; + word-wrap: break-word; +} +.doc-email-richbody.richtext-mode { + background: var(--bg); +} +.doc-rich-empty-import { + position: absolute; + top: 90px; + right: 18px; + bottom: auto; + left: auto; + z-index: 2; + display: flex; + pointer-events: none; +} +.doc-rich-empty-import .doc-rich-empty-import-btn { + position: static; + float: none; + margin: 0; + opacity: 1; + pointer-events: auto; + transform: none; +} +.doc-email-richbody.richtext-mode ul.rich-checklist { + list-style: none; + padding-left: 1.8em; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li { + position: relative; + min-height: 1.6em; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li::before { + content: ""; + position: absolute; + left: -1.55em; + top: 0.3em; + width: 0.95em; + height: 0.95em; + box-sizing: border-box; + border: 1.5px solid color-mix(in srgb, var(--fg) 48%, transparent); + border-radius: 3px; + background: var(--sidebar-bg, var(--panel)); + cursor: pointer; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::before { + border-color: var(--accent, var(--red)); + background: var(--accent, var(--red)); +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::after { + content: ""; + position: absolute; + left: -1.27em; + top: 0.44em; + width: 0.28em; + height: 0.5em; + border: solid #fff; + border-width: 0 1.5px 1.5px 0; + transform: rotate(45deg); + pointer-events: none; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"] { + color: color-mix(in srgb, var(--fg) 62%, transparent); + text-decoration: line-through; +} +.doc-email-richbody.richtext-mode hr { + border: 0; + border-top: 1px solid var(--border); + margin: 1.25em 0; +} +.doc-email-richbody.richtext-mode hr.richtext-page-break { + height: 8px; + border-top: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); + border-bottom: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); + margin: 1.6em 0; + opacity: 0.8; +} +.doc-email-richbody.richtext-mode table { + width: 100%; + margin: 12px 0; + border-collapse: collapse; + table-layout: fixed; +} +.doc-email-richbody.richtext-mode th, +.doc-email-richbody.richtext-mode td { + min-width: 48px; + padding: 7px 8px; + border: 1px solid var(--border); + vertical-align: top; + overflow-wrap: anywhere; +} +.doc-email-richbody.richtext-mode th { + background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 85%, var(--fg) 15%); + font-weight: 600; +} +.doc-email-richbody.richtext-mode code, +.doc-email-richbody.richtext-mode span[style*="OdysseusInlineCode"] { + padding: 0.12em 0.35em; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + border-radius: 3px; + background: color-mix(in srgb, var(--fg) 8%, transparent); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + white-space: break-spaces; +} +.doc-email-richbody.richtext-mode pre { + margin: 1em 0; + padding: 12px 14px; + overflow-x: auto; + border: 1px solid var(--border); + border-radius: 4px; + background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 82%, #000 18%) !important; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + line-height: 1.5; + white-space: pre-wrap; + tab-size: 2; +} + +::highlight(doc-find-results) { + color: inherit; + background-color: color-mix(in srgb, #facc15 48%, transparent); +} + +::highlight(doc-find-current) { + color: inherit; + background-color: color-mix(in srgb, var(--accent, #ef4444) 68%, #facc15); +} + +::highlight(doc-ai-selections) { + color: inherit; + background-color: color-mix(in srgb, var(--accent, #ef4444) 30%, transparent); +} +.doc-email-richbody img.richtext-image { + display: block; + margin: 12px 0; + max-width: 100%; + height: auto; + border-radius: 4px; + cursor: pointer; +} +.doc-email-richbody figure.richtext-image { + margin: 12px 0; + max-width: 100%; +} +.doc-email-richbody figure.richtext-image:not(:has(img)) { display: none; } +.doc-email-richbody figure.richtext-image img.richtext-image { + display: block; + margin-top: 0; + margin-bottom: 0; + max-width: 100%; + height: auto; + border-radius: 4px; +} +.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-60) .richtext-image-caption { width: 60%; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-35) .richtext-image-caption { width: 35%; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-left) .richtext-image-caption { margin-left: 0; margin-right: auto; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-center) .richtext-image-caption { margin-left: auto; margin-right: auto; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-right) .richtext-image-caption { margin-left: auto; margin-right: 0; } +.doc-email-richbody img.richtext-image-size-100 { width: 100%; } +.doc-email-richbody img.richtext-image-size-60 { width: 60%; } +.doc-email-richbody img.richtext-image-size-35 { width: 35%; } +.doc-email-richbody img.richtext-image-align-left { margin-left: 0; margin-right: auto; } +.doc-email-richbody img.richtext-image-align-center { margin-left: auto; margin-right: auto; } +.doc-email-richbody img.richtext-image-align-right { margin-left: auto; margin-right: 0; } +.doc-email-richbody img.richtext-image[data-editor-image-selected] { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 3px; +} +.doc-rich-image-toolbar { + position: fixed; + display: inline-flex; + align-items: center; + gap: 2px; + padding: 2px; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); + z-index: 10032; +} +.doc-rich-image-toolbar button { + width: 24px; + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + border: 0; + border-radius: 3px; + background: transparent; + color: inherit; + cursor: pointer; +} +.doc-rich-image-toolbar button:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--accent, var(--red)); +} +.doc-rich-image-toolbar .doc-rich-image-remove:hover { + color: var(--accent, var(--red)); +} +.doc-rich-image-source-menu { + position: fixed; + width: 260px; + min-width: 260px; + padding: 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 4px 16px color-mix(in srgb, var(--fg) 20%, transparent); + z-index: 10033; + font-size: 11px; +} +.doc-rich-image-source-title { + padding: 5px 7px 4px; + color: var(--fg); + font-weight: 700; + opacity: .75; +} +.doc-rich-image-source-menu > button, +.doc-rich-gallery-item { + width: 100%; + display: flex; + align-items: center; + gap: 7px; + padding: 7px 8px; + border: 0; + border-radius: 4px; + background: transparent; + color: var(--fg); + font: inherit; + text-align: left; + cursor: pointer; +} +.doc-rich-image-source-menu > button[data-image-source="computer"] { + padding-left: 9px; +} +.doc-rich-image-source-menu > button:hover, +.doc-rich-gallery-item:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--accent, var(--red)); +} +.doc-rich-gallery-list { + display: grid; + gap: 2px; + max-height: 300px; + overflow-y: auto; +} +.doc-rich-gallery-item { + min-height: 44px; + min-width: 0; +} +.doc-rich-gallery-item > span:last-child { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.doc-rich-gallery-thumb { + width: 40px; + height: 40px; + flex: 0 0 40px; + border: 1px solid var(--border); + border-radius: 4px; + background-position: center; + background-size: cover; + background-repeat: no-repeat; +} +.rich-color-letter { + border-bottom: 2px solid var(--accent, var(--red)); + font-weight: 700; + line-height: 14px; +} +.rich-color-swatch { + width: 13px !important; + height: 13px !important; + border: 1px solid color-mix(in srgb, var(--fg) 35%, transparent); + border-radius: 50%; +} +.rich-highlight-swatch { border-radius: 50%; } +.rich-color-swatch.is-transparent { + background: + linear-gradient(135deg, transparent 43%, var(--red) 44%, var(--red) 56%, transparent 57%), + var(--bg); +} +.rich-color-palette-menu { + min-width: min(292px, calc(100vw - 16px)); +} +.rich-color-palette-menu .rich-color-reset { + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + margin-bottom: 3px; + padding-bottom: 7px; +} +.rich-color-palette-grid { + display: grid; + grid-template-columns: repeat(2, minmax(126px, 1fr)); + gap: 0 4px; + padding: 0 4px; +} +.rich-color-palette-group { + min-width: 0; +} +.rich-color-palette-label { + padding: 4px 8px 3px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 9px; + font-weight: 600; + text-transform: uppercase; +} +.rich-color-palette-group .doc-overflow-item { + min-width: 0; + padding-inline: 8px; +} +.rich-toolbar-color-custom { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-top: 3px; + padding: 7px 9px 4px 10px; + border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + color: var(--fg); + font-size: 11px; +} +.rich-toolbar-color-input.cp-swatch-input { + width: 22px; + height: 22px; + flex: 0 0 22px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 28%, var(--border)); + border-radius: 50%; + cursor: pointer; +} +.rich-toolbar-color-input.cp-swatch-input:focus-visible { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 2px; +} + +.rich-toolbar-slider-row { + display: grid; + grid-template-columns: auto minmax(104px, 1fr) 42px; + align-items: center; + gap: 9px; + min-width: 220px; + padding: 10px; + color: var(--fg); + font-size: 11px; +} +.rich-toolbar-range { + width: 100%; + min-width: 0; + height: 8px; + margin: 0; + border: 0; + border-radius: 999px; + background: color-mix(in srgb, var(--fg) 25%, transparent); + accent-color: var(--red); + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} +.rich-toolbar-range::-webkit-slider-thumb { + width: 9px; + height: 9px; + border: 0; + border-radius: 50%; + background: var(--red); + cursor: pointer; + -webkit-appearance: none; + appearance: none; + transition: width 0.12s ease, height 0.12s ease; +} +.rich-toolbar-range:hover::-webkit-slider-thumb, +.rich-toolbar-range:focus::-webkit-slider-thumb, +.rich-toolbar-range:active::-webkit-slider-thumb { + width: 16px; + height: 16px; +} +.rich-toolbar-range::-moz-range-thumb { + width: 9px; + height: 9px; + border: 0; + border-radius: 50%; + background: var(--red); + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.rich-toolbar-range:hover::-moz-range-thumb, +.rich-toolbar-range:focus::-moz-range-thumb, +.rich-toolbar-range:active::-moz-range-thumb { + width: 16px; + height: 16px; +} +.rich-toolbar-range:focus-visible { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 4px; +} +.rich-toolbar-slider-value { + width: 42px; + box-sizing: border-box; + padding: 2px 3px; + border: 1px solid transparent; + border-radius: 3px; + background: transparent; + text-align: right; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-variant-numeric: tabular-nums; + -webkit-appearance: textfield; + appearance: textfield; +} +.rich-toolbar-slider-value::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +.rich-toolbar-slider-value:focus { + border-color: var(--accent, var(--red)); + outline: none; + color: var(--fg); +} + +.rich-script-icon, +.rich-font-icon, +.rich-font-size-icon { + display: inline-block; + line-height: 1; + font-size: 11px; +} +.rich-highlight-icon { + display: block; + flex-shrink: 0; + color: var(--accent-primary, var(--red)); +} + +.rich-script-icon sup, +.rich-script-icon sub { + font-size: 7px; + line-height: 0; +} + +.rich-font-icon { + font-family: Georgia, serif; + font-size: 12px; +} + +.rich-font-size-icon { + min-width: 16px; + font-size: 10px; + font-weight: 600; + font-variant-numeric: tabular-nums; + text-align: center; +} +.rich-align-center { text-align: center; } +.rich-align-right { text-align: right; } +.doc-email-richbody .email-quoted-history { + margin-top: 14px; + padding: 10px 0 10px 12px; + border-left: 3px solid var(--border); + color: color-mix(in srgb, var(--fg) 70%, transparent); + font-size: 0.92em; + line-height: 1.45; + white-space: pre-wrap; + user-select: text; + pointer-events: none; +} +.doc-email-richbody h1, .doc-email-richbody h2, .doc-email-richbody h3, +.doc-email-richbody h4, .doc-email-richbody h5, .doc-email-richbody h6 { margin: 0.4em 0; } +.email-more-menu { + position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; + min-width: 160px; background: var(--bg); border: 1px solid var(--border); + border-radius: 6px; box-shadow: 0 -4px 12px rgba(0,0,0,0.2); padding: 4px; + z-index: 100; +} +.email-send-btn { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 20%, transparent); + color: var(--accent-primary, var(--red)); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); + border-radius: 6px; + padding: 0 14px; height: 28px; font-size: 11px; font-weight: 600; cursor: pointer; + font-family: inherit; transition: all 0.15s; white-space: nowrap; + display: inline-flex; align-items: center; gap: 6px; +} +/* Split send button: [ Send │ ▾ ] — the caret drops the send-options menu UP. */ +.email-send-split { position: relative; display: inline-flex; } +.email-send-split .email-send-main { + border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; +} +.email-send-split .email-send-caret { + border-top-left-radius: 0; border-bottom-left-radius: 0; + padding: 0 8px; gap: 0; + /* its left border is the single divider between the two halves */ + border-left: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); +} +.email-send-split .email-send-caret svg { transition: transform 0.15s ease; } +.email-send-split .email-send-caret[aria-expanded="true"] svg { transform: none; } +.email-send-btn svg { flex-shrink: 0; } +.email-send-btn:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); + border-color: var(--accent-primary, var(--red)); +} +.email-draft-btn { + background: none; border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border-radius: 6px; + padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; + font-family: inherit; transition: all 0.15s; white-space: nowrap; +} +.email-draft-btn:hover { border-color: var(--fg); color: var(--fg); } +.email-discard-btn { + background: transparent; + /* Dim the label text via color-mix instead of `opacity:0.6` on the whole + button — opacity multiplies through to the SVG and washes out the accent + X glyph. Per-element color dimming keeps the X at full accent strength. */ + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 14px; font-size: 12px; cursor: pointer; font-family: inherit; +} +/* The ✕ glyph itself reads in accent. */ +.email-discard-btn svg { color: var(--accent-primary, var(--red)); opacity: 1; } +.email-discard-btn:hover { opacity: 1; color: var(--red, #e55); border-color: var(--red, #e55); } + +/* Compose email "more" menu button — icon only, matches draft btn height */ +#doc-email-more-btn { + background: none; border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border-radius: 6px; + width: 28px; height: 28px; padding: 0; + cursor: pointer; font-family: inherit; transition: all 0.15s; + display: inline-flex; align-items: center; justify-content: center; +} +#doc-email-more-btn:hover { + border-color: var(--fg); color: var(--fg); +} +.email-more-wrap { display: inline-flex; } +.email-attachments { + display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 0 58px; +} +.email-attachment-chip { + display: inline-flex; align-items: center; gap: 4px; + padding: 4px 8px; font-size: 11px; background: var(--hover-bg, rgba(255,255,255,0.05)); + border: 1px solid var(--border); border-radius: 12px; color: var(--fg); + text-decoration: none; cursor: pointer; transition: background 0.15s, border-color 0.15s; + max-width: 200px; min-width: 0; +} +.email-attachment-chip:hover { + background: color-mix(in srgb, var(--accent) 15%, transparent); + border-color: var(--accent); +} +.email-attachment-chip.is-expanded { + max-width: 90vw; +} +.email-attachment-chip > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + flex: 1 1 auto; min-width: 0; +} +.email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-attachment-chip-related { + border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); +} +/* "Open in editor" launch icon — same prominent style on desktop AND mobile + (was 24px / dim / no border on desktop, easy to miss). Accent-tinted + background + border makes it read as a real action. */ +.email-attachment-open, +.email-attachment-download, +.email-attachment-calendar-open, +.cal-event-email-source { + display: inline-flex; align-items: center; gap: 4px; + height: 22px; padding: 0 9px; border-radius: 999px; + margin-left: 6px; flex: 0 0 auto; + min-width: 52px; justify-content: center; + font-size: 10px; font-weight: 500; letter-spacing: 0.02em; + color: var(--accent-primary, var(--red)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); + cursor: pointer; transition: background 0.1s, border-color 0.1s, color 0.1s; +} +.email-attachment-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-open svg, +.email-attachment-open > svg, +.email-attachment-download svg, +.email-attachment-download > svg, +.email-attachment-calendar-open svg, +.email-attachment-calendar-open > svg, +.cal-event-email-source svg, +.cal-event-email-source > svg { + width: 12px; height: 12px; + opacity: 0.9; +} +.email-attachment-open:hover, +.email-attachment-download:hover, +.email-attachment-calendar-open:hover, +.cal-event-email-source:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 70%, transparent); +} +.email-attachment-download { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-calendar-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.cal-event-email-source { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-open-label { line-height: 1; } +.email-attachment-chip.is-expanded .email-attachment-open, +.email-attachment-chip.is-expanded .email-attachment-download, +.email-attachment-chip.is-expanded .email-attachment-calendar-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-chip.is-expanded .email-attachment-open-label { + display: none; +} +@media (max-width: 768px) { + .doc-email-collapse-btn { + background: inherit; + } + /* Mobile: keep the pill but ensure a comfortable touch target. */ + .email-attachment-open { + height: 26px; padding: 0 10px; + min-height: 26px !important; + min-width: 58px; + } + .email-attachments, + .email-compose-atts { + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-left: 0; + padding-bottom: 2px; + } + .email-attachments::-webkit-scrollbar, + .email-compose-atts::-webkit-scrollbar { + display: none; + } + /* Attachment chip body — modest minimum height so the open icon sits + neatly without dominating. */ + .email-attachment-chip, + .email-compose-chip { + flex: 0 0 auto; + padding: 6px 8px !important; + min-height: 36px !important; + } + .email-attachment-chip.is-expanded { + flex-wrap: nowrap; + align-items: center; + max-width: min(92vw, 360px); + } + .email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + flex: 1 1 auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.25; + min-width: 0; + } + .email-attachment-chip.is-expanded .att-size { + margin-left: 0; + } + .email-attachment-chip.is-expanded .email-attachment-open, + .email-attachment-chip.is-expanded .email-attachment-download, + .email-attachment-chip.is-expanded .email-attachment-calendar-open { + width: 28px; + min-width: 28px; + padding: 0; + margin-left: 4px; + } + .email-compose-chip .compose-chip-name { + max-width: 190px; + } +} + +/* Compose attachment chips (when sending new email) */ +.email-compose-atts { + display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; + padding: 6px 0 0 0; +} +.email-compose-chip { + display: inline-flex; align-items: center; gap: 4px; + flex: 0 1 auto; + min-width: 0; + max-width: min(240px, 100%); + height: 24px; min-height: 24px; box-sizing: border-box; + padding: 2px 3px 2px 7px; font-size: 11px; + background: var(--hover-bg, rgba(255,255,255,0.05)); + border: 1px solid var(--border); border-radius: 12px; color: var(--fg); +} +.email-compose-chip .compose-chip-name { max-width: 180px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.email-compose-chip .compose-chip-remove { + background: none; border: none; color: var(--fg); opacity: 0.5; + height: 18px; font-size: 14px; line-height: 18px; padding: 0 3px; cursor: pointer; + flex-shrink: 0; +} +.email-compose-chip .compose-chip-remove:hover { opacity: 1; color: var(--red, #e55); } + +.pdf-annotation-line-value { + width: 48px; + height: 22px; + box-sizing: border-box; + padding: 0 5px; + border: 1px solid var(--border); + border-radius: 4px; + outline: none; + background: var(--bg-elev, var(--panel)); + color: var(--fg); + font-family: inherit; + font-size: 10px; + font-weight: 500; + line-height: 1; + text-align: right; +} +.pdf-annotation-line-value:hover { border-color: color-mix(in srgb, var(--fg) 38%, var(--border)); } +.pdf-annotation-line-value:focus { border-color: var(--accent, var(--red)); } + +.email-odysseus-attach-menu { + position: fixed; + z-index: 10050; + width: 300px; + max-width: calc(100vw - 16px); + max-height: min(420px, calc(100vh - 16px)); + padding: 7px; + border: 1px solid var(--border); + border-radius: 7px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 12px 30px rgba(0,0,0,0.28); + overflow: hidden; +} +.email-odysseus-attach-local { + width: 100%; + height: 30px; + display: flex; + align-items: center; + gap: 7px; + padding: 0 9px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, var(--border)); + border-radius: 5px; + background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-local:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); +} +.email-odysseus-attach-tabs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 5px; + margin: 7px 0; +} +.email-odysseus-attach-tabs button { + height: 26px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border: 1px solid var(--border); + border-radius: 5px; + background: transparent; + color: var(--fg); + opacity: 0.72; + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-tabs button.active { + opacity: 1; + color: var(--accent-primary, var(--red)); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); +} +.email-odysseus-attach-tabs button svg { + flex-shrink: 0; +} +.email-odysseus-attach-search-wrap { + height: 28px; + display: flex; + align-items: center; + gap: 6px; + margin: -1px 0 7px; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 5px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + color: color-mix(in srgb, var(--fg) 48%, transparent); +} +.email-odysseus-attach-search-wrap:focus-within { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + color: var(--accent-primary, var(--red)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, transparent); +} +.email-odysseus-attach-search { + min-width: 0; + flex: 1; + border: 0; + outline: 0; + background: transparent; + color: var(--fg); + font: inherit; + font-size: 11px; +} +.email-odysseus-attach-search::placeholder { + color: color-mix(in srgb, var(--fg) 38%, transparent); +} +.email-odysseus-attach-list { + max-height: 320px; + overflow: auto; + display: flex; + flex-direction: column; + gap: 4px; +} +.email-odysseus-attach-row { + width: 100%; + min-height: 38px; + display: flex; + align-items: center; + gap: 8px; + padding: 6px 7px; + border: 1px solid transparent; + border-radius: 5px; + background: transparent; + color: var(--fg); + text-align: left; + font: inherit; + cursor: pointer; +} +.email-odysseus-attach-row:hover { + background: var(--hover-bg, rgba(255,255,255,0.06)); + border-color: var(--border); +} +.email-odysseus-attach-row.is-selected { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); +} +.email-odysseus-attach-dot { + width: 8px; + height: 8px; + flex: 0 0 auto; + display: inline-block; + border: 1px solid var(--border); + border-radius: 50%; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.email-odysseus-attach-row.is-selected .email-odysseus-attach-dot { + border-color: var(--accent-primary, var(--red)); + background: var(--accent-primary, var(--red)); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); +} +.email-odysseus-attach-icon, +.email-odysseus-attach-thumb { + width: 28px; + height: 28px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 7%, transparent); + overflow: hidden; +} +.email-odysseus-attach-main { + min-width: 0; + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} +.email-odysseus-attach-title, +.email-odysseus-attach-meta { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-odysseus-attach-title { + font-size: 11px; + line-height: 1.2; +} +.email-odysseus-attach-meta { + font-size: 10px; + line-height: 1.15; + opacity: 0.52; +} +.email-odysseus-attach-empty { + padding: 14px 8px; + color: var(--muted, #888); + font-size: 11px; + text-align: center; +} +.email-odysseus-attach-actions { + display: flex; + align-items: center; + gap: 6px; + margin-top: 2px; + padding-top: 5px; + border-top: 1px solid color-mix(in srgb, var(--border) 75%, transparent); + background: var(--bg); + position: sticky; + bottom: 0; +} +.email-odysseus-attach-count { + flex: 1; + min-width: 0; + color: var(--muted, #888); + font-size: 11px; +} +.email-odysseus-attach-selected { + height: 28px; + min-width: 72px; + padding: 0 12px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + background: var(--accent-primary, var(--red)); + color: #fff; + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-selected svg { + flex-shrink: 0; +} + +.email-cc-toggle { + background: none; border: none; color: var(--fg); + opacity: 0.4; font-size: 11px; cursor: pointer; + padding: 4px 8px; font-family: inherit; +} +.email-cc-toggle:hover { + opacity: 1; + color: var(--accent, #4a9eff); + background: none !important; +} + +@media (max-width: 768px) { + .doc-email-collapse-btn { + display: flex; + } +} + +.email-autocomplete { + position: absolute; top: 100%; left: 58px; right: 0; z-index: 1000; + background: var(--bg); border: 1px solid var(--border); border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 240px; overflow-y: auto; + margin-top: 2px; +} +.contact-suggestion { + display: flex; justify-content: space-between; gap: 8px; + padding: 6px 10px; font-size: 12px; cursor: pointer; + border-bottom: 1px solid var(--border); +} +.contact-suggestion:last-child { border-bottom: none; } +.contact-suggestion:hover, +.contact-suggestion.active, +.contact-suggestion[aria-selected="true"] { + background: color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 15%, transparent); + outline: 1px solid color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 40%, transparent); + outline-offset: -1px; +} +.contact-suggestion .contact-name { font-weight: 600; color: var(--fg); } +.contact-suggestion .contact-email { opacity: 0.6; font-size: 11px; } +.contact-suggestion-status { + padding: 7px 10px; + color: color-mix(in srgb, var(--fg) 65%, transparent); + font-size: 11px; + cursor: default; +} +.contact-suggestion-status.is-loading { opacity: 0.72; } +.contact-suggestion-status.is-unavailable { + color: color-mix(in srgb, var(--red) 82%, var(--fg)); +} +.email-ai-reply-btn { + background: none; + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; + font-family: inherit; transition: all 0.15s; + display: inline-flex; align-items: center; white-space: nowrap; +} +.email-ai-reply-btn:hover { + border-color: var(--accent, #4a9eff); + color: var(--accent, #4a9eff); +} +/* Align select/new buttons in popup library toolbar */ +#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn, #email-lib-compose-btn { position: relative; top: -4px; } +/* Select + Refresh sit slightly lower than Compose on desktop. */ +#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -2px; } + +#email-lib-modal.email-settings-mode .email-accounts-row, +#email-lib-modal.email-settings-mode .admin-card > .memory-toolbar, +#email-lib-modal.email-settings-mode #email-lib-bulk, +#email-lib-modal.email-settings-mode #email-lib-grid, +#email-lib-modal.email-settings-mode #email-lib-sync-status, +#email-lib-modal.email-settings-mode #email-lib-fab { + display: none !important; +} +#email-lib-settings-page[hidden] { + display: none !important; +} +#email-lib-modal.email-settings-mode #email-lib-settings-page { + display: flex; +} +.email-settings-page { + display: flex; + flex: 1; + min-height: 0; + min-width: 0; + flex-direction: column; + gap: 6px; + overflow: hidden; + padding: 2px 0 8px; + width: 100%; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; +} +.settings-email-preferences-card { + display: contents; +} +.settings-email-preferences-card .email-settings-section { + margin: 0 0 10px; + padding: 14px 12px 12px; + background: var(--panel); +} +.settings-email-preferences-card .email-settings-section:last-child { + margin-bottom: 0; +} +.settings-email-preferences-card .email-settings-style-section { + margin-bottom: 4px; +} +.settings-email-default-card #settings-email-default-host { + display: flex; + justify-content: flex-end; + min-width: 0; + margin-left: auto; +} +.settings-email-default-card .settings-email-default-title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} +.settings-email-default-card .settings-email-default-title > span { + min-width: 0; +} +.settings-email-default-card .settings-email-default-title > span > svg { + color: var(--accent, var(--red)); +} +.settings-email-default-card .email-settings-account-picker { + margin-left: 0 !important; +} +.settings-email-default-card .email-settings-support-copy { + position: relative; + top: 6px; +} +.settings-email-default-spacer { + height: 12px; +} +.settings-email-default-card .cookbook-srv-default-label { + position: relative; + top: 2px; +} +.settings-email-default-card .cookbook-srv-default-icon > svg { + position: relative; + top: 7px; +} +.settings-email-settings-legacy { + display: none; +} + +/* Keep the standalone Email Settings cards aligned with the expandable + preference cards below them. */ +[data-settings-panel="email"] > .admin-card { + padding-top: 14px; +} +.email-settings-page-host { + display: flex; + flex: 0 0 auto; + min-height: 0; + padding: 0; + overflow: visible; +} +.settings-email-preferences-card > .email-settings-page-host { width: 100%; } +.email-settings-page-head { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + min-height: 32px; + padding-bottom: 2px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); +} +.email-settings-account-picker { + min-width: 0; + max-width: 100%; +} +.email-settings-account-picker select { + min-width: 0; + max-width: 100%; +} +.email-settings-back { + position: relative; + top: -2px; +} +.email-settings-header-back { + background: transparent; + color: color-mix(in srgb, var(--fg) 58%, transparent); + border: 1px solid transparent; + width: auto; + height: 24px; + padding: 0 8px 0 6px; + align-items: center; + justify-content: center; + gap: 4px; + cursor: pointer; + border-radius: 999px; + flex-shrink: 0; + position: relative; + font-size: 11px; + font-weight: 600; + line-height: 1; +} +.email-settings-header-back:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); + border-color: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--fg); +} +.email-settings-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; +} +.email-settings-title { + display: inline-flex; + align-items: center; + gap: 6px; + flex: 1 1 auto; + width: 100%; + min-width: 0; + font-size: 12px; + font-weight: 700; +} +.email-settings-title svg { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-settings-close { + width: 24px; + height: 24px; + border: 1px solid var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 70%, transparent); + cursor: pointer; + font-size: 14px; + line-height: 1; +} +.email-settings-close:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)); +} +.email-settings-body { + display: flex; + flex-direction: column; + flex: 1 1 auto; + gap: 6px; + padding-top: 4px; + box-sizing: border-box; + min-width: 0; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + overscroll-behavior: contain; +} +.email-settings-loading, +.email-settings-error { + font-size: 12px; + color: color-mix(in srgb, var(--fg) 65%, transparent); + padding: 8px 2px; +} +.email-settings-loading { + display: inline-flex; + align-items: center; + gap: 7px; +} +.email-settings-loading-css-whirlpool { + display: inline-block; + width: 14px; + height: 14px; + box-sizing: border-box; + border: 1.5px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + border-top-color: var(--accent, var(--red)); + border-right-color: color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); + border-radius: 50%; + animation: whirlpool-spin 0.72s linear infinite; + flex: 0 0 auto; +} +.email-settings-loading-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: var(--accent, var(--red)); +} +.email-settings-section { + display: flex; + flex-direction: column; + flex: 0 0 auto; + gap: 7px; + margin: 0; + padding: 14px 8px 8px; + min-width: 0; + box-sizing: border-box; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + transition: opacity 0.15s ease, background 0.15s ease; +} +.email-settings-section.is-disabled { + opacity: 0.58; + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +.email-settings-section-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.email-settings-section-head > div:first-child { + min-width: 0; + flex: 1 1 auto; +} +.email-settings-section-head > .email-settings-enabled-control, +.email-settings-section-head > button { + margin-left: auto; +} +.email-settings-section-toggle { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + width: 100%; + min-width: 0; + padding: 0; + border: 0; + background: transparent; + color: inherit; + text-align: left; + cursor: pointer; +} +.email-settings-section-toggle:hover, +.email-settings-section-toggle:active { + background: transparent; +} +.email-settings-section-toggle-copy { + display: flex; + flex-direction: column; + align-items: flex-start; + min-width: 0; + flex: 1 1 auto; +} +.email-settings-section-toggle > .email-settings-enabled-control { + margin-left: auto; + flex: 0 0 auto; +} +.email-settings-section-chevron { + flex: 0 0 auto; + color: color-mix(in srgb, var(--fg) 62%, transparent); + transition: transform .14s ease, color .14s ease; +} +.email-settings-section-toggle:hover .email-settings-section-chevron, +.email-settings-section-toggle:focus-visible .email-settings-section-chevron { + color: var(--accent, var(--red)); +} +.email-settings-section-title { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 14px; + line-height: 1; + font-weight: 600; + letter-spacing: 0; + margin: 0; + padding: 0; +} +.email-settings-section-title svg { + color: var(--accent, var(--red)); + opacity: 0.6; + flex-shrink: 0; +} +.email-settings-account-scope { + margin-left: 4px; + font-size: 11px; + font-weight: 500; + color: color-mix(in srgb, var(--fg) 60%, transparent); +} +.email-settings-section-body { + display: flex; + flex-direction: column; + gap: 7px; + min-width: 0; + width: 100%; +} +.email-settings-section[open] > .email-settings-section-body { + min-height: 0; + flex: 0 0 auto; + height: auto; + max-height: none; + overflow: visible; + overscroll-behavior: contain; + padding: 0 4px 4px 0; + scrollbar-width: thin; +} +.email-settings-section[open] { + min-height: 0; + flex: 0 0 auto; +} +.email-settings-auto-reply-section[open], +.email-settings-cleanup-section[open]:has(.email-unsub-card) { + flex: 1 1 auto; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + align-content: stretch; +} +.email-settings-auto-reply-section[open] > .email-settings-section-body, +.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body { + flex: 0 0 auto; + height: 100%; + max-height: 100%; + overflow-y: auto; +} +.email-settings-section[open] > .email-settings-section-body > * { + flex: 0 0 auto; +} +.email-settings-section-body-head { + display: flex; + justify-content: flex-end; +} +.email-settings-display-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.email-settings-display-title { + display: inline-flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.email-settings-display-title > svg { + flex: 0 0 auto; + color: var(--accent, var(--red)); +} +.email-settings-display-title-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + position: relative; + top: 10px; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-display-title-row .email-settings-section-title { + flex: 1 1 auto; + width: auto; + padding-bottom: 0; + border-bottom: 0; +} +.email-settings-style-section .email-settings-section-title { + display: flex; + width: 100%; + position: relative; + top: 0; + padding-bottom: 8px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-style-section .email-settings-section-head, +.email-settings-style-section .email-settings-section-body { + position: static; +} +.email-settings-style-section .email-settings-section-head { margin-top: 8px; } +.email-settings-style-section .email-settings-section-body { margin-top: 12px; } +.email-settings-style-section .email-settings-section-desc { + margin-top: 16px; + top: 0; +} +.email-settings-auto-reply-section .email-settings-section-title, +.email-settings-cleanup-section .email-settings-section-title { + display: flex; + width: 100%; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-cleanup-section .email-settings-section-title { + position: relative; + top: 10px; +} +.email-settings-auto-reply-section .email-settings-section-desc, +.email-settings-cleanup-section .email-settings-section-desc { + margin-top: 10px; +} +.email-settings-display-section .email-settings-section-desc, +.email-settings-cleanup-section .email-settings-section-desc, +.email-settings-inline-images-section .email-settings-section-desc { + margin-top: 12px; + top: 12px; +} +.email-settings-auto-reply-title-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + position: relative; + top: 10px; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-auto-reply-section .email-settings-section-head { + margin-top: 8px; +} +.email-settings-auto-reply-section .email-settings-section-body { + margin-top: 8px; +} +.email-settings-auto-reply-section .email-settings-section-desc { + margin-top: 12px; + top: 0; +} +.email-settings-display-section .email-settings-section-desc { + margin-top: 10px; + top: 10px; +} +.email-settings-auto-reply-section .email-settings-section-body { + margin-top: 14px; +} +.email-settings-auto-reply-title-row .email-settings-section-title { + flex: 1 1 auto; + width: auto; + padding-bottom: 0; + border-bottom: 0; +} +.email-settings-support-copy { + color: color-mix(in srgb, var(--fg) 65%, transparent); + font-size: 11px; + line-height: 1.3; +} +.email-settings-clean-btn > svg { + color: var(--accent, var(--red)); +} +.email-settings-clean-btn { + height: 32px; +} +.email-unsub-rescan-btn { + height: 32px; +} +.email-settings-clean-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 7px; + min-width: 0; + margin-left: auto; +} +.email-settings-clean-status { + display: none; + font-size: 10.5px !important; + flex: 0 1 auto; + width: auto !important; + max-width: min(52vw, 360px); + min-width: 0; + overflow: hidden; + text-align: right; + white-space: nowrap; + text-overflow: ellipsis; + position: relative; + top: 2px; +} +.email-settings-clean-status.is-busy { + order: 0; + align-self: center; + width: auto !important; + max-width: 100%; + margin-right: auto; + text-align: left; +} +.email-settings-clean-btn { + flex: 0 0 auto; +} +.email-settings-default .cookbook-srv-default-icon > svg { + position: relative; + top: 3px; +} +.email-unsub-auto-safe-btn > svg { + position: relative; + top: 2px; +} +.email-unsub-auto-safe-btn { + height: 29px; +} +.email-unsub-status, +.email-unsub-panel-status { + position: relative; + top: 2px; +} +.email-unsub-panel-status { + flex: 1 1 220px; + min-width: 0; + margin-right: auto; + overflow: hidden; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-size: 11px; + line-height: 1.3; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-unsub-panel-status.is-error { + color: var(--red, #e57373); +} +.email-unsub-card .email-tag { + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); +} +.email-unsub-duplicate-badge { + position: relative; + top: 2px; +} +.email-unsub-accent-icon { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-unsub-card { + position: relative; + transition: opacity .16s ease; +} +.email-unsub-card-details { + display: flex; + flex-direction: column; + gap: 7px; +} +.email-unsub-card-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 24px; + height: 24px; + padding: 0; + border: 0; + background: transparent; + color: color-mix(in srgb, var(--fg) 62%, transparent); + cursor: pointer; +} +.email-unsub-card-toggle:hover, +.email-unsub-card-toggle:focus-visible { + color: var(--accent, var(--red)); +} +.email-unsub-card-toggle > svg { + transition: transform .14s ease; +} +.email-unsub-done-badge { + display: inline-flex; + align-items: center; + gap: 3px; +} +.email-unsub-done-btn { + display: inline-flex; + align-items: center; + gap: 3px; + flex: 0 0 auto; + padding: 3px 6px; + position: relative; + top: -6px; +} +.email-unsub-card-details .email-unsub-done-btn { + top: 0; +} +.email-unsub-done-btn > svg { + color: var(--accent, var(--red)); +} +.email-unsub-ignore-btn { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 22px; + height: 22px; + padding: 0; + position: absolute; + top: 5px; + right: 6px; + color: color-mix(in srgb, var(--fg) 58%, transparent); +} +.email-unsub-card-details .email-unsub-ignore-btn { + position: static; +} +.email-unsub-ignore-btn:hover { + color: var(--accent, var(--red)); +} +.email-unsub-remaining-actions { + display: inline-flex; + align-items: center; + gap: 7px; + flex-wrap: wrap; +} +.email-unsub-remaining-actions button { + display: inline-flex; + align-items: center; + gap: 4px; + position: relative; + top: -2px; +} +.email-unsub-remaining-actions svg { + flex: 0 0 auto; +} +.email-unsub-link-btn { + position: relative; + top: 2px; +} +.email-unsub-status.is-error { + color: var(--accent, var(--red)) !important; +} +.email-settings-section-desc { + position: relative; + top: 8px; + margin-top: 4px; + font-size: 10.5px; + line-height: 1.25; + color: color-mix(in srgb, var(--fg) 55%, transparent); +} +.email-settings-inline-images-section .email-settings-section-desc { + margin-bottom: 4px; +} +.email-settings-inline-images-spacer { + height: 12px; +} +.email-settings-display-spacer { + height: 10px; +} +.email-settings-display-section .email-settings-section-desc { + margin-top: 10px; + top: 10px; +} +.email-settings-display-section, +.email-settings-inline-images-section { + padding-bottom: 18px; +} +.email-settings-cleanup-section .email-settings-section-desc { + margin-top: 14px; + top: 14px; +} +.email-settings-auto-reply-section .email-settings-section-desc { + margin-top: 12px; + top: 10px; +} +.email-settings-toggle, +.email-settings-check { + display: flex; + align-items: center; + gap: 7px; + font-size: 12px; + line-height: 1.25; +} +.email-settings-toggle { + flex-shrink: 0; +} +.email-settings-enabled-control { + display: inline-flex; + align-items: center; + gap: 7px; + flex-shrink: 0; +} +.email-settings-enabled-state { + font-size: 10px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.email-settings-section.is-enabled .email-settings-enabled-state { + color: var(--accent, var(--red)); +} +.email-settings-section.is-disabled .email-settings-enabled-state { + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.email-settings-display-section.is-enabled .email-settings-enabled-state { + color: var(--accent, var(--red)); +} +.email-settings-check { + min-height: 24px; + padding: 3px 0; +} +.email-settings-check > span:first-child { + min-width: 0; + margin-right: auto; +} +.email-settings-check-spacer { + flex: 1; +} +.email-settings-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 7px; + min-width: 0; +} +.email-settings-grid label, +.email-settings-field { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; +} +.email-settings-grid span, +.email-settings-field > span { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0; + color: color-mix(in srgb, var(--fg) 56%, transparent); +} +.email-settings-page input[type="date"], +.email-settings-page input[type="datetime-local"], +.email-settings-page select, +.email-settings-page textarea { + width: 100%; + min-width: 0; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + color: var(--fg); + font: inherit; + font-size: 12px; +} +.email-settings-page input[type="date"], +.email-settings-page input[type="datetime-local"], +.email-settings-page select { + height: 28px; + padding: 0 7px; +} +.email-settings-page textarea { + resize: vertical; + min-height: 72px; + padding: 7px; + line-height: 1.35; +} +.email-settings-static-value { + min-height: 28px; + display: flex; + align-items: center; + padding: 0 7px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--panel, var(--bg)) 74%, transparent); + color: color-mix(in srgb, var(--fg) 72%, transparent); + font-size: 12px; +} +.email-settings-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 8px; + margin-top: 2px; +} + +/* Email Settings actions use the same quiet outlined treatment everywhere: + the Settings links, the modal settings page, and unsubscribe actions. */ +[data-settings-panel="email"] #set-email-open-integrations, +[data-settings-panel="email"] #set-email-open-tasks, +[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn, +#email-lib-settings-page .email-settings-section button.memory-toolbar-btn { + background: transparent; + border: 1px solid var(--border); + color: var(--fg); + font-family: inherit; + font-weight: 400; +} +[data-settings-panel="email"] #set-email-open-integrations:hover, +[data-settings-panel="email"] #set-email-open-tasks:hover, +[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn:hover, +#email-lib-settings-page .email-settings-section button.memory-toolbar-btn:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); + color: var(--fg); +} +[data-settings-panel="email"] #set-email-open-integrations > svg, +[data-settings-panel="email"] #set-email-open-tasks > svg { + color: var(--accent, var(--red)); + opacity: 1 !important; +} +[data-settings-panel="email"] .email-style-settings-extract, +[data-settings-panel="email"] .email-style-settings-save, +#email-lib-settings-page .email-style-settings-extract, +#email-lib-settings-page .email-style-settings-save { + background: transparent; + border: 1px solid var(--border); + color: var(--fg); + font-weight: 400; +} +[data-settings-panel="email"] .email-style-settings-extract:hover, +[data-settings-panel="email"] .email-style-settings-save:hover, +#email-lib-settings-page .email-style-settings-extract:hover, +#email-lib-settings-page .email-style-settings-save:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); + color: var(--fg); +} +.email-settings-status, +.email-style-settings-status { + margin-right: auto; + font-size: 11px; + color: color-mix(in srgb, var(--fg) 62%, transparent); +} +.email-settings-status.is-success, +.email-style-settings-status.is-success { + color: var(--color-success, #4caf50); + position: relative; + top: 2px; +} +.email-settings-status.is-error, +.email-style-settings-status.is-error { + color: var(--accent, var(--red)); + position: relative; + top: 2px; +} +.email-settings-save, +.email-style-settings-save { + height: 28px; + min-width: 64px; + justify-content: center; + display: inline-flex; + align-items: center; + gap: 5px; +} +.email-settings-save { + height: 32px; +} +.email-style-settings-extract { + height: 32px; +} +.email-style-settings-save { + height: 32px; +} +.email-settings-save svg, +.email-style-settings-save svg { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-style-settings-extract svg { + color: var(--accent, var(--red)); + position: relative; + top: 2px; +} +.email-settings-task-setting { + color: color-mix(in srgb, var(--fg) 72%, transparent); +} +.email-settings-inline-link { + appearance: none; + border: 0; + padding: 0; + background: transparent; + color: var(--accent, var(--red)); + font: inherit; + text-decoration: underline; + cursor: pointer; +} +.email-style-extract-help { + width: 18px; + height: 18px; + min-width: 18px; + padding: 0; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: color-mix(in srgb, var(--fg) 68%, transparent); + display: inline-flex; + align-items: center; + justify-content: center; + font: inherit; + font-size: 11px; + font-weight: 700; + line-height: 1; + cursor: help; +} +.email-style-extract-help:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); +} +@media (max-width: 420px) { + .email-settings-grid { + grid-template-columns: 1fr; + } +} + + +/* ── Notes ── */ +/* Notes panel — body-level flex sibling of chat-container */ +body.notes-view .chat-container { flex: 1; min-width: 0; } + +/* Mobile: notes panel takes over full screen */ +@media (max-width: 768px) { + body.notes-view .notes-pane { + position: fixed; + inset: 0; + max-width: 100%; + width: 100% !important; + /* Desktop sets height: min(80vh, 820px) which wins over inset:0's bottom:0 + and leaves a gap at the bottom of the viewport. Force full height here. */ + height: 100dvh !important; + max-height: 100dvh !important; + z-index: 170; + /* Bottom-sheet treatment like the document editor: stroked, rounded top, + slides up from the bottom. */ + border: 1px solid var(--border); + border-bottom: none; + border-radius: 14px 14px 0 0; + /* Pane itself never scrolls — its inner .notes-pane-body is the scroller. + Two nested scrollers caused the body's flex:1 to lose its height bound + on Firefox mobile and the last row got clipped off-screen. */ + overflow: hidden; + animation: sheet-enter 0.25s cubic-bezier(0.2, 0.8, 0.2, 1) both; + transform-origin: bottom center; + } + /* Required for the flex:1 body to actually constrain to remaining pane + height instead of expanding to its content (default min-height:auto on + flex items). Without this both grid and list views overflow the viewport + bottom on phones. */ + body.notes-view .notes-pane-body { + min-height: 0; + -webkit-overflow-scrolling: touch; + } + body.notes-view #notes-divider { + display: none; + } + body.notes-view .notes-mobile-grabber { + display: block; + flex-shrink: 0; + height: 18px; + position: relative; + background: var(--bg); + touch-action: none; + cursor: grab; + } + body.notes-view .notes-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; + } + body.notes-view .chat-container { + display: none; + } + body.notes-view .mobile-new-chat-btn, + body.notes-view .hamburger-btn { + display: none !important; + } +} +/* ── Mobile notes UX ──────────────────────────────────── + Tiles become read-only previews on touch ≤768px wide. + Tap opens a fullscreen overlay where editing happens; long-press + toggles drag-to-reorder. Wired up in static/js/notes.js. */ +body.notes-mobile-mode .note-card-corner, +body.notes-mobile-mode .note-card-corner-edit, +body.notes-mobile-mode .note-card-corner-archive, +body.notes-mobile-mode .note-card-corner-copy, +body.notes-mobile-mode .note-card-corner-trash, +body.notes-mobile-mode .note-card-corner-unarchive, +body.notes-mobile-mode .note-card-corner-done, +body.notes-mobile-mode .note-card-edit-corner, +body.notes-mobile-mode .note-card-done, +body.notes-mobile-mode .note-card-actions, +body.notes-mobile-mode .note-cl-quickadd, +body.notes-mobile-mode .note-card .note-checkbox-rm, +/* Panel-fullscreen toggle is redundant on mobile — the panel always + fills the viewport already. */ +body.notes-mobile-mode #notes-fullscreen-toggle { + display: none !important; +} + +/* Header toggle icons (archive view, list/grid switch) — defaults are + tuned for the dense desktop header; on mobile bump them slightly so + they read as tap targets without dominating the header. */ +body.notes-mobile-mode #notes-archive-toggle, +body.notes-mobile-mode #notes-view-toggle { + width: 30px; + height: 30px; + padding: 6px; +} +body.notes-mobile-mode #notes-archive-toggle svg, +body.notes-mobile-mode #notes-view-toggle svg { + width: 17px; + height: 17px; +} +/* The whole card is the tap target on mobile — bigger feedback */ +body.notes-mobile-mode .note-card { + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform 0.12s ease, box-shadow 0.12s ease; +} + +/* Drag-to-reorder mode — desaturate the grid and sweep a subtle + shimmer across every tile so it's obvious you're in rearrange mode + (the Google Keep "active edit" cue). Also disable scroll inside the + panel so the drag works smoothly. */ +body.notes-drag-mode .notes-pane-body { + overflow: hidden; + touch-action: none; +} diff --git a/static/css/style-part-07.css b/static/css/15-notes.css similarity index 55% rename from static/css/style-part-07.css rename to static/css/15-notes.css index 0c6fc8fee..3c4103273 100644 --- a/static/css/style-part-07.css +++ b/static/css/15-notes.css @@ -1,3002 +1,19 @@ -/* Fragment 8/9 of the former static/style.css. +/* 15-notes - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -.doc-editor-textarea, -#doc-editor-code.email-mode .doc-editor-textarea { - color: var(--fg) !important; - caret-color: var(--fg) !important; - z-index: 1; -} - -/* Emoji picker (monochrome icon picker) */ -.emoji-picker-btn { - background: none; border: none; color: var(--fg); opacity: 0.35; - padding: 4px 7px; cursor: pointer; min-height: 28px; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.1s; -} -.emoji-picker-btn:hover { opacity: 1; } -.emoji-picker { - width: min(300px, 92vw); max-height: min(300px, 55vh); - background: var(--panel); border: 1px solid var(--border); border-radius: 8px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3); - display: flex; flex-direction: column; overflow: hidden; -} -.emoji-picker-search { - padding: 8px 12px; background: var(--panel); border: none; - border-bottom: 1px solid var(--border); color: var(--fg); - font-size: 12px; font-family: inherit; outline: none; -} -.emoji-picker-groups { overflow-y: auto; padding: 4px 0; flex: 1; } -.emoji-picker-group { padding: 0 6px 4px 6px; } -.emoji-picker-group-name { - font-size: 10px; font-weight: 600; opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.5px; - padding: 4px 4px 2px 4px; -} -.emoji-picker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; } -.emoji-picker-item { - background: none; border: none; cursor: pointer; - padding: 4px; border-radius: 4px; - display: flex; align-items: center; justify-content: center; - color: var(--fg); transition: background 0.1s; -} -.emoji-picker-item:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } -.emoji-picker-item svg { width: 16px; height: 16px; } - -.email-avatar { - width: 28px; height: 28px; border-radius: 50%; background: var(--accent, #4a9eff); - color: #fff; display: flex; align-items: center; justify-content: center; - font-size: 12px; font-weight: 600; flex-shrink: 0; margin-top: 1px; -} -.email-item-content { flex: 1; min-width: 0; overflow: hidden; } -.email-item-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } -.email-sender { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.email-filter-chip { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; - margin: 4px 8px 6px; - padding: 4px 8px; - border-radius: 12px; - font-size: 11px; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - color: var(--fg); -} -.email-filter-chip-clear { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 14px; - line-height: 1; - padding: 0 2px; - opacity: 0.6; -} -.email-date { font-size: 10px; opacity: 0.5; white-space: nowrap; flex-shrink: 0; } -.email-subject { font-size: 11px; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } -.email-sent-chip, -.email-folder-chip { - display: inline-flex; - align-items: center; - height: 14px; - padding: 0 5px; - margin-right: 5px; - border-radius: 999px; - font-size: 8px; - line-height: 1; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - vertical-align: 1px; -} -.email-folder-chip { - position: relative; - top: 4px; - color: color-mix(in srgb, var(--fg) 75%, transparent); - background: rgba(127, 127, 127, 0.13); - border-color: rgba(127, 127, 127, 0.24); -} -.email-meta-date-group { - display: inline-flex; - align-items: center; - gap: 5px; - white-space: nowrap; -} -.email-meta-date-group .email-folder-chip { - margin-right: 0; -} -.email-card-expanded .email-sent-chip, -.email-card-reader .email-sent-chip { - position: relative; - top: 4px; -} -.email-tags { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 3px; margin-left: 6px; vertical-align: middle; position: relative; top: -2px; } -.email-card-tags { - flex: 0 0 auto; - min-width: 0; - max-width: 48%; - position: relative; - top: -4px; -} -.email-card-expanded .email-tags, -.email-card-reader .email-tags { top: -4px; } -.doclib-card-expanded .email-card-tags { top: -4px; } -.email-card-expanded .email-card-titlerow .email-tag, -.email-card-expanded .email-card-titlerow .email-tags-more { - transform: translateY(-2px); -} -@media (max-width: 768px) { - .email-card-expanded .email-card-titlerow .email-tag, - .email-card-expanded .email-card-titlerow .email-tags-more { - transform: translateY(2px); - } -} -.email-card-tags.email-tags-expanded { - position: absolute; - z-index: 4; - right: calc(var(--email-status-width, 44px) + 6px); - top: -5px; - max-width: min(58%, 260px); - padding: 4px 5px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent); -} -.email-tags-more { - appearance: none; - border: 0; - box-sizing: border-box; - border-radius: 999px; - background: rgba(127, 127, 127, 0.16); - color: var(--fg); - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 1px; - height: 13px !important; - min-height: 0 !important; - min-width: 23px; - padding: 1px 4px; - font-family: inherit; - font-size: 9px; - line-height: 1; - margin: 0; - opacity: 0.68; - position: relative; - top: 0; - vertical-align: middle; -} -.email-tags-more svg { display: block; flex: 0 0 auto; } -.email-tags-expanded .email-tags-more svg { transform: rotate(180deg); } -.email-tags-toggle-inline { - position: absolute; - right: 94px; - top: 50%; - transform: translateY(calc(-50% - 5px)); - height: 20px !important; - min-width: 34px; - padding: 2px 6px !important; - font-size: 10px !important; - line-height: 1 !important; - opacity: 0.62; - border-radius: 999px !important; -} -.email-tags-toggle-inline.active { - opacity: 1; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 36%, transparent); -} -.email-tags-toggle-inline.email-tags-empty { - color: var(--warning, #d6b35a); - background: color-mix(in srgb, var(--warning, #d6b35a) 12%, transparent); - border-color: color-mix(in srgb, var(--warning, #d6b35a) 34%, transparent); -} -.email-tag { - display: inline-block; - font-size: 9px; - line-height: 1; - padding: 2px 5px; - border-radius: 8px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.3px; - background: rgba(127, 127, 127, 0.18); - color: var(--fg); -} -.email-tag-clickable { - appearance: none; - border: 0; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - font-family: inherit; - font-size: 9px; - font-weight: 500; - height: 13px !important; - min-height: 0 !important; - line-height: 1; - margin: 0; - padding: 2px 5px !important; - text-transform: uppercase; - vertical-align: middle; - flex: 0 0 auto; -} -.email-tag-work { background: rgba(96, 165, 250, 0.22); color: #60a5fa; } -.email-tag-personal { background: rgba(74, 222, 128, 0.22); color: #4ade80; } -.email-tag-finance { background: rgba(250, 204, 21, 0.22); color: #facc15; } -.email-tag-bills { background: rgba(251, 146, 60, 0.22); color: #fb923c; } -.email-tag-receipt { background: rgba(251, 146, 60, 0.22); color: #fb923c; } -.email-tag-travel { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } -.email-tag-newsletter { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } -.email-tag-promo, -.email-tag-marketing { background: rgba(244, 114, 182, 0.22); color: #f472b6; } -.email-tag-notification { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } -.email-tag-security { background: rgba(248, 113, 113, 0.22); color: #f87171; } -.email-tag-urgent { background: color-mix(in srgb, var(--color-error, #e06c75) 25%, transparent); color: var(--color-error, #e06c75); font-weight: 600; } -.email-tag-reply-soon { background: rgba(240, 173, 78, 0.22); color: #f0ad4e; } -.email-tag-social { background: rgba(56, 189, 248, 0.22); color: #38bdf8; } -.email-tag-shopping { background: rgba(236, 72, 153, 0.22); color: #ec4899; } -.email-tag-calendar { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } -.email-menu-wrap { position: relative; flex-shrink: 0; } -.email-menu-btn { - background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; - padding: 4px; transition: opacity 0.15s; - position: relative; top: 0px; -} -.email-dropdown { - position: absolute; right: 0; top: 100%; z-index: 9999; - min-width: 120px; background: var(--bg); border: 1px solid var(--border); - border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); padding: 4px 0; -} -.email-loading { padding: 16px; text-align: center; font-size: 12px; opacity: 0.5; } -.email-load-more { padding: 8px; text-align: center; } -.email-load-more-btn { - background: transparent; border: 1px solid var(--border); border-radius: 4px; - color: var(--fg); padding: 4px 12px; font-size: 11px; cursor: pointer; -} -.email-load-more-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } -.email-spinner { - display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); - border-top-color: #fff; border-radius: 50%; animation: email-spin 0.6s linear infinite; - vertical-align: middle; margin-right: 4px; -} - -/* Email section doesn't collapse — hide the auto-injected chevron */ -#email-section .section-collapse-btn { display: none !important; } - -#email-section:hover #email-compose-btn { - opacity: 0.7 !important; -} -#email-compose-btn:hover { - opacity: 1 !important; - background: none !important; -} -#email-compose-btn svg { - width: 11px; - height: 11px; - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); -} -#email-compose-btn .list-item-plus-label { - top: calc(50% - 0.5px); - color: var(--fg); - font-size: 10px; -} -#email-compose-btn:hover svg { - transform: rotate(180deg) scale(1.15); -} - -/* Library row "+" — identical to the email compose "+": hidden until the row - is hovered, no accent box, and the same rotate-in animation. */ -#library-new-doc-btn, -#notes-new-note-btn { - opacity: 0.4 !important; - transition: opacity 0.15s ease; -} -#tool-library-btn:hover #library-new-doc-btn, -#tool-notes-btn:hover #notes-new-note-btn { - opacity: 0.7 !important; -} -#library-new-doc-btn:hover, -#notes-new-note-btn:hover { - opacity: 1 !important; - background: none !important; -} -#library-new-doc-btn svg, -#notes-new-note-btn svg { - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); -} -#library-new-doc-btn .list-item-plus-label, -#notes-new-note-btn .list-item-plus-label { - top: calc(50% + 1px); -} -#library-new-doc-btn:hover svg, -#notes-new-note-btn:hover svg { - transform: rotate(180deg) scale(1.15); -} - - -/* MD toolbar attach button — matches other toolbar buttons */ -.md-toolbar-attach-btn { - background: none; border: none; color: var(--fg); opacity: 0.35; - padding: 4px 7px; cursor: pointer; min-height: 28px; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.1s; -} -.md-toolbar-attach-btn:hover { opacity: 1; } - -/* Email reader icon buttons — vertical icon + label stack. */ -.memory-toolbar-btn.reader-icon-btn { - width: auto; - height: 44px; - /* padding: 4px 2px; */ - position: relative; - top: 1px; - display: inline-flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 3px; - flex: 0 0 auto; -} -.memory-toolbar-btn.reader-icon-btn svg { width: 18px; height: 18px; } -.memory-toolbar-btn.reader-icon-btn[data-act="reply"] svg { - color: inherit; - stroke: currentColor; -} -.memory-toolbar-btn.reader-icon-btn.active { - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - color: var(--accent, var(--red)); -} -.memory-toolbar-btn.reader-icon-btn[data-act="more"] { - color: inherit; -} -.memory-toolbar-btn.reader-icon-btn[data-act="more"] svg { fill: currentColor; } -.memory-toolbar-btn.reader-icon-btn.reader-more-active { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); - color: inherit; -} -/* Cookbook Serve dropdown triggers (the cached-model kebab + the saved-config - arrow) — accent-colored to signal they open a menu, with a filled tint when - their dropdown is open. Matches the email reader More button pattern. */ -.hwfit-cached-menu-btn, -.cookbook-saved-arrow { - color: var(--accent, var(--red)); - /* Base .memory-item-btn is flex with align-items but no justify-content, - so the kebab SVG sat left-aligned inside the button instead of being - horizontally centered. Pin it center. */ - justify-content: center; -} -.hwfit-cached-menu-btn.cookbook-menu-active, -.cookbook-saved-arrow.cookbook-menu-active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - color: var(--accent, var(--red)); -} - -/* Card action menus share one hit target and baseline. Keep this late in the - cascade because modal buttons and Cookbook Serve add their own offsets. */ -.memory-item-actions > .memory-item-btn, -.memory-item-actions .hwfit-cached-menu-btn { - box-sizing: border-box; - width: 28px; - min-width: 28px; - height: 22px; - min-height: 22px; - margin: 0 !important; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - color: var(--color-muted); - font-size: 11px; - display: inline-flex; - align-items: center; - align-self: center; - justify-content: center; - position: relative; - top: 0 !important; - transform: none !important; - line-height: 1; - transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s; -} -.memory-item-actions .hwfit-cached-menu-btn:hover { - opacity: 1 !important; - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); - color: var(--fg); -} -.memory-item-actions .hwfit-cached-menu-btn { - transform: translateY(2px) !important; -} - -@media (min-width: 769px) { - .task-card .memory-item-actions { - top: 6px; - } - .task-card .memory-item-actions > .memory-item-btn { - transform: none !important; - } -} - -/* All kebab menus use the same muted tone as the expand/collapse chevron. */ -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -) { - color: var(--color-muted, var(--fg)) !important; -} -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -) svg { - color: inherit; - fill: currentColor; -} -/* Card kebabs are lightweight menu affordances, not bordered buttons. The - old base hover rule gave only some library cards a boxed outline while - chats, tasks, and cookbook stayed borderless. */ -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -):hover { - background: none !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1; -} -/* Skills used a separate, larger kebab with a different hover treatment. - Match every other card action trigger. */ -.skill-kebab-btn { - box-sizing: border-box; - width: 28px; - min-width: 28px; - height: 22px; - min-height: 22px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - color: var(--color-muted) !important; - opacity: 0.55; - top: -2px; -} -@media (max-width: 768px) { - .memory-item-actions > .memory-item-btn, - .memory-item-actions .hwfit-cached-menu-btn { - width: 40px; - min-width: 40px; - height: 40px; - min-height: 40px; - } - /* Card menus all need the same comfortable tap target. These components - previously escaped the shared action-button sizing through more-specific - desktop selectors. */ - .memory-menu-btn, - .skill-kebab-btn, - .task-card .memory-item-actions .memory-item-btn { - width: 40px !important; - min-width: 40px !important; - height: 40px !important; - min-height: 40px !important; - } - .memory-menu-btn { opacity: 0.7; } - /* Document cards seat their actions on a slightly higher title baseline - than chats/research cards. Bring only that kebab down 4px. */ - #doclib-modal .doclib-card[data-doc-id] > .doclib-document-header .memory-item-actions .memory-item-btn { - transform: translateY(-2px) !important; - } -} -/* Mobile: center the kebab/menu dropdowns in the viewport so they don't - slide left/right as the window width changes (they were positioned via - inline `right: ...px` relative to the kebab button). Stable position is - easier to land on by touch. The inline `top:` set by the menu code is - preserved; we only override the horizontal axis. */ -@media (max-width: 768px) { - .email-card-dropdown, - .cookbook-saved-menu, - .cookbook-dep-menu { - left: 50% !important; - right: auto !important; - transform: translateX(-50%); - } -} -/* Mobile-only Cancel item appended to dropdowns (kebab/More menus). On - desktop outside-click closes them cleanly; on touch that's fiddly, so - give an explicit Cancel row. Visually separated from the action items - above by a top divider + slight muted color. */ -.dropdown-cancel-mobile { display: none; } -@media (max-width: 768px) { - .dropdown-cancel-mobile { - display: flex; - border-top: 1px solid var(--border); - margin-top: 4px; - padding-top: 8px; - opacity: 0.85; - } -} -/* Launch-command Copy button morphs into Cancel once Launch has been clicked. */ -.hwfit-serve-copy.hwfit-serve-cancel { - border-color: var(--color-danger, #e06c75) !important; - color: var(--color-danger, #e06c75) !important; -} -.hwfit-serve-copy.hwfit-serve-cancel:hover { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); -} - -/* One visual contract for card kebab menus. These menus are built by several - modules, and native button defaults previously made identical rows render - with different fonts, heights, and hover movement. */ -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) { - box-sizing: border-box; - padding: 4px !important; - color: var(--fg); - background: var(--panel, var(--bg)) !important; - border: 1px solid var(--border) !important; - border-radius: 8px !important; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important; - font-family: inherit !important; - font-size: 11px !important; - line-height: 1.2; -} -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) > .dropdown-item-compact { - box-sizing: border-box; - width: 100%; - min-height: 28px; - padding: 6px 8px; - gap: 10px; - border: 0; - border-radius: 6px; - color: var(--fg); - background: transparent; - font: inherit !important; - line-height: 1.2; - text-align: left; - white-space: nowrap; -} -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) > .dropdown-item-compact:hover:not(:disabled) { - color: var(--fg); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - transform: none; -} -@media (max-width: 768px) { - :is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu - ) > .dropdown-item-compact { - min-height: 44px; - padding: 12px !important; - gap: 10px !important; - font-size: 14px !important; - } -} -/* Email modal title unread badge — small accent pill next to "Email". */ -.email-lib-unread-badge { - display: inline-block; - font-size: 0.55em; - font-weight: 700; - background: var(--accent-primary, var(--red)); - color: var(--bg, #1a1a1a); - border-radius: 9px; - padding: 1px 7px; - margin-left: 8px; - vertical-align: 2px; - line-height: 1.3; - letter-spacing: 0.02em; - cursor: pointer; -} -.email-lib-unread-badge.email-lib-away-badge { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); - padding: 0 7px; -} -.email-lib-auto-reply-badge { - display: inline-block; - margin-left: 7px; - padding: 1px 7px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border)); - border-radius: 9px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - color: var(--accent, var(--red)); - font-size: 0.55em; - font-weight: 700; - line-height: 1.3; - letter-spacing: 0.01em; - vertical-align: 2px; - cursor: pointer; -} -.email-lib-auto-reply-status-dot { - width: 6px; - height: 6px; - margin-left: 6px; - border-radius: 50%; - background: var(--accent, var(--red)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); - vertical-align: 2px; -} -.modal-header h4 > .email-lib-auto-reply-badge { - font-size: 0.65em; - white-space: nowrap; - vertical-align: 1px; -} -#email-lib-modal.email-lib-unread-active .doclib-modal-content { - box-shadow: - 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent), - 0 0 18px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), - var(--shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.35)); -} -#email-lib-modal .email-lib-header-actions .minimize-btn { - position: relative; - left: 6px; -} -/* Per-card prev/next nav — only visible on the currently expanded card, - sits at the right of the title row next to the done check. */ -.email-card-nav-arrows { - display: none; - margin-left: auto; - gap: 2px; - flex-shrink: 0; - align-items: center; - position: relative; - top: -3px; - left: 19px; -} -.doclib-card.email-card-expanded .email-card-nav-arrows { - display: inline-flex; -} -.email-card-header-menu { - display: none; - align-items: center; - justify-content: center; - flex-shrink: 0; - width: 28px; - height: 28px; - padding: 0; - border: none; - border-radius: 6px; - background: none; - color: var(--fg); - opacity: 0.65; - cursor: pointer; - position: relative; - top: -3px; - transition: opacity 0.12s, background 0.12s; -} -.doclib-card.email-card-expanded .email-card-header-menu { - display: inline-flex; -} -.email-card-header-menu:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.email-card-nav-btn { - background: none; - border: none; - color: var(--fg); - opacity: 0.65; - cursor: pointer; - padding: 6px 8px; - border-radius: 6px; - display: inline-flex; - align-items: center; - justify-content: center; - transition: opacity 0.12s, background 0.12s; -} -.email-card-nav-btn:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} - -/* ── Email document editor ── */ -.doc-email-header { - display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; - border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; -} -.doc-email-fields { - display: flex; - flex-direction: column; - gap: 6px; - min-height: 0; -} -.doc-email-collapse-btn { - width: 100%; - min-height: 24px; - display: none; - align-items: center; - gap: 7px; - padding: 2px 4px 3px; - border: none; - background: transparent; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - opacity: 0.72; - text-align: left; -} -.doc-email-collapse-btn:hover { opacity: 1; color: var(--accent, var(--red)); } -.doc-email-collapse-btn svg { - flex-shrink: 0; - opacity: 0.65; - transition: transform 0.14s ease; -} -.doc-email-collapse-summary { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - opacity: 0.74; -} -.doc-email-header.doc-email-header-collapsed .doc-email-collapse-btn svg { - transform: rotate(180deg); -} -.email-field { display: flex; align-items: center; gap: 8px; position: relative; } -.email-field label { font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.5; min-width: 50px; text-align: right; flex-shrink: 0; } -.email-field input { - flex: 1; width: 100%; background: transparent; border: 1px solid var(--border); border-radius: 4px; - padding: 5px 8px; font-size: 13px; font-family: inherit; color: var(--fg); outline: none; - min-width: 0; -} -.email-field input:focus { border-color: var(--accent, #4a9eff); } -@container docpane (max-width: 460px) { - .doc-email-header .email-field { - display: grid; - grid-template-columns: 1fr; - gap: 3px; - align-items: stretch; - } - .doc-email-header .email-field label { - min-width: 0; - text-align: left; - } - .doc-email-header .email-field input { - width: 100%; - } -} -/* Cc toggle and attach button are absolute so they don't steal width - from the To input. Anchored to the input's vertical center + 4px - nudge so the label sits where the user expects it. */ -.email-field .email-cc-toggle { - position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); - z-index: 2; -} -/* Accent-tinted prefix label (To / Cc / Bcc / Subject) sitting at - the left edge of each input. Replaces both the old tag - and the all-placeholder accent. */ -.email-field .email-field-prefix { - position: absolute; - left: 10px; - top: 50%; - transform: translateY(-50%); - font-size: 11px; - font-weight: 600; - color: var(--accent-primary, var(--red)); - pointer-events: none; - z-index: 1; - line-height: 1; -} -.email-field input { padding-left: 44px; } -.email-field #doc-email-subject { padding-left: 64px; } -/* X close button inside Cc / Bcc fields. */ -.email-field .email-cc-close { - position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); - background: none; border: none; color: var(--fg); - opacity: 0.4; cursor: pointer; - width: 22px; height: 22px; - display: inline-flex; align-items: center; justify-content: center; - border-radius: 4px; - padding: 0; - z-index: 2; -} -.email-field .email-cc-close:hover { - opacity: 1; - color: var(--accent, var(--red)); - background: color-mix(in srgb, currentColor 10%, transparent); -} -.email-field input { padding-right: 60px; } -.email-field #doc-email-cc, .email-field #doc-email-bcc { padding-right: 32px; } -.email-field #doc-email-subject { padding-right: 8px; } - -.doc-email-actions { - display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; - border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; - align-items: center; - position: relative; - z-index: 30; - pointer-events: auto; -} -#doc-actions-footer .email-send-split { - margin-left: auto; -} -.doc-save-state-icon { display: none; flex: 0 0 auto; } -.doc-save-button[data-save-state="dirty"] { color: var(--accent, var(--red)); } -.doc-save-button[data-save-state="saving"] { color: var(--fg-muted, var(--fg)); } -.doc-save-button[data-save-state="saved"] { color: var(--fg); } -.doc-save-button[data-save-state="error"] { color: var(--color-danger, #e06c75); } -.doc-stats-wrap { - position: relative; - margin-left: 0; - flex: 0 0 auto; -} -.doc-stats-btn { - height: 28px; - border: 0; - border-radius: 4px; - padding: 0 7px; - background: transparent; - color: var(--fg-muted, var(--fg)); - font: inherit; - font-size: 10px; - cursor: pointer; - white-space: nowrap; -} -.doc-stats-btn:hover, -.doc-stats-btn[aria-expanded="true"] { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 7%, transparent); -} -.doc-stats-btn.doc-stats-selected { - color: var(--accent, var(--red)); - font-weight: 600; -} -.doc-stats-popover { - position: fixed; - right: auto; - bottom: auto; - width: 190px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); - color: var(--fg); - font-size: 11px; -} -.doc-stats-scope { - padding-bottom: 6px; - margin-bottom: 5px; - border-bottom: 1px solid var(--border); - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - text-transform: uppercase; -} -.doc-stats-row { - min-height: 22px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 14px; -} -/* WYSIWYG email body — what the recipient sees, edited in place. */ -.doc-email-richbody { - flex: 1; - min-height: 0; - overflow-y: auto; - padding: 12px 16px; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.6; - color: var(--fg); - background: var(--bg); - outline: none; - white-space: pre-wrap; - text-align: left; - word-wrap: break-word; -} -.doc-email-richbody.richtext-mode { - background: var(--bg); -} -.doc-rich-empty-import { - position: absolute; - top: 90px; - right: 18px; - bottom: auto; - left: auto; - z-index: 2; - display: flex; - pointer-events: none; -} -.doc-rich-empty-import .doc-rich-empty-import-btn { - position: static; - float: none; - margin: 0; - opacity: 1; - pointer-events: auto; - transform: none; -} -.doc-email-richbody.richtext-mode ul.rich-checklist { - list-style: none; - padding-left: 1.8em; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li { - position: relative; - min-height: 1.6em; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li::before { - content: ""; - position: absolute; - left: -1.55em; - top: 0.3em; - width: 0.95em; - height: 0.95em; - box-sizing: border-box; - border: 1.5px solid color-mix(in srgb, var(--fg) 48%, transparent); - border-radius: 3px; - background: var(--sidebar-bg, var(--panel)); - cursor: pointer; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::before { - border-color: var(--accent, var(--red)); - background: var(--accent, var(--red)); -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::after { - content: ""; - position: absolute; - left: -1.27em; - top: 0.44em; - width: 0.28em; - height: 0.5em; - border: solid #fff; - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); - pointer-events: none; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"] { - color: color-mix(in srgb, var(--fg) 62%, transparent); - text-decoration: line-through; -} -.doc-email-richbody.richtext-mode hr { - border: 0; - border-top: 1px solid var(--border); - margin: 1.25em 0; -} -.doc-email-richbody.richtext-mode hr.richtext-page-break { - height: 8px; - border-top: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); - border-bottom: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); - margin: 1.6em 0; - opacity: 0.8; -} -.doc-email-richbody.richtext-mode table { - width: 100%; - margin: 12px 0; - border-collapse: collapse; - table-layout: fixed; -} -.doc-email-richbody.richtext-mode th, -.doc-email-richbody.richtext-mode td { - min-width: 48px; - padding: 7px 8px; - border: 1px solid var(--border); - vertical-align: top; - overflow-wrap: anywhere; -} -.doc-email-richbody.richtext-mode th { - background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 85%, var(--fg) 15%); - font-weight: 600; -} -.doc-email-richbody.richtext-mode code, -.doc-email-richbody.richtext-mode span[style*="OdysseusInlineCode"] { - padding: 0.12em 0.35em; - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 0.9em; - white-space: break-spaces; -} -.doc-email-richbody.richtext-mode pre { - margin: 1em 0; - padding: 12px 14px; - overflow-x: auto; - border: 1px solid var(--border); - border-radius: 4px; - background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 82%, #000 18%) !important; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 0.9em; - line-height: 1.5; - white-space: pre-wrap; - tab-size: 2; -} - -::highlight(doc-find-results) { - color: inherit; - background-color: color-mix(in srgb, #facc15 48%, transparent); -} - -::highlight(doc-find-current) { - color: inherit; - background-color: color-mix(in srgb, var(--accent, #ef4444) 68%, #facc15); -} - -::highlight(doc-ai-selections) { - color: inherit; - background-color: color-mix(in srgb, var(--accent, #ef4444) 30%, transparent); -} -.doc-email-richbody img.richtext-image { - display: block; - margin: 12px 0; - max-width: 100%; - height: auto; - border-radius: 4px; - cursor: pointer; -} -.doc-email-richbody figure.richtext-image { - margin: 12px 0; - max-width: 100%; -} -.doc-email-richbody figure.richtext-image:not(:has(img)) { display: none; } -.doc-email-richbody figure.richtext-image img.richtext-image { - display: block; - margin-top: 0; - margin-bottom: 0; - max-width: 100%; - height: auto; - border-radius: 4px; -} -.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-60) .richtext-image-caption { width: 60%; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-35) .richtext-image-caption { width: 35%; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-left) .richtext-image-caption { margin-left: 0; margin-right: auto; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-center) .richtext-image-caption { margin-left: auto; margin-right: auto; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-right) .richtext-image-caption { margin-left: auto; margin-right: 0; } -.doc-email-richbody img.richtext-image-size-100 { width: 100%; } -.doc-email-richbody img.richtext-image-size-60 { width: 60%; } -.doc-email-richbody img.richtext-image-size-35 { width: 35%; } -.doc-email-richbody img.richtext-image-align-left { margin-left: 0; margin-right: auto; } -.doc-email-richbody img.richtext-image-align-center { margin-left: auto; margin-right: auto; } -.doc-email-richbody img.richtext-image-align-right { margin-left: auto; margin-right: 0; } -.doc-email-richbody img.richtext-image[data-editor-image-selected] { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 3px; -} -.doc-rich-image-toolbar { - position: fixed; - display: inline-flex; - align-items: center; - gap: 2px; - padding: 2px; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 10032; -} -.doc-rich-image-toolbar button { - width: 24px; - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - border: 0; - border-radius: 3px; - background: transparent; - color: inherit; - cursor: pointer; -} -.doc-rich-image-toolbar button:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--accent, var(--red)); -} -.doc-rich-image-toolbar .doc-rich-image-remove:hover { - color: var(--accent, var(--red)); -} -.doc-rich-image-source-menu { - position: fixed; - width: 260px; - min-width: 260px; - padding: 5px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 4px 16px color-mix(in srgb, var(--fg) 20%, transparent); - z-index: 10033; - font-size: 11px; -} -.doc-rich-image-source-title { - padding: 5px 7px 4px; - color: var(--fg); - font-weight: 700; - opacity: .75; -} -.doc-rich-image-source-menu > button, -.doc-rich-gallery-item { - width: 100%; - display: flex; - align-items: center; - gap: 7px; - padding: 7px 8px; - border: 0; - border-radius: 4px; - background: transparent; - color: var(--fg); - font: inherit; - text-align: left; - cursor: pointer; -} -.doc-rich-image-source-menu > button[data-image-source="computer"] { - padding-left: 9px; -} -.doc-rich-image-source-menu > button:hover, -.doc-rich-gallery-item:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--accent, var(--red)); -} -.doc-rich-gallery-list { - display: grid; - gap: 2px; - max-height: 300px; - overflow-y: auto; -} -.doc-rich-gallery-item { - min-height: 44px; - min-width: 0; -} -.doc-rich-gallery-item > span:last-child { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.doc-rich-gallery-thumb { - width: 40px; - height: 40px; - flex: 0 0 40px; - border: 1px solid var(--border); - border-radius: 4px; - background-position: center; - background-size: cover; - background-repeat: no-repeat; -} -.rich-color-letter { - border-bottom: 2px solid var(--accent, var(--red)); - font-weight: 700; - line-height: 14px; -} -.rich-color-swatch { - width: 13px !important; - height: 13px !important; - border: 1px solid color-mix(in srgb, var(--fg) 35%, transparent); - border-radius: 50%; -} -.rich-highlight-swatch { border-radius: 50%; } -.rich-color-swatch.is-transparent { - background: - linear-gradient(135deg, transparent 43%, var(--red) 44%, var(--red) 56%, transparent 57%), - var(--bg); -} -.rich-color-palette-menu { - min-width: min(292px, calc(100vw - 16px)); -} -.rich-color-palette-menu .rich-color-reset { - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - margin-bottom: 3px; - padding-bottom: 7px; -} -.rich-color-palette-grid { - display: grid; - grid-template-columns: repeat(2, minmax(126px, 1fr)); - gap: 0 4px; - padding: 0 4px; -} -.rich-color-palette-group { - min-width: 0; -} -.rich-color-palette-label { - padding: 4px 8px 3px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 9px; - font-weight: 600; - text-transform: uppercase; -} -.rich-color-palette-group .doc-overflow-item { - min-width: 0; - padding-inline: 8px; -} -.rich-toolbar-color-custom { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-top: 3px; - padding: 7px 9px 4px 10px; - border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - color: var(--fg); - font-size: 11px; -} -.rich-toolbar-color-input.cp-swatch-input { - width: 22px; - height: 22px; - flex: 0 0 22px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 28%, var(--border)); - border-radius: 50%; - cursor: pointer; -} -.rich-toolbar-color-input.cp-swatch-input:focus-visible { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 2px; -} - -.rich-toolbar-slider-row { - display: grid; - grid-template-columns: auto minmax(104px, 1fr) 42px; - align-items: center; - gap: 9px; - min-width: 220px; - padding: 10px; - color: var(--fg); - font-size: 11px; -} -.rich-toolbar-range { - width: 100%; - min-width: 0; - height: 8px; - margin: 0; - border: 0; - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 25%, transparent); - accent-color: var(--red); - cursor: pointer; - -webkit-appearance: none; - appearance: none; -} -.rich-toolbar-range::-webkit-slider-thumb { - width: 9px; - height: 9px; - border: 0; - border-radius: 50%; - background: var(--red); - cursor: pointer; - -webkit-appearance: none; - appearance: none; - transition: width 0.12s ease, height 0.12s ease; -} -.rich-toolbar-range:hover::-webkit-slider-thumb, -.rich-toolbar-range:focus::-webkit-slider-thumb, -.rich-toolbar-range:active::-webkit-slider-thumb { - width: 16px; - height: 16px; -} -.rich-toolbar-range::-moz-range-thumb { - width: 9px; - height: 9px; - border: 0; - border-radius: 50%; - background: var(--red); - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.rich-toolbar-range:hover::-moz-range-thumb, -.rich-toolbar-range:focus::-moz-range-thumb, -.rich-toolbar-range:active::-moz-range-thumb { - width: 16px; - height: 16px; -} -.rich-toolbar-range:focus-visible { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 4px; -} -.rich-toolbar-slider-value { - width: 42px; - box-sizing: border-box; - padding: 2px 3px; - border: 1px solid transparent; - border-radius: 3px; - background: transparent; - text-align: right; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-variant-numeric: tabular-nums; - -webkit-appearance: textfield; - appearance: textfield; -} -.rich-toolbar-slider-value::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -.rich-toolbar-slider-value:focus { - border-color: var(--accent, var(--red)); - outline: none; - color: var(--fg); -} - -.rich-script-icon, -.rich-font-icon, -.rich-font-size-icon { - display: inline-block; - line-height: 1; - font-size: 11px; -} -.rich-highlight-icon { - display: block; - flex-shrink: 0; - color: var(--accent-primary, var(--red)); -} - -.rich-script-icon sup, -.rich-script-icon sub { - font-size: 7px; - line-height: 0; -} - -.rich-font-icon { - font-family: Georgia, serif; - font-size: 12px; -} - -.rich-font-size-icon { - min-width: 16px; - font-size: 10px; - font-weight: 600; - font-variant-numeric: tabular-nums; - text-align: center; -} -.rich-align-center { text-align: center; } -.rich-align-right { text-align: right; } -.doc-email-richbody .email-quoted-history { - margin-top: 14px; - padding: 10px 0 10px 12px; - border-left: 3px solid var(--border); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.92em; - line-height: 1.45; - white-space: pre-wrap; - user-select: text; - pointer-events: none; -} -.doc-email-richbody h1, .doc-email-richbody h2, .doc-email-richbody h3, -.doc-email-richbody h4, .doc-email-richbody h5, .doc-email-richbody h6 { margin: 0.4em 0; } -.email-more-menu { - position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; - min-width: 160px; background: var(--bg); border: 1px solid var(--border); - border-radius: 6px; box-shadow: 0 -4px 12px rgba(0,0,0,0.2); padding: 4px; - z-index: 100; -} -.email-send-btn { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 20%, transparent); - color: var(--accent-primary, var(--red)); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); - border-radius: 6px; - padding: 0 14px; height: 28px; font-size: 11px; font-weight: 600; cursor: pointer; - font-family: inherit; transition: all 0.15s; white-space: nowrap; - display: inline-flex; align-items: center; gap: 6px; -} -/* Split send button: [ Send │ ▾ ] — the caret drops the send-options menu UP. */ -.email-send-split { position: relative; display: inline-flex; } -.email-send-split .email-send-main { - border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; -} -.email-send-split .email-send-caret { - border-top-left-radius: 0; border-bottom-left-radius: 0; - padding: 0 8px; gap: 0; - /* its left border is the single divider between the two halves */ - border-left: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); -} -.email-send-split .email-send-caret svg { transition: transform 0.15s ease; } -.email-send-split .email-send-caret[aria-expanded="true"] svg { transform: none; } -.email-send-btn svg { flex-shrink: 0; } -.email-send-btn:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); - border-color: var(--accent-primary, var(--red)); -} -.email-draft-btn { - background: none; border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border-radius: 6px; - padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; - font-family: inherit; transition: all 0.15s; white-space: nowrap; -} -.email-draft-btn:hover { border-color: var(--fg); color: var(--fg); } -.email-discard-btn { - background: transparent; - /* Dim the label text via color-mix instead of `opacity:0.6` on the whole - button — opacity multiplies through to the SVG and washes out the accent - X glyph. Per-element color dimming keeps the X at full accent strength. */ - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; font-family: inherit; -} -/* The ✕ glyph itself reads in accent. */ -.email-discard-btn svg { color: var(--accent-primary, var(--red)); opacity: 1; } -.email-discard-btn:hover { opacity: 1; color: var(--red, #e55); border-color: var(--red, #e55); } - -/* Compose email "more" menu button — icon only, matches draft btn height */ -#doc-email-more-btn { - background: none; border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border-radius: 6px; - width: 28px; height: 28px; padding: 0; - cursor: pointer; font-family: inherit; transition: all 0.15s; - display: inline-flex; align-items: center; justify-content: center; -} -#doc-email-more-btn:hover { - border-color: var(--fg); color: var(--fg); -} -.email-more-wrap { display: inline-flex; } -.email-attachments { - display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 0 58px; -} -.email-attachment-chip { - display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px; font-size: 11px; background: var(--hover-bg, rgba(255,255,255,0.05)); - border: 1px solid var(--border); border-radius: 12px; color: var(--fg); - text-decoration: none; cursor: pointer; transition: background 0.15s, border-color 0.15s; - max-width: 200px; min-width: 0; -} -.email-attachment-chip:hover { - background: color-mix(in srgb, var(--accent) 15%, transparent); - border-color: var(--accent); -} -.email-attachment-chip.is-expanded { - max-width: 90vw; -} -.email-attachment-chip > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - flex: 1 1 auto; min-width: 0; -} -.email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-attachment-chip-related { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); -} -/* "Open in editor" launch icon — same prominent style on desktop AND mobile - (was 24px / dim / no border on desktop, easy to miss). Accent-tinted - background + border makes it read as a real action. */ -.email-attachment-open, -.email-attachment-download, -.email-attachment-calendar-open, -.cal-event-email-source { - display: inline-flex; align-items: center; gap: 4px; - height: 22px; padding: 0 9px; border-radius: 999px; - margin-left: 6px; flex: 0 0 auto; - min-width: 52px; justify-content: center; - font-size: 10px; font-weight: 500; letter-spacing: 0.02em; - color: var(--accent-primary, var(--red)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); - cursor: pointer; transition: background 0.1s, border-color 0.1s, color 0.1s; -} -.email-attachment-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-open svg, -.email-attachment-open > svg, -.email-attachment-download svg, -.email-attachment-download > svg, -.email-attachment-calendar-open svg, -.email-attachment-calendar-open > svg, -.cal-event-email-source svg, -.cal-event-email-source > svg { - width: 12px; height: 12px; - opacity: 0.9; -} -.email-attachment-open:hover, -.email-attachment-download:hover, -.email-attachment-calendar-open:hover, -.cal-event-email-source:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 70%, transparent); -} -.email-attachment-download { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-calendar-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.cal-event-email-source { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-open-label { line-height: 1; } -.email-attachment-chip.is-expanded .email-attachment-open, -.email-attachment-chip.is-expanded .email-attachment-download, -.email-attachment-chip.is-expanded .email-attachment-calendar-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-chip.is-expanded .email-attachment-open-label { - display: none; -} -@media (max-width: 768px) { - .doc-email-collapse-btn { - background: inherit; - } - /* Mobile: keep the pill but ensure a comfortable touch target. */ - .email-attachment-open { - height: 26px; padding: 0 10px; - min-height: 26px !important; - min-width: 58px; - } - .email-attachments, - .email-compose-atts { - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-left: 0; - padding-bottom: 2px; - } - .email-attachments::-webkit-scrollbar, - .email-compose-atts::-webkit-scrollbar { - display: none; - } - /* Attachment chip body — modest minimum height so the open icon sits - neatly without dominating. */ - .email-attachment-chip, - .email-compose-chip { - flex: 0 0 auto; - padding: 6px 8px !important; - min-height: 36px !important; - } - .email-attachment-chip.is-expanded { - flex-wrap: nowrap; - align-items: center; - max-width: min(92vw, 360px); - } - .email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - flex: 1 1 auto; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.25; - min-width: 0; - } - .email-attachment-chip.is-expanded .att-size { - margin-left: 0; - } - .email-attachment-chip.is-expanded .email-attachment-open, - .email-attachment-chip.is-expanded .email-attachment-download, - .email-attachment-chip.is-expanded .email-attachment-calendar-open { - width: 28px; - min-width: 28px; - padding: 0; - margin-left: 4px; - } - .email-compose-chip .compose-chip-name { - max-width: 190px; - } -} - -/* Compose attachment chips (when sending new email) */ -.email-compose-atts { - display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; - padding: 6px 0 0 0; -} -.email-compose-chip { - display: inline-flex; align-items: center; gap: 4px; - flex: 0 1 auto; - min-width: 0; - max-width: min(240px, 100%); - height: 24px; min-height: 24px; box-sizing: border-box; - padding: 2px 3px 2px 7px; font-size: 11px; - background: var(--hover-bg, rgba(255,255,255,0.05)); - border: 1px solid var(--border); border-radius: 12px; color: var(--fg); -} -.email-compose-chip .compose-chip-name { max-width: 180px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.email-compose-chip .compose-chip-remove { - background: none; border: none; color: var(--fg); opacity: 0.5; - height: 18px; font-size: 14px; line-height: 18px; padding: 0 3px; cursor: pointer; - flex-shrink: 0; -} -.email-compose-chip .compose-chip-remove:hover { opacity: 1; color: var(--red, #e55); } - -.pdf-annotation-line-value { - width: 48px; - height: 22px; - box-sizing: border-box; - padding: 0 5px; - border: 1px solid var(--border); - border-radius: 4px; - outline: none; - background: var(--bg-elev, var(--panel)); - color: var(--fg); - font-family: inherit; - font-size: 10px; - font-weight: 500; - line-height: 1; - text-align: right; -} -.pdf-annotation-line-value:hover { border-color: color-mix(in srgb, var(--fg) 38%, var(--border)); } -.pdf-annotation-line-value:focus { border-color: var(--accent, var(--red)); } - -.email-odysseus-attach-menu { - position: fixed; - z-index: 10050; - width: 300px; - max-width: calc(100vw - 16px); - max-height: min(420px, calc(100vh - 16px)); - padding: 7px; - border: 1px solid var(--border); - border-radius: 7px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 12px 30px rgba(0,0,0,0.28); - overflow: hidden; -} -.email-odysseus-attach-local { - width: 100%; - height: 30px; - display: flex; - align-items: center; - gap: 7px; - padding: 0 9px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, var(--border)); - border-radius: 5px; - background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-local:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); -} -.email-odysseus-attach-tabs { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 5px; - margin: 7px 0; -} -.email-odysseus-attach-tabs button { - height: 26px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border: 1px solid var(--border); - border-radius: 5px; - background: transparent; - color: var(--fg); - opacity: 0.72; - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-tabs button.active { - opacity: 1; - color: var(--accent-primary, var(--red)); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); -} -.email-odysseus-attach-tabs button svg { - flex-shrink: 0; -} -.email-odysseus-attach-search-wrap { - height: 28px; - display: flex; - align-items: center; - gap: 6px; - margin: -1px 0 7px; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 5px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.email-odysseus-attach-search-wrap:focus-within { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - color: var(--accent-primary, var(--red)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, transparent); -} -.email-odysseus-attach-search { - min-width: 0; - flex: 1; - border: 0; - outline: 0; - background: transparent; - color: var(--fg); - font: inherit; - font-size: 11px; -} -.email-odysseus-attach-search::placeholder { - color: color-mix(in srgb, var(--fg) 38%, transparent); -} -.email-odysseus-attach-list { - max-height: 320px; - overflow: auto; - display: flex; - flex-direction: column; - gap: 4px; -} -.email-odysseus-attach-row { - width: 100%; - min-height: 38px; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 7px; - border: 1px solid transparent; - border-radius: 5px; - background: transparent; - color: var(--fg); - text-align: left; - font: inherit; - cursor: pointer; -} -.email-odysseus-attach-row:hover { - background: var(--hover-bg, rgba(255,255,255,0.06)); - border-color: var(--border); -} -.email-odysseus-attach-row.is-selected { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); -} -.email-odysseus-attach-dot { - width: 8px; - height: 8px; - flex: 0 0 auto; - display: inline-block; - border: 1px solid var(--border); - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.email-odysseus-attach-row.is-selected .email-odysseus-attach-dot { - border-color: var(--accent-primary, var(--red)); - background: var(--accent-primary, var(--red)); - box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); -} -.email-odysseus-attach-icon, -.email-odysseus-attach-thumb { - width: 28px; - height: 28px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 7%, transparent); - overflow: hidden; -} -.email-odysseus-attach-main { - min-width: 0; - flex: 1; - display: flex; - flex-direction: column; - gap: 2px; -} -.email-odysseus-attach-title, -.email-odysseus-attach-meta { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-odysseus-attach-title { - font-size: 11px; - line-height: 1.2; -} -.email-odysseus-attach-meta { - font-size: 10px; - line-height: 1.15; - opacity: 0.52; -} -.email-odysseus-attach-empty { - padding: 14px 8px; - color: var(--muted, #888); - font-size: 11px; - text-align: center; -} -.email-odysseus-attach-actions { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - padding-top: 5px; - border-top: 1px solid color-mix(in srgb, var(--border) 75%, transparent); - background: var(--bg); - position: sticky; - bottom: 0; -} -.email-odysseus-attach-count { - flex: 1; - min-width: 0; - color: var(--muted, #888); - font-size: 11px; -} -.email-odysseus-attach-selected { - height: 28px; - min-width: 72px; - padding: 0 12px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - background: var(--accent-primary, var(--red)); - color: #fff; - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-selected svg { - flex-shrink: 0; -} - -.email-cc-toggle { - background: none; border: none; color: var(--fg); - opacity: 0.4; font-size: 11px; cursor: pointer; - padding: 4px 8px; font-family: inherit; -} -.email-cc-toggle:hover { - opacity: 1; - color: var(--accent, #4a9eff); - background: none !important; -} - -@media (max-width: 768px) { - .doc-email-collapse-btn { - display: flex; - } -} - -.email-autocomplete { - position: absolute; top: 100%; left: 58px; right: 0; z-index: 1000; - background: var(--bg); border: 1px solid var(--border); border-radius: 6px; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 240px; overflow-y: auto; - margin-top: 2px; -} -.contact-suggestion { - display: flex; justify-content: space-between; gap: 8px; - padding: 6px 10px; font-size: 12px; cursor: pointer; - border-bottom: 1px solid var(--border); -} -.contact-suggestion:last-child { border-bottom: none; } -.contact-suggestion:hover, -.contact-suggestion.active, -.contact-suggestion[aria-selected="true"] { - background: color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 15%, transparent); - outline: 1px solid color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 40%, transparent); - outline-offset: -1px; -} -.contact-suggestion .contact-name { font-weight: 600; color: var(--fg); } -.contact-suggestion .contact-email { opacity: 0.6; font-size: 11px; } -.contact-suggestion-status { - padding: 7px 10px; - color: color-mix(in srgb, var(--fg) 65%, transparent); - font-size: 11px; - cursor: default; -} -.contact-suggestion-status.is-loading { opacity: 0.72; } -.contact-suggestion-status.is-unavailable { - color: color-mix(in srgb, var(--red) 82%, var(--fg)); -} -.email-ai-reply-btn { - background: none; - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; - font-family: inherit; transition: all 0.15s; - display: inline-flex; align-items: center; white-space: nowrap; -} -.email-ai-reply-btn:hover { - border-color: var(--accent, #4a9eff); - color: var(--accent, #4a9eff); -} -/* Align select/new buttons in popup library toolbar */ -#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn, #email-lib-compose-btn { position: relative; top: -4px; } -/* Select + Refresh sit slightly lower than Compose on desktop. */ -#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -2px; } - -#email-lib-modal.email-settings-mode .email-accounts-row, -#email-lib-modal.email-settings-mode .admin-card > .memory-toolbar, -#email-lib-modal.email-settings-mode #email-lib-bulk, -#email-lib-modal.email-settings-mode #email-lib-grid, -#email-lib-modal.email-settings-mode #email-lib-sync-status, -#email-lib-modal.email-settings-mode #email-lib-fab { - display: none !important; -} -#email-lib-settings-page[hidden] { - display: none !important; -} -#email-lib-modal.email-settings-mode #email-lib-settings-page { - display: flex; -} -.email-settings-page { - display: flex; - flex: 1; - min-height: 0; - min-width: 0; - flex-direction: column; - gap: 6px; - overflow: hidden; - padding: 2px 0 8px; - width: 100%; - box-sizing: border-box; - -webkit-overflow-scrolling: touch; -} -.settings-email-preferences-card { - display: contents; -} -.settings-email-preferences-card .email-settings-section { - margin: 0 0 10px; - padding: 14px 12px 12px; - background: var(--panel); -} -.settings-email-preferences-card .email-settings-section:last-child { - margin-bottom: 0; -} -.settings-email-preferences-card .email-settings-style-section { - margin-bottom: 4px; -} -.settings-email-default-card #settings-email-default-host { - display: flex; - justify-content: flex-end; - min-width: 0; - margin-left: auto; -} -.settings-email-default-card .settings-email-default-title { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} -.settings-email-default-card .settings-email-default-title > span { - min-width: 0; -} -.settings-email-default-card .settings-email-default-title > span > svg { - color: var(--accent, var(--red)); -} -.settings-email-default-card .email-settings-account-picker { - margin-left: 0 !important; -} -.settings-email-default-card .email-settings-support-copy { - position: relative; - top: 6px; -} -.settings-email-default-spacer { - height: 12px; -} -.settings-email-default-card .cookbook-srv-default-label { - position: relative; - top: 2px; -} -.settings-email-default-card .cookbook-srv-default-icon > svg { - position: relative; - top: 7px; -} -.settings-email-settings-legacy { - display: none; -} - -/* Keep the standalone Email Settings cards aligned with the expandable - preference cards below them. */ -[data-settings-panel="email"] > .admin-card { - padding-top: 14px; -} -.email-settings-page-host { - display: flex; - flex: 0 0 auto; - min-height: 0; - padding: 0; - overflow: visible; -} -.settings-email-preferences-card > .email-settings-page-host { width: 100%; } -.email-settings-page-head { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; - min-height: 32px; - padding-bottom: 2px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.email-settings-account-picker { - min-width: 0; - max-width: 100%; -} -.email-settings-account-picker select { - min-width: 0; - max-width: 100%; -} -.email-settings-back { - position: relative; - top: -2px; -} -.email-settings-header-back { - background: transparent; - color: color-mix(in srgb, var(--fg) 58%, transparent); - border: 1px solid transparent; - width: auto; - height: 24px; - padding: 0 8px 0 6px; - align-items: center; - justify-content: center; - gap: 4px; - cursor: pointer; - border-radius: 999px; - flex-shrink: 0; - position: relative; - font-size: 11px; - font-weight: 600; - line-height: 1; -} -.email-settings-header-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); -} -.email-settings-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 8px; -} -.email-settings-title { - display: inline-flex; - align-items: center; - gap: 6px; - flex: 1 1 auto; - width: 100%; - min-width: 0; - font-size: 12px; - font-weight: 700; -} -.email-settings-title svg { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-settings-close { - width: 24px; - height: 24px; - border: 1px solid var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 70%, transparent); - cursor: pointer; - font-size: 14px; - line-height: 1; -} -.email-settings-close:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)); -} -.email-settings-body { - display: flex; - flex-direction: column; - flex: 1 1 auto; - gap: 6px; - padding-top: 4px; - box-sizing: border-box; - min-width: 0; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; - overscroll-behavior: contain; -} -.email-settings-loading, -.email-settings-error { - font-size: 12px; - color: color-mix(in srgb, var(--fg) 65%, transparent); - padding: 8px 2px; -} -.email-settings-loading { - display: inline-flex; - align-items: center; - gap: 7px; -} -.email-settings-loading-css-whirlpool { - display: inline-block; - width: 14px; - height: 14px; - box-sizing: border-box; - border: 1.5px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - border-top-color: var(--accent, var(--red)); - border-right-color: color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); - border-radius: 50%; - animation: whirlpool-spin 0.72s linear infinite; - flex: 0 0 auto; -} -.email-settings-loading-spinner { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - color: var(--accent, var(--red)); -} -.email-settings-section { - display: flex; - flex-direction: column; - flex: 0 0 auto; - gap: 7px; - margin: 0; - padding: 14px 8px 8px; - min-width: 0; - box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - transition: opacity 0.15s ease, background 0.15s ease; -} -.email-settings-section.is-disabled { - opacity: 0.58; - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -.email-settings-section-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.email-settings-section-head > div:first-child { - min-width: 0; - flex: 1 1 auto; -} -.email-settings-section-head > .email-settings-enabled-control, -.email-settings-section-head > button { - margin-left: auto; -} -.email-settings-section-toggle { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - width: 100%; - min-width: 0; - padding: 0; - border: 0; - background: transparent; - color: inherit; - text-align: left; - cursor: pointer; -} -.email-settings-section-toggle:hover, -.email-settings-section-toggle:active { - background: transparent; -} -.email-settings-section-toggle-copy { - display: flex; - flex-direction: column; - align-items: flex-start; - min-width: 0; - flex: 1 1 auto; -} -.email-settings-section-toggle > .email-settings-enabled-control { - margin-left: auto; - flex: 0 0 auto; -} -.email-settings-section-chevron { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); - transition: transform .14s ease, color .14s ease; -} -.email-settings-section-toggle:hover .email-settings-section-chevron, -.email-settings-section-toggle:focus-visible .email-settings-section-chevron { - color: var(--accent, var(--red)); -} -.email-settings-section-title { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 14px; - line-height: 1; - font-weight: 600; - letter-spacing: 0; - margin: 0; - padding: 0; -} -.email-settings-section-title svg { - color: var(--accent, var(--red)); - opacity: 0.6; - flex-shrink: 0; -} -.email-settings-account-scope { - margin-left: 4px; - font-size: 11px; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -.email-settings-section-body { - display: flex; - flex-direction: column; - gap: 7px; - min-width: 0; - width: 100%; -} -.email-settings-section[open] > .email-settings-section-body { - min-height: 0; - flex: 0 0 auto; - height: auto; - max-height: none; - overflow: visible; - overscroll-behavior: contain; - padding: 0 4px 4px 0; - scrollbar-width: thin; -} -.email-settings-section[open] { - min-height: 0; - flex: 0 0 auto; -} -.email-settings-auto-reply-section[open], -.email-settings-cleanup-section[open]:has(.email-unsub-card) { - flex: 1 1 auto; - display: grid; - grid-template-rows: auto minmax(0, 1fr); - align-content: stretch; -} -.email-settings-auto-reply-section[open] > .email-settings-section-body, -.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body { - flex: 0 0 auto; - height: 100%; - max-height: 100%; - overflow-y: auto; -} -.email-settings-section[open] > .email-settings-section-body > * { - flex: 0 0 auto; -} -.email-settings-section-body-head { - display: flex; - justify-content: flex-end; -} -.email-settings-display-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.email-settings-display-title { - display: inline-flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.email-settings-display-title > svg { - flex: 0 0 auto; - color: var(--accent, var(--red)); -} -.email-settings-display-title-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - position: relative; - top: 10px; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-display-title-row .email-settings-section-title { - flex: 1 1 auto; - width: auto; - padding-bottom: 0; - border-bottom: 0; -} -.email-settings-style-section .email-settings-section-title { - display: flex; - width: 100%; - position: relative; - top: 0; - padding-bottom: 8px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-style-section .email-settings-section-head, -.email-settings-style-section .email-settings-section-body { - position: static; -} -.email-settings-style-section .email-settings-section-head { margin-top: 8px; } -.email-settings-style-section .email-settings-section-body { margin-top: 12px; } -.email-settings-style-section .email-settings-section-desc { - margin-top: 16px; - top: 0; -} -.email-settings-auto-reply-section .email-settings-section-title, -.email-settings-cleanup-section .email-settings-section-title { - display: flex; - width: 100%; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-cleanup-section .email-settings-section-title { - position: relative; - top: 10px; -} -.email-settings-auto-reply-section .email-settings-section-desc, -.email-settings-cleanup-section .email-settings-section-desc { - margin-top: 10px; -} -.email-settings-display-section .email-settings-section-desc, -.email-settings-cleanup-section .email-settings-section-desc, -.email-settings-inline-images-section .email-settings-section-desc { - margin-top: 12px; - top: 12px; -} -.email-settings-auto-reply-title-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - position: relative; - top: 10px; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-auto-reply-section .email-settings-section-head { - margin-top: 8px; -} -.email-settings-auto-reply-section .email-settings-section-body { - margin-top: 8px; -} -.email-settings-auto-reply-section .email-settings-section-desc { - margin-top: 12px; - top: 0; -} -.email-settings-display-section .email-settings-section-desc { - margin-top: 10px; - top: 10px; -} -.email-settings-auto-reply-section .email-settings-section-body { - margin-top: 14px; -} -.email-settings-auto-reply-title-row .email-settings-section-title { - flex: 1 1 auto; - width: auto; - padding-bottom: 0; - border-bottom: 0; -} -.email-settings-support-copy { - color: color-mix(in srgb, var(--fg) 65%, transparent); - font-size: 11px; - line-height: 1.3; -} -.email-settings-clean-btn > svg { - color: var(--accent, var(--red)); -} -.email-settings-clean-btn { - height: 32px; -} -.email-unsub-rescan-btn { - height: 32px; -} -.email-settings-clean-actions { - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; - gap: 7px; - min-width: 0; - margin-left: auto; -} -.email-settings-clean-status { - display: none; - font-size: 10.5px !important; - flex: 0 1 auto; - width: auto !important; - max-width: min(52vw, 360px); - min-width: 0; - overflow: hidden; - text-align: right; - white-space: nowrap; - text-overflow: ellipsis; - position: relative; - top: 2px; -} -.email-settings-clean-status.is-busy { - order: 0; - align-self: center; - width: auto !important; - max-width: 100%; - margin-right: auto; - text-align: left; -} -.email-settings-clean-btn { - flex: 0 0 auto; -} -.email-settings-default .cookbook-srv-default-icon > svg { - position: relative; - top: 3px; -} -.email-unsub-auto-safe-btn > svg { - position: relative; - top: 2px; -} -.email-unsub-auto-safe-btn { - height: 29px; -} -.email-unsub-status, -.email-unsub-panel-status { - position: relative; - top: 2px; -} -.email-unsub-panel-status { - flex: 1 1 220px; - min-width: 0; - margin-right: auto; - overflow: hidden; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-size: 11px; - line-height: 1.3; - text-align: left; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-unsub-panel-status.is-error { - color: var(--red, #e57373); -} -.email-unsub-card .email-tag { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); -} -.email-unsub-duplicate-badge { - position: relative; - top: 2px; -} -.email-unsub-accent-icon { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-unsub-card { - position: relative; - transition: opacity .16s ease; -} -.email-unsub-card-details { - display: flex; - flex-direction: column; - gap: 7px; -} -.email-unsub-card-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 24px; - height: 24px; - padding: 0; - border: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 62%, transparent); - cursor: pointer; -} -.email-unsub-card-toggle:hover, -.email-unsub-card-toggle:focus-visible { - color: var(--accent, var(--red)); -} -.email-unsub-card-toggle > svg { - transition: transform .14s ease; -} -.email-unsub-done-badge { - display: inline-flex; - align-items: center; - gap: 3px; -} -.email-unsub-done-btn { - display: inline-flex; - align-items: center; - gap: 3px; - flex: 0 0 auto; - padding: 3px 6px; - position: relative; - top: -6px; -} -.email-unsub-card-details .email-unsub-done-btn { - top: 0; -} -.email-unsub-done-btn > svg { - color: var(--accent, var(--red)); -} -.email-unsub-ignore-btn { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 22px; - height: 22px; - padding: 0; - position: absolute; - top: 5px; - right: 6px; - color: color-mix(in srgb, var(--fg) 58%, transparent); -} -.email-unsub-card-details .email-unsub-ignore-btn { - position: static; -} -.email-unsub-ignore-btn:hover { - color: var(--accent, var(--red)); -} -.email-unsub-remaining-actions { - display: inline-flex; - align-items: center; - gap: 7px; - flex-wrap: wrap; -} -.email-unsub-remaining-actions button { - display: inline-flex; - align-items: center; - gap: 4px; - position: relative; - top: -2px; -} -.email-unsub-remaining-actions svg { - flex: 0 0 auto; -} -.email-unsub-link-btn { - position: relative; - top: 2px; -} -.email-unsub-status.is-error { - color: var(--accent, var(--red)) !important; -} -.email-settings-section-desc { - position: relative; - top: 8px; - margin-top: 4px; - font-size: 10.5px; - line-height: 1.25; - color: color-mix(in srgb, var(--fg) 55%, transparent); -} -.email-settings-inline-images-section .email-settings-section-desc { - margin-bottom: 4px; -} -.email-settings-inline-images-spacer { - height: 12px; -} -.email-settings-display-spacer { - height: 10px; -} -.email-settings-display-section .email-settings-section-desc { - margin-top: 10px; - top: 10px; -} -.email-settings-display-section, -.email-settings-inline-images-section { - padding-bottom: 18px; -} -.email-settings-cleanup-section .email-settings-section-desc { - margin-top: 14px; - top: 14px; -} -.email-settings-auto-reply-section .email-settings-section-desc { - margin-top: 12px; - top: 10px; -} -.email-settings-toggle, -.email-settings-check { - display: flex; - align-items: center; - gap: 7px; - font-size: 12px; - line-height: 1.25; -} -.email-settings-toggle { - flex-shrink: 0; -} -.email-settings-enabled-control { - display: inline-flex; - align-items: center; - gap: 7px; - flex-shrink: 0; -} -.email-settings-enabled-state { - font-size: 10px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.email-settings-section.is-enabled .email-settings-enabled-state { - color: var(--accent, var(--red)); -} -.email-settings-section.is-disabled .email-settings-enabled-state { - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.email-settings-display-section.is-enabled .email-settings-enabled-state { - color: var(--accent, var(--red)); -} -.email-settings-check { - min-height: 24px; - padding: 3px 0; -} -.email-settings-check > span:first-child { - min-width: 0; - margin-right: auto; -} -.email-settings-check-spacer { - flex: 1; -} -.email-settings-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 7px; - min-width: 0; -} -.email-settings-grid label, -.email-settings-field { - display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; -} -.email-settings-grid span, -.email-settings-field > span { - font-size: 10px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0; - color: color-mix(in srgb, var(--fg) 56%, transparent); -} -.email-settings-page input[type="date"], -.email-settings-page input[type="datetime-local"], -.email-settings-page select, -.email-settings-page textarea { - width: 100%; - min-width: 0; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.email-settings-page input[type="date"], -.email-settings-page input[type="datetime-local"], -.email-settings-page select { - height: 28px; - padding: 0 7px; -} -.email-settings-page textarea { - resize: vertical; - min-height: 72px; - padding: 7px; - line-height: 1.35; -} -.email-settings-static-value { - min-height: 28px; - display: flex; - align-items: center; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--panel, var(--bg)) 74%, transparent); - color: color-mix(in srgb, var(--fg) 72%, transparent); - font-size: 12px; -} -.email-settings-actions { - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; - gap: 8px; - margin-top: 2px; -} - -/* Email Settings actions use the same quiet outlined treatment everywhere: - the Settings links, the modal settings page, and unsubscribe actions. */ -[data-settings-panel="email"] #set-email-open-integrations, -[data-settings-panel="email"] #set-email-open-tasks, -[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn, -#email-lib-settings-page .email-settings-section button.memory-toolbar-btn { - background: transparent; - border: 1px solid var(--border); - color: var(--fg); - font-family: inherit; - font-weight: 400; -} -[data-settings-panel="email"] #set-email-open-integrations:hover, -[data-settings-panel="email"] #set-email-open-tasks:hover, -[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn:hover, -#email-lib-settings-page .email-settings-section button.memory-toolbar-btn:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); - color: var(--fg); -} -[data-settings-panel="email"] #set-email-open-integrations > svg, -[data-settings-panel="email"] #set-email-open-tasks > svg { - color: var(--accent, var(--red)); - opacity: 1 !important; -} -[data-settings-panel="email"] .email-style-settings-extract, -[data-settings-panel="email"] .email-style-settings-save, -#email-lib-settings-page .email-style-settings-extract, -#email-lib-settings-page .email-style-settings-save { - background: transparent; - border: 1px solid var(--border); - color: var(--fg); - font-weight: 400; -} -[data-settings-panel="email"] .email-style-settings-extract:hover, -[data-settings-panel="email"] .email-style-settings-save:hover, -#email-lib-settings-page .email-style-settings-extract:hover, -#email-lib-settings-page .email-style-settings-save:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); - color: var(--fg); -} -.email-settings-status, -.email-style-settings-status { - margin-right: auto; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.email-settings-status.is-success, -.email-style-settings-status.is-success { - color: var(--color-success, #4caf50); - position: relative; - top: 2px; -} -.email-settings-status.is-error, -.email-style-settings-status.is-error { - color: var(--accent, var(--red)); - position: relative; - top: 2px; -} -.email-settings-save, -.email-style-settings-save { - height: 28px; - min-width: 64px; - justify-content: center; - display: inline-flex; - align-items: center; - gap: 5px; -} -.email-settings-save { - height: 32px; -} -.email-style-settings-extract { - height: 32px; -} -.email-style-settings-save { - height: 32px; -} -.email-settings-save svg, -.email-style-settings-save svg { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-style-settings-extract svg { - color: var(--accent, var(--red)); - position: relative; - top: 2px; -} -.email-settings-task-setting { - color: color-mix(in srgb, var(--fg) 72%, transparent); -} -.email-settings-inline-link { - appearance: none; - border: 0; - padding: 0; - background: transparent; - color: var(--accent, var(--red)); - font: inherit; - text-decoration: underline; - cursor: pointer; -} -.email-style-extract-help { - width: 18px; - height: 18px; - min-width: 18px; - padding: 0; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: color-mix(in srgb, var(--fg) 68%, transparent); - display: inline-flex; - align-items: center; - justify-content: center; - font: inherit; - font-size: 11px; - font-weight: 700; - line-height: 1; - cursor: help; -} -.email-style-extract-help:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); -} -@media (max-width: 420px) { - .email-settings-grid { - grid-template-columns: 1fr; - } -} - - -/* ── Notes ── */ -/* Notes panel — body-level flex sibling of chat-container */ -body.notes-view .chat-container { flex: 1; min-width: 0; } - -/* Mobile: notes panel takes over full screen */ -@media (max-width: 768px) { - body.notes-view .notes-pane { - position: fixed; - inset: 0; - max-width: 100%; - width: 100% !important; - /* Desktop sets height: min(80vh, 820px) which wins over inset:0's bottom:0 - and leaves a gap at the bottom of the viewport. Force full height here. */ - height: 100dvh !important; - max-height: 100dvh !important; - z-index: 170; - /* Bottom-sheet treatment like the document editor: stroked, rounded top, - slides up from the bottom. */ - border: 1px solid var(--border); - border-bottom: none; - border-radius: 14px 14px 0 0; - /* Pane itself never scrolls — its inner .notes-pane-body is the scroller. - Two nested scrollers caused the body's flex:1 to lose its height bound - on Firefox mobile and the last row got clipped off-screen. */ - overflow: hidden; - animation: sheet-enter 0.25s cubic-bezier(0.2, 0.8, 0.2, 1) both; - transform-origin: bottom center; - } - /* Required for the flex:1 body to actually constrain to remaining pane - height instead of expanding to its content (default min-height:auto on - flex items). Without this both grid and list views overflow the viewport - bottom on phones. */ - body.notes-view .notes-pane-body { - min-height: 0; - -webkit-overflow-scrolling: touch; - } - body.notes-view #notes-divider { - display: none; - } - body.notes-view .notes-mobile-grabber { - display: block; - flex-shrink: 0; - height: 18px; - position: relative; - background: var(--bg); - touch-action: none; - cursor: grab; - } - body.notes-view .notes-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; - } - body.notes-view .chat-container { - display: none; - } - body.notes-view .mobile-new-chat-btn, - body.notes-view .hamburger-btn { - display: none !important; - } -} -/* ── Mobile notes UX ──────────────────────────────────── - Tiles become read-only previews on touch ≤768px wide. - Tap opens a fullscreen overlay where editing happens; long-press - toggles drag-to-reorder. Wired up in static/js/notes.js. */ -body.notes-mobile-mode .note-card-corner, -body.notes-mobile-mode .note-card-corner-edit, -body.notes-mobile-mode .note-card-corner-archive, -body.notes-mobile-mode .note-card-corner-copy, -body.notes-mobile-mode .note-card-corner-trash, -body.notes-mobile-mode .note-card-corner-unarchive, -body.notes-mobile-mode .note-card-corner-done, -body.notes-mobile-mode .note-card-edit-corner, -body.notes-mobile-mode .note-card-done, -body.notes-mobile-mode .note-card-actions, -body.notes-mobile-mode .note-cl-quickadd, -body.notes-mobile-mode .note-card .note-checkbox-rm, -/* Panel-fullscreen toggle is redundant on mobile — the panel always - fills the viewport already. */ -body.notes-mobile-mode #notes-fullscreen-toggle { - display: none !important; -} - -/* Header toggle icons (archive view, list/grid switch) — defaults are - tuned for the dense desktop header; on mobile bump them slightly so - they read as tap targets without dominating the header. */ -body.notes-mobile-mode #notes-archive-toggle, -body.notes-mobile-mode #notes-view-toggle { - width: 30px; - height: 30px; - padding: 6px; -} -body.notes-mobile-mode #notes-archive-toggle svg, -body.notes-mobile-mode #notes-view-toggle svg { - width: 17px; - height: 17px; -} -/* The whole card is the tap target on mobile — bigger feedback */ -body.notes-mobile-mode .note-card { - cursor: pointer; - -webkit-tap-highlight-color: transparent; - transition: transform 0.12s ease, box-shadow 0.12s ease; -} - -/* Drag-to-reorder mode — desaturate the grid and sweep a subtle - shimmer across every tile so it's obvious you're in rearrange mode - (the Google Keep "active edit" cue). Also disable scroll inside the - panel so the drag works smoothly. */ -body.notes-drag-mode .notes-pane-body { - overflow: hidden; - touch-action: none; -} body.notes-drag-mode .note-card { cursor: grab; transform: scale(0.985); @@ -6010,3 +3027,1036 @@ body.notes-drag-mode .note-card { align-items: center; gap: 6px; } +.note-cl-dot { + width: 8px; + height: 8px; + border-radius: 50%; + border: 1.5px solid color-mix(in srgb, var(--fg) 30%, transparent); + flex-shrink: 0; + cursor: pointer; + transition: all 0.15s; +} +.note-cl-dot:hover { border-color: var(--fg); transform: scale(1.2); } +.note-cl-row.done .note-cl-dot { + background: var(--accent); + border-color: var(--accent); +} +.note-cl-row.done .note-cl-text { + opacity: 0.4; + background: linear-gradient(currentColor, currentColor) no-repeat; + background-size: 0 1px; + background-position: 0 calc(50% - 1px); + animation: cl-strike 0.32s ease-out forwards; + transition: opacity 0.2s ease; +} +/* Draws the strikethrough line left-to-right when the row is marked + done, instead of snapping it in full-width on the same frame. */ +@keyframes cl-strike { + to { background-size: 100% 1px; } +} +.note-cl-text { + background: transparent; + border: none; + border-bottom: 1px solid var(--border); + color: var(--fg); + font-size: 12px; + padding: 3px 0 3px 3px; + flex: 1 1 auto; + width: auto; + min-width: 0; + outline: none; +} +.note-cl-text:focus { border-color: var(--fg); } +.note-cl-rm { + background: none; + border: none; + color: var(--fg); + opacity: 0.3; + cursor: pointer; + font-size: 14px; + padding: 0; + width: 24px; + height: 14px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: flex-end; + line-height: 1; +} +.note-cl-rm:hover { opacity: 0.8; color: var(--red); } +.note-cl-add { + background: none; + border: none; + color: var(--fg); + opacity: 0.4; + cursor: pointer; + font-size: 11px; + padding: 4px 0; + text-align: left; +} + +/* ── Calendar ── */ +.cal-modal-content { width:min(680px, 94vw); max-height:88vh; overflow-x:hidden; position:relative; } +#calendar-modal .cal-modal-content { animation: none; } +/* Was overflow-y:auto with grid + day-detail flowing naturally. Now uses a + true splitter layout: the body is a flex column that doesn't scroll + itself; the grid takes the remaining space and scrolls if needed; the + day-detail has an explicit height (driven by the splitter drag via + --cal-detail-h) and shrinks the grid visually as it grows. */ +#cal-body { display:flex; flex-direction:column; gap:8px; overflow:hidden; padding:0; min-height:0; } +#cal-body > .cal-grid { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overflow-x: hidden; } +@media (max-width: 768px) { + /* Let the calendar pane shrink to nothing on mobile so the day-detail + splitter can be dragged all the way to the top, hiding the calendar + entirely. Without min-height:0 the grid (or the week-wrap below) + refuses to shrink past its built-in floor. */ + #cal-body > .cal-grid, + #cal-body > .cal-wk-wrap { + min-height: 0; + max-height: none; + } + /* Let the week grid fill the calendar pane and scroll its hours internally + instead of overflowing #cal-body (which clips it and feels cramped). */ + #cal-body > .cal-wk-wrap { + flex: 1 1 auto; + overflow: auto; + } +} +.cal-toolbar { display:flex; align-items:center; gap:6px; margin-bottom:7px; flex-wrap:wrap; line-height:1; max-width:100%; row-gap:6px; } + +/* Quick-add bar: natural-language event creation. Sits directly under + the toolbar; focused with `Q`; opens the bespoke form pre-filled. */ +.cal-quickadd-row { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 8px; + padding: 1px 10px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + transition: border-color 0.15s, background 0.15s; + position: relative; +} +/* Two-tone placeholder hint: "Quick add" in accent, the example in grey, both + at one low opacity. Overlays the (empty) input; hidden once the user types. */ +.cal-quickadd-hint { + position: absolute; + left: 10px; + right: 10px; + top: 50%; + transform: translateY(-50%); + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--fg); + opacity: 0.5; + pointer-events: none; + white-space: nowrap; + overflow: hidden; +} +.cal-quickadd-hint .qa-hint-accent { color: var(--accent, var(--red)); } +/* ↵ enter glyph in the hint — makes it obvious the field submits on Enter. */ +.cal-quickadd-hint .qa-hint-enter { margin-left: auto; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.8; } +/* Cycling example text fades out before swap, then fades back in. */ +.cal-quickadd-hint .qa-hint-example { + display: block; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-style: italic; + transition: opacity 0.18s ease; +} +.cal-quickadd-hint .qa-hint-example.qa-hint-fade { opacity: 0; } +.cal-title-hint { + position: absolute; + left: 0; + top: 50%; + transform: translateY(calc(-50% - 2px)); + display: none; + align-items: center; + gap: 7px; + font-size: 18px; + line-height: 1.2; + white-space: nowrap; + pointer-events: none; + color: color-mix(in srgb, var(--fg) 42%, transparent); +} +.cal-hero-title:placeholder-shown ~ .cal-title-hint { display: inline-flex; } +.cal-title-hint .cal-title-enter-ico { color: var(--accent, var(--red)); flex-shrink: 0; } +/* Pure-CSS toggle: hide the hint as soon as the field has real text (no + per-keystroke JS, so typing never triggers a re-render / focus loss). */ +.cal-quickadd-input:not(:placeholder-shown) ~ .cal-quickadd-hint { display: none; } +.cal-quickadd-row:focus-within { + border-color: color-mix(in srgb, var(--accent, var(--fg)) 55%, var(--border)); + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); +} +.cal-quickadd-icon { + color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent); + display: inline-flex; + align-items: center; + flex-shrink: 0; + opacity: 0.7; +} +.cal-quickadd-input { + flex: 1; + min-width: 0; + background: transparent; + border: none; + outline: none; + color: var(--fg); + font-family: inherit; + font-size: 12px; + height: 22px; + padding: 0; +} +.cal-quickadd-status { + font-size: 10px; + opacity: 0.55; + font-variant-numeric: tabular-nums; +} +.cal-toolbar > *, .cal-toolbar-nav > *, .cal-toolbar-right > * { vertical-align:middle; } +.cal-toolbar-nav { display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; } +.cal-toolbar-right { + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 6px; + flex: 1 1 100%; + width: 100%; + margin-left: 0; + margin-top: 6px; + flex-wrap: wrap; +} +.cal-title { font-size:13px; font-weight:600; white-space:nowrap; height:24px; line-height:30px; padding:0 6px; display:inline-block; vertical-align:middle; box-sizing:border-box; } +.cal-toolbar-clock { + margin-left: 8px; + color: var(--accent, var(--red)); + font-size: 11px; + font-weight: 600; + font-variant-numeric: tabular-nums; +} +button.cal-nav { background:color-mix(in srgb, var(--fg) 6%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 8px; height:24px; line-height:22px; cursor:pointer; font-size:11px; font-family:inherit; box-sizing:border-box; vertical-align:middle; } +button.cal-nav:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); } +button.cal-nav.cal-toolbar-arrow { + display: none; + align-items: center; + justify-content: center; + width: 24px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + border-radius: 50%; + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + font-size: 18px; + line-height: 1; + opacity: 0.84; +} +.cal-toolbar-arrow-glyph { + position: relative; + top: -1px; + line-height: 1; +} +@media (max-width: 768px) { + button.cal-nav.cal-toolbar-arrow { display: inline-flex !important; } + .cal-toolbar-nav { + flex: 1 1 100%; + width: 100%; + min-width: 0; + } + #cal-next { margin-left: auto; } +} +.cal-week-start-toggle { + display: inline-flex; + align-items: center; + gap: 0; + width: max-content; + padding: 0; + height: 24px; + box-sizing: border-box; + border: 1px solid var(--border); + border-radius: 5px; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.cal-week-start-btn { + box-sizing: border-box; + height: 22px !important; + min-height: 22px !important; + margin-top: 0 !important; + padding: 0 7px; + border: 1px solid transparent; + border-radius: 4px; + background: transparent; + color: color-mix(in srgb, var(--fg) 68%, transparent); + cursor: pointer; + font-family: inherit; + font-size: 10px; + line-height: 20px; + transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; +} +.cal-settings-actions { + display: flex; + justify-content: flex-end; + width: 100%; +} +#cal-settings-panel .cal-week-start-toggle { + display: flex; + margin-left: auto; +} +.cal-settings-sync-status { + position: relative; + top: 2px; + font-size: 11px; + opacity: 0.6; +} +.cal-settings-sync-status.is-error { + color: var(--red); + opacity: 1; +} +.cal-week-start-btn:hover { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.cal-week-start-btn.active, +.cal-week-start-btn.active:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); +} +.cal-side-nav { + position: absolute; + top: var(--cal-side-nav-y, 50%); + transform: translateY(-50%); + width: 32px; + height: 32px; + border-radius: 50%; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + cursor: pointer; + z-index: 6; + opacity: 0; + pointer-events: none; + font-family: inherit; + font-size: 24px; + line-height: 28px; + padding: 0 0 3px; + transition: opacity 0.12s ease, background 0.12s ease, transform 0.12s ease; +} +.cal-modal-content:hover .cal-side-nav, +.cal-side-nav:focus-visible { + opacity: 0.84; + pointer-events: auto; +} +.cal-side-nav:hover { + opacity: 1; + background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); + transform: translateY(-50%) scale(1.04); +} +button.cal-today-btn { font-size:10px; opacity:0.5; } +button.cal-add-btn { background:var(--accent); color:#fff; border:none; border-radius:50%; width:24px; height:24px; line-height:22px; font-size:18px; cursor:pointer; flex-shrink:0; padding:0; box-sizing:border-box; font-family:inherit; vertical-align:middle; text-align:center; } +button.cal-add-btn.cal-add-btn-text { + width:auto; min-width:0; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 12px; + padding: 0 10px 0 6px; + display: inline-flex; + align-items: center; + gap: 4px; + height: 24px; + line-height: 1; + margin-top: 0; + transition: background 0.15s, border-color 0.15s; +} +button.cal-add-btn.cal-add-btn-text:hover { + background: color-mix(in srgb, var(--fg) 14%, transparent); + border-color: var(--accent); + opacity: 1; +} +.cal-add-plus { + font-size: 16px; + line-height: 1; + color: var(--accent, var(--red)); + font-weight: 500; + display: inline-block; + transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + padding-bottom: 2px; +} +button.cal-add-btn.cal-add-btn-text:hover .cal-add-plus { + transform: rotate(180deg); +} +.cal-add-label { font-size:11px; font-weight:600; line-height:1; opacity:0.85; } +/* Small variant of the toolbar's +New button. Inherits the .cal-add-btn-text + styles (pill with "+" + "New" + spring-rotate on the +), so the + animation is identical. Just shrunk a bit. */ +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { + height: 20px; + border-radius: 10px; + /* At rest: tight, equal padding around the "+" so it sits centred + with no trailing gap. On hover the right pad and the flex gap grow + to make room for the revealed "New". */ + padding: 0 5px; + gap: 0; + transition: padding 0.25s ease, gap 0.25s ease, background 0.15s, border-color 0.15s; +} +/* Don't touch .cal-add-plus on the small variant — the toolbar +New is + the reference and we want the same rotation centre / motion. The label + is shrunk and hidden until hover so the pill is compact at rest. */ +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { + font-size: 10px; + max-width: 0; + margin-left: 0; + opacity: 0; + overflow: hidden; + white-space: nowrap; + transition: max-width 0.25s ease, opacity 0.18s ease, margin-left 0.25s ease; +} +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover .cal-add-label { + max-width: 40px; + margin-left: 3px; + opacity: 0.85; +} +.cal-add-btn:hover { opacity:0.85; } + +/* Mobile: bump the +New pill and the +/- zoom buttons up to a tap-friendly + size so they're easy to hit with a thumb. */ +@media (max-width: 768px) { + /* Toolbar +New (relocated by JS into the quickadd row on mobile) */ + button.cal-add-btn.cal-add-btn-text { + height: 36px; + border-radius: 18px; + padding: 0 14px 0 12px; + gap: 6px; + flex-shrink: 0; + } + /* Day-detail +New: just a "+" on mobile — drop the label entirely. */ + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { + width: 28px; + height: 28px; + border-radius: 50%; + padding: 0; + gap: 0; + justify-content: center; + } + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { + display: none; + } + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { + font-size: 18px; + padding-bottom: 0; + } + .cal-add-plus { font-size: 22px; padding-bottom: 1px; } + .cal-add-label { font-size: 13px; } + .cal-wk-zoom { + width: 36px; + height: 36px; + font-size: 20px; + border-radius: 8px; + opacity: 0.85; + } + /* The +New button sits as a sibling of the quickadd row inside a flex + wrapper, so it's clearly outside the input's bordered box. The row + itself keeps its own styling. */ + .cal-quickadd-wrap { + display: flex; + align-items: stretch; + gap: 8px; + margin: 0 0 8px; + } + .cal-quickadd-wrap > #cal-quickadd-row { + flex: 1; + min-width: 0; + margin: 0; + } + .cal-quickadd-wrap > .cal-add-btn-text { + align-self: stretch; + flex-shrink: 0; + } + /* Nudge the "+" glyph up one pixel on the round day-detail button so it + sits visually centred inside the circle. */ + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { + transform: translateY(-1px); + } + /* Event form: bigger Create / Cancel buttons so they're easy to tap. */ + .cal-form-actions button.cal-btn { + height: 44px; + padding: 0 20px; + font-size: 14px; + border-radius: 8px; + min-width: 96px; + } + /* Left-align the day/month + tags row in the day-detail panel and + the calendar/category chip row so they sit flush against the + left edge on a narrow screen. */ + .cal-detail-header { + justify-content: flex-start; + gap: 12px; + } + .cal-filters { + justify-content: flex-start; + } +} + +/* Refresh button spin — driven by JS toggling .cal-syncing on the button. + transform-box + transform-origin keep the rotation pivoted at the SVG's + own centre so it spins in place instead of orbiting / drifting up. */ +#cal-sync svg, +#email-lib-refresh-btn svg { display: block; transform-box: fill-box; transform-origin: 50% 50%; } +/* Brief checkmark flash after a refresh completes. Lasts ~900ms in JS; + the small bounce + accent color give a clear "done" cue. */ +#cal-sync.cal-sync-done svg, +#email-lib-refresh-btn.email-lib-refresh-done svg { + color: var(--accent, #2ea043); + animation: refresh-check-pop 0.32s ease-out; +} +@keyframes refresh-check-pop { + 0% { transform: scale(0.55); opacity: 0.4; } + 60% { transform: scale(1.18); opacity: 1; } + 100% { transform: scale(1); opacity: 1; } +} +#cal-sync.cal-syncing svg, +#email-lib-refresh-btn.email-lib-refreshing svg { animation: spin 0.6s linear infinite; } +.email-undone-toggle.active, +.email-attach-toggle.active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); + color: var(--accent, var(--red)); + font-weight: 600; +} +/* Hide the gallery-style reader-btn labels on desktop — only the mobile + @media block re-displays them under each icon. Desktop keeps compact + icon-only reader buttons. */ +.reader-btn-label { + display: inline-block; + font-size: 9px; + font-weight: 500; + line-height: 1; + letter-spacing: 0.02em; + white-space: nowrap; + opacity: 0.85; +} + +/* Inline attachment-filter toggle nested inside the search input. The + input has padding-right:34px set inline so its text doesn't slide + under the button. */ +.email-search-wrap { display: flex; align-items: center; } +/* Keep the email search input and the adjacent "Select" button identical in + height regardless of base-vs-mobile overrides — pin both explicitly. */ +.email-search-row .memory-search-input { + height: 32px; + min-height: 32px; + box-sizing: border-box; +} +.email-search-row .email-search-select-btn { + height: 32px; + min-height: 32px; + box-sizing: border-box; + margin-top: 6px; + flex-shrink: 0; +} +/* Select moved into the dropdown row — dock it to the right and match the + selects' vertical nudge (.memory-sort-select has top:3px). */ +.memory-category-filters .email-filter-select-btn, +.memory-category-filters .email-filter-refresh-btn { + position: relative; + top: 3px; + flex-shrink: 0; +} +.memory-category-filters .email-filter-refresh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 8px; +} +.memory-category-filters .email-reminders-clear-btn { + position: relative; + top: -2px; + flex-shrink: 0; +} +.email-attach-toggle-inline { + position: absolute !important; + right: 4px !important; + top: 50%; + transform: translateY(calc(-50% - 5px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-attach-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-attach-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +/* Inline undone-toggle nested inside the search input, sits LEFT of the + attach toggle. Same visual treatment. */ +.email-undone-toggle-inline { + position: absolute !important; + right: 34px !important; + top: 50%; + transform: translateY(calc(-50% - 3px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-reminder-toggle-inline { + position: absolute !important; + right: 64px !important; + top: 50%; + transform: translateY(calc(-50% - 3px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-reminder-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-reminder-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +.email-undone-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-undone-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +/* Round (circular) toggles in the search bar, matching the app's default icon + buttons. Always fully visible (not dimmed until hover). */ +.email-attach-toggle-inline, +.email-undone-toggle-inline, +.email-reminder-toggle-inline { border-radius: 50% !important; opacity: 1 !important; } +.email-search-options-btn { + position: absolute !important; + right: 5px !important; + top: 50% !important; + transform: translateY(calc(-50% - 5px)); + width: 26px !important; + height: 26px !important; + min-width: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 999px !important; + background: transparent !important; + border: 0 !important; + color: inherit !important; + opacity: 0.7; + z-index: 2; +} +.email-search-options-btn:hover, +.email-search-options-btn.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +.email-search-options-menu { + position: absolute; + top: calc(100% + 2px); + right: 0; + z-index: 75; + min-width: 140px; + padding: 4px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--panel, var(--bg)); + box-shadow: 0 8px 24px rgba(0,0,0,0.3); +} +.email-search-options-title { + padding: 5px 9px 4px; + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + opacity: 0.65; + white-space: nowrap; +} +.email-search-options-menu button { + width: 100%; + border: 0; + background: transparent; + color: var(--fg); + display: flex; + align-items: center; + gap: 10px; + height: auto; + min-height: 0; + padding: 6px 8px; + border-radius: 8px; + font-family: inherit; + font-size: 11px; + font-weight: 400; + line-height: normal; + letter-spacing: normal; + text-align: left; + cursor: pointer; +} +.email-search-options-menu button:hover, +.email-search-options-menu button.active { + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + color: var(--accent, var(--red)); +} +.email-search-option-icon { + width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + opacity: 0.5; +} +.email-search-option-icon svg { width: 14px; height: 14px; } +.email-search-date-panel { + width: 220px; + padding: 6px 7px 5px; + border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); +} +.email-search-date-panel input { + width: 100%; + height: 26px; + box-sizing: border-box; + padding: 2px 6px; + border: 1px solid var(--border); + border-radius: 5px; + outline: none; + background: var(--bg); + color: var(--fg); + font: inherit; +} +.email-search-date-panel input:focus { border-color: var(--accent, var(--red)); } +.email-search-date-actions { display: flex; justify-content: flex-end; gap: 5px; } +.email-search-options-menu .email-search-date-actions [data-email-date-apply] { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +@media (max-width: 768px) { + .email-search-options-menu button { + min-height: 44px; + padding: 12px 12px !important; + font-size: 14px !important; + gap: 10px !important; + } + .email-search-options-menu .email-search-option-icon { + width: 18px; + height: 18px; + } + .email-search-options-menu .email-search-option-icon svg { + width: 16px; + height: 16px; + } +} +/* Mobile: enlarge the icons inside the inline search-bar toggles + (done / attachment / reminders) — buttons themselves stay the same, + only the SVG glyph scales up so it's tappable + visible. */ +@media (max-width: 768px) { + .email-filter-btn { + height: 30px; + min-height: 30px; + font-size: 12px; + top: -6px; + padding: 0 9px; + } + .email-folder-btn { + top: -4px; + } + + #email-lib-folder, + .memory-category-filters .email-filter-select-btn, + .memory-category-filters .email-filter-refresh-btn, + .memory-category-filters .email-filter-settings-btn { + height: 30px !important; + min-height: 30px !important; + box-sizing: border-box; + } + + .memory-category-filters .email-filter-refresh-btn, + .memory-category-filters .email-filter-settings-btn { + width: 30px; + padding: 0 !important; + } + .memory-category-filters .email-filter-settings-btn > svg { + width: 15px; + height: 15px; + } + + .email-search-row .memory-search-input { + height: 38px; + min-height: 38px; + } + + #email-lib-chip-bar { + padding-right: 42px !important; + padding-left: 30px !important; + gap: 5px !important; + } + + .email-lib-chip-bar-icon { + width: 14px !important; + height: 14px !important; + left: 9px !important; + } + + #email-lib-search { + font-size: 14px !important; + min-width: 88px !important; + top: 0 !important; + } + + #email-lib-search::placeholder { + font-size: 14px !important; + } + + #email-lib-chip-bar.has-email-lib-pills #email-lib-search { + min-width: 24px !important; + flex: 1 1 24px !important; + } + + .email-lib-pill { + min-height: 24px !important; + height: 24px !important; + line-height: 24px !important; + font-size: 12px !important; + padding: 0 6px 0 8px !important; + max-width: 180px !important; + } + + .email-lib-pill svg { + width: 14px !important; + height: 14px !important; + } + + .email-lib-pill-x { + font-size: 13px !important; + top: -4.5px !important; + } + + .email-lib-filter-pill { + min-height: 24px !important; + height: 24px !important; + line-height: 24px !important; + gap: 4px !important; + padding: 0 6px 0 8px !important; + } + + .email-lib-filter-pill .email-lib-pill-icon { + width: 17px !important; + height: 17px !important; + } + + .email-lib-filter-pill svg { + width: 18px !important; + height: 18px !important; + } + + .email-lib-filter-pill .email-lib-pill-x { + font-size: 13px !important; + top: -4.5px !important; + } + + .email-tags-toggle-inline { + right: 6px; + height: 28px !important; + min-width: 42px; + padding: 0 8px !important; + font-size: 11px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-attach-toggle-inline { + right: 56px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-undone-toggle-inline { + right: 92px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-reminder-toggle-inline { + right: 128px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-attach-toggle-inline svg, + .email-undone-toggle-inline svg, + .email-reminder-toggle-inline svg, + .email-filter-refresh-btn svg { + width: 16px !important; + height: 16px !important; + } +} +.email-accounts-row { + display: flex; + align-items: center; + gap: 10px; + padding: 0 0 6px; +} +.email-accounts-row .doclib-desc { flex-shrink: 0; } +/* Only the direct-child compose button gets pushed right; nested chips + inside #email-lib-accounts pack to the left as normal flex items. */ +.email-accounts-row > .memory-toolbar-btn { flex-shrink: 0; margin-left: auto; } +.email-account-chip { + display: inline-flex; + align-items: center; + gap: 5px; +} +.email-account-chip.email-account-has-unread { + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + color: color-mix(in srgb, var(--accent, var(--red)) 86%, var(--fg)); +} +.email-account-unread-dot { + width: 6px; + height: 6px; + border-radius: 999px; + background: var(--accent, var(--red)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + flex: 0 0 auto; +} +.email-account-chip-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} +.email-account-unread-count { + font-size: 9px; + line-height: 1; + font-weight: 700; + color: var(--accent, var(--red)); + opacity: 0.9; + position: relative; + top: 0; +} +.email-account-away-label { + color: var(--accent, var(--red)); + font-size: 9px; + font-weight: 700; + line-height: 1; + flex: 0 0 auto; +} +.email-accounts-loading-whirlpool { + width: 14px; + height: 14px; + margin: 3px 4px 0 1px; + display: inline-flex; + flex: 0 0 auto; +} +.email-accounts-loading-label { + font-size: 10px; + opacity: 0.55; + position: relative; + top: 2px; + white-space: nowrap; +} +.email-loading-with-label { + /* Fill the email list grid so the spinner lands in the middle of the + email window rather than the top of the empty list section. */ + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + text-align: center; + pointer-events: none; +} +.date-section-header { + display: flex; + align-items: center; + gap: 7px; + padding: 8px 9px 4px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 10px; + font-weight: 700; + letter-spacing: 0; + line-height: 1.2; + text-transform: uppercase; + user-select: none; +} +.date-section-header::after { + content: ''; + flex: 1 1 auto; + height: 1px; + background: color-mix(in srgb, var(--border) 72%, transparent); +} +#session-list .date-section-header { + padding-left: 10px; + padding-right: 8px; + cursor: pointer; +} +#session-list .date-section-header.collapsed { + color: color-mix(in srgb, var(--fg) 36%, transparent); +} +#session-list .date-section-header.collapsed::after { + opacity: 0.45; +} +#session-list .date-section-header:focus-visible { + outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); + outline-offset: 2px; + border-radius: 5px; +} +/* Bootstrap row shown while the session list hydrates, and on load failure. + Reads as a normal list row but is not selectable. */ +.session-list-bootstrap { + cursor: default; + pointer-events: none; +} +#email-lib-grid .date-section-header { + padding: 10px 5px 3px; +} +#email-lib-modal .email-lib-sync-status { + flex: 0 0 auto; + min-height: 14px; + padding: 1px 4px 0; + font-size: 10.5px; + line-height: 1.2; + text-align: center; + opacity: 0.58; + color: var(--muted-text, currentColor); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + top: 4px; +} diff --git a/static/css/16-calendar.css b/static/css/16-calendar.css new file mode 100644 index 000000000..d4112f705 --- /dev/null +++ b/static/css/16-calendar.css @@ -0,0 +1,1175 @@ +/* 16-calendar - 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. + */ +@media (max-width: 768px) { + #email-lib-modal .email-lib-sync-status { + min-height: 13px; + padding-right: 5px; + font-size: 10px; + } +} +/* Nudge the subject + sender/date meta line 4px right inside each + email card so they don't sit flush against the read/unread/done + markers on the left. Includes #email-lib-modal selector to + override the expanded card's `padding: 4px 0 6px` shorthand + which was zeroing out the title's padding-left. */ +#email-lib-grid .email-card-titlerow, +#email-lib-grid .memory-item-meta, +#email-lib-modal .doclib-card-expanded .email-card-titlerow { + padding-left: 4px; +} +.email-loading-label { + font-size: 11px; + opacity: 0.6; +} + +/* Refresh button now lives top-right in the modal header next to the close X. + Borderless (matches the close X), and a fixed square box so the spin and the + refresh→checkmark icon swap never change its size or nudge neighbours. */ +.email-lib-header-actions #email-lib-refresh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + border: none; + background: none; + padding: 0; + width: 24px; + height: 24px; +} +/* Bump the icon up on mobile for a bigger visual — but keep the button BOX at + 24px so it never exceeds the header's natural height (the title/close set + that). A taller box would grow the sticky header and push the list down. */ +@media (max-width: 768px) { + .email-lib-header-actions #email-lib-refresh-btn svg { + width: 17px; + height: 17px; + } +} +.cal-view-toggle { + display: inline-flex; + align-items: stretch; + border: 1px solid var(--border); + border-radius: 5px; + overflow: hidden; + vertical-align: middle; + box-sizing: border-box; + padding: 0; + margin: 0; + line-height: 1; +} +.cal-view-btn + .cal-view-btn { border-left: 1px solid var(--border); } +.cal-view-btn:hover { opacity: 0.75; } +.cal-view-btn.active { + background: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--accent, var(--red)); + opacity: 1; + font-weight: 600; +} +/* Toolbar holds only the toggle button; the chip row renders below the + toolbar on its own line and wraps freely. */ +.cal-filters { + display:flex; + gap:6px; + margin:6px 0 8px; + flex-wrap:wrap; + padding:6px 8px; + border:1px solid color-mix(in srgb, var(--fg) 8%, transparent); + border-radius:8px; + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 4%, transparent), transparent), + color-mix(in srgb, var(--fg) 2.5%, var(--bg)); + box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 4%, transparent); +} +/* Round the native color-input swatch in calendar settings. The outer + wraps a colored fill drawn by the browser; we need to round both + the wrapper and the fill so it actually appears circular. */ +.cal-s-color { border-radius: 50%; overflow: hidden; } +.cal-filter-item { display:inline-flex; align-items:center; gap:4px; cursor:pointer; font-size:10px; padding:2px 8px; line-height:1.4; border-radius:10px; background:color-mix(in srgb, var(--bg) 72%, transparent); border:1px solid color-mix(in srgb, var(--fg) 12%, transparent); transition:opacity 0.15s, background 0.15s, border-color 0.15s, transform 0.15s; } +.cal-filter-item:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); } +.cal-filter-item.cal-filter-important { font-weight:700; color:#e5a33a; border-color:color-mix(in srgb, #e5a33a 40%, transparent); } +/* Match the toolbar's other buttons (cal-nav, cal-view-btn, cal-add-btn-text): + 24px tall, 11px font, 5px corners. */ +.cal-filter-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font-size: 11px; + font-family: inherit; + font-weight: 500; + padding: 0 10px; + height: 24px; + line-height: 1; + border-radius: 5px; + box-sizing: border-box; + vertical-align: middle; + opacity: 0.65; + position: relative; + top: -3px; +} +.cal-filter-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } +.cal-filter-toggle-glyph { + display: inline-block; + position: relative; + top: 0; + margin-right: 3px; + color: var(--accent, var(--red)); +} +.cal-filter-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; } +/* The grid is now a flex column: one row of weekday headers + six + .cal-week-row rows. Each row holds the 7 day cells in its own + internal grid plus the absolute-positioned multi-day overlays. + --bars on a week-row is the number of multi-day events overlapping + it; cells in that row use it to reserve top padding so the bar + overlays don't cover the day numbers or single-event rows. */ +.cal-grid { display:flex; flex-direction:column; gap:1px; background:color-mix(in srgb, var(--fg) 8%, transparent); border-radius:6px; overflow:hidden; } +.cal-week-headers { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } +.cal-week-row { position:relative; display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } +.cal-weekday { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); text-align:center; font-size:10px; font-weight:600; opacity:0.4; padding:5px 0; } +.cal-day { + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 34px), + var(--bg); + min-height:78px; + padding:3px; + cursor:pointer; + position:relative; + transition:background 0.12s, box-shadow 0.12s; + overflow:hidden; +} +.cal-day:hover { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); } +.cal-day.cal-today { + box-shadow: inset 0 0 0 2px var(--accent, var(--red)); + background: var(--bg); + border-radius: 8px; +} +.cal-day.cal-selected:not(.cal-today) .cal-day-num { + color: var(--accent, var(--fg)); + opacity: 1; + font-weight: 700; +} +.cal-day.cal-drag-over { background:color-mix(in srgb, var(--accent) 18%, var(--bg)); } +.cal-day.cal-range-select { background:color-mix(in srgb, var(--accent, var(--red)) 25%, var(--bg)); box-shadow:inset 0 0 0 1px var(--accent, var(--red)); } +.cal-day-num { font-size:10px; font-weight:600; display:block; margin-bottom:1px; opacity:0.7; line-height:1.2; } +.cal-dots { display:flex; flex-direction:row; align-items:center; gap:2px; flex-wrap:nowrap; margin-bottom:1px; height:6px; } +.cal-dot { width:5px; height:5px; border-radius:50%; display:inline-block; cursor:grab; flex-shrink:0; } +.cal-dot-more { font-size:7px; opacity:0.4; } +.cal-event-row { + display:flex; + align-items:center; + gap:4px; + padding:2px 4px; + border-radius:4px; + cursor:grab; + line-height:1.2; + margin-bottom:2px; + min-height:17px; + background:color-mix(in srgb, var(--fg) 5%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); + animation:cal-event-enter 0.16s ease-out both; + transition:background 0.15s, box-shadow 0.15s, transform 0.15s; +} +.cal-event-row:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); transform:translateY(-1px); } +.cal-event-row-dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } +.cal-event-row-time { font-size:10px; opacity:0.72; font-variant-numeric:tabular-nums; flex-shrink:0; font-weight:600; } +.cal-event-row-name { font-size:10px; opacity:0.96; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; font-weight:600; } +.cal-event-reminder { + display: inline-flex; + width: 12px; + height: 12px; + margin-left: 5px; + vertical-align: -2px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + opacity: 0.9; + flex: 0 0 auto; +} +.cal-event-reminder svg { + width: 100%; + height: 100%; + display: block; +} +.cal-event-row .cal-event-reminder { + width: 10px; + height: 10px; + margin-left: 4px; +} +.cal-event-source { + display:inline-flex; + align-items:center; + justify-content:center; + width:12px; + height:12px; + margin-left:5px; + border-radius:50%; + font-size:7px; + font-weight:800; + line-height:1; + color:var(--cal-event-fg, var(--fg)); + background:color-mix(in srgb, currentColor 16%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); + vertical-align:1px; +} +.cal-event-source svg { + width: 10px; + height: 10px; + display: block; +} +.cal-event-row .cal-event-source { + width:11px; + height:11px; + font-size:7px; + margin-left:4px; + color:color-mix(in srgb, var(--fg) 72%, transparent); +} +.cal-multiday .cal-event-reminder, +.cal-wk-allday-event .cal-event-reminder { + color: var(--cal-event-fg, currentColor); + opacity: 0.88; +} +.cal-multiday .cal-event-source, +.cal-wk-allday-event .cal-event-source { + color: var(--cal-event-fg, currentColor); +} +.cal-event-more { font-size:9px; opacity:0.55; padding-left:6px; } +/* Multi-day bar — positioned as an absolute overlay inside its + .cal-week-row so it spans uninterrupted across every column it + covers. --col is the starting column (0–6), --span is the number + of days it covers within the row, --slot stacks parallel bars. */ +.cal-multiday { + position: absolute; + /* Fractional offsets let timed events that cross midnight render at + true proportional width. start-frac shifts the left edge into the + first day; end-frac trims the right edge inside the last day. + All-day events default to (0, 1) and still fill whole columns. */ + left: calc((var(--col, 0) + var(--start-frac, 0)) * (100% / 7)); + width: calc( + (var(--span, 1) - var(--start-frac, 0) - (1 - var(--end-frac, 1))) + * (100% / 7) + ); + top: calc(2px + var(--slot, 0) * 12px); + height: 12px; + font-size: 9px; + line-height: 12px; + padding: 0 5px; + border-radius: 4px; + color: var(--cal-event-fg, #fff); + font-weight: 700; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: grab; + opacity: 0.98; + z-index: 2; + pointer-events: auto; + box-shadow: 0 1px 2px color-mix(in srgb, #000 18%, transparent); +} +/* Splitter between the month/week grid and the day-tasks panel — drag + vertically to give the day panel more room. Height clamped by the + `--cal-detail-h` variable set on #cal-body by the splitter drag JS. */ +.cal-splitter { + height: 8px; + margin: 6px -10px 0; + cursor: row-resize; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + user-select: none; + touch-action: none; +} +/* Mobile: fat hitbox so the splitter is easy to grab with a thumb, + without changing how the grip itself looks. */ +@media (max-width: 768px) { + .cal-splitter { + height: 24px; + margin-top: 0; + margin-bottom: 0; + } + .cal-splitter-grip { + width: 56px; + height: 4px; + } +} +.cal-splitter-grip { + width: 42px; + height: 3px; + border-radius: 2px; + background: color-mix(in srgb, var(--fg) 25%, transparent); + transition: background 0.12s; +} +.cal-splitter:hover .cal-splitter-grip, +.cal-splitter-dragging .cal-splitter-grip { + background: var(--accent, var(--red)); +} +.cal-day-detail { + margin-top: 4px; + border-top: 1px solid var(--border); + padding-top: 8px; + height: var(--cal-detail-h, 240px); + overflow-y: auto; + overscroll-behavior: contain; + flex-shrink: 0; +} +.cal-detail-header { display:flex; justify-content:space-between; align-items:center; font-size:12px; font-weight:600; margin-bottom:6px; padding-right:8px; } +.cal-empty { font-size:11px; opacity:0.3; padding:4px 0; } +.cal-event-item { display:flex; gap:9px; padding:8px 9px; border-radius:6px; cursor:pointer; align-items:flex-start; background:color-mix(in srgb, var(--fg) 3.5%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); margin-bottom:4px; } +.cal-event-item:hover { background:color-mix(in srgb, var(--fg) 8%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); } +.cal-event-dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:4px; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } +.cal-event-info { flex:1; min-width:0; } +.cal-event-name { font-size:13px; font-weight:700; line-height:1.25; color:color-mix(in srgb, var(--fg) 96%, transparent); overflow-wrap:anywhere; } +.cal-event-tag { + display: inline-block; + font-size: 10px; + font-weight: 500; + padding: 1px 6px; + border-radius: 8px; + border: 1px solid; + margin-left: 6px; + vertical-align: 1px; + opacity: 0.85; + white-space: nowrap; +} +.cal-event-time { font-size:11px; opacity:0.68; line-height:1.35; margin-top:2px; font-weight:500; } +.cal-event-loc { font-size:11px; opacity:0.58; line-height:1.35; margin-top:1px; } +/* ── Week view: hour grid ────────────────────────────────────────── + Vertical hour rail on the left, 7 day columns on the right with their + own all-day strip and an absolute-positioned hour grid below it. */ +.cal-wk-wrap { + display: flex; + flex-direction: row; + gap: 0; + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 3%, transparent), transparent 120px), + var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + /* The wrap itself scrolls so the hour rail and day columns move + together; column headers and the rail spacer are sticky-pinned. */ + overflow: auto; + max-height: calc(100vh - 220px); + min-height: 360px; + position: relative; +} +.cal-wk-now-below-hint { + position: absolute; + height: 2px; + border-radius: 2px 2px 0 0; + background: var(--accent, var(--red)); + pointer-events: none; + z-index: 38; +} +@media (max-width: 768px) { + .cal-wk-wrap { + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + } + .cal-wk-wrap > .cal-wk-rail, + .cal-wk-wrap > .cal-wk-cols { + transform: translateY(var(--wk-edge-pull, 0)); + } + .cal-wk-wrap.cal-wk-edge-release > .cal-wk-rail, + .cal-wk-wrap.cal-wk-edge-release > .cal-wk-cols { + transition: transform 0.14s cubic-bezier(0.2, 0.75, 0.25, 1); + } + .cal-wk-wrap.cal-wk-pull-top { + border-top-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); + } + .cal-wk-wrap.cal-wk-pull-bottom { + border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); + } +} +.cal-wk-rail { + flex: 0 0 56px; + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + background: color-mix(in srgb, var(--fg) 2%, var(--bg)); + position: sticky; + left: 0; + z-index: 5; +} +.cal-wk-rail-spacer { + /* Match the 32px day header. The 24px all-day row gets its own spacer + below, so the zoom controls sit high without shifting the hour grid. */ + height: 32px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 0; + background: color-mix(in srgb, var(--fg) 2%, var(--bg)); + z-index: 9; + display: flex; + align-items: center; + justify-content: center; + gap: 2px; + padding: 0 4px; +} +.cal-wk-rail-allday-spacer { + height: 24px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 32px; + z-index: 8; + background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); +} +.cal-wk-zoom { + position: relative; + top: -3px; + width: 22px; + height: 22px; + border: 1px solid var(--border); + background: var(--bg); + color: var(--fg); + border-radius: 50%; + font-family: inherit; + font-size: 14px; + font-weight: 600; + line-height: 1; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + opacity: 0.55; + transition: opacity 0.15s, background 0.15s, border-color 0.15s; +} +.cal-wk-zoom:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--fg)) 50%, var(--border)); +} +.cal-wk-rail-cell { + font-size: 11px; + color: color-mix(in srgb, var(--fg) 55%, transparent); + padding: 4px 8px 0; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); + flex-shrink: 0; + display: flex; + align-items: flex-start; + justify-content: flex-end; + font-variant-numeric: tabular-nums; +} +.cal-wk-cols { + flex: 1; + display: grid; + grid-template-columns: repeat(7, 1fr); + position: relative; +} +.cal-wk-col { + border-left: 1px solid var(--border); + display: flex; + flex-direction: column; + min-width: 0; +} +.cal-wk-col:first-child { border-left: none; } +.cal-wk-col-head { + height: 32px; + padding: 0 6px; + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + flex-shrink: 0; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); + position: sticky; + top: 0; + z-index: 40; +} +/* Today on a Sunday still wins the accent treatment. */ +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); } +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dn, +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dt { color: var(--accent, var(--fg)); } +.cal-wk-dn { font-weight: 600; opacity: 0.6; } +.cal-wk-allday { + height: 24px; + padding: 2px 4px; + display: flex; + flex-direction: row; + gap: 2px; + align-items: center; + background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); + border-bottom: 1px solid var(--border); + flex-shrink: 0; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; + position: sticky; + top: 32px; + z-index: 39; +} +.cal-wk-allday-event { + font-size: 10.5px; + font-weight: 700; + padding: 3px 6px; + border-radius: 4px; + color: var(--cal-event-fg, var(--fg)); + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* Hour grid body */ +.cal-wk-grid { + position: relative; + flex: 0 0 auto; + cursor: cell; + user-select: none; + background: + repeating-linear-gradient( + to bottom, + transparent 0, + transparent calc(var(--wk-hour-px, 52px) / 2 - 1px), + color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--wk-hour-px, 52px) / 2), + transparent calc(var(--wk-hour-px, 52px) / 2 + 1px) + ); +} +.cal-wk-cell { + border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); + box-sizing: border-box; +} +.cal-wk-cell:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); } +/* "Now" line */ +.cal-wk-now { + position: absolute; + left: 0; right: 0; + height: 0; + border-top: 2px solid color-mix(in srgb, var(--accent, var(--red, #f87171)) 90%, transparent); + pointer-events: none; + z-index: 7; + filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent)); +} +/* Event blocks */ +.cal-wk-block { + position: absolute; + left: calc((var(--lane, 0) * (100% / var(--lane-count, 1))) + 2px); + width: calc((100% / var(--lane-count, 1)) - 4px); + right:auto; + border-left: 4px solid var(--fg); + border-radius: 5px; + padding: 5px 7px 7px; + font-size: 12px; + line-height: 1.3; + cursor: pointer; + /* `clip` preserves the rounded-card boundary without creating an overflow + container, allowing the label inside to stick to the week scroller. */ + overflow: clip; + z-index: 2; + box-shadow: 0 2px 6px color-mix(in srgb, #000 14%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); + animation:cal-event-enter 0.18s ease-out both; + transition: width 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), height 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), filter 0.12s, transform 0.12s, box-shadow 0.16s; + box-sizing: border-box; +} +.cal-wk-block:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); } +.cal-wk-block-label { + position: sticky; + top: 37px; + z-index: 1; + min-width: 0; + pointer-events: none; +} +.cal-wk-has-allday .cal-wk-block-label { + top: 61px; +} +.cal-wk-block-name { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color:color-mix(in srgb, var(--fg) 96%, transparent); } +.cal-wk-block-time { font-size: 10.5px; opacity: 0.82; font-variant-numeric: tabular-nums; margin-top: 2px; font-weight:600; } +@media (hover: hover) and (pointer: fine) { + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left { + left: auto; + right: calc(100% - var(--lane-right) + 2px); + } + .cal-wk-block.cal-wk-block-crowded:hover { + width: var(--hover-width, 170px); + height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; + overflow: clip; + z-index: 30; + filter: brightness(1.08); + transform: translateY(var(--hover-shift-y, -2px)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-hover-width-only:hover { + height: var(--event-height) !important; + transform: translateY(-1px); + } + .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-label { + position: absolute; + top: 5px; + width: calc(var(--hover-width, 170px) - 18px); + animation: cal-wk-hover-label-reveal 0.2s ease-out both; + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left:hover .cal-wk-block-label { + left: auto; + right: 7px; + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-right:hover .cal-wk-block-label { + left: 7px; + right: auto; + } + .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-name { + display: block; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + } +} +@media (max-width: 768px) { + /* Mobile week view: the hour rail already shows the time, so drop it from the + card and let the title wrap to fill the freed space (more readable text). */ + .cal-wk-block-time { display: none; } + .cal-wk-block-name { + white-space: normal; + line-height: 1.15; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left { + left: auto; + right: calc(100% - var(--lane-right) + 2px); + } + .cal-wk-block.cal-wk-mobile-expanded { + width: var(--hover-width, 170px); + height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; + overflow: clip; + z-index: 30; + filter: brightness(1.08); + transform: translateY(var(--hover-shift-y, 0)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); + } + .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-label { + position: absolute; + top: 5px; + width: calc(var(--hover-width, 170px) - 18px); + animation: cal-wk-hover-label-reveal 0.2s ease-out both; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left .cal-wk-block-label { + left: auto; + right: 7px; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-right .cal-wk-block-label { + left: 7px; + right: auto; + } + .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-name { + display: block; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + -webkit-line-clamp: unset; + } +} +/* Resize handle: a 6 px hit zone along the block's bottom edge. The + visible 2-px stripe only shows on hover so blocks at rest stay clean. */ +.cal-wk-block-resize { + position: absolute; + left: 4px; + right: 4px; + bottom: 0; + height: 6px; + cursor: ns-resize; + z-index: 3; +} +/* Drag-to-create ghost */ +.cal-wk-ghost { + position: absolute; + left: 2px; + right: 2px; + background: color-mix(in srgb, var(--accent, var(--fg)) 28%, transparent); + border: 1px dashed color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent); + border-radius: 4px; + pointer-events: none; + z-index: 3; + font-size: 10px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + color: var(--accent, var(--fg)); + font-variant-numeric: tabular-nums; +} +.cal-wk-slot-selected { + background: color-mix(in srgb, var(--accent, var(--fg)) 20%, var(--bg)); + border-style: solid; + border-color: var(--accent, var(--fg)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--fg)) 20%, transparent); + overflow: visible; + pointer-events: auto; + cursor: pointer; +} +.cal-wk-slot-resize { + position: absolute; + left: 14%; + right: 14%; + bottom: -7px; + height: 14px; + z-index: 4; + cursor: ns-resize; + touch-action: none; + pointer-events: auto; +} +.cal-year { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; } +.cal-year-month { background:color-mix(in srgb, var(--fg) 3%, var(--bg)); border:1px solid var(--border); border-radius:6px; padding:6px; cursor:pointer; transition:background 0.15s, border-color 0.15s, transform 0.15s; } +.cal-year-month:hover { background:color-mix(in srgb, var(--fg) 8%, var(--bg)); border-color:var(--accent); transform:translateY(-1px); } +.cal-year-month-title { font-size:11px; font-weight:600; text-align:center; margin-bottom:4px; opacity:0.7; } +.cal-year-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; } +.cal-year-wd { font-size:7px; text-align:center; opacity:0.3; padding:1px 0; } +.cal-year-cell { font-size:8px; text-align:center; padding:2px 0; min-height:14px; border-radius:2px; transition:transform 0.12s, background 0.12s; } +.cal-year-day:hover { background:var(--accent, var(--red)); color:#fff; opacity:1; transform:scale(1.15); font-weight:700; z-index:2; position:relative; } +.cal-year-today { color:var(--accent, var(--red)); font-weight:700; opacity:1; } +.cal-year-has { opacity:1; background:color-mix(in srgb, var(--accent) 15%, transparent); } +.cal-year-has.cal-year-today { background:color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } +.cal-loading { display:flex; justify-content:center; padding:40px 0; } +.cal-badge { background:var(--accent); border-radius:50%; width:6px; height:6px; margin-left:auto; display:inline-block; flex-shrink:0; } +/* Search input — in-panel variant lives inside the day-detail panel and + spans its width; no width animation since growing/shrinking on every + keystroke would jitter beside the events list. */ +.cal-search-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:0 8px; height:24px; color:var(--fg); font-size:11px; font-family:inherit; width:120px; box-sizing:border-box; transition:width 0.15s; } +.cal-search-input:focus { outline:none; border-color:var(--accent); width:180px; } +.cal-search-input.cal-day-search { width:100%; margin-bottom:0; transition:none; padding-left: 28px; } +/* Wrap for the search input so the magnifying-glass icon can be absolute- + positioned inside the field. */ +.cal-search-wrap { + position: relative; + display: block; + margin-bottom: 6px; +} +.cal-search-wrap .cal-search-icon { + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + color: var(--accent, var(--red)); +} +.cal-day-search-meta { font-size:10px; opacity:0.5; margin:0 2px 6px; } +.cal-search-results { display:flex; flex-direction:column; gap:4px; } +.cal-search-count { font-size:10px; opacity:0.5; padding:4px 2px 8px; } +/* Agenda view */ +.cal-agenda { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-left:4px; } +.cal-agenda-day { display:flex; flex-direction:column; gap:5px; position:relative; padding-left:14px; } +.cal-agenda-date { font-size:12px; font-weight:800; opacity:0.72; padding:5px 2px 3px; border-bottom:1px solid var(--border); } +.cal-agenda-today-badge { + display: inline-block; + margin-left: 6px; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + padding: 1px 6px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + vertical-align: 1px; +} +.cal-agenda-empty { font-size:11px; opacity:0.4; padding:8px; font-style:italic; } +.cal-agenda-event { + display:flex; + gap:9px; + padding:9px 10px; + border-radius:6px; + cursor:pointer; + align-items:center; + position:relative; + background:color-mix(in srgb, var(--fg) 3.5%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); + animation:cal-event-enter 0.18s ease-out both; + transition:background 0.15s, box-shadow 0.15s, transform 0.15s; +} +.cal-agenda-event:hover { + background:color-mix(in srgb, var(--fg) 8%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); + transform:translateX(2px); +} +.cal-agenda-event .cal-event-more, +.cal-event-item .cal-event-more { opacity:0; transition:opacity 0.15s; } +.cal-event-item { position:relative; } + +/* ── Personal Assistant ── */ +.sidebar-assistant-entry { gap: 6px; min-height: 29px; box-sizing: border-box; align-items: center; } +.sidebar-assistant-entry .sidebar-action-icon { position: relative; left: -2px; } +#sidebar-assistant-gear:hover { opacity: 0.8 !important; } +.assistant-settings-form { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; } +.assistant-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: color-mix(in srgb, var(--fg) 70%, transparent); } +.assistant-field > span { font-size: 11px; opacity: 0.6; } +.assistant-field input[type="text"], +.assistant-field input[type="time"], +.assistant-field select, +.assistant-field textarea { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 5px; + padding: 7px 10px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + box-sizing: border-box; +} +.assistant-field textarea { resize: vertical; min-height: 60px; } +.assistant-field input:focus, +.assistant-field select:focus, +.assistant-field textarea:focus { outline: none; border-color: var(--accent, var(--red)); } +.assistant-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } +.assistant-field-row > .assistant-field { flex: 1 1 180px; } +.assistant-field-check { flex-direction: row !important; align-items: center; gap: 6px; padding-bottom: 7px; } +.assistant-checkins { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); } +.assistant-checkins h5 { margin: 0 0 2px; font-size: 12px; font-weight: 600; opacity: 0.8; } +.assistant-checkin-row { + display: flex; + flex-direction: column; + gap: 4px; + padding: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); +} +.assistant-checkin-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } +.assistant-checkin-head input[type="text"], +.assistant-checkin-head input[type="time"] { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 4px; + padding: 4px 8px; + color: var(--fg); + font-size: 12px; + font-family: inherit; +} +.assistant-checkin-head input[type="text"] { flex: 1; min-width: 120px; } +.assistant-checkin-head input[type="time"] { width: 90px; } +.assistant-checkin-run { + background: none; + border: 1px solid var(--border); + color: var(--fg); + font-family: inherit; + font-size: 11px; + padding: 4px 8px; + border-radius: 4px; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s; +} +.assistant-checkin-run:hover { opacity: 1; } +.assistant-checkin-prompt { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 8px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + resize: vertical; + min-height: 48px; +} +.assistant-checkin-prompt:focus { outline: none; border-color: var(--accent, var(--red)); } +.assistant-checkin-meta { font-size: 10px; opacity: 0.45; } +.assistant-settings-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--border); +} +.assistant-tools-grid { + display: flex; + flex-direction: column; + gap: 6px; + max-height: 240px; + overflow-y: auto; + padding: 6px; + border: 1px solid var(--border); + border-radius: 6px; + margin-top: 4px; +} +.assistant-tool-group { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} +.assistant-tool-group-label { + font-size: 10px; + font-weight: 600; + opacity: 0.5; + text-transform: uppercase; + letter-spacing: 0.03em; + width: 100%; + margin-bottom: 1px; +} +.assistant-tool-item { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 11px; + padding: 2px 6px; + border: 1px solid var(--border); + border-radius: 4px; + cursor: pointer; + transition: background 0.1s, border-color 0.1s; + user-select: none; +} +.assistant-tool-item:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.assistant-tool-item:has(input:checked) { + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 10%, transparent); +} +.assistant-tool-item input[type="checkbox"] { + width: 12px; + height: 12px; + margin: 0; +} + +/* Empty state */ +.cal-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px 20px; text-align:center; gap:10px; } +.cal-empty-title { font-size:14px; font-weight:600; opacity:0.8; } +.cal-empty-msg { font-size:12px; opacity:0.5; max-width:320px; line-height:1.5; margin-bottom:8px; } +.cal-allday-switch { margin:0; flex-shrink:0; } +/* Stack the "All day" label above its toggle so it doesn't get squeezed / + truncated next to the date inputs in the row. */ +.cal-allday-ctrl { display:flex; flex-direction:column; align-items:center; gap:3px; flex-shrink:0; } +.cal-allday-label { font-size:10px; opacity:0.5; white-space:nowrap; line-height:1; } +.cal-form { display:flex; flex-direction:column; gap:8px; } +.cal-form-title { font-size:13px; font-weight:600; margin-bottom:2px; } +.cal-title-wrap { position: relative; } +/* Calendar-picker select sits 2px higher; its border-left/background tint is + set in JS to the chosen calendar's colour. */ +.cal-f-cal-select { position: relative; top: -4px; } + +/* ── Bespoke event form ──────────────────────────────────────────── + Big clock-face date/time hero with the title under it. Details panel + stays collapsed until the title gets focus or "Add details" is clicked. +*/ +.cal-form-bespoke { + position: relative; + width: min(720px, calc(100% - 24px)); + max-width: none; + margin: 12px auto; + padding: 18px 28px 14px; + box-sizing: border-box; + background: var(--bg); + /* Optional per-event tint set by JS via --ev-color. When unset, falls + back to the theme's neutral border so the card looks like normal. */ + border: 1px solid var(--border); + border-radius: 14px; + box-shadow: 0 8px 24px color-mix(in srgb, var(--fg) 6%, transparent); + transition: border-color 0.18s, box-shadow 0.18s; +} +/* When a colour is picked, lift the card with a soft tinted border + glow + so the choice is unmistakable. */ +.cal-form-bespoke[style*="--ev-color"] { + border-color: color-mix(in srgb, var(--ev-color) 55%, var(--border)); + box-shadow: 0 8px 28px color-mix(in srgb, var(--ev-color) 22%, transparent); +} +.cal-form-bespoke[style*="--ev-color"] .cal-btn-primary { + background: var(--ev-color); + border-color: var(--ev-color); +} +.cal-form-bespoke.cal-form-bg-image .cal-btn-primary { + background: var(--accent, var(--red)); + border-color: var(--accent, var(--red)); + color: var(--bg); +} +.cal-form-close { + position: absolute; top: 10px; right: 12px; + background: none; border: none; color: var(--fg); + opacity: 0.35; cursor: pointer; font-size: 20px; line-height: 1; + padding: 4px 8px; border-radius: 6px; +} +.cal-form-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 8%, transparent); } +.cal-form-mobile-cancel { display: none; } + +/* "Today is …" header pinned at the top of the form. Keeps you oriented + even when the event date you're picking is way off in the future. */ +.cal-form-today { + text-align: center; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + opacity: 0.45; + margin-bottom: 0; +} + +/* Hero — clock face + date label. Spacing between the two lives entirely + on the clock's bottom margin so it's predictable. */ +.cal-hero { + display: flex; flex-direction: column; align-items: center; + gap: 0; margin-bottom: 12px; +} +.cal-hero-time { + display: inline-flex; align-items: baseline; gap: 8px; + font-family: ui-monospace, SFMono-Regular, "Menlo", "Consolas", monospace; + font-variant-numeric: tabular-nums; + font-size: 56px; + letter-spacing: 0.02em; + font-weight: 200; + color: var(--fg); + line-height: 1; + /* Behaves like a button — strip native chrome but keep it tappable. */ + background: none; + border: none; + /* Pill that reads as a real hit target for a 56-px clock face. + Top padding kept tight so the clock sits close to the "Today is" line. */ + padding: 6px 38px 18px; + margin: 0 0 24px; + border-radius: 18px; + cursor: pointer; + line-height: 1; + transition: background 0.15s, color 0.15s; +} +/* The clock is a `` and a global `button:hover` rule paints + every button's background on hover. Suppress it explicitly. */ +.cal-hero-time:hover { background: transparent; border-color: transparent; } +.cal-hero-clock { font-feature-settings: "tnum"; display: inline-flex; align-items: baseline; } +/* Per-segment hover so it's clear hh vs mm are individually clickable. */ +.cal-hero-clock-hh, .cal-hero-clock-mm { + display: inline-block; + border-radius: 6px; + padding: 0 2px; + transition: background 0.12s; +} +.cal-hero-clock-hh:hover, .cal-hero-clock-mm:hover { + background: color-mix(in srgb, var(--fg) 9%, transparent); +} +.cal-hero-sep { padding: 0 2px; opacity: 0.55; pointer-events: none; } +.cal-hero-ampm { + font-size: 14px; + letter-spacing: 0.12em; + opacity: 0.55; + font-weight: 500; + text-transform: uppercase; +} +.cal-hero-date { + font-size: 16px; + opacity: 0.6; + letter-spacing: 0.04em; + background: none; + border: none; + color: inherit; + font-family: inherit; + padding: 4px 10px; + border-radius: 6px; + cursor: pointer; + margin: 7px 0 0; /* extra nudge below the clock */ + transition: background 0.15s, opacity 0.15s; +} +.cal-hero-date:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); opacity: 0.85; } + +/* Title input — flat, large, no chrome until focused */ +.cal-input.cal-hero-title { + font-size: 18px; + text-align: left; + padding: 10px 12px 10px 0; + background: transparent; + border: 1px solid transparent; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); + border-radius: 0; + margin-bottom: 4px; +} + +/* Details panel — animated reveal via max-height + opacity. */ +.cal-form-bespoke .cal-form-details { + display: block; + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 240ms ease, opacity 180ms ease, margin 220ms ease; + margin-top: 0; +} +/* The detail children themselves keep the existing flex/gap rules from + the regular .cal-form, so we don't restyle inputs/rows here. */ +.cal-form-bespoke .cal-form-details > * + * { margin-top: 8px; } + + +/* Location row: input + Apple Maps pin link. Pin only lights up when the + field has content; clicking opens maps.apple.com (native Maps app on + Apple devices, web fallback elsewhere). All theme vars. */ +.cal-loc-row { + display: flex; + align-items: stretch; + gap: 6px; +} +.cal-loc-input-wrap { + position: relative; + flex: 1; + min-width: 0; +} +.cal-loc-hint { + position: absolute; + inset: 0 auto 0 10px; + display: none; + align-items: center; + gap: 5px; + pointer-events: none; + color: color-mix(in srgb, var(--fg) 42%, transparent); + font-size: 12px; +} +.cal-loc-map { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + flex-shrink: 0; + border: 1px solid var(--border); + border-radius: 5px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + text-decoration: none; + transition: background 0.15s, border-color 0.15s, opacity 0.15s; +} +.cal-loc-map:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); border-color: var(--accent, var(--red)); } + +@media (max-width: 520px) { + .cal-hero-time { font-size: 44px; } + .cal-form-bespoke { margin: 16px 12px; padding: 32px 16px 14px; } + .cal-form-mobile-cancel { + display: inline-flex; + position: absolute; + top: 8px; + right: 8px; + width: 30px; + height: 30px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + border-radius: 999px; + background: color-mix(in srgb, var(--bg) 82%, transparent); + color: var(--fg); + opacity: 0.72; + cursor: pointer; + z-index: 3; + -webkit-tap-highlight-color: transparent; + } + .cal-form-mobile-cancel:active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); + } +} +.cal-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:7px 10px; color:var(--fg); font-size:12px; width:100%; box-sizing:border-box; } +.cal-input:focus { outline:none; border-color:var(--accent); } +.cal-input-time { width:auto; flex:1; } +.cal-form-row { display:flex; gap:8px; align-items:center; } +.cal-form-actions { display:flex; gap:8px; justify-content:flex-end; margin-top:4px; } diff --git a/static/css/style-part-08.css b/static/css/17-research.css similarity index 57% rename from static/css/style-part-08.css rename to static/css/17-research.css index df8418a28..d2fa2b1d1 100644 --- a/static/css/style-part-08.css +++ b/static/css/17-research.css @@ -1,2206 +1,19 @@ -/* Fragment 9/9 of the former static/style.css. +/* 17-research - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -.note-cl-dot { - width: 8px; - height: 8px; - border-radius: 50%; - border: 1.5px solid color-mix(in srgb, var(--fg) 30%, transparent); - flex-shrink: 0; - cursor: pointer; - transition: all 0.15s; -} -.note-cl-dot:hover { border-color: var(--fg); transform: scale(1.2); } -.note-cl-row.done .note-cl-dot { - background: var(--accent); - border-color: var(--accent); -} -.note-cl-row.done .note-cl-text { - opacity: 0.4; - background: linear-gradient(currentColor, currentColor) no-repeat; - background-size: 0 1px; - background-position: 0 calc(50% - 1px); - animation: cl-strike 0.32s ease-out forwards; - transition: opacity 0.2s ease; -} -/* Draws the strikethrough line left-to-right when the row is marked - done, instead of snapping it in full-width on the same frame. */ -@keyframes cl-strike { - to { background-size: 100% 1px; } -} -.note-cl-text { - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - color: var(--fg); - font-size: 12px; - padding: 3px 0 3px 3px; - flex: 1 1 auto; - width: auto; - min-width: 0; - outline: none; -} -.note-cl-text:focus { border-color: var(--fg); } -.note-cl-rm { - background: none; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - font-size: 14px; - padding: 0; - width: 24px; - height: 14px; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: flex-end; - line-height: 1; -} -.note-cl-rm:hover { opacity: 0.8; color: var(--red); } -.note-cl-add { - background: none; - border: none; - color: var(--fg); - opacity: 0.4; - cursor: pointer; - font-size: 11px; - padding: 4px 0; - text-align: left; -} - -/* ── Calendar ── */ -.cal-modal-content { width:min(680px, 94vw); max-height:88vh; overflow-x:hidden; position:relative; } -#calendar-modal .cal-modal-content { animation: none; } -/* Was overflow-y:auto with grid + day-detail flowing naturally. Now uses a - true splitter layout: the body is a flex column that doesn't scroll - itself; the grid takes the remaining space and scrolls if needed; the - day-detail has an explicit height (driven by the splitter drag via - --cal-detail-h) and shrinks the grid visually as it grows. */ -#cal-body { display:flex; flex-direction:column; gap:8px; overflow:hidden; padding:0; min-height:0; } -#cal-body > .cal-grid { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overflow-x: hidden; } -@media (max-width: 768px) { - /* Let the calendar pane shrink to nothing on mobile so the day-detail - splitter can be dragged all the way to the top, hiding the calendar - entirely. Without min-height:0 the grid (or the week-wrap below) - refuses to shrink past its built-in floor. */ - #cal-body > .cal-grid, - #cal-body > .cal-wk-wrap { - min-height: 0; - max-height: none; - } - /* Let the week grid fill the calendar pane and scroll its hours internally - instead of overflowing #cal-body (which clips it and feels cramped). */ - #cal-body > .cal-wk-wrap { - flex: 1 1 auto; - overflow: auto; - } -} -.cal-toolbar { display:flex; align-items:center; gap:6px; margin-bottom:7px; flex-wrap:wrap; line-height:1; max-width:100%; row-gap:6px; } - -/* Quick-add bar: natural-language event creation. Sits directly under - the toolbar; focused with `Q`; opens the bespoke form pre-filled. */ -.cal-quickadd-row { - display: flex; - align-items: center; - gap: 8px; - margin: 0 0 8px; - padding: 1px 10px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - transition: border-color 0.15s, background 0.15s; - position: relative; -} -/* Two-tone placeholder hint: "Quick add" in accent, the example in grey, both - at one low opacity. Overlays the (empty) input; hidden once the user types. */ -.cal-quickadd-hint { - position: absolute; - left: 10px; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: flex; - align-items: center; - gap: 4px; - font-size: 12px; - color: var(--fg); - opacity: 0.5; - pointer-events: none; - white-space: nowrap; - overflow: hidden; -} -.cal-quickadd-hint .qa-hint-accent { color: var(--accent, var(--red)); } -/* ↵ enter glyph in the hint — makes it obvious the field submits on Enter. */ -.cal-quickadd-hint .qa-hint-enter { margin-left: auto; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.8; } -/* Cycling example text fades out before swap, then fades back in. */ -.cal-quickadd-hint .qa-hint-example { - display: block; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-style: italic; - transition: opacity 0.18s ease; -} -.cal-quickadd-hint .qa-hint-example.qa-hint-fade { opacity: 0; } -.cal-title-hint { - position: absolute; - left: 0; - top: 50%; - transform: translateY(calc(-50% - 2px)); - display: none; - align-items: center; - gap: 7px; - font-size: 18px; - line-height: 1.2; - white-space: nowrap; - pointer-events: none; - color: color-mix(in srgb, var(--fg) 42%, transparent); -} -.cal-hero-title:placeholder-shown ~ .cal-title-hint { display: inline-flex; } -.cal-title-hint .cal-title-enter-ico { color: var(--accent, var(--red)); flex-shrink: 0; } -/* Pure-CSS toggle: hide the hint as soon as the field has real text (no - per-keystroke JS, so typing never triggers a re-render / focus loss). */ -.cal-quickadd-input:not(:placeholder-shown) ~ .cal-quickadd-hint { display: none; } -.cal-quickadd-row:focus-within { - border-color: color-mix(in srgb, var(--accent, var(--fg)) 55%, var(--border)); - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); -} -.cal-quickadd-icon { - color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent); - display: inline-flex; - align-items: center; - flex-shrink: 0; - opacity: 0.7; -} -.cal-quickadd-input { - flex: 1; - min-width: 0; - background: transparent; - border: none; - outline: none; - color: var(--fg); - font-family: inherit; - font-size: 12px; - height: 22px; - padding: 0; -} -.cal-quickadd-status { - font-size: 10px; - opacity: 0.55; - font-variant-numeric: tabular-nums; -} -.cal-toolbar > *, .cal-toolbar-nav > *, .cal-toolbar-right > * { vertical-align:middle; } -.cal-toolbar-nav { display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; } -.cal-toolbar-right { - display: inline-flex; - align-items: center; - justify-content: flex-start; - gap: 6px; - flex: 1 1 100%; - width: 100%; - margin-left: 0; - margin-top: 6px; - flex-wrap: wrap; -} -.cal-title { font-size:13px; font-weight:600; white-space:nowrap; height:24px; line-height:30px; padding:0 6px; display:inline-block; vertical-align:middle; box-sizing:border-box; } -.cal-toolbar-clock { - margin-left: 8px; - color: var(--accent, var(--red)); - font-size: 11px; - font-weight: 600; - font-variant-numeric: tabular-nums; -} -button.cal-nav { background:color-mix(in srgb, var(--fg) 6%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 8px; height:24px; line-height:22px; cursor:pointer; font-size:11px; font-family:inherit; box-sizing:border-box; vertical-align:middle; } -button.cal-nav:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); } -button.cal-nav.cal-toolbar-arrow { - display: none; - align-items: center; - justify-content: center; - width: 24px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 1; - opacity: 0.84; -} -.cal-toolbar-arrow-glyph { - position: relative; - top: -1px; - line-height: 1; -} -@media (max-width: 768px) { - button.cal-nav.cal-toolbar-arrow { display: inline-flex !important; } - .cal-toolbar-nav { - flex: 1 1 100%; - width: 100%; - min-width: 0; - } - #cal-next { margin-left: auto; } -} -.cal-week-start-toggle { - display: inline-flex; - align-items: center; - gap: 0; - width: max-content; - padding: 0; - height: 24px; - box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 5px; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.cal-week-start-btn { - box-sizing: border-box; - height: 22px !important; - min-height: 22px !important; - margin-top: 0 !important; - padding: 0 7px; - border: 1px solid transparent; - border-radius: 4px; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - cursor: pointer; - font-family: inherit; - font-size: 10px; - line-height: 20px; - transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; -} -.cal-settings-actions { - display: flex; - justify-content: flex-end; - width: 100%; -} -#cal-settings-panel .cal-week-start-toggle { - display: flex; - margin-left: auto; -} -.cal-settings-sync-status { - position: relative; - top: 2px; - font-size: 11px; - opacity: 0.6; -} -.cal-settings-sync-status.is-error { - color: var(--red); - opacity: 1; -} -.cal-week-start-btn:hover { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.cal-week-start-btn.active, -.cal-week-start-btn.active:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); -} -.cal-side-nav { - position: absolute; - top: var(--cal-side-nav-y, 50%); - transform: translateY(-50%); - width: 32px; - height: 32px; - border-radius: 50%; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - cursor: pointer; - z-index: 6; - opacity: 0; - pointer-events: none; - font-family: inherit; - font-size: 24px; - line-height: 28px; - padding: 0 0 3px; - transition: opacity 0.12s ease, background 0.12s ease, transform 0.12s ease; -} -.cal-modal-content:hover .cal-side-nav, -.cal-side-nav:focus-visible { - opacity: 0.84; - pointer-events: auto; -} -.cal-side-nav:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); - transform: translateY(-50%) scale(1.04); -} -button.cal-today-btn { font-size:10px; opacity:0.5; } -button.cal-add-btn { background:var(--accent); color:#fff; border:none; border-radius:50%; width:24px; height:24px; line-height:22px; font-size:18px; cursor:pointer; flex-shrink:0; padding:0; box-sizing:border-box; font-family:inherit; vertical-align:middle; text-align:center; } -button.cal-add-btn.cal-add-btn-text { - width:auto; min-width:0; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 12px; - padding: 0 10px 0 6px; - display: inline-flex; - align-items: center; - gap: 4px; - height: 24px; - line-height: 1; - margin-top: 0; - transition: background 0.15s, border-color 0.15s; -} -button.cal-add-btn.cal-add-btn-text:hover { - background: color-mix(in srgb, var(--fg) 14%, transparent); - border-color: var(--accent); - opacity: 1; -} -.cal-add-plus { - font-size: 16px; - line-height: 1; - color: var(--accent, var(--red)); - font-weight: 500; - display: inline-block; - transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); - padding-bottom: 2px; -} -button.cal-add-btn.cal-add-btn-text:hover .cal-add-plus { - transform: rotate(180deg); -} -.cal-add-label { font-size:11px; font-weight:600; line-height:1; opacity:0.85; } -/* Small variant of the toolbar's +New button. Inherits the .cal-add-btn-text - styles (pill with "+" + "New" + spring-rotate on the +), so the - animation is identical. Just shrunk a bit. */ -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { - height: 20px; - border-radius: 10px; - /* At rest: tight, equal padding around the "+" so it sits centred - with no trailing gap. On hover the right pad and the flex gap grow - to make room for the revealed "New". */ - padding: 0 5px; - gap: 0; - transition: padding 0.25s ease, gap 0.25s ease, background 0.15s, border-color 0.15s; -} -/* Don't touch .cal-add-plus on the small variant — the toolbar +New is - the reference and we want the same rotation centre / motion. The label - is shrunk and hidden until hover so the pill is compact at rest. */ -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { - font-size: 10px; - max-width: 0; - margin-left: 0; - opacity: 0; - overflow: hidden; - white-space: nowrap; - transition: max-width 0.25s ease, opacity 0.18s ease, margin-left 0.25s ease; -} -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover .cal-add-label { - max-width: 40px; - margin-left: 3px; - opacity: 0.85; -} -.cal-add-btn:hover { opacity:0.85; } - -/* Mobile: bump the +New pill and the +/- zoom buttons up to a tap-friendly - size so they're easy to hit with a thumb. */ -@media (max-width: 768px) { - /* Toolbar +New (relocated by JS into the quickadd row on mobile) */ - button.cal-add-btn.cal-add-btn-text { - height: 36px; - border-radius: 18px; - padding: 0 14px 0 12px; - gap: 6px; - flex-shrink: 0; - } - /* Day-detail +New: just a "+" on mobile — drop the label entirely. */ - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { - width: 28px; - height: 28px; - border-radius: 50%; - padding: 0; - gap: 0; - justify-content: center; - } - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { - display: none; - } - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { - font-size: 18px; - padding-bottom: 0; - } - .cal-add-plus { font-size: 22px; padding-bottom: 1px; } - .cal-add-label { font-size: 13px; } - .cal-wk-zoom { - width: 36px; - height: 36px; - font-size: 20px; - border-radius: 8px; - opacity: 0.85; - } - /* The +New button sits as a sibling of the quickadd row inside a flex - wrapper, so it's clearly outside the input's bordered box. The row - itself keeps its own styling. */ - .cal-quickadd-wrap { - display: flex; - align-items: stretch; - gap: 8px; - margin: 0 0 8px; - } - .cal-quickadd-wrap > #cal-quickadd-row { - flex: 1; - min-width: 0; - margin: 0; - } - .cal-quickadd-wrap > .cal-add-btn-text { - align-self: stretch; - flex-shrink: 0; - } - /* Nudge the "+" glyph up one pixel on the round day-detail button so it - sits visually centred inside the circle. */ - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { - transform: translateY(-1px); - } - /* Event form: bigger Create / Cancel buttons so they're easy to tap. */ - .cal-form-actions button.cal-btn { - height: 44px; - padding: 0 20px; - font-size: 14px; - border-radius: 8px; - min-width: 96px; - } - /* Left-align the day/month + tags row in the day-detail panel and - the calendar/category chip row so they sit flush against the - left edge on a narrow screen. */ - .cal-detail-header { - justify-content: flex-start; - gap: 12px; - } - .cal-filters { - justify-content: flex-start; - } -} - -/* Refresh button spin — driven by JS toggling .cal-syncing on the button. - transform-box + transform-origin keep the rotation pivoted at the SVG's - own centre so it spins in place instead of orbiting / drifting up. */ -#cal-sync svg, -#email-lib-refresh-btn svg { display: block; transform-box: fill-box; transform-origin: 50% 50%; } -/* Brief checkmark flash after a refresh completes. Lasts ~900ms in JS; - the small bounce + accent color give a clear "done" cue. */ -#cal-sync.cal-sync-done svg, -#email-lib-refresh-btn.email-lib-refresh-done svg { - color: var(--accent, #2ea043); - animation: refresh-check-pop 0.32s ease-out; -} -@keyframes refresh-check-pop { - 0% { transform: scale(0.55); opacity: 0.4; } - 60% { transform: scale(1.18); opacity: 1; } - 100% { transform: scale(1); opacity: 1; } -} -#cal-sync.cal-syncing svg, -#email-lib-refresh-btn.email-lib-refreshing svg { animation: spin 0.6s linear infinite; } -.email-undone-toggle.active, -.email-attach-toggle.active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); - color: var(--accent, var(--red)); - font-weight: 600; -} -/* Hide the gallery-style reader-btn labels on desktop — only the mobile - @media block re-displays them under each icon. Desktop keeps compact - icon-only reader buttons. */ -.reader-btn-label { - display: inline-block; - font-size: 9px; - font-weight: 500; - line-height: 1; - letter-spacing: 0.02em; - white-space: nowrap; - opacity: 0.85; -} - -/* Inline attachment-filter toggle nested inside the search input. The - input has padding-right:34px set inline so its text doesn't slide - under the button. */ -.email-search-wrap { display: flex; align-items: center; } -/* Keep the email search input and the adjacent "Select" button identical in - height regardless of base-vs-mobile overrides — pin both explicitly. */ -.email-search-row .memory-search-input { - height: 32px; - min-height: 32px; - box-sizing: border-box; -} -.email-search-row .email-search-select-btn { - height: 32px; - min-height: 32px; - box-sizing: border-box; - margin-top: 6px; - flex-shrink: 0; -} -/* Select moved into the dropdown row — dock it to the right and match the - selects' vertical nudge (.memory-sort-select has top:3px). */ -.memory-category-filters .email-filter-select-btn, -.memory-category-filters .email-filter-refresh-btn { - position: relative; - top: 3px; - flex-shrink: 0; -} -.memory-category-filters .email-filter-refresh-btn { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0 8px; -} -.memory-category-filters .email-reminders-clear-btn { - position: relative; - top: -2px; - flex-shrink: 0; -} -.email-attach-toggle-inline { - position: absolute !important; - right: 4px !important; - top: 50%; - transform: translateY(calc(-50% - 5px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-attach-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-attach-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -/* Inline undone-toggle nested inside the search input, sits LEFT of the - attach toggle. Same visual treatment. */ -.email-undone-toggle-inline { - position: absolute !important; - right: 34px !important; - top: 50%; - transform: translateY(calc(-50% - 3px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-reminder-toggle-inline { - position: absolute !important; - right: 64px !important; - top: 50%; - transform: translateY(calc(-50% - 3px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-reminder-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-reminder-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -.email-undone-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-undone-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -/* Round (circular) toggles in the search bar, matching the app's default icon - buttons. Always fully visible (not dimmed until hover). */ -.email-attach-toggle-inline, -.email-undone-toggle-inline, -.email-reminder-toggle-inline { border-radius: 50% !important; opacity: 1 !important; } -.email-search-options-btn { - position: absolute !important; - right: 5px !important; - top: 50% !important; - transform: translateY(calc(-50% - 5px)); - width: 26px !important; - height: 26px !important; - min-width: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 999px !important; - background: transparent !important; - border: 0 !important; - color: inherit !important; - opacity: 0.7; - z-index: 2; -} -.email-search-options-btn:hover, -.email-search-options-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -.email-search-options-menu { - position: absolute; - top: calc(100% + 2px); - right: 0; - z-index: 75; - min-width: 140px; - padding: 4px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--panel, var(--bg)); - box-shadow: 0 8px 24px rgba(0,0,0,0.3); -} -.email-search-options-title { - padding: 5px 9px 4px; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - opacity: 0.65; - white-space: nowrap; -} -.email-search-options-menu button { - width: 100%; - border: 0; - background: transparent; - color: var(--fg); - display: flex; - align-items: center; - gap: 10px; - height: auto; - min-height: 0; - padding: 6px 8px; - border-radius: 8px; - font-family: inherit; - font-size: 11px; - font-weight: 400; - line-height: normal; - letter-spacing: normal; - text-align: left; - cursor: pointer; -} -.email-search-options-menu button:hover, -.email-search-options-menu button.active { - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - color: var(--accent, var(--red)); -} -.email-search-option-icon { - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - opacity: 0.5; -} -.email-search-option-icon svg { width: 14px; height: 14px; } -.email-search-date-panel { - width: 220px; - padding: 6px 7px 5px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.email-search-date-panel input { - width: 100%; - height: 26px; - box-sizing: border-box; - padding: 2px 6px; - border: 1px solid var(--border); - border-radius: 5px; - outline: none; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.email-search-date-panel input:focus { border-color: var(--accent, var(--red)); } -.email-search-date-actions { display: flex; justify-content: flex-end; gap: 5px; } -.email-search-options-menu .email-search-date-actions [data-email-date-apply] { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -@media (max-width: 768px) { - .email-search-options-menu button { - min-height: 44px; - padding: 12px 12px !important; - font-size: 14px !important; - gap: 10px !important; - } - .email-search-options-menu .email-search-option-icon { - width: 18px; - height: 18px; - } - .email-search-options-menu .email-search-option-icon svg { - width: 16px; - height: 16px; - } -} -/* Mobile: enlarge the icons inside the inline search-bar toggles - (done / attachment / reminders) — buttons themselves stay the same, - only the SVG glyph scales up so it's tappable + visible. */ -@media (max-width: 768px) { - .email-filter-btn { - height: 30px; - min-height: 30px; - font-size: 12px; - top: -6px; - padding: 0 9px; - } - .email-folder-btn { - top: -4px; - } - - #email-lib-folder, - .memory-category-filters .email-filter-select-btn, - .memory-category-filters .email-filter-refresh-btn, - .memory-category-filters .email-filter-settings-btn { - height: 30px !important; - min-height: 30px !important; - box-sizing: border-box; - } - - .memory-category-filters .email-filter-refresh-btn, - .memory-category-filters .email-filter-settings-btn { - width: 30px; - padding: 0 !important; - } - .memory-category-filters .email-filter-settings-btn > svg { - width: 15px; - height: 15px; - } - - .email-search-row .memory-search-input { - height: 38px; - min-height: 38px; - } - - #email-lib-chip-bar { - padding-right: 42px !important; - padding-left: 30px !important; - gap: 5px !important; - } - - .email-lib-chip-bar-icon { - width: 14px !important; - height: 14px !important; - left: 9px !important; - } - - #email-lib-search { - font-size: 14px !important; - min-width: 88px !important; - top: 0 !important; - } - - #email-lib-search::placeholder { - font-size: 14px !important; - } - - #email-lib-chip-bar.has-email-lib-pills #email-lib-search { - min-width: 24px !important; - flex: 1 1 24px !important; - } - - .email-lib-pill { - min-height: 24px !important; - height: 24px !important; - line-height: 24px !important; - font-size: 12px !important; - padding: 0 6px 0 8px !important; - max-width: 180px !important; - } - - .email-lib-pill svg { - width: 14px !important; - height: 14px !important; - } - - .email-lib-pill-x { - font-size: 13px !important; - top: -4.5px !important; - } - - .email-lib-filter-pill { - min-height: 24px !important; - height: 24px !important; - line-height: 24px !important; - gap: 4px !important; - padding: 0 6px 0 8px !important; - } - - .email-lib-filter-pill .email-lib-pill-icon { - width: 17px !important; - height: 17px !important; - } - - .email-lib-filter-pill svg { - width: 18px !important; - height: 18px !important; - } - - .email-lib-filter-pill .email-lib-pill-x { - font-size: 13px !important; - top: -4.5px !important; - } - - .email-tags-toggle-inline { - right: 6px; - height: 28px !important; - min-width: 42px; - padding: 0 8px !important; - font-size: 11px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-attach-toggle-inline { - right: 56px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-undone-toggle-inline { - right: 92px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-reminder-toggle-inline { - right: 128px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-attach-toggle-inline svg, - .email-undone-toggle-inline svg, - .email-reminder-toggle-inline svg, - .email-filter-refresh-btn svg { - width: 16px !important; - height: 16px !important; - } -} -.email-accounts-row { - display: flex; - align-items: center; - gap: 10px; - padding: 0 0 6px; -} -.email-accounts-row .doclib-desc { flex-shrink: 0; } -/* Only the direct-child compose button gets pushed right; nested chips - inside #email-lib-accounts pack to the left as normal flex items. */ -.email-accounts-row > .memory-toolbar-btn { flex-shrink: 0; margin-left: auto; } -.email-account-chip { - display: inline-flex; - align-items: center; - gap: 5px; -} -.email-account-chip.email-account-has-unread { - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - color: color-mix(in srgb, var(--accent, var(--red)) 86%, var(--fg)); -} -.email-account-unread-dot { - width: 6px; - height: 6px; - border-radius: 999px; - background: var(--accent, var(--red)); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - flex: 0 0 auto; -} -.email-account-chip-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} -.email-account-unread-count { - font-size: 9px; - line-height: 1; - font-weight: 700; - color: var(--accent, var(--red)); - opacity: 0.9; - position: relative; - top: 0; -} -.email-account-away-label { - color: var(--accent, var(--red)); - font-size: 9px; - font-weight: 700; - line-height: 1; - flex: 0 0 auto; -} -.email-accounts-loading-whirlpool { - width: 14px; - height: 14px; - margin: 3px 4px 0 1px; - display: inline-flex; - flex: 0 0 auto; -} -.email-accounts-loading-label { - font-size: 10px; - opacity: 0.55; - position: relative; - top: 2px; - white-space: nowrap; -} -.email-loading-with-label { - /* Fill the email list grid so the spinner lands in the middle of the - email window rather than the top of the empty list section. */ - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - text-align: center; - pointer-events: none; -} -.date-section-header { - display: flex; - align-items: center; - gap: 7px; - padding: 8px 9px 4px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 10px; - font-weight: 700; - letter-spacing: 0; - line-height: 1.2; - text-transform: uppercase; - user-select: none; -} -.date-section-header::after { - content: ''; - flex: 1 1 auto; - height: 1px; - background: color-mix(in srgb, var(--border) 72%, transparent); -} -#session-list .date-section-header { - padding-left: 10px; - padding-right: 8px; - cursor: pointer; -} -#session-list .date-section-header.collapsed { - color: color-mix(in srgb, var(--fg) 36%, transparent); -} -#session-list .date-section-header.collapsed::after { - opacity: 0.45; -} -#session-list .date-section-header:focus-visible { - outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); - outline-offset: 2px; - border-radius: 5px; -} -/* Bootstrap row shown while the session list hydrates, and on load failure. - Reads as a normal list row but is not selectable. */ -.session-list-bootstrap { - cursor: default; - pointer-events: none; -} -#email-lib-grid .date-section-header { - padding: 10px 5px 3px; -} -#email-lib-modal .email-lib-sync-status { - flex: 0 0 auto; - min-height: 14px; - padding: 1px 4px 0; - font-size: 10.5px; - line-height: 1.2; - text-align: center; - opacity: 0.58; - color: var(--muted-text, currentColor); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - position: relative; - top: 4px; -} -@media (max-width: 768px) { - #email-lib-modal .email-lib-sync-status { - min-height: 13px; - padding-right: 5px; - font-size: 10px; - } -} -/* Nudge the subject + sender/date meta line 4px right inside each - email card so they don't sit flush against the read/unread/done - markers on the left. Includes #email-lib-modal selector to - override the expanded card's `padding: 4px 0 6px` shorthand - which was zeroing out the title's padding-left. */ -#email-lib-grid .email-card-titlerow, -#email-lib-grid .memory-item-meta, -#email-lib-modal .doclib-card-expanded .email-card-titlerow { - padding-left: 4px; -} -.email-loading-label { - font-size: 11px; - opacity: 0.6; -} - -/* Refresh button now lives top-right in the modal header next to the close X. - Borderless (matches the close X), and a fixed square box so the spin and the - refresh→checkmark icon swap never change its size or nudge neighbours. */ -.email-lib-header-actions #email-lib-refresh-btn { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - border: none; - background: none; - padding: 0; - width: 24px; - height: 24px; -} -/* Bump the icon up on mobile for a bigger visual — but keep the button BOX at - 24px so it never exceeds the header's natural height (the title/close set - that). A taller box would grow the sticky header and push the list down. */ -@media (max-width: 768px) { - .email-lib-header-actions #email-lib-refresh-btn svg { - width: 17px; - height: 17px; - } -} -.cal-view-toggle { - display: inline-flex; - align-items: stretch; - border: 1px solid var(--border); - border-radius: 5px; - overflow: hidden; - vertical-align: middle; - box-sizing: border-box; - padding: 0; - margin: 0; - line-height: 1; -} -.cal-view-btn + .cal-view-btn { border-left: 1px solid var(--border); } -.cal-view-btn:hover { opacity: 0.75; } -.cal-view-btn.active { - background: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--accent, var(--red)); - opacity: 1; - font-weight: 600; -} -/* Toolbar holds only the toggle button; the chip row renders below the - toolbar on its own line and wraps freely. */ -.cal-filters { - display:flex; - gap:6px; - margin:6px 0 8px; - flex-wrap:wrap; - padding:6px 8px; - border:1px solid color-mix(in srgb, var(--fg) 8%, transparent); - border-radius:8px; - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 4%, transparent), transparent), - color-mix(in srgb, var(--fg) 2.5%, var(--bg)); - box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 4%, transparent); -} -/* Round the native color-input swatch in calendar settings. The outer - wraps a colored fill drawn by the browser; we need to round both - the wrapper and the fill so it actually appears circular. */ -.cal-s-color { border-radius: 50%; overflow: hidden; } -.cal-filter-item { display:inline-flex; align-items:center; gap:4px; cursor:pointer; font-size:10px; padding:2px 8px; line-height:1.4; border-radius:10px; background:color-mix(in srgb, var(--bg) 72%, transparent); border:1px solid color-mix(in srgb, var(--fg) 12%, transparent); transition:opacity 0.15s, background 0.15s, border-color 0.15s, transform 0.15s; } -.cal-filter-item:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); } -.cal-filter-item.cal-filter-important { font-weight:700; color:#e5a33a; border-color:color-mix(in srgb, #e5a33a 40%, transparent); } -/* Match the toolbar's other buttons (cal-nav, cal-view-btn, cal-add-btn-text): - 24px tall, 11px font, 5px corners. */ -.cal-filter-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font-size: 11px; - font-family: inherit; - font-weight: 500; - padding: 0 10px; - height: 24px; - line-height: 1; - border-radius: 5px; - box-sizing: border-box; - vertical-align: middle; - opacity: 0.65; - position: relative; - top: -3px; -} -.cal-filter-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } -.cal-filter-toggle-glyph { - display: inline-block; - position: relative; - top: 0; - margin-right: 3px; - color: var(--accent, var(--red)); -} -.cal-filter-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; } -/* The grid is now a flex column: one row of weekday headers + six - .cal-week-row rows. Each row holds the 7 day cells in its own - internal grid plus the absolute-positioned multi-day overlays. - --bars on a week-row is the number of multi-day events overlapping - it; cells in that row use it to reserve top padding so the bar - overlays don't cover the day numbers or single-event rows. */ -.cal-grid { display:flex; flex-direction:column; gap:1px; background:color-mix(in srgb, var(--fg) 8%, transparent); border-radius:6px; overflow:hidden; } -.cal-week-headers { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } -.cal-week-row { position:relative; display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } -.cal-weekday { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); text-align:center; font-size:10px; font-weight:600; opacity:0.4; padding:5px 0; } -.cal-day { - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 34px), - var(--bg); - min-height:78px; - padding:3px; - cursor:pointer; - position:relative; - transition:background 0.12s, box-shadow 0.12s; - overflow:hidden; -} -.cal-day:hover { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); } -.cal-day.cal-today { - box-shadow: inset 0 0 0 2px var(--accent, var(--red)); - background: var(--bg); - border-radius: 8px; -} -.cal-day.cal-selected:not(.cal-today) .cal-day-num { - color: var(--accent, var(--fg)); - opacity: 1; - font-weight: 700; -} -.cal-day.cal-drag-over { background:color-mix(in srgb, var(--accent) 18%, var(--bg)); } -.cal-day.cal-range-select { background:color-mix(in srgb, var(--accent, var(--red)) 25%, var(--bg)); box-shadow:inset 0 0 0 1px var(--accent, var(--red)); } -.cal-day-num { font-size:10px; font-weight:600; display:block; margin-bottom:1px; opacity:0.7; line-height:1.2; } -.cal-dots { display:flex; flex-direction:row; align-items:center; gap:2px; flex-wrap:nowrap; margin-bottom:1px; height:6px; } -.cal-dot { width:5px; height:5px; border-radius:50%; display:inline-block; cursor:grab; flex-shrink:0; } -.cal-dot-more { font-size:7px; opacity:0.4; } -.cal-event-row { - display:flex; - align-items:center; - gap:4px; - padding:2px 4px; - border-radius:4px; - cursor:grab; - line-height:1.2; - margin-bottom:2px; - min-height:17px; - background:color-mix(in srgb, var(--fg) 5%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); - animation:cal-event-enter 0.16s ease-out both; - transition:background 0.15s, box-shadow 0.15s, transform 0.15s; -} -.cal-event-row:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); transform:translateY(-1px); } -.cal-event-row-dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } -.cal-event-row-time { font-size:10px; opacity:0.72; font-variant-numeric:tabular-nums; flex-shrink:0; font-weight:600; } -.cal-event-row-name { font-size:10px; opacity:0.96; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; font-weight:600; } -.cal-event-reminder { - display: inline-flex; - width: 12px; - height: 12px; - margin-left: 5px; - vertical-align: -2px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - opacity: 0.9; - flex: 0 0 auto; -} -.cal-event-reminder svg { - width: 100%; - height: 100%; - display: block; -} -.cal-event-row .cal-event-reminder { - width: 10px; - height: 10px; - margin-left: 4px; -} -.cal-event-source { - display:inline-flex; - align-items:center; - justify-content:center; - width:12px; - height:12px; - margin-left:5px; - border-radius:50%; - font-size:7px; - font-weight:800; - line-height:1; - color:var(--cal-event-fg, var(--fg)); - background:color-mix(in srgb, currentColor 16%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); - vertical-align:1px; -} -.cal-event-source svg { - width: 10px; - height: 10px; - display: block; -} -.cal-event-row .cal-event-source { - width:11px; - height:11px; - font-size:7px; - margin-left:4px; - color:color-mix(in srgb, var(--fg) 72%, transparent); -} -.cal-multiday .cal-event-reminder, -.cal-wk-allday-event .cal-event-reminder { - color: var(--cal-event-fg, currentColor); - opacity: 0.88; -} -.cal-multiday .cal-event-source, -.cal-wk-allday-event .cal-event-source { - color: var(--cal-event-fg, currentColor); -} -.cal-event-more { font-size:9px; opacity:0.55; padding-left:6px; } -/* Multi-day bar — positioned as an absolute overlay inside its - .cal-week-row so it spans uninterrupted across every column it - covers. --col is the starting column (0–6), --span is the number - of days it covers within the row, --slot stacks parallel bars. */ -.cal-multiday { - position: absolute; - /* Fractional offsets let timed events that cross midnight render at - true proportional width. start-frac shifts the left edge into the - first day; end-frac trims the right edge inside the last day. - All-day events default to (0, 1) and still fill whole columns. */ - left: calc((var(--col, 0) + var(--start-frac, 0)) * (100% / 7)); - width: calc( - (var(--span, 1) - var(--start-frac, 0) - (1 - var(--end-frac, 1))) - * (100% / 7) - ); - top: calc(2px + var(--slot, 0) * 12px); - height: 12px; - font-size: 9px; - line-height: 12px; - padding: 0 5px; - border-radius: 4px; - color: var(--cal-event-fg, #fff); - font-weight: 700; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: grab; - opacity: 0.98; - z-index: 2; - pointer-events: auto; - box-shadow: 0 1px 2px color-mix(in srgb, #000 18%, transparent); -} -/* Splitter between the month/week grid and the day-tasks panel — drag - vertically to give the day panel more room. Height clamped by the - `--cal-detail-h` variable set on #cal-body by the splitter drag JS. */ -.cal-splitter { - height: 8px; - margin: 6px -10px 0; - cursor: row-resize; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - user-select: none; - touch-action: none; -} -/* Mobile: fat hitbox so the splitter is easy to grab with a thumb, - without changing how the grip itself looks. */ -@media (max-width: 768px) { - .cal-splitter { - height: 24px; - margin-top: 0; - margin-bottom: 0; - } - .cal-splitter-grip { - width: 56px; - height: 4px; - } -} -.cal-splitter-grip { - width: 42px; - height: 3px; - border-radius: 2px; - background: color-mix(in srgb, var(--fg) 25%, transparent); - transition: background 0.12s; -} -.cal-splitter:hover .cal-splitter-grip, -.cal-splitter-dragging .cal-splitter-grip { - background: var(--accent, var(--red)); -} -.cal-day-detail { - margin-top: 4px; - border-top: 1px solid var(--border); - padding-top: 8px; - height: var(--cal-detail-h, 240px); - overflow-y: auto; - overscroll-behavior: contain; - flex-shrink: 0; -} -.cal-detail-header { display:flex; justify-content:space-between; align-items:center; font-size:12px; font-weight:600; margin-bottom:6px; padding-right:8px; } -.cal-empty { font-size:11px; opacity:0.3; padding:4px 0; } -.cal-event-item { display:flex; gap:9px; padding:8px 9px; border-radius:6px; cursor:pointer; align-items:flex-start; background:color-mix(in srgb, var(--fg) 3.5%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); margin-bottom:4px; } -.cal-event-item:hover { background:color-mix(in srgb, var(--fg) 8%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); } -.cal-event-dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:4px; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } -.cal-event-info { flex:1; min-width:0; } -.cal-event-name { font-size:13px; font-weight:700; line-height:1.25; color:color-mix(in srgb, var(--fg) 96%, transparent); overflow-wrap:anywhere; } -.cal-event-tag { - display: inline-block; - font-size: 10px; - font-weight: 500; - padding: 1px 6px; - border-radius: 8px; - border: 1px solid; - margin-left: 6px; - vertical-align: 1px; - opacity: 0.85; - white-space: nowrap; -} -.cal-event-time { font-size:11px; opacity:0.68; line-height:1.35; margin-top:2px; font-weight:500; } -.cal-event-loc { font-size:11px; opacity:0.58; line-height:1.35; margin-top:1px; } -/* ── Week view: hour grid ────────────────────────────────────────── - Vertical hour rail on the left, 7 day columns on the right with their - own all-day strip and an absolute-positioned hour grid below it. */ -.cal-wk-wrap { - display: flex; - flex-direction: row; - gap: 0; - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 3%, transparent), transparent 120px), - var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - /* The wrap itself scrolls so the hour rail and day columns move - together; column headers and the rail spacer are sticky-pinned. */ - overflow: auto; - max-height: calc(100vh - 220px); - min-height: 360px; - position: relative; -} -.cal-wk-now-below-hint { - position: absolute; - height: 2px; - border-radius: 2px 2px 0 0; - background: var(--accent, var(--red)); - pointer-events: none; - z-index: 38; -} -@media (max-width: 768px) { - .cal-wk-wrap { - -webkit-overflow-scrolling: touch; - overscroll-behavior-y: contain; - } - .cal-wk-wrap > .cal-wk-rail, - .cal-wk-wrap > .cal-wk-cols { - transform: translateY(var(--wk-edge-pull, 0)); - } - .cal-wk-wrap.cal-wk-edge-release > .cal-wk-rail, - .cal-wk-wrap.cal-wk-edge-release > .cal-wk-cols { - transition: transform 0.14s cubic-bezier(0.2, 0.75, 0.25, 1); - } - .cal-wk-wrap.cal-wk-pull-top { - border-top-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); - } - .cal-wk-wrap.cal-wk-pull-bottom { - border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); - } -} -.cal-wk-rail { - flex: 0 0 56px; - border-right: 1px solid var(--border); - display: flex; - flex-direction: column; - background: color-mix(in srgb, var(--fg) 2%, var(--bg)); - position: sticky; - left: 0; - z-index: 5; -} -.cal-wk-rail-spacer { - /* Match the 32px day header. The 24px all-day row gets its own spacer - below, so the zoom controls sit high without shifting the hour grid. */ - height: 32px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 0; - background: color-mix(in srgb, var(--fg) 2%, var(--bg)); - z-index: 9; - display: flex; - align-items: center; - justify-content: center; - gap: 2px; - padding: 0 4px; -} -.cal-wk-rail-allday-spacer { - height: 24px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 32px; - z-index: 8; - background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); -} -.cal-wk-zoom { - position: relative; - top: -3px; - width: 22px; - height: 22px; - border: 1px solid var(--border); - background: var(--bg); - color: var(--fg); - border-radius: 50%; - font-family: inherit; - font-size: 14px; - font-weight: 600; - line-height: 1; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - opacity: 0.55; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.cal-wk-zoom:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--fg)) 50%, var(--border)); -} -.cal-wk-rail-cell { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 55%, transparent); - padding: 4px 8px 0; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); - flex-shrink: 0; - display: flex; - align-items: flex-start; - justify-content: flex-end; - font-variant-numeric: tabular-nums; -} -.cal-wk-cols { - flex: 1; - display: grid; - grid-template-columns: repeat(7, 1fr); - position: relative; -} -.cal-wk-col { - border-left: 1px solid var(--border); - display: flex; - flex-direction: column; - min-width: 0; -} -.cal-wk-col:first-child { border-left: none; } -.cal-wk-col-head { - height: 32px; - padding: 0 6px; - display: flex; - align-items: center; - gap: 6px; - font-size: 11px; - flex-shrink: 0; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); - position: sticky; - top: 0; - z-index: 40; -} -/* Today on a Sunday still wins the accent treatment. */ -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); } -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dn, -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dt { color: var(--accent, var(--fg)); } -.cal-wk-dn { font-weight: 600; opacity: 0.6; } -.cal-wk-allday { - height: 24px; - padding: 2px 4px; - display: flex; - flex-direction: row; - gap: 2px; - align-items: center; - background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); - border-bottom: 1px solid var(--border); - flex-shrink: 0; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: none; - position: sticky; - top: 32px; - z-index: 39; -} -.cal-wk-allday-event { - font-size: 10.5px; - font-weight: 700; - padding: 3px 6px; - border-radius: 4px; - color: var(--cal-event-fg, var(--fg)); - cursor: pointer; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Hour grid body */ -.cal-wk-grid { - position: relative; - flex: 0 0 auto; - cursor: cell; - user-select: none; - background: - repeating-linear-gradient( - to bottom, - transparent 0, - transparent calc(var(--wk-hour-px, 52px) / 2 - 1px), - color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--wk-hour-px, 52px) / 2), - transparent calc(var(--wk-hour-px, 52px) / 2 + 1px) - ); -} -.cal-wk-cell { - border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); - box-sizing: border-box; -} -.cal-wk-cell:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); } -/* "Now" line */ -.cal-wk-now { - position: absolute; - left: 0; right: 0; - height: 0; - border-top: 2px solid color-mix(in srgb, var(--accent, var(--red, #f87171)) 90%, transparent); - pointer-events: none; - z-index: 7; - filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent)); -} -/* Event blocks */ -.cal-wk-block { - position: absolute; - left: calc((var(--lane, 0) * (100% / var(--lane-count, 1))) + 2px); - width: calc((100% / var(--lane-count, 1)) - 4px); - right:auto; - border-left: 4px solid var(--fg); - border-radius: 5px; - padding: 5px 7px 7px; - font-size: 12px; - line-height: 1.3; - cursor: pointer; - /* `clip` preserves the rounded-card boundary without creating an overflow - container, allowing the label inside to stick to the week scroller. */ - overflow: clip; - z-index: 2; - box-shadow: 0 2px 6px color-mix(in srgb, #000 14%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); - animation:cal-event-enter 0.18s ease-out both; - transition: width 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), height 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), filter 0.12s, transform 0.12s, box-shadow 0.16s; - box-sizing: border-box; -} -.cal-wk-block:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); } -.cal-wk-block-label { - position: sticky; - top: 37px; - z-index: 1; - min-width: 0; - pointer-events: none; -} -.cal-wk-has-allday .cal-wk-block-label { - top: 61px; -} -.cal-wk-block-name { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color:color-mix(in srgb, var(--fg) 96%, transparent); } -.cal-wk-block-time { font-size: 10.5px; opacity: 0.82; font-variant-numeric: tabular-nums; margin-top: 2px; font-weight:600; } -@media (hover: hover) and (pointer: fine) { - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left { - left: auto; - right: calc(100% - var(--lane-right) + 2px); - } - .cal-wk-block.cal-wk-block-crowded:hover { - width: var(--hover-width, 170px); - height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; - overflow: clip; - z-index: 30; - filter: brightness(1.08); - transform: translateY(var(--hover-shift-y, -2px)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-hover-width-only:hover { - height: var(--event-height) !important; - transform: translateY(-1px); - } - .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-label { - position: absolute; - top: 5px; - width: calc(var(--hover-width, 170px) - 18px); - animation: cal-wk-hover-label-reveal 0.2s ease-out both; - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left:hover .cal-wk-block-label { - left: auto; - right: 7px; - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-right:hover .cal-wk-block-label { - left: 7px; - right: auto; - } - .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-name { - display: block; - white-space: normal; - overflow: visible; - text-overflow: clip; - overflow-wrap: anywhere; - } -} -@media (max-width: 768px) { - /* Mobile week view: the hour rail already shows the time, so drop it from the - card and let the title wrap to fill the freed space (more readable text). */ - .cal-wk-block-time { display: none; } - .cal-wk-block-name { - white-space: normal; - line-height: 1.15; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left { - left: auto; - right: calc(100% - var(--lane-right) + 2px); - } - .cal-wk-block.cal-wk-mobile-expanded { - width: var(--hover-width, 170px); - height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; - overflow: clip; - z-index: 30; - filter: brightness(1.08); - transform: translateY(var(--hover-shift-y, 0)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); - } - .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-label { - position: absolute; - top: 5px; - width: calc(var(--hover-width, 170px) - 18px); - animation: cal-wk-hover-label-reveal 0.2s ease-out both; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left .cal-wk-block-label { - left: auto; - right: 7px; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-right .cal-wk-block-label { - left: 7px; - right: auto; - } - .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-name { - display: block; - white-space: normal; - overflow: visible; - text-overflow: clip; - overflow-wrap: anywhere; - -webkit-line-clamp: unset; - } -} -/* Resize handle: a 6 px hit zone along the block's bottom edge. The - visible 2-px stripe only shows on hover so blocks at rest stay clean. */ -.cal-wk-block-resize { - position: absolute; - left: 4px; - right: 4px; - bottom: 0; - height: 6px; - cursor: ns-resize; - z-index: 3; -} -/* Drag-to-create ghost */ -.cal-wk-ghost { - position: absolute; - left: 2px; - right: 2px; - background: color-mix(in srgb, var(--accent, var(--fg)) 28%, transparent); - border: 1px dashed color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent); - border-radius: 4px; - pointer-events: none; - z-index: 3; - font-size: 10px; - font-weight: 600; - display: flex; - align-items: center; - justify-content: center; - color: var(--accent, var(--fg)); - font-variant-numeric: tabular-nums; -} -.cal-wk-slot-selected { - background: color-mix(in srgb, var(--accent, var(--fg)) 20%, var(--bg)); - border-style: solid; - border-color: var(--accent, var(--fg)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--fg)) 20%, transparent); - overflow: visible; - pointer-events: auto; - cursor: pointer; -} -.cal-wk-slot-resize { - position: absolute; - left: 14%; - right: 14%; - bottom: -7px; - height: 14px; - z-index: 4; - cursor: ns-resize; - touch-action: none; - pointer-events: auto; -} -.cal-year { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; } -.cal-year-month { background:color-mix(in srgb, var(--fg) 3%, var(--bg)); border:1px solid var(--border); border-radius:6px; padding:6px; cursor:pointer; transition:background 0.15s, border-color 0.15s, transform 0.15s; } -.cal-year-month:hover { background:color-mix(in srgb, var(--fg) 8%, var(--bg)); border-color:var(--accent); transform:translateY(-1px); } -.cal-year-month-title { font-size:11px; font-weight:600; text-align:center; margin-bottom:4px; opacity:0.7; } -.cal-year-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; } -.cal-year-wd { font-size:7px; text-align:center; opacity:0.3; padding:1px 0; } -.cal-year-cell { font-size:8px; text-align:center; padding:2px 0; min-height:14px; border-radius:2px; transition:transform 0.12s, background 0.12s; } -.cal-year-day:hover { background:var(--accent, var(--red)); color:#fff; opacity:1; transform:scale(1.15); font-weight:700; z-index:2; position:relative; } -.cal-year-today { color:var(--accent, var(--red)); font-weight:700; opacity:1; } -.cal-year-has { opacity:1; background:color-mix(in srgb, var(--accent) 15%, transparent); } -.cal-year-has.cal-year-today { background:color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } -.cal-loading { display:flex; justify-content:center; padding:40px 0; } -.cal-badge { background:var(--accent); border-radius:50%; width:6px; height:6px; margin-left:auto; display:inline-block; flex-shrink:0; } -/* Search input — in-panel variant lives inside the day-detail panel and - spans its width; no width animation since growing/shrinking on every - keystroke would jitter beside the events list. */ -.cal-search-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:0 8px; height:24px; color:var(--fg); font-size:11px; font-family:inherit; width:120px; box-sizing:border-box; transition:width 0.15s; } -.cal-search-input:focus { outline:none; border-color:var(--accent); width:180px; } -.cal-search-input.cal-day-search { width:100%; margin-bottom:0; transition:none; padding-left: 28px; } -/* Wrap for the search input so the magnifying-glass icon can be absolute- - positioned inside the field. */ -.cal-search-wrap { - position: relative; - display: block; - margin-bottom: 6px; -} -.cal-search-wrap .cal-search-icon { - position: absolute; - left: 8px; - top: 50%; - transform: translateY(-50%); - pointer-events: none; - color: var(--accent, var(--red)); -} -.cal-day-search-meta { font-size:10px; opacity:0.5; margin:0 2px 6px; } -.cal-search-results { display:flex; flex-direction:column; gap:4px; } -.cal-search-count { font-size:10px; opacity:0.5; padding:4px 2px 8px; } -/* Agenda view */ -.cal-agenda { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-left:4px; } -.cal-agenda-day { display:flex; flex-direction:column; gap:5px; position:relative; padding-left:14px; } -.cal-agenda-date { font-size:12px; font-weight:800; opacity:0.72; padding:5px 2px 3px; border-bottom:1px solid var(--border); } -.cal-agenda-today-badge { - display: inline-block; - margin-left: 6px; - font-size: 9px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - vertical-align: 1px; -} -.cal-agenda-empty { font-size:11px; opacity:0.4; padding:8px; font-style:italic; } -.cal-agenda-event { - display:flex; - gap:9px; - padding:9px 10px; - border-radius:6px; - cursor:pointer; - align-items:center; - position:relative; - background:color-mix(in srgb, var(--fg) 3.5%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); - animation:cal-event-enter 0.18s ease-out both; - transition:background 0.15s, box-shadow 0.15s, transform 0.15s; -} -.cal-agenda-event:hover { - background:color-mix(in srgb, var(--fg) 8%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); - transform:translateX(2px); -} -.cal-agenda-event .cal-event-more, -.cal-event-item .cal-event-more { opacity:0; transition:opacity 0.15s; } -.cal-event-item { position:relative; } - -/* ── Personal Assistant ── */ -.sidebar-assistant-entry { gap: 6px; min-height: 29px; box-sizing: border-box; align-items: center; } -.sidebar-assistant-entry .sidebar-action-icon { position: relative; left: -2px; } -#sidebar-assistant-gear:hover { opacity: 0.8 !important; } -.assistant-settings-form { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; } -.assistant-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: color-mix(in srgb, var(--fg) 70%, transparent); } -.assistant-field > span { font-size: 11px; opacity: 0.6; } -.assistant-field input[type="text"], -.assistant-field input[type="time"], -.assistant-field select, -.assistant-field textarea { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 5px; - padding: 7px 10px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - box-sizing: border-box; -} -.assistant-field textarea { resize: vertical; min-height: 60px; } -.assistant-field input:focus, -.assistant-field select:focus, -.assistant-field textarea:focus { outline: none; border-color: var(--accent, var(--red)); } -.assistant-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } -.assistant-field-row > .assistant-field { flex: 1 1 180px; } -.assistant-field-check { flex-direction: row !important; align-items: center; gap: 6px; padding-bottom: 7px; } -.assistant-checkins { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); } -.assistant-checkins h5 { margin: 0 0 2px; font-size: 12px; font-weight: 600; opacity: 0.8; } -.assistant-checkin-row { - display: flex; - flex-direction: column; - gap: 4px; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.assistant-checkin-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } -.assistant-checkin-head input[type="text"], -.assistant-checkin-head input[type="time"] { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 4px; - padding: 4px 8px; - color: var(--fg); - font-size: 12px; - font-family: inherit; -} -.assistant-checkin-head input[type="text"] { flex: 1; min-width: 120px; } -.assistant-checkin-head input[type="time"] { width: 90px; } -.assistant-checkin-run { - background: none; - border: 1px solid var(--border); - color: var(--fg); - font-family: inherit; - font-size: 11px; - padding: 4px 8px; - border-radius: 4px; - cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s; -} -.assistant-checkin-run:hover { opacity: 1; } -.assistant-checkin-prompt { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 4px; - padding: 6px 8px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - resize: vertical; - min-height: 48px; -} -.assistant-checkin-prompt:focus { outline: none; border-color: var(--accent, var(--red)); } -.assistant-checkin-meta { font-size: 10px; opacity: 0.45; } -.assistant-settings-actions { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 8px; - padding-top: 8px; - border-top: 1px solid var(--border); -} -.assistant-tools-grid { - display: flex; - flex-direction: column; - gap: 6px; - max-height: 240px; - overflow-y: auto; - padding: 6px; - border: 1px solid var(--border); - border-radius: 6px; - margin-top: 4px; -} -.assistant-tool-group { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.assistant-tool-group-label { - font-size: 10px; - font-weight: 600; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.03em; - width: 100%; - margin-bottom: 1px; -} -.assistant-tool-item { - display: inline-flex; - align-items: center; - gap: 3px; - font-size: 11px; - padding: 2px 6px; - border: 1px solid var(--border); - border-radius: 4px; - cursor: pointer; - transition: background 0.1s, border-color 0.1s; - user-select: none; -} -.assistant-tool-item:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.assistant-tool-item:has(input:checked) { - border-color: var(--accent); - background: color-mix(in srgb, var(--accent) 10%, transparent); -} -.assistant-tool-item input[type="checkbox"] { - width: 12px; - height: 12px; - margin: 0; -} - -/* Empty state */ -.cal-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px 20px; text-align:center; gap:10px; } -.cal-empty-title { font-size:14px; font-weight:600; opacity:0.8; } -.cal-empty-msg { font-size:12px; opacity:0.5; max-width:320px; line-height:1.5; margin-bottom:8px; } -.cal-allday-switch { margin:0; flex-shrink:0; } -/* Stack the "All day" label above its toggle so it doesn't get squeezed / - truncated next to the date inputs in the row. */ -.cal-allday-ctrl { display:flex; flex-direction:column; align-items:center; gap:3px; flex-shrink:0; } -.cal-allday-label { font-size:10px; opacity:0.5; white-space:nowrap; line-height:1; } -.cal-form { display:flex; flex-direction:column; gap:8px; } -.cal-form-title { font-size:13px; font-weight:600; margin-bottom:2px; } -.cal-title-wrap { position: relative; } -/* Calendar-picker select sits 2px higher; its border-left/background tint is - set in JS to the chosen calendar's colour. */ -.cal-f-cal-select { position: relative; top: -4px; } - -/* ── Bespoke event form ──────────────────────────────────────────── - Big clock-face date/time hero with the title under it. Details panel - stays collapsed until the title gets focus or "Add details" is clicked. -*/ -.cal-form-bespoke { - position: relative; - width: min(720px, calc(100% - 24px)); - max-width: none; - margin: 12px auto; - padding: 18px 28px 14px; - box-sizing: border-box; - background: var(--bg); - /* Optional per-event tint set by JS via --ev-color. When unset, falls - back to the theme's neutral border so the card looks like normal. */ - border: 1px solid var(--border); - border-radius: 14px; - box-shadow: 0 8px 24px color-mix(in srgb, var(--fg) 6%, transparent); - transition: border-color 0.18s, box-shadow 0.18s; -} -/* When a colour is picked, lift the card with a soft tinted border + glow - so the choice is unmistakable. */ -.cal-form-bespoke[style*="--ev-color"] { - border-color: color-mix(in srgb, var(--ev-color) 55%, var(--border)); - box-shadow: 0 8px 28px color-mix(in srgb, var(--ev-color) 22%, transparent); -} -.cal-form-bespoke[style*="--ev-color"] .cal-btn-primary { - background: var(--ev-color); - border-color: var(--ev-color); -} -.cal-form-bespoke.cal-form-bg-image .cal-btn-primary { - background: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); - color: var(--bg); -} -.cal-form-close { - position: absolute; top: 10px; right: 12px; - background: none; border: none; color: var(--fg); - opacity: 0.35; cursor: pointer; font-size: 20px; line-height: 1; - padding: 4px 8px; border-radius: 6px; -} -.cal-form-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cal-form-mobile-cancel { display: none; } - -/* "Today is …" header pinned at the top of the form. Keeps you oriented - even when the event date you're picking is way off in the future. */ -.cal-form-today { - text-align: center; - font-size: 11px; - letter-spacing: 0.08em; - text-transform: uppercase; - opacity: 0.45; - margin-bottom: 0; -} - -/* Hero — clock face + date label. Spacing between the two lives entirely - on the clock's bottom margin so it's predictable. */ -.cal-hero { - display: flex; flex-direction: column; align-items: center; - gap: 0; margin-bottom: 12px; -} -.cal-hero-time { - display: inline-flex; align-items: baseline; gap: 8px; - font-family: ui-monospace, SFMono-Regular, "Menlo", "Consolas", monospace; - font-variant-numeric: tabular-nums; - font-size: 56px; - letter-spacing: 0.02em; - font-weight: 200; - color: var(--fg); - line-height: 1; - /* Behaves like a button — strip native chrome but keep it tappable. */ - background: none; - border: none; - /* Pill that reads as a real hit target for a 56-px clock face. - Top padding kept tight so the clock sits close to the "Today is" line. */ - padding: 6px 38px 18px; - margin: 0 0 24px; - border-radius: 18px; - cursor: pointer; - line-height: 1; - transition: background 0.15s, color 0.15s; -} -/* The clock is a `` and a global `button:hover` rule paints - every button's background on hover. Suppress it explicitly. */ -.cal-hero-time:hover { background: transparent; border-color: transparent; } -.cal-hero-clock { font-feature-settings: "tnum"; display: inline-flex; align-items: baseline; } -/* Per-segment hover so it's clear hh vs mm are individually clickable. */ -.cal-hero-clock-hh, .cal-hero-clock-mm { - display: inline-block; - border-radius: 6px; - padding: 0 2px; - transition: background 0.12s; -} -.cal-hero-clock-hh:hover, .cal-hero-clock-mm:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); -} -.cal-hero-sep { padding: 0 2px; opacity: 0.55; pointer-events: none; } -.cal-hero-ampm { - font-size: 14px; - letter-spacing: 0.12em; - opacity: 0.55; - font-weight: 500; - text-transform: uppercase; -} -.cal-hero-date { - font-size: 16px; - opacity: 0.6; - letter-spacing: 0.04em; - background: none; - border: none; - color: inherit; - font-family: inherit; - padding: 4px 10px; - border-radius: 6px; - cursor: pointer; - margin: 7px 0 0; /* extra nudge below the clock */ - transition: background 0.15s, opacity 0.15s; -} -.cal-hero-date:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); opacity: 0.85; } - -/* Title input — flat, large, no chrome until focused */ -.cal-input.cal-hero-title { - font-size: 18px; - text-align: left; - padding: 10px 12px 10px 0; - background: transparent; - border: 1px solid transparent; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); - border-radius: 0; - margin-bottom: 4px; -} - -/* Details panel — animated reveal via max-height + opacity. */ -.cal-form-bespoke .cal-form-details { - display: block; - max-height: 0; - opacity: 0; - overflow: hidden; - transition: max-height 240ms ease, opacity 180ms ease, margin 220ms ease; - margin-top: 0; -} -/* The detail children themselves keep the existing flex/gap rules from - the regular .cal-form, so we don't restyle inputs/rows here. */ -.cal-form-bespoke .cal-form-details > * + * { margin-top: 8px; } - - -/* Location row: input + Apple Maps pin link. Pin only lights up when the - field has content; clicking opens maps.apple.com (native Maps app on - Apple devices, web fallback elsewhere). All theme vars. */ -.cal-loc-row { - display: flex; - align-items: stretch; - gap: 6px; -} -.cal-loc-input-wrap { - position: relative; - flex: 1; - min-width: 0; -} -.cal-loc-hint { - position: absolute; - inset: 0 auto 0 10px; - display: none; - align-items: center; - gap: 5px; - pointer-events: none; - color: color-mix(in srgb, var(--fg) 42%, transparent); - font-size: 12px; -} -.cal-loc-map { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - flex-shrink: 0; - border: 1px solid var(--border); - border-radius: 5px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - text-decoration: none; - transition: background 0.15s, border-color 0.15s, opacity 0.15s; -} -.cal-loc-map:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); border-color: var(--accent, var(--red)); } - -@media (max-width: 520px) { - .cal-hero-time { font-size: 44px; } - .cal-form-bespoke { margin: 16px 12px; padding: 32px 16px 14px; } - .cal-form-mobile-cancel { - display: inline-flex; - position: absolute; - top: 8px; - right: 8px; - width: 30px; - height: 30px; - align-items: center; - justify-content: center; - padding: 0; - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--bg) 82%, transparent); - color: var(--fg); - opacity: 0.72; - cursor: pointer; - z-index: 3; - -webkit-tap-highlight-color: transparent; - } - .cal-form-mobile-cancel:active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); - } -} -.cal-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:7px 10px; color:var(--fg); font-size:12px; width:100%; box-sizing:border-box; } -.cal-input:focus { outline:none; border-color:var(--accent); } -.cal-input-time { width:auto; flex:1; } -.cal-form-row { display:flex; gap:8px; align-items:center; } -.cal-form-actions { display:flex; gap:8px; justify-content:flex-end; margin-top:4px; } button.cal-btn { background:color-mix(in srgb, var(--fg) 8%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 14px; height:28px; font-size:11px; font-weight:500; cursor:pointer; font-family:inherit; display:inline-flex; align-items:center; justify-content:center; box-sizing:border-box; transition:background 0.15s, border-color 0.15s; } button.cal-btn:hover { background:color-mix(in srgb, var(--fg) 14%, transparent); } button.cal-btn.cal-btn-primary { background:var(--accent, var(--red)); color:var(--bg); border-color:var(--accent, var(--red)); } diff --git a/static/css/style-part-02.css b/static/css/style-part-02.css deleted file mode 100644 index a3ceff612..000000000 --- a/static/css/style-part-02.css +++ /dev/null @@ -1,6011 +0,0 @@ -/* Fragment 3/9 of the former static/style.css. - * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. - * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. - */ - .loading-dot:nth-child(1) { - animation: loading-bounce 1.4s infinite ease-in-out both; - } - .loading-dot:nth-child(2) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.32s; - } - .loading-dot:nth-child(3) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.64s; - } - .session-loading-state { - min-height: 100%; - display: flex; - flex-direction: column; - justify-content: flex-start; - gap: 12px; - color: var(--fg); - font-size: 12px; - pointer-events: none; - position: relative; - padding: 16px 14px 24px; - box-sizing: border-box; - overflow: hidden; - } - .session-loading-state::before { content: none; } - .session-skeleton-bubble { - width: min(78%, 680px); - border: 1px solid color-mix(in srgb, var(--border) 74%, transparent); - border-radius: 10px; - background: color-mix(in srgb, var(--panel, var(--bg)) 72%, transparent); - padding: 10px 12px 12px; - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.14); - } - .session-skeleton-bubble.is-user { - align-self: flex-end; - width: min(64%, 520px); - } - .session-skeleton-bubble.is-ai { - align-self: flex-start; - } - .session-skeleton-line { - position: relative; - overflow: hidden; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - border-radius: 999px; - } - .session-skeleton-line { - height: 10px; - margin-top: 8px; - opacity: 0.62; - } - .session-skeleton-line::after { - content: ''; - position: absolute; - inset: -1px; - transform: translateX(-120%); - background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--fg) 14%, transparent), transparent); - animation: session-skeleton-shimmer 1.25s ease-in-out infinite; - } - .session-skeleton-bubble:nth-child(2) .session-skeleton-line::after { animation-delay: 0.12s; } - .session-skeleton-bubble:nth-child(3) .session-skeleton-line::after { animation-delay: 0.24s; } - .session-skeleton-bubble:nth-child(4) .session-skeleton-line::after { animation-delay: 0.36s; } - @keyframes session-skeleton-shimmer { - 100% { transform: translateX(120%); } - } - @media (max-width: 768px) { - .session-loading-state { padding: 12px 10px 18px; gap: 10px; } - .session-skeleton-bubble { width: 86%; padding: 9px 10px 11px; border-radius: 9px; } - .session-skeleton-bubble.is-user { width: 72%; } - .session-skeleton-line { height: 9px; margin-top: 7px; } - } - @keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } - } - /* Modal styling */ - .modal { - position:fixed; - top:0; left:0; width:100%; height:100%; - background:none; - display:flex; align-items:center; justify-content:center; - z-index:250; - backdrop-filter:none; - pointer-events:none; - } - /* Cookbook always sits above Gallery so the "Serve a model in - Cookbook…" flow (and any other "open Cookbook from inside another - modal") is visible no matter which modal was opened first. */ - #cookbook-modal { z-index: 260; } - .modal.hidden { display:none; } - - /* Tool windows open centered in the CHAT AREA (the space right of the - sidebar + icon rail), rather than the full viewport. - We narrow the overlay to the chat area so its flex-centering lands the - window there; fullscreen / docked states use position:fixed so they - escape this narrowed overlay and still fill the screen. The - --sidebar-w / --icon-rail-w vars track collapse state live, so when a - window (e.g. Cookbook) hides the sidebar this naturally re-centers. - Desktop only — on mobile these are full-screen sheets. */ - @media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal { - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)); - width: calc(100% - (var(--icon-rail-w, 48px) + var(--sidebar-w, 0px))); - box-sizing: border-box; - /* Slide in sync with the sidebar's 0.25s collapse/expand so the - centered window glides instead of jumping when the nav toggles. */ - transition: left 0.25s ease, width 0.25s ease; - } - } - .modal-content { - background:var(--panel); - border:1px solid var(--border); - width:min(520px, 92vw); max-height:85vh; padding:10px; - box-sizing:border-box; font-size:14px; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - letter-spacing: -0.015em; - display:flex; flex-direction:column; - position:relative; - overflow-y:auto; - border-radius:10px; - box-shadow:0 8px 32px rgba(0,0,0,0.45); - pointer-events:auto; - animation: modal-enter 0.25s ease-out both; - } - .memory-modal-content, - .tasks-modal-content, - .preset-modal-content, - #cookbook-modal .modal-content, - #theme-popup, - .doclib-modal-content, - .gallery-modal-content { - container-type: inline-size; - } - .modal-header { - display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; - cursor:grab; user-select:none; - /* Pin the header (with its close button) to the top of the - scrollable modal-content so users can always dismiss the modal - even after scrolling far down — especially important on mobile - where the modal can be taller than the viewport. */ - position: sticky; - top: 0; - z-index: 5; - /* Inherit the modal-content's background so the header matches the - panel body. Many tool modals set their content to var(--bg) inline - while this header was hard-coded to var(--panel) — making the title - strip a different shade in every theme. `inherit` tracks whatever - the content uses (var(--bg) or the default var(--panel)) and stays - opaque, so the sticky header still masks scrolled content. */ - background-color: inherit; - } - .modal-header:active { cursor:grabbing; } - /* Edge/corner window resize (windowResize.js). While a resize is in - progress, suppress text selection and force the active resize cursor - across the whole document so it does not flicker as the pointer passes - over child elements mid-drag. */ - body.window-resizing-active { user-select:none !important; } - body.window-resizing-active * { cursor:inherit !important; } - /* Suppress only TRANSITIONS while resizing so the edge tracks the cursor - crisply. We deliberately do NOT toggle `animation` here: toggling - animation off→on re-triggers the modal open-animation (a scale-in) on - mouseup, which both mis-measures the final size and visibly "pops" the - window. windowResize.js instead kills the one-shot open animation inline - once, in begin(). */ - .window-resizing { - transition:none !important; - } - /* Cookbook opts out of the sticky header on mobile; the title bar scrolls - away with the content instead of following. Background is inherited from - the shared .modal-header rule so frosted/glass themes stay consistent. */ - #cookbook-modal .modal-header { - position: static; - top: auto; - } - .modal-header h4 { - margin:0; - /* Push every header control (opacity slider, minimize, close) into one - group on the right — works whether or not optional controls like the - opacity slider are present, so the minimize button never floats to - the centre when a sibling is hidden. */ - margin-right:auto; - font-size:1rem; - font-weight:600; - letter-spacing:-0.03em; - color:var(--red); - } - /* Headers with an optional Peek control already have the title's - auto-margin grouping all right-side controls. A second auto-margin on - the injected minimize button splits the group and makes it look - different from the other modal headers. */ - .modal-header .theme-opacity-wrap + .modal-minimize-btn { - margin-left: 0 !important; - } - .close-btn, - .modal-close { - background:transparent; - color:color-mix(in srgb, var(--fg) 58%, transparent); - border:1px solid transparent; - font-size:0; - width:24px; - height:24px; - padding:0; - display:inline-flex; - align-items:center; - justify-content:center; - line-height:1; - text-indent:0; - cursor:pointer; - border-radius:999px; - line-height:1; - flex-shrink:0; - position:relative; - } - .close-btn > svg, - .modal-close > svg { - display:none; - } - .close-btn::before, - .modal-close::before { - content:""; - width:10px; - height:10px; - display:block; - background:currentColor; - -webkit-mask: - url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") - center / contain no-repeat; - mask: - url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") - center / contain no-repeat; - } - .close-btn:hover, - .modal-close:hover { - background:color-mix(in srgb, var(--fg) 10%, transparent); - border-color:color-mix(in srgb, var(--fg) 12%, transparent); - color:var(--fg); - } - /* Minimize button — sits beside the close button on every modal */ - .minimize-btn { - background:transparent; - color:color-mix(in srgb, var(--fg) 58%, transparent); - border:1px solid transparent; - font-size:0; - font-weight:700; - width:24px; - height:24px; - padding:0; - display:inline-flex; - align-items:center; - justify-content:center; - line-height:1; - cursor:pointer; - border-radius:999px; - flex-shrink:0; - margin-left:auto; /* push to the right so it docks next to .close-btn */ - margin-right:4px; - } - .minimize-btn::before { - content:""; - width:9px; - height:2px; - border-radius:999px; - background:currentColor; - display:block; - transform:translateY(2px); - } - .modal-minimize-btn.minimize-btn::before { - content:none; - display:none; - } - .minimize-btn:hover { - background:color-mix(in srgb, var(--fg) 10%, transparent); - border-color:color-mix(in srgb, var(--fg) 12%, transparent); - color:var(--fg); - } - @media (max-width: 768px) { - .minimize-btn { display: none !important; } - #modal-dock { display: none !important; } - } - /* Minimized modals are hidden but stay in the DOM with their state intact */ - .modal.minimized { display:none !important; } - /* Bottom dock for minimized modals */ - #modal-dock { - position:fixed; - bottom:var(--composer-clearance, 0px); - left:0; - right:0; - display:flex; - flex-wrap:wrap; - gap:4px; - padding:4px 8px; - z-index:240; - pointer-events:none; - justify-content:flex-start; - } - #modal-dock:empty { display:none; } - .modal-dock-item { - background:var(--panel); - border:1px solid var(--border); - border-bottom:none; - border-radius:6px 6px 0 0; - padding:4px 4px 4px 10px; - display:inline-flex; - align-items:center; - gap:6px; - cursor:pointer; - pointer-events:auto; - font-size:12px; - color:var(--fg); - max-width:220px; - box-shadow:0 -2px 8px rgba(0,0,0,0.25); - transition:background 0.15s; - } - .modal-dock-item:hover { background:var(--bg); } - .modal-dock-label { - white-space:nowrap; - overflow:hidden; - text-overflow:ellipsis; - max-width:180px; - } - .modal-dock-close { - background:transparent; - border:none; - color:var(--fg); - cursor:pointer; - font-size:14px; - padding:0 4px; - line-height:1; - opacity:0.6; - } - .modal-dock-close:hover { color:var(--red); opacity:1; } - .modal-body { flex:1; overflow-y:auto; } - .modal-body button { margin-top:6px; } - /* Styled confirm dialog — keeps backdrop */ - #styled-confirm-overlay { - background:rgba(0,0,0,0.5); - backdrop-filter:blur(4px); - pointer-events:auto !important; - z-index: 99999 !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - top: 0; left: 0; width: 100%; height: 100%; - } - #styled-confirm-overlay .modal-content { - position: relative; - z-index: 10001; - } - .email-attachment-warning-modal { - z-index: 99999 !important; - } - .email-attachment-warning-modal .modal-content { - position: relative; - z-index: 100001; - } - .styled-confirm-box { - width:360px; max-width:90vw; - max-height:none; /* override modal-content's 85vh */ - padding:14px 18px; - } - .styled-confirm-box .modal-header { margin-bottom:4px; } - .styled-confirm-box .modal-body p { - margin:8px 0 12px; color:var(--fg); font-size:0.92rem; line-height:1.45; - white-space:pre-line; - } - .styled-confirm-box .modal-footer { - display:flex; justify-content:flex-end; gap:8px; padding-top:6px; - border-top:1px solid var(--border); margin-top:4px; - } - @media (max-width:768px) { - .styled-confirm-box { - width: 85vw; - padding: 12px 16px; - font-size: 0.88rem; - border-radius: 12px; - } - .styled-confirm-box .modal-header h4 { font-size: 0.9rem; } - .styled-confirm-box .modal-body p { font-size: 0.85rem; margin: 6px 0 10px; } - .styled-confirm-box .modal-footer { gap: 10px; } - .styled-confirm-box .confirm-btn { - flex: 1; - /* More bottom than top padding nudges the label up ~2px so it isn't - sitting low in the taller mobile buttons. */ - padding: 8px 12px 12px; - font-size: 0.85rem; - border-radius: 8px; - text-align: center; - } - } - .confirm-btn { - /* Asymmetric padding nudges the label UP ~2px from where it was (more - bottom than top padding), so the confirm-dialog text isn't sitting low. */ - padding:3px 16px 5px; border-radius:4px; font-size:0.85rem; - cursor:pointer; border:1px solid var(--border); - font-family:inherit; - } - @media (max-width: 820px) { - /* Mobile: flip the asymmetry to shift the text 1 px UP from - centre instead (the bigger touch targets on mobile read better - with the label sitting slightly high). */ - .confirm-btn { padding:2px 16px 4px; } - } - .confirm-btn-secondary { background:var(--bg); color:var(--fg); } - .confirm-btn-secondary:hover { background:var(--border); } - #styled-confirm-cancel .styled-confirm-cancel-label { - position:relative; - top:2px; - } - .confirm-btn-primary { background:var(--accent-primary, var(--red, #4a9eff)); color:#fff; border-color:transparent; } - .confirm-btn-primary:hover { filter:brightness(1.15); } - .confirm-btn-danger { background:var(--color-danger); color:#fff; border-color:transparent; } - .confirm-btn-danger:hover { background:var(--color-error); } - .cookbook-gguf-delete-box { - width:560px; - max-width:92vw; - } - .cookbook-gguf-delete-list { - display:flex; - flex-direction:column; - gap:6px; - max-height:42vh; - overflow:auto; - padding:2px 2px 4px; - } - .cookbook-gguf-delete-row { - display:grid; - grid-template-columns:18px minmax(0,1fr); - gap:7px 8px; - align-items:start; - padding:7px 8px; - border:1px solid var(--border); - border-radius:7px; - background:color-mix(in srgb, var(--panel, var(--bg)) 92%, var(--fg) 8%); - cursor:pointer; - } - .cookbook-gguf-delete-row:hover { - border-color:color-mix(in srgb, var(--accent-primary, var(--fg)) 45%, var(--border)); - } - .cookbook-gguf-delete-cb { - -webkit-appearance:none; - appearance:none; - width:8px !important; - height:8px !important; - min-width:8px; - min-height:8px; - padding:0; - margin:4px 0 0; - border:1px solid var(--border); - border-radius:50%; - background:transparent; - box-sizing:content-box; - cursor:pointer; - transition:background 0.15s, border-color 0.15s, transform 0.12s; - } - .cookbook-gguf-delete-cb:hover { - border-color:var(--accent, var(--red)); - transform:scale(1.12); - } - .cookbook-gguf-delete-cb:checked { - background:var(--accent, var(--red)); - border-color:var(--accent, var(--red)); - } - .cookbook-gguf-delete-main, - .cookbook-gguf-delete-path { - min-width:0; - overflow:hidden; - text-overflow:ellipsis; - white-space:nowrap; - } - .cookbook-gguf-delete-main { - font-size:0.86rem; - color:var(--fg); - } - .cookbook-gguf-delete-path { - grid-column:2; - margin-top:-2px; - font-size:0.74rem; - opacity:0.58; - } - /* Styled prompt — text-input dialog (used in place of window.prompt) */ - #styled-prompt-overlay { - background:rgba(0,0,0,0.5); - backdrop-filter:blur(4px); - pointer-events:auto !important; - z-index: 99999 !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - } - #styled-prompt-overlay .modal-content { - position: relative; - z-index: 10001; - } - .styled-prompt-box { width: min(400px, 92vw); max-width: 100%; box-sizing: border-box; } - .styled-prompt-box .modal-body { padding-top: 4px; } - .styled-prompt-input { - width:100%; - box-sizing:border-box; - margin-top:8px; - padding:9px 12px; - border:1px solid var(--border); - border-radius:6px; - background:var(--bg); - color:var(--fg); - font:inherit; - font-size:0.95rem; - outline:none; - transition: border-color 0.15s, box-shadow 0.15s; - } - .styled-prompt-input:focus { - border-color: var(--accent-primary, var(--red, #4a9eff)); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-primary, var(--red, #4a9eff)) 25%, transparent); - } - @media (max-width:768px) { - .styled-prompt-box { width: 85vw; } - .styled-prompt-input { font-size: 0.9rem; padding: 10px 12px; } - } - /* Scroll navigation buttons */ - .scroll-nav-btn { - position:fixed; - background:var(--panel); - color: var(--accent); - border:none; - border-radius:10px; - width:38px; height:38px; - padding:0; - display:flex; align-items:center; justify-content:center; - font-size:14px; - line-height:1; - font-family:inherit; - cursor:pointer; - opacity:0; - pointer-events:none; - transition: opacity .2s, transform .3s cubic-bezier(0.25, 1, 0.5, 1), background .15s; - z-index:100; - transform: translateY(0); - } - .scroll-nav-btn::before { - content: ''; - position: absolute; - inset: 0; - border-radius: 10px; - padding: 1px; - background: linear-gradient(to bottom, var(--border), color-mix(in srgb, var(--border) 30%, transparent)); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - pointer-events: none; - } - .scroll-nav-btn.agent-working::after, - body.chat-agent-working #scroll-bottom-btn.show::after { - content: ''; - position: absolute; - inset: 0; - border-radius: 10px; - padding: 1px; - background: conic-gradient( - from 0deg, - transparent 0deg, - transparent 245deg, - color-mix(in srgb, var(--accent, var(--red)) 78%, transparent) 315deg, - transparent 360deg - ); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - opacity: 0.65; - pointer-events: none; - animation: scrollAgentWorkingRing 1.05s linear infinite; - } - @keyframes scrollAgentWorkingRing { - to { transform: rotate(360deg); } - } - #scroll-bottom-btn.show { opacity:1; pointer-events:auto; } - .scroll-nav-caret { - display: inline-block; - font-family: Arial, Helvetica, sans-serif; - font-size: 16px; - line-height: 1; - transform: translateY(-1px); - pointer-events: none; - } - @media (hover: hover) and (pointer: fine) { - #scroll-bottom-btn.show:hover { - border-color: color-mix(in srgb, var(--fg) 25%, transparent); - } - } - #scroll-bottom-btn.slide-out { - transform: translateY(20px); - opacity: 0 !important; - pointer-events: none; - } - /* Focus outline for accessibility */ - :focus-visible { - outline: 2px solid var(--red); - outline-offset: 2px; - } - /* Hamburger menu button */ - .hamburger { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - background: none; - border: 1px solid transparent; - border-radius: 6px; - padding: 0; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; - } - .hamburger:hover { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: var(--border); - } - .hamburger span { - display: block; - width: 16px; - height: 2px; - background: var(--fg); - border-radius: 1px; - transition: transform 0.2s, opacity 0.2s; - } - .hamburger span + span { margin-top: 3px; } - /* Agent indicator */ - #agent-indicator { - position: fixed; - top: 20px; - right: 20px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - display: none; - z-index: 100; - cursor: pointer; - transition: all 0.2s ease; - } - #agent-indicator.active { - display: block; - border-color: var(--color-agent-active); - box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); - } - #agent-indicator:hover { - border-color: var(--color-agent-active); - background: var(--panel); - } - #research-toggle-btn:disabled { - opacity: 0.6; - cursor: not-allowed; - } - -/* ── Drag & Drop ── */ - -/* ---------- Color palette (dark / light) ---------- */ - - /* Drag and drop styling */ - .section[dnd-active="true"] { - background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: var(--red) !important; - } - - .section[dnd-over="true"] { - background: color-mix(in srgb, var(--red) 20%, transparent) !important; - border-color: var(--red) !important; - transform: scale(1.02); - } - - .drag-handle { - cursor: grab; - opacity: 0.5; - padding: 0 6px; - user-select: none; - } - - .drag-handle:hover { - opacity: 0.8; - } - - .drag-handle:active { - cursor: grabbing; - } - -/* ── UI Controls (Radio, Presets, Toolbar, Settings) ── */ - - - /* Custom radio button styling */ - .radio-option { - display: flex; - align-items: center; - gap: 10px; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - cursor: pointer; - transition: all 0.2s ease; - } - - .radio-option:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: var(--fg); - } - - .radio-option input[type="radio"] { - appearance: none; - -webkit-appearance: none; - width: 18px; - height: 18px; - border: 2px solid var(--border); - border-radius: 50%; - outline: none; - margin: 0; - background: var(--bg); - transition: all 0.2s ease; - position: relative; - flex-shrink: 0; - } - - .radio-option input[type="radio"]:checked { - border-color: var(--red); - background: var(--red); - } - - .radio-option input[type="radio"]:focus { - box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 30%, transparent); - } - - .radio-label { - color: var(--fg); - font-size: 14px; - user-select: none; - } - - /* Preset buttons */ - .preset-btn { - height: 27.2px; /* 15% smaller than 32px */ - padding: 0 8.5px; /* 15% smaller than 10px */ - margin-left: 4px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font-family: 'Fira Code', monospace; - font-size: 10.2px; /* 15% smaller than 12px */ - cursor: pointer; - transition: all 0.2s ease; - } - - .preset-btn:hover { - background: var(--panel); - border-color: var(--fg); - } - - .preset-btn.active { - background: var(--panel); - border-color: var(--fg); - box-shadow: 0 0 0 1px var(--fg), 0 0 8px color-mix(in srgb, var(--fg) 16%, transparent); - font-weight: 600; - } - - /* All preset buttons use the same blue color */ - .preset-btn { - border-color: var(--red); /* Blue color */ - } - - .preset-btn.active { - border-color: var(--red); /* Blue color when active */ - box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); - } - - /* Custom preset modal: the base .preset-modal-content sets overflow:hidden - (for desktop rounded-corner clipping), which on the mobile sheet clipped - the footer (Start/Cancel) off the bottom with no way to reach it. Let the - whole sheet scroll — same as every other mobile modal (.modal-content is - overflow-y:auto on mobile) — so the footer is always reachable. No flex - changes, so the body can't collapse. */ - #custom-preset-modal .preset-modal-content { - overflow-y: auto !important; - } - #custom-preset-modal .modal-body label { - font-size: 13px; - font-weight: 500; - color: var(--fg); - margin-top: 8px; - margin-bottom: 4px; - display: block; - } - - #custom-preset-modal .modal-body input, - #custom-preset-modal .modal-body textarea { - width: 100%; - margin-bottom: 8px; - box-sizing: border-box; - } - #custom-preset-modal .modal-body textarea, - #custom-preset-modal .modal-body input[type="text"] { - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - padding: 8px 10px; - font-size: 13px; - font-family: inherit; - transition: border-color 0.15s; - } - #custom-preset-modal .modal-body textarea { - resize: vertical; - } - #custom-preset-modal .modal-body textarea:focus, - #custom-preset-modal .modal-body input[type="text"]:focus { - outline: none; - border-color: var(--red); - } - - #custom-preset-modal .modal-footer { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 12px; - padding-top: 10px; - border-top: 1px solid var(--border); - } - - #custom-preset-modal .modal-footer button { - padding: 7px 14px; - border-radius: 6px; - font-size: 12px; - font-weight: 500; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: all 0.15s; - } - #custom-preset-modal .modal-footer button:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - } - - #custom-preset-modal .modal-footer button#save-custom-preset { - /* The theme's accent is stored in --red (theme.js sets --red = accentHex) - and --accent is undefined, so the canonical accent expression is - var(--accent, var(--red)). The old bare var(--accent) resolved to nothing - which, with color:var(--bg), made the button invisible. */ - background: var(--accent, var(--red)); - color: var(--bg); - border-color: var(--accent, var(--red)); - } - #custom-preset-modal .modal-footer button#save-custom-preset:hover { - opacity: 0.85; - } - /* Toolbar visibility tab */ - .toolbar-hint { - font-size: 12px; - color: color-mix(in srgb, var(--fg) 55%, transparent); - margin-bottom: 12px; - } - /* ── Appearance visibility toggles ── */ - .vis-toggles { - display: flex; - flex-direction: column; - } - .vis-row { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - padding: 5px 6px; - border-radius: 4px; - transition: background 0.12s; - } - .vis-row:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); - } - .vis-row input[type="checkbox"] { - display: none; - } - .vis-icon { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - color: color-mix(in srgb, var(--fg) 40%, transparent); - } - .vis-icon-text { - font-size: 10px; - font-weight: 700; - font-family: inherit; - letter-spacing: -0.5px; - } - .vis-label { - flex: 1; - font-size: 12px; - color: var(--fg); - user-select: none; - } - .vis-switch { - position: relative; - width: 30px; - height: 16px; - background: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 8px; - transition: background 0.2s; - flex-shrink: 0; - } - .vis-switch::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 12px; - height: 12px; - background: var(--panel); - border-radius: 50%; - transition: transform 0.2s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); - } - .vis-row input:checked + .vis-switch { - background: var(--red); - } - .vis-row input:checked + .vis-switch::after { - transform: translateX(14px); - } - .vis-row input:checked ~ .vis-icon, - .vis-row:has(input:checked) .vis-icon { - color: var(--fg); - } - .persona-display-name-row .vis-label { - position: relative; - top: -4px; - } - .persona-display-name-row .vis-switch { - position: relative; - left: 4px; - } - .md-attach-paperclip-icon { - position: relative; - top: 1px; - } - /* Compare model selector — match the calendar modal's clean header - (no border underline). */ - #compare-model-overlay .modal-header h4 { - pointer-events: none; - } - /* Compare model selector: keep manually-resized/tiny windows contained. - Picker dropdowns are appended to document.body, so the card itself can - clip and scroll without cropping the dropdown list. */ - #compare-model-overlay .modal-content { - display: flex; - flex-direction: column; - max-height: min(720px, calc(100dvh - 48px)); - overflow: hidden; - min-height: 180px; - } - #compare-model-overlay .modal-body { - overflow: auto; - flex: 1 1 auto; - min-height: 0; - } - .vis-hint { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 30%, transparent); - font-weight: 400; - margin-left: 2px; - } - /* Settings toggle — admin-only lock indicator */ - .ui-vis-lock { - display: none; - } - /* (legacy toolbar-toggle styles removed — now using .vis-* classes) */ - - /* Demo highlight pulse */ - .odysseus-highlight { - outline: 2px solid var(--accent, var(--red)) !important; - outline-offset: 1px; - box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - animation: ody-pulse 1.5s ease-in-out infinite; - z-index: 100; - position: relative; - } - @keyframes ody-pulse { - 0%, 100% { outline-color: var(--accent, var(--red)); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); } - 50% { outline-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); } - } - /* Floating breathing halo. Rendered as a body-level div positioned over - the target, so we don't fight the target's own outline / box-shadow / - overflow chain. JS keeps it in sync with the target's bounding rect. */ - .tour-halo { - position: fixed; - pointer-events: none; - border: 3px solid var(--accent, var(--red)); - border-radius: 10px; - z-index: 10000; - animation: ody-breathe 1.4s ease-in-out infinite; - opacity: 0; - transform: scale(0.94); - transition: opacity 0.35s ease-out, transform 0.35s ease-out; - } - .tour-halo.tour-fade-in { - opacity: 1; - transform: scale(1); - } - @keyframes ody-breathe { - 0%, 100% { - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent), - 0 0 22px 4px color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - border-color: var(--accent, var(--red)); - } - 50% { - box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 30%, transparent), - 0 0 40px 14px color-mix(in srgb, var(--accent, var(--red)) 70%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 80%, transparent); - } - } - /* While the tour is active, lift overflow:hidden on common clipping - ancestors so the halo around a highlighted child isn't cropped. */ - body.tour-active .sidebar, - body.tour-active .sidebar-inner, - body.tour-active .chat-input-bar, - body.tour-active .chat-input-top, - body.tour-active .chat-input-wrap, - body.tour-active .chat-input-right, - body.tour-active .mode-toggle, - body.tour-active .model-picker-wrap { - overflow: visible !important; - } - - /* ── Secret tour hint (drag-to-snap on first modal open) ── */ - .tour-hint { - position: fixed; - z-index: 10002; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 12px 14px 10px; - width: 240px; - font-size: 0.78rem; - line-height: 1.5; - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.32); - opacity: 0; - transform: translateY(-4px); - transition: opacity 0.28s ease-out, transform 0.28s ease-out; - pointer-events: auto; - } - .tour-hint.tour-hint-in { opacity: 1; transform: translateY(0); } - .tour-hint.tour-hint-out { opacity: 0; transform: translateY(-4px); } - .tour-hint-visual { - display: flex; - justify-content: center; - margin-bottom: 8px; - color: var(--accent, var(--red)); - } - .tour-hint-visual svg { display: block; } - .tour-hint-text { margin-bottom: 10px; opacity: 0.92; } - .tour-hint-text b { color: var(--accent, var(--red)); font-weight: 600; } - .tour-hint-dismiss { - display: block; - margin: 0 0 0 auto; - background: none; - border: 1px solid var(--border); - color: var(--fg); - border-radius: 6px; - padding: 3px 12px; - cursor: pointer; - font-family: inherit; - font-size: 0.72rem; - opacity: 0.85; - transition: opacity 0.15s, background 0.15s; - } - .tour-hint-dismiss:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, transparent); } - - /* SVG dance: cursor approaches title bar, drags right, modal snaps to a - right-half zone, holds, returns. 3.2s loop. */ - .th-cursor { animation: th-cursor 3.2s ease-in-out infinite; transform-origin: 0 0; } - .th-modal-group { animation: th-modal-slide 3.2s ease-in-out infinite; transform-origin: 39px 31px; } - .th-zone { animation: th-zone-flash 3.2s ease-in-out infinite; } - @keyframes th-cursor { - 0%, 5% { transform: translate(35px, 32px); } - 18% { transform: translate(35px, 22px); } - 48% { transform: translate(80px, 22px); } - 72% { transform: translate(80px, 22px); } - 90%, 100% { transform: translate(35px, 32px); } - } - @keyframes th-modal-slide { - 0%, 18% { transform: translate(0, 0) scale(1, 1); } - 48% { transform: translate(45px, 0) scale(1, 1); } - 58% { transform: translate(30px, -19px) scale(1.4, 2.4); } - 72% { transform: translate(30px, -19px) scale(1.4, 2.4); } - 90%, 100% { transform: translate(0, 0) scale(1, 1); } - } - @keyframes th-zone-flash { - 0%, 38% { opacity: 0; } - 48% { opacity: 0.22; } - 58%, 72% { opacity: 0; } - 100% { opacity: 0; } - } - @media (prefers-reduced-motion: reduce) { - .th-cursor, .th-modal-group, .th-zone { animation: none !important; } - } - .odysseus-hl-label { - position: absolute; - top: -22px; - left: 8px; - background: var(--red); - color: var(--bg); - font-size: 0.7rem; - padding: 2px 8px; - border-radius: 4px; - white-space: nowrap; - z-index: 101; - pointer-events: none; - } - - /* Generated images inside chat bubbles */ - .msg.generated-image-wrap .body { text-align: center; } - .generated-image { - max-width: 100%; - max-height: 512px; - border-radius: 8px; - cursor: pointer; - transition: transform 0.2s; - display: inline-block; - margin: 0 auto; - } - .generated-image:hover { transform: scale(1.02); } - .generated-image-caption { - font-size: 0.8rem; - opacity: 0.5; - margin-top: 6px; - font-style: italic; - text-align: center; - } - - /* Setup wizard */ - .setup-wizard { padding: 16px; max-width: 500px; } - .setup-wizard .setup-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 12px; color: var(--fg); } - .setup-wizard .setup-label { font-size: 0.85rem; color: var(--fg); opacity: 0.7; margin-bottom: 8px; } - .setup-wizard .setup-presets { display: flex; flex-wrap: wrap; gap: 8px; } - .setup-wizard .setup-preset-btn { - padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; - font-size: 0.85rem; transition: all 0.2s; - } - .setup-wizard .setup-preset-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .setup-wizard .setup-input { - display: block; width: 100%; padding: 8px 12px; margin-bottom: 8px; - background: var(--panel); border: 1px solid var(--border); - border-radius: 6px; color: var(--fg); font-size: 0.85rem; box-sizing: border-box; - } - .setup-wizard .setup-input:focus { border-color: var(--red); outline: none; } - .setup-wizard .setup-connect-btn { - padding: 8px 20px; background: var(--red); color: var(--bg); - border: none; border-radius: 6px; cursor: pointer; font-weight: 600; margin-top: 4px; - } - .setup-wizard .setup-connect-btn:hover { opacity: 0.9; } - .setup-wizard .setup-status { font-size: 0.8rem; margin-top: 8px; color: var(--fg); opacity: 0.7; } - .setup-wizard .setup-model-list { display: flex; flex-wrap: wrap; gap: 8px; } - .setup-wizard .setup-model-btn { - padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; - font-size: 0.85rem; transition: all 0.2s; - } - .setup-wizard .setup-model-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .setup-wizard .setup-step.hidden { display: none; } - - /* Dropdown menu styles */ - .dropdown { - position: absolute; - top: calc(100% + 6px); - right: 0; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 10px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); - z-index: 1000; - display: none; - min-width: 220px; - padding: 6px; - backdrop-filter: blur(12px); - } - - .dropdown.show { - display: block; - animation: dropdown-in 0.15s ease-out; - } - @keyframes dropdown-in { - from { opacity:0; transform:translateY(-6px) scale(0.97); } - to { opacity:1; transform:translateY(0) scale(1); } - } - - .dropdown-item { - padding: 8px 10px; - border-radius: 6px; - cursor: pointer; - transition: background 0.12s ease; - display: flex; - align-items: center; - gap: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - } - - .dropdown-item:last-child { - border-bottom: none; - } - - .dropdown-item .menu-icon { - width: 28px; - height: 28px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - flex-shrink: 0; - } - - .dropdown-item .menu-text h4 { - margin: 0; - font-size: 12.5px; - font-weight: 500; - color: var(--fg); - line-height: 1.3; - } - - .dropdown-item .menu-text p { - margin: 0; - font-size: 10.5px; - color: var(--color-subheader); - line-height: 1.3; - } - - .dropdown-item:hover { - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .dropdown-item:hover .menu-icon { - background: color-mix(in srgb, var(--red) 15%, transparent); - } - - /* Compact dropdown items (session/model context menus) */ - .dropdown-item-compact { - cursor: pointer; - padding: 6px 8px; - font-size: 11px; - border-radius: 8px; - display: flex; - align-items: center; - gap: 10px; - color: var(--fg); - transition: background 0.1s; - } - .dropdown-item-compact:hover { - background: color-mix(in srgb, var(--accent) 10%, transparent); - } - .dropdown-item-compact .dropdown-icon { - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - opacity: 0.5; - } - .dropdown-item-compact .dropdown-icon svg { - width: 14px; - height: 14px; - } - .dropdown-item-compact .dropdown-shortcut { - margin-left: auto; - font-size: 9.5px; - opacity: 0.35; - font-weight: 400; - } - /* Keyboard shortcut hints (⌘+Alt+D etc.) are meaningless on touch — - hide them in the per-chat actions menu on mobile. */ - @media (max-width: 768px) { - .session-dropdown .dropdown-shortcut { display: none; } - } - .dropdown-item-danger { - color: var(--red) !important; - } - .dropdown-item-danger .dropdown-icon { - opacity: 0.7; - } - - /* Inline rename input for sessions */ - .session-rename-input { - width: 100%; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--accent, var(--accent-primary)); - border-radius: 4px; - padding: 2px 6px; - font-family: inherit; - font-size: 12px; - line-height: 1.3; - outline: none; - } - - /* Session dropdown container */ - .session-dropdown-menu { - position: fixed; - z-index: 1000; - display: none; - min-width: auto; - width: max-content; - padding: 4px; - animation: dropdown-in 0.15s ease-out; - } - - /* Folder move submenu */ - .session-folder-submenu { - position: fixed; - z-index: 1001; - display: none; - min-width: auto; - width: max-content; - padding: 4px; - } - - .dropdown-divider { - height: 1px; - background: var(--border); - margin: 4px 8px; - } - - /* Search toggle styles */ - .search-toggle { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 0; - border-bottom: 1px solid var(--border); - margin-bottom: 8px; - } - - .search-provider-label { - font-size: 14px; - color: var(--fg); - } - -/* ── Voice, Search, Themes, Comparison, Censor, Print ── */ - - /* Voice recording styles */ - #mic-btn { - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - transition: all 0.2s ease; - } - - #mic-btn:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: var(--fg); - } - - #mic-btn.recording { - background: var(--color-recording); - border-color: var(--color-recording); - animation: pulse 1.5s infinite; - } - - @keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } - } - - #recording-indicator { - position: fixed; - top: 10px; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin: 10px; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - } - - #recording-indicator.hidden { - display: none !important; - } - - .recording-content { - display: flex; - align-items: center; - gap: 12px; - color: white; - } - - .recording-icon { - color: var(--color-recording); - font-size: 20px; - animation: pulse 1.5s infinite; - } - - .recording-text { - font-size: 16px; - font-weight: 500; - } - - #stop-recording { - background: var(--color-recording); - color: white; - border: none; - border-radius: 6px; - padding: 6px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s ease; - } - - #stop-recording:hover { - background: var(--color-recording-hover); - } - - /* Error state for recording */ - #recording-indicator.error { - background: rgba(173, 26, 26, 0.9); - } - - .recording-error { - color: var(--color-recording); - font-size: 14px; - margin-top: 4px; - } - - @media (max-width: 768px) { - #recording-indicator { - margin: 8px; - padding: 10px; - } - - .recording-text { - font-size: 14px; - } - - #stop-recording { - padding: 6px 10px; - font-size: 12px; - } - } -/* SYNTAX HIGHLIGHTING — uses theme vars from style.css, no hardcoded overrides */ -.hljs { color: var(--hl-fg, #9cdef2); background: none !important; } -pre { background: var(--code-bg, var(--hl-bg, #282c34)) !important; } - - /* ---------- Search overlay (Ctrl+K command palette) ---------- */ - .search-overlay { - position: fixed; - top: 0; left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: flex-start; - justify-content: center; - padding-top: var(--search-overlay-top, 15vh); - z-index: 300; - backdrop-filter: blur(6px); - } - .search-overlay.hidden { display: none; } - .search-popup { - width: 520px; - max-width: 90vw; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 12px; - box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5), 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); - overflow: hidden; - display: flex; - flex-direction: column; - max-height: 60vh; - } - .search-popup input#search-input { - width: 100%; - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - outline: none; - color: var(--fg); - font-size: 16px; - font-family: 'Fira Code', monospace; - padding: 14px 16px; - box-sizing: border-box; - } - .search-popup input#search-input::placeholder { - color: color-mix(in srgb, var(--fg) 30%, transparent); - } - .search-results { - overflow-y: auto; - flex: 1; - padding: 4px; - } - .search-results:empty { - display: none; - } - .search-group-header { - font-size: 10px; - font-weight: 600; - color: var(--color-subheader); - text-transform: uppercase; - letter-spacing: 0.5px; - padding: 10px 12px 4px; - } - .search-result-item { - display: flex; - align-items: baseline; - gap: 8px; - padding: 8px 12px; - border-radius: 6px; - cursor: pointer; - transition: background 0.1s; - } - .search-result-item:hover, - .search-result-item.selected { - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .search-result-role { - font-size: 10px; - font-weight: 600; - color: var(--color-subheader); - flex-shrink: 0; - min-width: 24px; - } - .search-result-snippet { - flex: 1; - font-size: 12px; - color: var(--fg); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .search-result-time { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 35%, transparent); - flex-shrink: 0; - white-space: nowrap; - } - mark.search-highlight { - /* Was orange-on-orange (0.35 alpha bg + orange text) — too low-contrast - to read. Solid accent fill with bg-colored text reads clearly. */ - background: var(--accent, #e8a830); - color: var(--bg, #1a1a1a); - border-radius: 2px; - padding: 0 2px; - font-weight: 600; - } - .search-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 24px; - font-size: 13px; - } - - /* ---------- Theme popup (uses .modal > .modal-content frame) ---------- */ - #theme-modal { z-index: 260; } - #theme-popup .theme-popup-sub { color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 11px; margin-bottom: 10px; } - /* `max-height` instead of a fixed height so the popup shrinks to fit its - content (avoids the leftover whitespace after the time-based switching - card was removed). The inner .theme-tab-panel keeps overflow:auto, so - any taller content still scrolls inside. */ - #theme-popup { overflow-y: hidden; max-height: min(85vh, 600px); } - #theme-popup .modal-header { flex-shrink: 0; } - #theme-popup .admin-tabs { margin: 0 -10px 8px; padding: 0 10px; flex-shrink: 0; } - .theme-tab-panel { overflow-y: auto; min-height: 0; flex: 1; padding-bottom: 10px; } - - .theme-grid { - display: grid; grid-template-columns: repeat(auto-fill, minmax(66px, 1fr)); - gap: 6px; margin-bottom: 12px; - } - .theme-swatch { - border: 2px solid var(--border); border-radius: 8px; cursor: pointer; - padding: 5px; text-align: center; font-size: 0.65rem; color: var(--fg); - transition: border-color 0.15s, transform 0.15s; - } - .theme-swatch:hover { transform: scale(1.06); } - .theme-swatch.active { border-color: var(--red); box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 33%, transparent); } - .theme-swatch-colors { - display: flex; justify-content: center; margin-bottom: 3px; - } - .theme-swatch-colors span { - width: 15px; height: 15px; border-radius: 50%; - margin-left: -5px; - border: 1.5px solid color-mix(in srgb, var(--fg) 12%, transparent); - } - .theme-swatch-colors span:first-child { margin-left: 0; } - .theme-custom-label { font-size: 11px; font-weight: 600; color: color-mix(in srgb, var(--fg) 50%, transparent); text-transform: uppercase; letter-spacing: 0.06em; margin: 10px 0 6px; } - .theme-custom { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; } - .color-row { display: flex; align-items: center; gap: 4px; position: relative; } - .color-row label { font-size: 13px; font-weight: 500; color: var(--fg); opacity: 0.7; flex: 1; } - .color-row input[type="color"], - .color-row input.cp-swatch-input { - width: 24px; height: 24px; border: 1px solid var(--border); border-radius: 50%; - background: none; cursor: pointer; padding: 0; flex-shrink: 0; - overflow: hidden; - -webkit-appearance: none; - appearance: none; - } - .color-row input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } - .color-row input[type="color"]::-webkit-color-swatch { border: none; border-radius: 50%; } - .color-row input[type="color"]::-moz-color-swatch { border: none; border-radius: 50%; } - #theme-bg-effect-color { position: relative; top: 2px; } - .color-row input.cp-swatch-input { - color: transparent; - text-shadow: none; - caret-color: transparent; - font-size: 0; - user-select: none; - } - .color-row input.cp-swatch-input::selection { background: transparent; } - .color-row input.cp-swatch-input:focus { outline: 1px solid var(--red); outline-offset: 1px; } - .color-reset-btn { - width: 24px; height: 24px; border: none; background: none; cursor: pointer; - color: var(--fg); opacity: 0; font-size: 1.15rem; padding: 0; line-height: 1; - transition: opacity 0.15s, color 0.15s; flex-shrink: 0; pointer-events: none; - position: absolute; right: 6px; top: 50%; transform: translateY(-50%); - } - .color-row input[type="color"], - .color-row input.cp-swatch-input { margin-left: auto; } - /* Keep the harmony hex readout on the left, with the swatch dot anchored - to the right like the other color controls. */ - #theme-harmony-card #harmony-accent-hex { - order: -1; - margin-right: auto; - } - .color-row:has(.color-reset-btn.changed) input[type="color"], - .color-row:has(.color-reset-btn.changed) input.cp-swatch-input { margin-right: 28px; } - .color-reset-btn.changed { opacity: 0.4; pointer-events: auto; } - .color-reset-btn.changed:hover { opacity: 1; color: var(--red); } - .theme-custom-divider { - grid-column: 1 / -1; font-size: 11px; color: var(--fg); opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.04em; margin: 6px 0 2px; - border-top: 1px solid var(--border); padding-top: 6px; - } - .theme-swatch[data-custom] { position: relative; overflow: visible; } - /* Accent-coloured circular X — always visible (mobile-friendly), sits - INSIDE the swatch's top-right corner so it never crops into a - neighbouring swatch. */ - .theme-delete-btn { - position: absolute; - top: -2px; - right: -2px; - width: 20px; - height: 20px; - padding: 0; - border: none; - border-radius: 50%; - background: var(--accent, var(--red, #d92534)); - color: #fff; - cursor: pointer; - z-index: 2; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); - transition: transform 0.12s, background 0.12s; - } - .theme-delete-btn:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 80%, white); - transform: scale(1.12); - } - .theme-delete-btn:active { transform: scale(0.95); } - .theme-delete-btn svg { - display: block; - /* Optical nudge — the X reads off-center inside the circle without it. */ - position: relative; - left: 1px; - top: -1px; - } - .theme-save-row { - display: flex; gap: 6px; margin-top: 8px; - } - .theme-save-row input { - flex: 1; padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 12px; font-family: inherit; - } - .theme-save-row input:focus { outline: none; border-color: var(--red); } - .theme-save-row input::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } - .theme-save-row button { - padding: 6px 12px; border: 1px solid var(--red); border-radius: 6px; - background: transparent; color: var(--red); cursor: pointer; - font-size: 12px; font-family: inherit; white-space: nowrap; transition: all 0.15s; - } - .theme-save-row button:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } - .theme-save-error { - font-size: 11px; color: var(--red); margin-top: 2px; display: none; - } - /* Import/Export */ - .theme-io-row { display: flex; gap: 6px; margin-top: 6px; } - .theme-io-btn { - flex: 1; padding: 5px 10px; border: 1px solid var(--border); border-radius: 6px; - background: transparent; color: var(--fg); cursor: pointer; - font-size: 12px; opacity: 0.7; transition: all 0.15s; font-family: inherit; - } - .theme-io-btn:hover { opacity: 1; border-color: var(--fg); background: color-mix(in srgb, var(--fg) 5%, transparent); } - - #memory-import-btn svg, - #memory-export-btn svg { - color: var(--accent, var(--red)); - } - .theme-import-area { - width: 100%; margin-top: 6px; padding: 6px 8px; - border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 0.7rem; - font-family: inherit; resize: vertical; min-height: 48px; - } - .theme-import-area:focus { outline: none; border-color: var(--red); } - .theme-import-area.hidden { display: none; } - .theme-import-actions { display: flex; gap: 6px; margin-top: 4px; } - .theme-import-actions.hidden { display: none; } - /* Font & Density */ - .theme-fd-row { display: flex; gap: 8px; margin-bottom: 8px; } - .theme-fd-group { flex: 1; display: flex; flex-direction: column; gap: 3px; } - .theme-fd-label { font-size: 12px; font-weight: 500; color: var(--fg); opacity: 0.6; } - .theme-fd-select { - padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 12px; - font-family: inherit; cursor: pointer; - } - .theme-fd-select:focus { outline: none; border-color: var(--red); } - @media (max-width: 768px) { - /* The switch is too cramped beside the Font and Density selects on - narrow screens; give Frosted a dedicated row instead. */ - .theme-fd-row:first-of-type { - flex-wrap: wrap; - } - .theme-fd-row:first-of-type #theme-frosted-group { - flex: 1 1 100%; - flex-direction: row; - align-items: center; - justify-content: space-between; - min-width: 0; - } - } - .theme-fd-range { - width: 100%; - max-width: 100%; - box-sizing: border-box; - margin: 0; - padding: 0; - height: 24px; - background: transparent; - cursor: pointer; - -webkit-appearance: none; - appearance: none; - accent-color: var(--red); - } - .theme-fd-range::-webkit-slider-runnable-track { - height: 4px; background: var(--border); border-radius: 2px; - } - .theme-fd-range::-moz-range-track { - height: 4px; background: var(--border); border-radius: 2px; - } - .theme-fd-range::-webkit-slider-thumb { - -webkit-appearance: none; appearance: none; - width: 14px; height: 14px; border-radius: 50%; - background: var(--red); border: none; margin-top: -5px; cursor: pointer; - } - .theme-fd-range::-moz-range-thumb { - width: 14px; height: 14px; border-radius: 50%; - background: var(--red); border: none; cursor: pointer; - } - .theme-fd-range:focus { outline: none; } - /* Color Harmony Generator */ - .theme-harmony-row { display: flex; gap: 8px; align-items: flex-end; } - .harmony-generate-btn { - padding: 5px 14px; border: 1px solid var(--red); border-radius: 6px; - background: transparent; color: var(--red); cursor: pointer; - font-size: 12px; white-space: nowrap; transition: all 0.15s; - font-family: inherit; width: 100%; - } - .harmony-generate-btn:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } - .harmony-preview { - display: flex; height: 20px; border-radius: 6px; overflow: hidden; - margin-top: 8px; border: 1px solid var(--border); - } - .harmony-preview:empty { display: none; border: none; } - .harmony-preview span { flex: 1; } - #theme-reset-btn { - margin-top: 6px; width: 100%; padding: 6px; border: 1px solid var(--border); - border-radius: 6px; background: var(--bg); color: var(--fg); cursor: pointer; - font-size: 12px; font-family: inherit; opacity: 0.7; transition: opacity 0.15s; - } - #theme-reset-btn:hover { opacity: 1; } - .theme-adv-toggle { - margin-top: 10px; margin-bottom: 10px; - padding: 6px 8px; cursor: pointer; - font-size: 11px; color: var(--red); opacity: 0.8; - border: 1px solid var(--border); border-radius: 6px; - transition: opacity 0.15s, background 0.15s; - user-select: none; - } - .theme-adv-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 4%, transparent); } - .theme-adv-toggle .theme-adv-arrow { - display: inline-block; transition: transform 0.2s; font-size: 10px; - margin-right: 4px; - } - .theme-adv-toggle.open .theme-adv-arrow { transform: rotate(90deg); } - .theme-adv-section { margin-top: 8px; margin-bottom: 10px; } - .theme-adv-section.hidden { display: none; } - .theme-adv-group { margin-bottom: 8px; } - .theme-adv-group-label { - font-size: 10px; color: var(--fg); opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; - } - .theme-adv-clear-btn { - margin-top: 6px; width: 100%; padding: 5px; border: 1px solid var(--border); - border-radius: 6px; background: transparent; color: var(--fg); opacity: 0.5; - cursor: pointer; font-size: 12px; font-family: inherit; transition: opacity 0.15s; - } - .theme-adv-clear-btn:hover { opacity: 1; } - - /* Customization is an editor, so keep its sections visually related while - preserving the existing controls and their order. */ - #theme-tab-customize { - display: flex; - flex-direction: column; - gap: 10px; - } - #theme-tab-customize > .admin-card, - #theme-tab-customize > .theme-adv-toggle, - #theme-tab-customize > .theme-adv-section { - margin-top: 0; - margin-bottom: 0; - } - #theme-tab-customize > .admin-card { - padding: 12px; - } - #theme-tab-customize > .admin-card > h2 { - display: flex; - align-items: center; - min-height: 20px; - margin: 0 0 10px; - padding-bottom: 7px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 65%, transparent); - font-size: 13px; - } - #theme-tab-customize .theme-custom { - gap: 6px 14px; - } - #theme-tab-customize .color-row { - min-height: 30px; - padding: 3px 6px; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--border) 55%, transparent); - background: color-mix(in srgb, var(--fg) 2%, transparent); - } - #theme-tab-customize .color-row label { - font-size: 12px; - } - #theme-tab-customize .theme-adv-toggle { - flex-shrink: 0; - margin: 0; - background: color-mix(in srgb, var(--fg) 2%, transparent); - } - #theme-tab-customize .theme-adv-section { - padding: 10px 12px 2px; - border: 1px solid color-mix(in srgb, var(--border) 75%, transparent); - border-radius: 7px; - background: color-mix(in srgb, var(--panel) 55%, transparent); - } - #theme-tab-customize .theme-adv-group { - margin: 0; - padding: 0 0 9px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 48%, transparent); - } - #theme-tab-customize .theme-adv-group + .theme-adv-group { - padding-top: 9px; - } - #theme-tab-customize .theme-adv-group:last-of-type { - border-bottom: 0; - } - #theme-tab-customize .theme-adv-group-label { - margin-bottom: 6px; - color: var(--red); - opacity: 0.8; - font-weight: 600; - } - #theme-tab-customize .theme-fd-row, - #theme-tab-customize .theme-harmony-row { - gap: 12px; - margin-bottom: 10px; - } - #theme-tab-customize .theme-fd-row:last-child, - #theme-tab-customize .theme-harmony-row:last-child { - margin-bottom: 0; - } - #theme-tab-customize .theme-fd-group { - min-width: 0; - } - #theme-tab-customize .theme-fd-select { - min-height: 30px; - } - #theme-tab-customize #theme-harmony-card .theme-fd-label { - font-size: 11px; - } - #theme-tab-customize .theme-save-row, - #theme-tab-customize .theme-io-row { - gap: 8px; - } - #theme-tab-customize #theme-reset-btn { - margin-top: 8px; - } - @media (max-width: 560px) { - #theme-tab-customize .theme-custom { - grid-template-columns: 1fr; - } - #theme-tab-customize .theme-fd-row, - #theme-tab-customize .theme-harmony-row { - flex-direction: column; - align-items: stretch; - } - #theme-tab-customize .theme-fd-group, - #theme-tab-customize .theme-harmony-row .theme-fd-group { - width: 100%; - } - } - - - /* Mobile: bottom sheet modals — slide up from bottom */ - @media (max-width: 768px) { - .modal { - align-items: flex-end; - background: rgba(0,0,0,0.4); - pointer-events: auto; - /* Anchor the bottom sheet to the DYNAMIC viewport. The base overlay is - position:fixed; height:100% = the layout viewport, whose bottom sits - UNDER Firefox Android's bottom URL bar — so flex-end parked the sheet - (and its footer / Start button) beneath the bar. dvh excludes the bar - so the footer lands above it. Extra safe-area pad for iOS home bar. */ - height: 100dvh; - padding-bottom: env(safe-area-inset-bottom, 0px); - } - /* Confirm dialog stays centered, not a bottom sheet */ - #styled-confirm-overlay { - align-items: center; - } - .styled-confirm-box { - border-radius: 12px !important; - border: 1px solid var(--border) !important; - animation: modal-enter 0.25s ease-out both !important; - max-height: none !important; - } - .styled-confirm-box.modal-closing { - animation: modal-exit 0.18s ease-in both !important; - } - .styled-confirm-box::before { - display: none !important; - } - .styled-confirm-box .close-btn { - display: none !important; - } - #theme-popup { - width: 100% !important; - height: 65vh !important; - max-height: 65vh !important; - top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important; - position: fixed !important; - border-radius: 14px 14px 0 0; - border: none; - border-top: 1px solid var(--border); - padding: 6px 12px 12px; - animation: sheet-enter 0.2s ease-out forwards; - overflow-y: hidden !important; - } - #theme-popup .theme-tab-panel { - touch-action: pan-y; - overscroll-behavior: contain; - -webkit-overflow-scrolling: touch; - } - #theme-popup.sheet-ready { - animation: none; - } - #theme-popup.modal-closing { - animation: sheet-exit 0.15s ease-in both !important; - } - .modal-content, - .memory-modal-content, - .settings-modal-content, - #compare-model-overlay .modal-content { - width: 100% !important; - /* 85dvh leaves comfortable headroom above the sheet so the user can - still see the chat behind it and the drag handle has breathing - room. Was 65vh (too short, content clipped) → 90vh (too tall, top - hugged the status bar). */ - max-height: 85dvh !important; - max-height: 85vh !important; /* fallback for browsers without dvh */ - height: auto !important; - border-radius: 14px 14px 0 0; - border: none; - border-top: 1px solid var(--border); - padding-top: 6px !important; - /* Clip children to the rounded top corners — otherwise the sticky - modal-header's var(--panel) background paints a darker - rectangle past the radius and the corners look square again. */ - overflow: hidden; - animation: sheet-enter 0.2s ease-out forwards; - } - /* Keep the tall tool drawers on a shared mobile height ceiling. The - final mobile drawer rule below makes this content-sized so shorter - tools remain compact while tall ones leave a visible top gap. */ - #cookbook-modal .modal-content, - #tasks-modal .modal-content, - #calendar-modal .modal-content, - #memory-modal .memory-modal-content, - #settings-modal .settings-modal-content, - #email-lib-modal .modal-content, - #doclib-modal .doclib-modal-content { - max-height: 90vh !important; - max-height: 90dvh !important; - height: auto !important; - /* Reserve the iOS home-indicator / bottom-bar strip so an expanded - skill card's footer (Delete / Edit / Run) isn't hidden under it. - dvh already excludes the URL bar; this handles the safe area. */ - padding-bottom: env(safe-area-inset-bottom, 0px) !important; - } - /* Grab handle pill on the non-standard content classes too. Memory's - desktop rule defines a radial-glow ::before later in the file — - these body-prefixed selectors win the specificity battle on mobile. */ - body .memory-modal-content::before, - body .settings-modal-content::before { - content: ''; - display: block; - position: static; - inset: auto; - width: 36px; height: 4px; - background: var(--fg); - opacity: 0.25; - border-radius: 2px; - margin: 0 auto 4px; - flex-shrink: 0; - padding: 0; - border-top: 10px solid transparent; - border-bottom: 6px solid transparent; - background-clip: padding-box; - animation: none; - } - .memory-modal-content .close-btn, - .memory-modal-content .modal-close, - .settings-modal-content .close-btn, - .settings-modal-content .modal-close { - display: none !important; - } - .memory-modal-content, - .settings-modal-content { - touch-action: pan-y; - overscroll-behavior: contain; - } - /* Library modals use the same bottom-sheet sizing as the other tools. - The shared mobile drawer rule below provides the top breathing room. */ - #cookbook-modal .modal-content, - #calendar-modal .modal-content, - #email-lib-modal .modal-content, - #doclib-modal .modal-content, - #gallery-modal .modal-content, - #tasks-modal .modal-content { - /* vh-first as fallback for very old browsers, then dvh wins so - the modal shrinks/grows with the mobile URL bar. Wrong order - previously made the expanded chat preview extend below the - visible viewport on Chrome/Safari mobile (URL bar covered the - action buttons row). */ - max-height: 90vh !important; - max-height: 90dvh !important; - height: auto !important; - } - /* Anchor those modals to the top so the full height is usable */ - #cookbook-modal, - #calendar-modal, - #email-lib-modal, - #doclib-modal, - #gallery-modal, - #tasks-modal { - padding-top: 0 !important; - align-items: stretch !important; - } - /* Deep Research already gets the swipe grab-handle pill from the - shared `.modal-content::before` rule (the pane carries that class). - The previous header-level pill was redundant and rendered as a - SECOND pill stacked above the real one — removed. */ - /* The inner body must flex to fill the new full height, and the - tasks list inside it must scroll independently — otherwise the - list crops at whatever pre-mobile height the desktop layout had. */ - #tasks-modal .modal-content { - display: flex !important; - flex-direction: column !important; - } - #tasks-modal .modal-body { - flex: 1 1 auto !important; - min-height: 0 !important; - } - /* Memory/Skills: the body lacks flex:1, so on the fixed-height mobile - sheet (overflow:hidden) it grew past the viewport and clipped the - bottom of the skills list + its row action buttons with no way to - scroll there. Bound the body so the inner list scrolls internally. */ - #memory-modal .memory-modal-content { - display: flex !important; - flex-direction: column !important; - } - /* flex-basis MUST be 0 (not auto) — matches the working #doclib-modal - .modal-body. With basis:auto, Firefox (incl. mobile) sizes the body - to its content (~half height) instead of filling, while Chromium - fills it regardless — which is exactly why the skill expand worked - on desktop/Chromium but stuck at ~50% on Firefox mobile. */ - #memory-modal .memory-modal-body { - flex: 1 1 0 !important; - min-height: 0 !important; - overflow: hidden !important; - } - /* Same basis:0 fix down the rest of the skills chain so every layer - fills instead of sizing to content under Firefox. */ - #memory-modal .memory-tab-panel[data-memory-panel="skills"] { - flex: 1 1 0 !important; - min-height: 0 !important; - } - #memory-modal .memory-tab-panel[data-memory-panel="skills"] > .admin-card { - flex: 1 1 0 !important; - min-height: 0 !important; - } - /* The skills modal carries an extra toolbar row (search/sort/select + - bulk bar) the doc/email libraries don't, so the shared 82dvh preview - min-height overflowed here and pushed the expanded footer (Delete / - Edit / Run) off the bottom of the screen. Let flexbox size it from - the resolved card height instead, so the footer always pins inside - the visible area. */ - #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview { - min-height: 0 !important; - } - /* Once enter animation finishes, clear it so inline transform works for dragging */ - .modal-content.sheet-ready { - animation: none; - } - .modal-content.modal-closing { - animation: sheet-exit 0.15s ease-in both !important; - } - /* Grab handle — large touch target, visible pill */ - #theme-popup::before, - .modal-content::before { - content: ''; - display: block; - width: 36px; height: 4px; - background: var(--fg); opacity: 0.25; - border-radius: 2px; - margin: 0 auto 4px; - flex-shrink: 0; - padding: 0; - /* Expand touch target without changing visual size */ - border-top: 10px solid transparent; - border-bottom: 6px solid transparent; - background-clip: padding-box; - } - /* Hide close X on mobile — swipe down to dismiss */ - .modal-content .close-btn, - .modal-content .modal-close, - #theme-popup .close-btn { - display: none !important; - } - /* Hide the auto-injected minimize (_) button on mobile — the dock - chip already represents the minimized state, and the swipe-down - gesture is the canonical minimize action. */ - .modal-minimize-btn, - .minimize-btn, - [data-minimize] { - display: none !important; - } - /* Lock modals to vertical touch only, prevent horizontal dragging */ - .modal-content { - touch-action: pan-y; - overscroll-behavior: contain; - } - .modal-content .cookbook-body, - .modal-content .modal-body { - touch-action: pan-y; - overscroll-behavior: contain; - } - .modal-header { - cursor: default; - touch-action: none; - } - @keyframes sheet-enter { - from { transform: translateY(100%); } - to { transform: translateY(0); } - } - @keyframes sheet-exit { - from { transform: translateY(0); } - to { transform: translateY(100%); } - } - } - - /* ── Model A/B Comparison ── */ - - /* -- Extracted inline-style classes -- */ - .cmp-header-action-btn { - background: none; border: 1px solid var(--border); color: var(--fg); - cursor: pointer; padding: 3px 10px; font-size: 11px; font-weight: 600; - opacity: 0.7; transition: all 0.15s; line-height: 1; border-radius: 4px; - display: inline-flex; align-items: center; font-family: inherit; - } - .cmp-form-control { - padding: 8px; background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; font-size: 0.85em; - } - .cmp-btn-secondary { - background: transparent; color: var(--fg); border: 1px solid var(--border); - border-radius: 6px; cursor: pointer; - } - .cmp-btn-primary { - background: var(--fg); color: var(--bg); border: none; - border-radius: 6px; cursor: pointer; font-weight: 600; - transition: filter 0.12s, background 0.12s, color 0.12s; - } - /* Override global button:hover (which switches bg to var(--panel) = - very dark) — keep the bright primary look and just brighten slightly. */ - .cmp-btn-primary:hover:not(:disabled) { - background: var(--fg); color: var(--bg); - filter: brightness(1.1); - } - .cmp-btn-primary:active:not(:disabled) { filter: brightness(0.95); } - .cmp-btn-secondary:hover:not(:disabled) { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); color: var(--fg); - } - .cmp-model-row { - display: flex; align-items: center; gap: 8px; margin-bottom: 8px; - flex-wrap: wrap; - padding: 2px 0; - transition: margin-left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.15s, border-color 0.15s; - } - .cmp-model-row-duplicate { - margin-left: -4px; - margin-right: -4px; - padding: 4px; - border-radius: 6px; - background: color-mix(in srgb, var(--color-error) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--color-error) 20%, transparent); - } - .cmp-model-row-swap-target { - margin-left: -4px; - margin-right: -4px; - padding: 4px; - border-radius: 6px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); - } - .cmp-model-row-swap-target .cmp-row-label { - color: var(--accent, var(--red)); - opacity: 1; - } - .cmp-row-label { - color: var(--fg); font-size: 0.82em; font-weight: 600; min-width: 20px; - opacity: 0.4; text-align: center; flex-shrink: 0; - } - .cmp-model-meta { - order: 3; - flex: 1 0 calc(100% - 28px); - margin-left: 28px; - margin-top: -4px; - display: flex; - align-items: center; - gap: 5px; - min-width: 0; - font-size: 10px; - line-height: 1.2; - } - .cmp-model-meta-chip, - .cmp-model-meta-warning, - .cmp-model-meta-muted { - display: inline-flex; - align-items: center; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - border-radius: 999px; - padding: 1px 6px; - letter-spacing: 0; - } - .cmp-model-meta-chip { - color: color-mix(in srgb, var(--fg) 60%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - } - .cmp-model-meta-warning { - color: var(--color-error); - background: color-mix(in srgb, var(--color-error) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--color-error) 24%, transparent); - font-weight: 600; - } - .cmp-model-meta-muted { - color: color-mix(in srgb, var(--fg) 36%, transparent); - } - .cmp-rm-btn { - background: none; border: none; color: var(--accent, var(--red)); cursor: pointer; - min-width: 20px; font-size: 16px; font-weight: 600; opacity: 0.3; - transition: all 0.15s; padding: 0; line-height: 1; text-align: center; - position: relative; top: -2px; - order: 2; - margin-left: auto; - align-self: center; - } - .cmp-prov-select { - flex: 0 0 auto; width: 120px; font-size: 0.8em; - } - /* Eval-prompts picker — only shown during compare; absolute top-right - inside .chat-input-top, matching .model-picker-wrap's slot. */ - .chat-input-top > .cmp-eval-wrap { - position: relative; - grid-column: 2; - grid-row: 1; - top: -2px; right: 0; - z-index: 2; - } - .cmp-eval-btn { - display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 11px; font-weight: 500; - font-family: inherit; - cursor: pointer; opacity: 0.75; - transition: opacity 0.15s, border-color 0.15s; - } - .cmp-eval-btn:hover { opacity: 1; border-color: var(--fg); } - .cmp-eval-clear { - display: inline-flex; align-items: center; gap: 4px; margin-left: 4px; - border: 0; background: transparent; color: var(--fg); opacity: .55; - padding: 3px 5px; font: inherit; font-size: 10px; cursor: pointer; - } - .cmp-eval-clear:hover { opacity: 1; } - .cmp-eval-caret { opacity: 0.7; transform: rotate(180deg); } - .cmp-eval-menu { - position: absolute; bottom: calc(100% + 4px); right: 0; - min-width: 220px; max-width: 280px; - max-height: 360px; overflow-y: auto; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 -4px 16px rgba(0,0,0,0.3); - padding: 4px; - z-index: 1000; - } - .cmp-eval-menu.hidden { display: none; } - .cmp-eval-group-label { - font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px; - opacity: 0.45; font-weight: 600; - padding: 6px 8px 2px; - } - .cmp-eval-item { - display: block; width: 100%; - text-align: left; - padding: 5px 8px; - background: none; border: none; - color: var(--fg); font-size: 11px; - font-family: inherit; - border-radius: 4px; - cursor: pointer; - } - .cmp-eval-item:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - } - .cmp-eval-empty { - padding: 10px; text-align: center; - font-size: 11px; opacity: 0.5; - } - /* Tick on items that ship a known expected answer */ - .cmp-eval-item-tick { - float: right; - margin-left: 6px; - font-size: 10px; - color: var(--color-success, #4caf50); - opacity: 0.8; - } - /* Expected-answer panel — floats as its own little window above the - chat-input-bar. Distinct surface, padded, drop-shadow, slightly - lifted so it reads as a separate UI element, not part of the input. */ - .chat-input-bar:has(.cmp-eval-expected) { position: relative; } - .cmp-eval-expected { - position: absolute; - bottom: calc(100% + 8px); - right: 0; - display: inline-flex; align-items: center; gap: 8px; - padding: 8px 12px; - font-size: 11px; - background: var(--panel); - border: 1px solid color-mix(in srgb, var(--color-success, #4caf50) 50%, transparent); - border-radius: 8px; - box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2); - color: var(--fg); - width: fit-content; - z-index: 5; - pointer-events: auto; - } - .cmp-eval-expected.hidden { display: none; } - .cmp-eval-expected-label { - opacity: 0.6; - text-transform: uppercase; - font-size: 9px; - letter-spacing: 0.5px; - font-weight: 600; - } - .cmp-eval-expected-value { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 11px; - } - .cmp-eval-expected-close { - background: none; border: none; color: var(--fg); - font-size: 14px; line-height: 1; padding: 0 0 0 4px; - opacity: 0.5; cursor: pointer; font-family: inherit; - } - .cmp-eval-expected-close:hover { opacity: 1; } - /* Auto-grade badge — stamped on a pane after stream completes when an - eval prompt with a known expected answer was used. */ - .pane-grade-badge { - display: inline-flex; align-items: center; justify-content: center; - width: 18px; height: 18px; - margin: 0 4px; - font-size: 12px; font-weight: 700; - border-radius: 50%; - border: 1px solid currentColor; - flex-shrink: 0; - } - .pane-grade-badge.pass { - color: var(--color-success, #4caf50); - background: color-mix(in srgb, var(--color-success, #4caf50) 12%, transparent); - } - .pane-grade-badge.fail { - color: var(--color-error, #e55); - background: color-mix(in srgb, var(--color-error, #e55) 12%, transparent); - } - - /* Compare probe overlay */ - .compare-probe-overlay { - position: fixed; inset: 0; z-index: 300; - background: rgba(0,0,0,0.5); display: flex; - align-items: center; justify-content: center; - padding: 12px; - box-sizing: border-box; - } - .compare-probe-card { - width: min(460px, calc(100% - 16px)); - max-height: min(620px, calc(100dvh - 24px)); - box-sizing: border-box; - background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px; - display: flex; flex-direction: column; align-items: stretch; gap: 10px; - min-width: 280px; box-shadow: 0 8px 28px rgba(0,0,0,0.28); - overflow: hidden; - font-family: inherit; - } - .compare-probe-title { - font-size: 13px; font-weight: 400; color: var(--fg); - } - .compare-probe-title-label { font-weight: 600; } - .compare-probe-title-status { font-weight: 400; } - .compare-probe-list { - display: grid; grid-template-columns: 1fr 1fr; gap: 6px; min-width: 0; width: 100%; - overflow-y: auto; - } - .compare-probe-row { - display: flex; align-items: center; gap: 6px; padding: 6px 10px; - border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent); - font-size: 12px; transition: background 0.2s; overflow: hidden; - } - .compare-probe-row.fail { - background: color-mix(in srgb, var(--color-error, #f44) 8%, transparent); - } - .compare-probe-spinner { - width: 24px; text-align: center; font-size: 10px; flex-shrink: 0; - letter-spacing: -1px; opacity: 0.6; - } - .compare-probe-spinner.ok { color: var(--color-success); animation: none; } - .compare-probe-spinner.fail { color: var(--color-error, #f44); animation: none; } - .compare-probe-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .compare-probe-status { font-size: 11px; opacity: 0.5; flex-shrink: 0; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .compare-probe-status.ok { color: var(--color-success); opacity: 1; } - .compare-probe-status.fail { color: var(--color-error, #f44); opacity: 1; } - .compare-probe-action-btn { - display: inline-flex; align-items: center; gap: 5px; - min-height: 25px; padding: 3px 8px; background: transparent; color: var(--fg); border: 1px solid var(--border); - border-radius: 5px; cursor: pointer; font-size: 10px; font-family: inherit; - opacity: 0.78; transition: opacity 0.15s, border-color 0.15s, background 0.15s; white-space: nowrap; flex-shrink: 0; - } - .compare-probe-action-btn svg, - .compare-probe-footer-btn svg { width: 12px; height: 12px; flex: 0 0 auto; color: var(--accent, var(--red)); } - .compare-probe-action-btn:hover { opacity: 1; border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--fg) 5%, transparent); } - .compare-probe-feedback { display: flex; flex-direction: column; gap: 5px; } - .compare-probe-feedback:empty { display: none; } - .compare-probe-detail { - display: flex; align-items: center; gap: 6px; min-width: 0; - padding: 6px 8px; font-size: 10px; - background: color-mix(in srgb, var(--color-error, #f44) 6%, transparent); - border-left: 2px solid color-mix(in srgb, var(--color-error, #f44) 72%, transparent); - border-radius: 4px; - } - .compare-probe-detail-icon { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - width: 14px; - height: 14px; - color: var(--accent, var(--red)); - font-family: inherit; - font-size: 11px; - font-weight: 700; - line-height: 1; - } - .compare-probe-detail-message { flex: 1 1 auto; min-width: 0; line-height: 1.35; color: color-mix(in srgb, var(--fg) 72%, transparent); overflow-wrap: anywhere; } - .compare-probe-footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; margin-top: 2px; } - .compare-probe-footer-btn { - display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 5px 10px; - font-family: inherit; font-size: 11px; font-weight: 600; line-height: 1; - } - .compare-probe-start-anyway, - .compare-probe-start-anyway:hover:not(:disabled) { - background: var(--accent, var(--red)); - border: 1px solid var(--accent, var(--red)); - color: var(--bg); - } - .compare-probe-start-anyway svg { color: currentColor; } - .compare-probe-start-anyway span { font-weight: 700; } - @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } - @keyframes pane-shake { - 0%, 100% { transform: translateX(0); } - 15% { transform: translateX(-3px) rotate(-0.5deg); } - 30% { transform: translateX(3px) rotate(0.5deg); } - 45% { transform: translateX(-2px); } - 60% { transform: translateX(2px); } - 75% { transform: translateX(-1px); } - } - - .chat-container.compare-active { - display: flex; - flex-direction: column; - padding: 0; - overflow: hidden; - animation: compare-enter 0.3s ease-out; - } - @keyframes compare-enter { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } - } - .chat-container.compare-active .chat-input-bar { - margin-bottom: 0; - } - .chat-container.compare-active #model-picker-wrap { - display: none !important; - } - .compare-grid { - display: grid; - gap: 4px; - flex: 1 1 0; - min-height: 0; - overflow: hidden; - position: relative; - } - .compare-mobile-tabs { display: none; } - .compare-grid[data-cols="2"] { grid-template-columns: 1fr 1fr; } - .compare-grid[data-cols="3"] { grid-template-columns: 1fr 1fr 1fr; } - .compare-grid[data-cols="4"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="5"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="6"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="7"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="8"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid { grid-auto-rows: 1fr; } - .compare-shuffle-notice { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%) scale(0.94); - z-index: 30; - display: inline-flex; - align-items: center; - gap: 8px; - padding: 10px 14px; - border: 1px solid color-mix(in srgb, var(--red) 28%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 92%, transparent); - color: var(--fg); - box-shadow: 0 10px 30px color-mix(in srgb, #000 24%, transparent); - font-size: 12px; - font-weight: 700; - line-height: 1; - opacity: 0; - pointer-events: none; - transition: opacity 0.14s ease, transform 0.14s ease; - } - .compare-shuffle-notice.show { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - } - .compare-shuffle-notice-icon { - display: inline-flex; - color: var(--red); - animation: compare-shuffle-dice 0.56s ease both; - } - .compare-shuffle-notice-icon svg { - display: block; - width: 18px; - height: 18px; - } - @keyframes compare-shuffle-dice { - 0% { transform: rotate(-18deg) scale(0.9); } - 55% { transform: rotate(380deg) scale(1.08); } - 100% { transform: rotate(360deg) scale(1); } - } - /* Sequential waterfall layout — stacked rows, staggered left, flush right */ - .compare-grid.sequential-layout { - display: flex !important; - flex-direction: column !important; - grid-template-columns: none !important; - gap: 4px; - overflow-y: auto; - } - .compare-grid.sequential-layout .compare-pane { - flex-shrink: 0; - min-height: 200px; - transition: margin-left 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease; - } - /* Herringbone diagonal cut on left side of sequential pane headers */ - .compare-grid.sequential-layout .compare-pane .pane-header { - clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%); - padding-left: 26px; - } - .compare-pane { - display: flex; - flex-direction: column; - border: 1px solid var(--border); - border-radius: 6px; - overflow: hidden; - min-height: 0; - min-width: 0; - position: relative; - transition: border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; - } - .compare-pane::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - z-index: 2; - pointer-events: none; - background: transparent; - transform-origin: left center; - } - .compare-pane.is-streaming { - border-color: color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - } - .compare-pane.is-streaming::before { - background: linear-gradient(90deg, transparent 0%, var(--accent, var(--red)) 35%, var(--red) 50%, var(--accent, var(--red)) 65%, transparent 100%); - animation: compare-pane-progress 1.25s linear infinite; - } - .compare-pane.is-awaiting-input { - border-color: color-mix(in srgb, var(--red) 42%, var(--border)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--red) 12%, transparent); - } - .compare-pane.is-awaiting-input::before { - background: var(--red); - animation: none; - } - .compare-pane.is-done { - border-color: color-mix(in srgb, var(--green, #50fa7b) 20%, var(--border)); - } - .compare-pane.is-failed { - border-color: color-mix(in srgb, var(--color-error) 42%, var(--border)); - } - .compare-pane.is-failed::before { - background: var(--color-error); - } - @keyframes compare-pane-progress { - from { transform: translateX(-100%); } - to { transform: translateX(100%); } - } - .compare-pane .pane-header { - display: flex; - flex-direction: column; - align-items: stretch; - gap: 4px; - padding: 4px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-bottom: 1px solid var(--border); - font-size: 0.82em; - font-weight: 600; - color: var(--fg); - transition: background 0.4s; - flex-shrink: 0; - overflow: visible; - min-width: 0; - flex-wrap: nowrap; - } - .pane-header-row { display: flex; align-items: center; width: 100%; min-width: 0; } - .pane-header-primary { min-height: 24px; } - .pane-header-secondary { min-height: 20px; } - .pane-primary-actions, .pane-stats { display: flex; align-items: center; gap: 4px; min-width: 0; } - .pane-primary-actions { margin-left: auto; flex: 0 0 auto; } - .pane-stats { flex: 1 1 auto; overflow: hidden; } - .compare-header-bar button:hover, - .compare-header-bar button:focus-visible { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)) !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-header-bar button:active { - background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent) !important; - transform: scale(0.96); - } - .compare-more-wrap { position: relative; display: inline-flex; } - .compare-more-btn { - display: inline-flex; align-items: center; justify-content: center; - width: 28px; height: 24px; padding: 0 !important; - border: 1px solid transparent; border-radius: 5px; - background: none; color: var(--fg); cursor: pointer; - opacity: 0.5; transition: opacity .15s, background .15s; - } - .compare-header-bar .compare-close-btn { - width: 24px; height: 24px; min-width: 24px; padding: 0; - border-radius: 999px; font-size: 0; display: inline-flex; - align-items: center; justify-content: center; - } - .compare-header-bar .compare-close-btn:hover, - .compare-header-bar .compare-close-btn:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-header-bar .compare-more-btn:hover, - .compare-header-bar .compare-more-btn:focus-visible, - .compare-header-bar .compare-more-btn[aria-expanded="true"] { - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-more-menu { - position: absolute; top: calc(100% + 5px); right: 0; z-index: 10002; - display: none; flex-direction: column; gap: 3px; min-width: 132px; - padding: 5px; border: 1px solid var(--border); border-radius: 8px; - background: var(--panel, var(--bg)); box-shadow: 0 8px 24px rgba(0,0,0,.3); - } - .compare-more-menu.is-open { display: flex; } - .compare-more-menu > .compare-export-wrap, - .compare-more-menu > button { - width: 100%; display: flex; align-items: center; - } - .compare-more-menu #compare-export-btn, - .compare-more-menu #compare-shuffle-btn, - .compare-more-menu #compare-check-btn, - .compare-more-menu #compare-add-menu-btn { - width: 100%; min-height: 32px; height: 32px; - box-sizing: border-box; justify-content: flex-start; gap: 7px; - padding: 7px 9px !important; - border: 1px solid transparent !important; border-radius: 6px; - background: transparent !important; color: var(--fg) !important; - font-size: 11px; font-weight: 500; opacity: .72; - transition: background .12s ease, opacity .12s ease, color .12s ease; - } - .compare-more-menu #compare-export-btn:hover, - .compare-more-menu #compare-shuffle-btn:hover, - .compare-more-menu #compare-check-btn:hover, - .compare-more-menu #compare-add-menu-btn:hover, - .compare-more-menu #compare-export-btn:focus-visible, - .compare-more-menu #compare-shuffle-btn:focus-visible, - .compare-more-menu #compare-check-btn:focus-visible, - .compare-more-menu #compare-add-menu-btn:focus-visible { - opacity: 1; background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: transparent !important; color: var(--fg) !important; outline: none; - } - .compare-more-menu #compare-export-btn svg, - .compare-more-menu #compare-shuffle-btn svg, - .compare-more-menu #compare-check-btn svg, - .compare-more-menu #compare-add-menu-btn svg { flex: 0 0 auto; opacity: .65; } - .compare-more-menu #compare-export-btn span, - .compare-more-menu #compare-shuffle-btn span, - .compare-more-menu #compare-check-btn span, - .compare-more-menu #compare-add-menu-btn span { - font-size: 11px !important; margin-left: 0 !important; - color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; - } - .compare-more-menu #compare-export-btn, - .compare-more-menu #compare-shuffle-btn, - .compare-more-menu #compare-check-btn, - .compare-more-menu #compare-add-menu-btn { - color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; - } - .compare-more-menu #compare-export-btn svg, - .compare-more-menu #compare-shuffle-btn svg, - .compare-more-menu #compare-check-btn svg, - .compare-more-menu #compare-add-menu-btn svg { color: var(--fg) !important; opacity: .65 !important; } - .compare-export-item:hover, - .compare-export-item:focus-visible { - background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: transparent !important; outline: none; - } - .compare-export-item { display: flex; align-items: center; gap: 8px; } - .compare-export-item > svg { flex: 0 0 auto; opacity: .65; } - .compare-export-menu .compare-export-item, - .compare-export-menu .compare-export-item > span { - color: var(--fg) !important; - opacity: 1 !important; - } - .compare-add-flap { - position: absolute; z-index: 20; right: -8px; top: 50%; - width: 34px; min-width: 34px; height: 30px; box-sizing: border-box; - padding: 0 !important; - display: inline-flex; flex-direction: row; align-items: center; justify-content: center; - gap: 0; border: 1px solid var(--border); - border-radius: 0 7px 7px 0; background: var(--panel, var(--bg)); - color: var(--fg); cursor: pointer; opacity: .7; - box-shadow: 3px 2px 9px color-mix(in srgb, var(--fg) 12%, transparent); - transform: translate(0, -50%); transition: width .18s ease, opacity .15s, background .15s, box-shadow .15s; - } - .compare-add-flap:hover, .compare-add-flap:focus-visible { - width: 70px; opacity: 1; background: color-mix(in srgb, var(--fg) 8%, var(--panel)); - box-shadow: 4px 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); - outline: none; - } - .compare-add-flap span { - max-width: 0; overflow: hidden; white-space: nowrap; margin-left: 0 !important; - font-size: 10px; opacity: 0; transition: max-width .18s ease, margin-left .18s ease, opacity .12s; - } - .compare-add-flap:hover span, .compare-add-flap:focus-visible span { - max-width: 32px; margin-left: 3px !important; opacity: 1; - } - .compare-add-flap svg { flex-shrink: 0; position: relative; left: -3px; } - @media (max-width: 768px) { - .compare-add-flap { - top: 8px; right: 8px; width: auto; min-width: 32px; height: 32px; - padding: 5px 8px !important; flex-direction: row; transform: none; - border-right: 1px solid var(--border); border-radius: 6px; - } - .compare-add-flap span { opacity: 1; } - } - .pane-actions { - display: flex; gap: 4px; align-items: center; margin-left: auto; flex-shrink: 0; - } - .compare-pane-footer { - font-size: 0.72em; opacity: 0.4; padding: 4px 10px; - border-top: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; flex-shrink: 0; - } - /* Per-pane vote button. Sits at the bottom of each compare pane so - the action lives right under the response it judges. */ - .pane-vote-footer { - padding: 6px 8px; - border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex-shrink: 0; - } - .pane-vote-btn { - width: 100%; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 6px 10px; - font-family: inherit; - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: background 0.15s, border-color 0.15s, opacity 0.15s; - } - .pane-vote-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); - border-color: var(--accent, var(--fg)); - } - .pane-vote-btn:disabled { cursor: not-allowed; } - .pane-vote-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; - } - .pane-action-btn { - background: none; border: none; color: var(--fg); cursor: pointer; - opacity: 0.3; padding: 2px; border-radius: 4px; - display: flex; align-items: center; transition: all 0.15s; - } - .pane-action-btn:hover { opacity: 0.8; background: color-mix(in srgb, var(--fg) 6%, transparent); } - /* Pane title as clickable model-swap button */ - .pane-title-btn { - background: none; border: none; cursor: pointer; - font-size: 10px; font-weight: 400; font-family: inherit; - color: var(--fg); padding: 0; - text-align: left; display: flex; align-items: center; gap: 4px; - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - transition: opacity 0.15s; - min-width: 0; flex: 1 1 0; - } - .pane-title-btn:hover { opacity: 0.7; } - .pane-title-caret { font-size: 0.6em; opacity: 0.35; flex-shrink: 0; position: relative; top: 2px; } - .pane-title-btn:hover .pane-title-caret { opacity: 0.7; } - .pane-mode-badge { - display: inline-flex; - align-items: center; - gap: 4px; - flex: 0 0 auto; - height: 18px; - padding: 0 6px; - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - color: color-mix(in srgb, var(--fg) 76%, transparent); - font-size: 9px; - font-weight: 600; - line-height: 1; - letter-spacing: 0; - white-space: nowrap; - } - .pane-mode-dot { - width: 5px; - height: 5px; - border-radius: 50%; - background: currentColor; - opacity: 0.75; - flex: 0 0 auto; - } - .pane-mode-detail { - opacity: 0.52; - font-weight: 500; - } - .pane-mode-badge.pane-mode-icon-only { - width: 20px; - padding: 0; - justify-content: center; - } - .pane-mode-settings { - opacity: 0.65; - cursor: pointer; - } - .pane-settings-btn { width: 20px; height: 20px; justify-content: center; } - .pane-settings-btn[aria-expanded="true"] { opacity: 1; background: color-mix(in srgb, var(--fg) 9%, transparent); } - .response-metrics[data-action="settings"] { cursor: pointer; } - .response-thinking-state { color: color-mix(in srgb, var(--fg) 55%, transparent); } - .pane-mode-settings:hover, - .pane-mode-settings:focus-visible, - .pane-mode-settings[aria-expanded="true"] { - opacity: 1; - color: var(--fg); - background: color-mix(in srgb, var(--fg) 9%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--fg)) 45%, var(--border)); - outline: none; - } - .pane-mode-icon, - .pane-mode-icon svg { - display: block; - width: 12px; - height: 12px; - } - .pane-mode-agent { - color: color-mix(in srgb, var(--fg) 76%, transparent); - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - border-color: color-mix(in srgb, var(--border) 70%, transparent); - } - .pane-mode-search { - color: color-mix(in srgb, var(--accent, var(--red)) 82%, var(--fg)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, var(--border)); - } - .pane-mode-research { - color: color-mix(in srgb, #36c48f 82%, var(--fg)); - background: color-mix(in srgb, #36c48f 10%, var(--bg)); - border-color: color-mix(in srgb, #36c48f 28%, var(--border)); - } - .compare-check-label { - position: relative; - top: -2px; - font-size: 11px; - margin-left: 3px; - } - .compare-check-icon { - position: relative; - top: 1px; - } - #compare-check-btn > .spinner-whirlpool { - position: relative; - top: -2px; - transform: none; - } - .compare-pane .pane-close-btn { opacity: 0.3; } - .compare-pane .pane-close-btn:hover { opacity: 1; color: var(--color-error); } - /* Model swap dropdown under pane title */ - .pane-model-dropdown { - position: absolute; - z-index: 1000; - min-width: 220px; - max-height: 300px; - overflow-y: auto; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 16px rgba(0,0,0,0.3); - padding: 4px; - } - .pane-model-item { - display: block; width: 100%; - box-sizing: border-box; - padding: 8px 10px; font-size: 11px; line-height: 1.3; - text-align: left; background: none; border: none; border-radius: 6px; - color: var(--fg); cursor: pointer; transition: background 0.12s ease; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - } - .pane-model-item:hover, - .pane-model-item:focus-visible { background: color-mix(in srgb, var(--red) 10%, transparent); outline: none; } - .pane-model-item.current { color: var(--red); font-weight: 600; } - .pane-timer { - font-size: 10px; font-weight: 400; opacity: 0.45; font-variant-numeric: tabular-nums; - white-space: nowrap; padding-right: 4px; - } - .pane-summary { - display: inline-flex; - align-items: center; - position: relative; - top: 1px; - min-height: 16px; - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding: 0 5px; - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 9px; - font-weight: 500; - line-height: 1; - font-variant-numeric: tabular-nums; - cursor: pointer; - transition: max-width .15s ease, white-space .15s ease; - } - .pane-summary:empty { display: none; } - .pane-stats-popup { - position: fixed; z-index: 10040; width: 210px; padding: 6px; - background: var(--panel, var(--bg)); color: var(--fg); - border: 1px solid var(--border); border-radius: 7px; - box-shadow: 0 8px 24px rgba(0,0,0,.3); font-size: 11px; - } - .pane-stats-popup-title { padding: 4px 6px 7px; font-weight: 600; border-bottom: 1px solid var(--border); } - .pane-stats-popup-row { display: flex; justify-content: space-between; gap: 12px; padding: 6px; } - .pane-stats-popup-row span { color: color-mix(in srgb, var(--fg) 55%, transparent); } - .pane-stats-popup-row strong { font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; } - /* Metrics stay on the lower row even in dense comparisons. */ - .compare-grid[data-cols="4"] .pane-timer, - .compare-grid[data-cols="5"] .pane-timer, - .compare-grid[data-cols="6"] .pane-timer { - width: auto; order: initial; margin-top: 0; padding-bottom: 0; padding-left: 0; - } - .pane-finish-badge { - font-weight: 600; color: var(--green, #50fa7b); - position: relative; - top: 2px; - } - .compare-pane.winner .pane-header { - background: color-mix(in srgb, var(--green, #50fa7b) 12%, transparent); - border-bottom-color: color-mix(in srgb, var(--green, #50fa7b) 30%, var(--border)); - } - .compare-pane.winner .pane-title { - color: var(--green, #50fa7b); - } - .compare-pane.loser .pane-header { - opacity: 0.5; - } - .confetti-piece { - position: fixed; - pointer-events: none; - z-index: 1000000; - } - .compare-pane.expanded { grid-column: 1 / -1; } - .compare-pane .chat-history { - flex: 1 1 0; - min-height: 0; - overflow-y: auto !important; - overflow-x: hidden; - padding: 8px; - display: flex; - flex-direction: column; - } - .compare-pane .chat-history .msg { - flex-shrink: 0; - } - .compare-gen-image { - max-width: 100%; - max-height: 100%; - object-fit: contain; - border-radius: 4px; - cursor: pointer; - } - .compare-section { - margin-bottom: 14px; - } - .compare-section:last-child { - margin-bottom: 0; - } - .compare-section-label { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.6px; - opacity: 0.5; - margin-bottom: 5px; - font-weight: 600; - } - /* Active-type/mode readout next to "Type:"/"Mode:" — only needed on mobile - (where the tab/toggle text labels are hidden); hidden on desktop. */ - .compare-type-current, - .compare-mode-current { display: none; } - /* Contextual one-liner under the mode toggles describing what you just - changed — empty until the first toggle, then a subtle hint. */ - .compare-mode-hint { - font-size: 11px; - opacity: 0.55; - margin-top: 6px; - min-height: 0; - line-height: 1.3; - } - .compare-mode-hint:empty { display: none; } - .compare-type-tabs .compare-mode-tab > svg, - .compare-type-current > svg { - color: var(--accent, var(--red)); - } - - .compare-mode-tabs { - display: flex; - gap: 4px; - flex-wrap: wrap; - min-width: 0; - } - /* Type tabs match Mode toggles 1:1 (same flex column layout, same metrics) */ - .compare-mode-tab { - display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 56px; height: 38px; flex: 1 1 0; - padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; - cursor: pointer; user-select: none; transition: all 0.15s; - background: none; color: var(--fg); opacity: 0.35; - flex-shrink: 0; gap: 0; - font-family: inherit; - } - .compare-mode-tab > svg { - width: 13px; height: 13px; flex: 0 0 13px; - } - .compare-mode-tab:hover { - opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); - } - .compare-mode-tab.active { - opacity: 1; border-color: var(--fg); - background: color-mix(in srgb, var(--fg) 10%, transparent); - } - .compare-sources-box { - display: flex; align-items: center; gap: 6px; - padding: 6px 10px; margin-bottom: 8px; - border-radius: 6px; font-size: 0.78em; - background: color-mix(in srgb, var(--red) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - cursor: default; - } - .compare-sources-box .sources-label { font-weight: 600; } - /* Compact tool blocks inside compare panes */ - .compare-pane .agent-thread-node { - margin: 4px 0; font-size: 0.85em; - } - .compare-pane .agent-thread-cmd { - max-height: 80px; overflow-y: auto; - } - .compare-pane .agent-tool-output pre { - max-height: 120px; overflow-y: auto; - } - .compare-vote-bar { - display: flex; justify-content: center; gap: 8px; - padding: 8px; border-top: 1px solid var(--border); - flex-shrink: 0; flex-wrap: wrap; - } - .compare-vote-bar.hidden { display: none; } - .compare-vote-btn { - padding: 6px 13px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; font-size: 0.8em; - transition: all 0.15s; white-space: nowrap; - } - .compare-score-label { - position: relative; - top: -2px; - display: inline-block; - } - .compare-vote-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .compare-vote-tie { opacity: 0.7; } - .compare-rematch-btn { - display: flex; align-items: center; gap: 6px; margin-left: 8px; - border-color: color-mix(in srgb, var(--accent, var(--red)) 60%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--fg); opacity: 1; font-weight: 600; - } - .compare-rematch-btn:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent); } - /* Preview button accent in pane header */ - .pane-preview-btn.active { color: var(--red); } - /* Full-pane iframe for HTML preview */ - .compare-pane-iframe { - flex: 1; - width: 100%; - border: none; - border-radius: 0 0 8px 8px; - background: #fff; - } - /* ---- Add-pane "+" button in pane header (last pane only) ---- */ - .pane-add-btn { display: none !important; font-size: 16px; font-weight: 600; } - .compare-pane:last-child .pane-add-btn { display: flex !important; } - /* Dropdown for adding a pane */ - .add-pane-dropdown { - position: absolute; - right: 0; - z-index: 100; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 10px; - max-height: 300px; - overflow-y: auto; - min-width: 220px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); - padding: 6px; - backdrop-filter: blur(12px); - animation: dropdown-in 0.15s ease-out; - } - .add-pane-search { - width: 100%; - padding: 6px 8px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font-size: 0.85em; - box-sizing: border-box; - margin-bottom: 4px; - position: sticky; - top: 0; - z-index: 1; - } - /* Compare toggle buttons — icon + label stacked */ - .compare-toggle-label { - display: block; - font-size: 9px; - line-height: 1; - margin-top: 2px; - font-weight: 500; - } - .compare-blind-toggle, - .compare-save-toggle, - .compare-dice-toggle, - .compare-parallel-toggle, - .compare-reset-toggle { - display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 56px; height: auto; flex: 1 1 0; - padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; - cursor: pointer; user-select: none; transition: all 0.15s; - background: none; color: var(--fg); opacity: 0.35; - flex-shrink: 0; gap: 0; - } - .compare-blind-toggle:hover, - .compare-save-toggle:hover, - .compare-dice-toggle:hover, - .compare-parallel-toggle:hover, - .compare-reset-toggle:hover { - opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); - } - .compare-blind-toggle.active { - opacity: 1; color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - } - .compare-blind-toggle.active > svg { - color: var(--accent, var(--red)); - } - .compare-save-toggle.active { - opacity: 1; color: var(--color-save-green); border-color: var(--color-save-green); - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .compare-dice-toggle.active { - opacity: 1; color: var(--red); border-color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .compare-parallel-toggle { - opacity: 1; color: #e0a050; border-color: #e0a050; - background: rgba(224, 160, 80, 0.1); - } - .compare-parallel-toggle.active { - color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - } - @media (max-width: 520px) { - .compare-toggle-label { display: none; } - /* Tab text labels are hidden on mobile, so spell out the active type next - to "Type:" with its icon. */ - .compare-type-current { - display: inline-flex; - align-items: center; - gap: 4px; - margin-left: 5px; - font-weight: 600; - vertical-align: -3px; - } - .compare-type-current svg { width: 14px; height: 14px; } - /* Mode list — comma-separated, each name already coloured inline. */ - .compare-mode-current { - display: inline; - margin-left: 5px; - font-weight: 600; - } - .compare-blind-toggle, - .compare-save-toggle, - .compare-dice-toggle, - .compare-parallel-toggle, - .compare-reset-toggle { - width: 32px; height: 32px; min-width: 32px; padding: 0; - } - /* The Group tab's seq/parallel toggle is a single full-width button (not - a row of compact icons like the Compare header), so keep its label and - make it a comfortable touch target with the text beside the icon. */ - #group-mode-btn { - flex-direction: row !important; - width: auto !important; - height: auto !important; - min-height: 44px; - padding: 8px 14px !important; - gap: 8px !important; - font-size: 13px; - } - #group-mode-btn .compare-toggle-label { - display: inline !important; - font-size: 13px; - margin-top: 0; - } - #group-mode-btn svg { width: 18px; height: 18px; } - /* Compare header: hide labels + close button, show icons only */ - #compare-shuffle-btn span { - display: none; - } - #compare-shuffle-btn, - #compare-check-btn, - #compare-add-btn { - padding: 3px 6px; - } - /* Save space so the header buttons fit on one row: tighter padding + - smaller labels (icons kept full size). (Score now lives in the vote bar.) */ - .compare-header-bar button { padding: 3px 5px !important; } - .compare-header-bar #compare-check-btn > span, - .compare-header-bar #compare-add-btn > span { - font-size: 10px; margin-left: 2px; - } - /* Compare mobile: keep the close X visible — it's the only way out - now that the hamburger is hidden during compare mode. */ - .compare-header-bar { - padding: 14px 8px 10px 8px !important; - min-height: 44px; - } - .compare-pane .pane-header { - align-items: stretch; - row-gap: 3px; - } - .compare-pane .pane-title-btn { - flex: 1 1 auto; - width: auto; - padding-right: 2px; - } - /* Compare is already visibly in chat mode; the Chat speech-bubble - badge adds a redundant second row on narrow screens. */ - .compare-pane .pane-mode-chat { display: none; } - .compare-pane .pane-timer, - .compare-pane .pane-summary, - .compare-pane .pane-finish-badge, - .compare-pane .pane-mode-badge, - .compare-pane .pane-actions { - order: 1; - align-self: center; - } - .compare-pane .pane-finish-badge { top: 0; } - .compare-pane .pane-summary { - max-width: 110px; - } - .compare-pane .pane-mode-detail { - display: none; - } - .compare-grid[data-cols="4"] .compare-pane .pane-timer, - .compare-grid[data-cols="5"] .compare-pane .pane-timer, - .compare-grid[data-cols="6"] .compare-pane .pane-timer { - width: auto; - order: 1; - margin-top: 0; - padding-bottom: 0; - padding-left: 0; - } - .compare-pane .pane-actions { - margin-left: auto; - } - /* Mode tabs: icons only, centered */ - .compare-mode-tab span { display: none; } - .compare-mode-tabs { justify-content: center; } - /* Header action buttons: hide text labels on Export / Shuffle / - Model on mobile so the close X fits on the right. Score keeps - its "Score" label because its icon (4-square grid) reads too - similar to Shuffle's icon without text. */ - .compare-header-bar #compare-export-btn > span, - .compare-header-bar #compare-shuffle-btn > span { - display: none; - } - /* Override the desktop override: on mobile the model picker overlay - MUST cap its height to the viewport so the dropdown doesn't run - past the bottom of the screen. */ - #compare-model-overlay .modal-content { - max-height: 85dvh !important; - max-height: 85vh !important; - overflow: hidden !important; - } - #compare-model-overlay .modal-body { - overflow: auto !important; - flex: 1 1 auto !important; - min-height: 0 !important; - } - } - /* Hide number input spinners */ - input[type="number"]::-webkit-inner-spin-button, - input[type="number"]::-webkit-outer-spin-button { - -webkit-appearance: none; margin: 0; - } - - /* ── Sensitive info censor ── */ - .censored-item { - filter: blur(5px); - cursor: pointer; - transition: filter 0.2s; - border-radius: 2px; - padding: 0 2px; - background: rgba(255,100,100,0.08); - user-select: none; - } - .censored-item:hover { filter: blur(3px); background: rgba(255,100,100,0.15); } - .censored-item.revealed { - filter: none; - background: rgba(100,255,100,0.08); - user-select: auto; - cursor: text; - } - - #settings-menu-list .list-item.active { - background: color-mix(in srgb, var(--accent) 10%, transparent); - border-color: var(--accent); - } - - /* ── Print / PDF Export ── */ - @media print { - body { background: #fff !important; color: #000 !important; } - #sidebar, .sidebar, #icon-rail, .hamburger-btn, #sidebar-backdrop, .chat-input-bar, .input-bar-wrapper, - #welcome-screen, .chat-top-bar, .chat-meta-overlay, .msg-footer, - .modal, .toast, .overflow-wrapper, .mode-toggle, .incognito-btn, - button, .dropdown, .session-dropdown, - .agent-tool-spinner, .agent-thread-node.running { display: none !important; } - main.chat-container { width: 100% !important; margin: 0 !important; padding: 0 !important; max-height: none !important; overflow: visible !important; } - #chat-history { max-height: none !important; overflow: visible !important; height: auto !important; padding: 0 !important; } - .msg { break-inside: avoid; page-break-inside: avoid; border: none !important; box-shadow: none !important; } - .msg-ai { background: #f5f5f5 !important; color: #000 !important; } - .msg-user { background: #e8e8e8 !important; color: #000 !important; } - .msg .role { color: #333 !important; font-weight: bold; } - .msg .body { color: #000 !important; } - pre, code { background: #f0f0f0 !important; color: #000 !important; border: 1px solid #ccc !important; } - details { display: block !important; } - details[open] summary ~ * { display: block !important; } - details > summary { list-style: none; } - details > summary::before { content: "" !important; } - #chat-history::before { content: attr(data-print-title); display: block; font-size: 1.3em; font-weight: bold; margin-bottom: 1em; color: #000; } - a { color: #000 !important; text-decoration: underline; } - } - - -/* ── Components (from style.css) ── */ - -/* Self-hosted Fira Code font */ -@font-face { font-family: 'Fira Code'; font-weight: 300; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Light.woff2') format('woff2'); } -@font-face { font-family: 'Fira Code'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Regular.woff2') format('woff2'); } -@font-face { font-family: 'Fira Code'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-SemiBold.woff2') format('woff2'); } - -/* Scrollbar styling */ - -/* Code block styling */ -pre, code, .hljs { - font-size: 11px; - line-height: 1.5; -} - -/* WebKit (Chrome, Edge, Safari) */ -/* Utility class for red text */ -.red-text { - color: var(--red); -} - -/* Internal chat links (search results, session references) */ -a.chat-link { - color: var(--hl-function); - text-decoration: none; - border-bottom: 1px dotted var(--hl-function); - cursor: pointer; -} -a.chat-link:hover { - opacity: 0.8; - border-bottom-style: solid; -} - -a.chat-link[href^="#email-"] { - display: inline; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, transparent); - border-radius: 5px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - color: var(--fg); - font-weight: 600; - border-bottom-style: solid; -} -a.chat-link[href^="#email-"]:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -a.chat-link[href^="#notes-more-"], -a.chat-link[href^="#skills-more-"], -a.chat-link[href^="#memories-more-"], -a.chat-link[href^="#events-more-"], -a.chat-link[href^="#sessions-more-"] { - color: var(--muted); - border-bottom: 1px dotted color-mix(in srgb, var(--muted) 55%, transparent); - font-size: 11px; - font-weight: 500; -} -a.chat-link[href^="#notes-more-"]:hover, -a.chat-link[href^="#skills-more-"]:hover, -a.chat-link[href^="#memories-more-"]:hover, -a.chat-link[href^="#events-more-"]:hover, -a.chat-link[href^="#sessions-more-"]:hover { - color: var(--fg); - opacity: 1; - border-bottom-style: solid; -} -.more-list-expanded { - display: block; - max-height: 0; - overflow: hidden; - opacity: 0; - color: inherit; - font: inherit; - line-height: inherit; - transition: max-height 220ms ease, opacity 160ms ease; -} -.more-list-expanded.is-open { - opacity: 1; -} -.more-list-expanded-content { - color: inherit; - font: inherit; - line-height: inherit; -} - -/* Session items */ -.session-item { position: relative; } -.text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.session-menu-btn { padding: 0 2px !important; min-width: 20px; height: 20px; display: inline-flex !important; align-items: center; justify-content: center; background: none !important; border-color: transparent !important; } -.session-menu-btn:hover { background: none !important; border-color: transparent !important; } -@media (max-width: 768px) { - .session-menu-btn { display: none !important; } - .item-drag-handle { display: none !important; } -} -.session-menu-btn svg { transition: transform 0.2s ease; } - -/* First-time swipe hint */ -.swipe-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 0.7rem; - color: var(--color-error, #f44); - opacity: 0.8; - transition: opacity 0.5s ease; - pointer-events: none; - display: flex; - align-items: center; - gap: 4px; - z-index: 2; -} -.swipe-hint-arrow { - animation: swipe-nudge 1s ease-in-out infinite; -} -@keyframes swipe-nudge { - 0%, 100% { transform: translateX(0); } - 50% { transform: translateX(-6px); } -} - -/* Utility classes */ -.muted { opacity: 0.5; } -.muted-sm { opacity: 0.35; font-size: 0.8em; } -.accent-link { color: var(--accent-primary, var(--color-accent)); cursor: pointer; font-size: 0.85em; } -.models-empty-state { text-align: center; padding: 12px 8px; line-height: 1.6; } - -/* Provider logo inside favorite dot */ -.provider-logo { - border: none !important; - background: none !important; - width: 14px !important; height: 14px !important; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.15s; -} -.provider-logo svg { width: 14px; height: 14px; display: block; } -.provider-logo:hover { opacity: 1 !important; transform: scale(1.2); } - -/* Hide session menu button until hover — use width:0 so it doesn't steal space from text */ -.list-item .hamburger { opacity: 0; width: 0; min-width: 0; overflow: hidden; padding: 0 !important; transition: opacity 0.15s, width 0.15s, padding 0.15s; flex-shrink: 0; display: flex; align-items: center; justify-content: center; } -.list-item:hover .hamburger { opacity: 1; width: 24px; min-width: 24px; padding: 0 4px !important; } -@media (max-width: 768px) { - .list-item .hamburger { opacity: 0.5; width: 28px; min-width: 28px; padding: 0 4px !important; } - .list-item .hamburger:active { opacity: 1; } -} - -/* Hamburger menu button styling (overrides default button appearance) */ -button.hamburger { - background: none; - border: none; - padding: 0; - cursor: pointer; -} - -/* ============================================ */ -/* HEADER SIZING FOR CHAT MESSAGES */ -/* ============================================ */ -/* Markdown in chat messages — colorful, scannable */ -.msg h1, .msg h2, .msg h3, .msg h4, .msg h5, .msg h6 { - margin: 0.6em 0 0.3em 0; - line-height: 1.3; - border-bottom: none; - padding-bottom: 0; -} - -.msg h1 { - font-size: 1.15em; - font-weight: 700; - color: var(--hl-keyword, #c678dd); -} - -.msg h2 { - font-size: 1.1em; - font-weight: 600; - color: var(--hl-function, #5b8def); -} - -.msg h3 { - font-size: 1.05em; - font-weight: 600; - color: var(--hl-string, #98c379); -} - -.msg h4 { - font-size: 1.02em; - font-weight: 600; - color: var(--hl-builtin, #e5c07b); -} - -.msg h5 { - font-size: 1em; - font-weight: 600; - color: var(--hl-variable, #61afef); -} - -.msg h6 { - font-size: 0.95em; - font-weight: 600; - color: var(--hl-number, #d19a66); -} - -/* Bold text — subtle accent color */ -.msg strong, .msg b { - color: var(--hl-builtin, #e5c07b); - font-weight: 600; -} - -/* Italic — softer highlight */ -.msg em, .msg i { - color: var(--hl-params, #abb2bf); - font-style: italic; -} - -/* Bold + italic */ -.msg strong em, .msg em strong, -.msg b i, .msg i b, -.msg b em, .msg em b, -.msg strong i, .msg i strong { - color: var(--hl-keyword, #c678dd); -} - -/* Strikethrough */ -.msg del { - color: color-mix(in srgb, var(--fg) 45%, transparent); - text-decoration: line-through; -} - -/* Inline code */ -.msg code:not(pre code) { - color: var(--hl-string, #98c379); - background: color-mix(in srgb, var(--fg) 6%, transparent); - padding: 1px 5px; - border-radius: 4px; - font-size: 0.9em; -} - -/* Blockquotes */ -.msg blockquote { - border-left: 3px solid var(--hl-function, #5b8def); - padding: 4px 12px; - margin: 0.5em 0; - color: color-mix(in srgb, var(--fg) 75%, transparent); - background: var(--bg); - border-radius: 0 6px 6px 0; -} -.msg blockquote p { margin: 0.3em 0; } - -/* Horizontal rules */ -.msg hr { - border: none; - height: 1px; - background: linear-gradient(90deg, transparent, var(--border), transparent); - margin: 0.8em 0; -} - -/* Lists */ -.msg ul, .msg ol { - margin: 0.3em 0 0.3em 1.2em; - padding: 0; -} -.msg li { - margin: 0.15em 0; -} -.msg li::marker { - color: var(--hl-function, #5b8def); -} - -/* Links */ -.msg a { - color: var(--hl-function, #5b8def); - text-decoration: none; - border-bottom: 1px solid rgba(91, 141, 239, 0.3); - transition: border-color 0.15s; -} -.msg a:hover { - border-bottom-color: var(--hl-function, #5b8def); -} -.msg a.is-loading { - opacity: 0.65; - border-bottom-color: var(--accent, #bd93f9); - cursor: progress; -} - -/* Tables */ -.msg table { - border-collapse: collapse; - margin: 0.5em 0; - font-size: 0.9em; - display: block; - width: max-content; - min-width: 100%; - max-width: 100%; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - table-layout: auto; -} -.msg th { - background: color-mix(in srgb, var(--fg) 7%, transparent); - color: var(--hl-keyword, #c678dd); - font-weight: 600; - padding: 6px 12px; - border: 1px solid var(--border); - text-align: left; - min-width: 9ch; - word-break: normal; - overflow-wrap: break-word; -} -.msg td { - padding: 5px 12px; - border: 1px solid var(--border); - min-width: 9ch; - word-break: normal; - overflow-wrap: break-word; -} - -/* Agent UI Styling */ -.agent-controls { - background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 12px; - margin-bottom: 12px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.agent-toggle label { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-weight: 500; - margin-bottom: 8px; -} - -#workflow-selector { - margin-top: 8px; -} - -#workflow-type { - width: 100%; - padding: 6px 12px; - border: 1px solid #ced4da; - border-radius: 4px; - background: white; - font-size: 14px; -} - -.agent-progress { - background: #ffebee; - border: 1px solid #ef9a9a; - border-radius: 6px; - padding: 12px; - margin: 8px 0; - text-align: center; -} - -.agent-working { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - font-style: italic; - color: var(--red); -} - -.loading-dots::after { - content: '...'; - animation: dots 1.5s infinite; -} - -@keyframes dots { - 0%, 20% { opacity: 0; } - 40% { opacity: 0.5; } - 60%, 100% { opacity: 1; } -} - -.workflow-info { - background: #f8f9fa; - border: 1px solid var(--red); - border-radius: 6px; - padding: 8px 12px; - margin: 4px 0; - font-size: 0.9em; - color: var(--red); - text-align: center; -} - -/* Scrollbar uses --red from :root (set at top of file) */ - -/* Loading spinner */ -@keyframes spin { - to { transform: rotate(360deg); } -} -.spinner { - width: 24px; - height: 24px; - margin: 8px auto; - border: 3px solid var(--border); - border-top-color: var(--red); - border-radius: 50%; - animation: spin 0.9s linear infinite; -} - -/* Inline spinner for buttons */ -.btn-spinner { - display: inline-block; - width: 12px; - height: 12px; - border: 2px solid transparent; - border-top-color: currentColor; - border-radius: 50%; - animation: spin 1s linear infinite; - margin-right: 6px; -} - -/* Loading indicator for messages */ -.loading-indicator { - display: flex; - align-items: center; - justify-content: center; - padding: 10px; -} -.loading-dots { - display: flex; - gap: 4px; -} -.loading-dot { - width: 6px; - height: 6px; - background-color: var(--fg); - border-radius: 50%; - opacity: 0.6; -} -.loading-dot:nth-child(1) { - animation: loading-bounce 1.4s infinite ease-in-out both; -} -.loading-dot:nth-child(2) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.32s; -} -.loading-dot:nth-child(3) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.64s; -} -@keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } -} - -/* Hamburger menu button */ -.hamburger { - display: inline-flex; - flex-direction: column; - justify-content: space-between; - width: 24px; - height: 18px !important; - background: none; - border: none; - padding: 0; - cursor: pointer; -} -.hamburger span { - display: block; - width: 100%; - height: 3px; - background: var(--fg); - border-radius: 2px; -} - -/* Agent indicator */ -#agent-indicator { - position: fixed; - top: 20px; - right: 20px; - background: var(--sidebar-bg, var(--panel)); - color: var(--fg); - border: 1px solid var(--border); - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - display: none; - z-index: 100; - cursor: pointer; - transition: all 0.2s ease; -} -#agent-indicator.active { - display: block; - border-color: var(--color-agent-active); - box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); -} -#agent-indicator:hover { - border-color: var(--color-agent-active); - background: var(--panel); -} - -/* Preset buttons */ -.preset-btn { - height: 27.2px; - padding: 0 8.5px; - margin-left: 4px; - border: 1px solid var(--red); - border-radius: 4px; - background: var(--sidebar-bg, var(--panel)); - color: var(--fg); - font-family: inherit; - font-size: 10.2px; - cursor: pointer; - transition: all 0.2s ease; -} - -.preset-btn:hover { - background: var(--panel); - border-color: var(--fg); -} - -.preset-btn.active { - background: var(--panel); - border-color: var(--red); - box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); - font-weight: 600; -} - -/* Custom preset modal — inherits from .modal base class */ - -/* Unified chat input area */ -.chat-input-area { - display: flex; - flex-direction: column; - gap: 8px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin-top: 12px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.chat-controls-row { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; -} - -.chat-controls-left { - display: flex; - align-items: center; - gap: 8px; -} - -.chat-controls-right { - display: flex; - align-items: center; - gap: 8px; -} - -.control-group { - display: flex; - align-items: center; - gap: 4px; -} - -.control-label { - font-size: 11px; - color: var(--fg); - opacity: 0.8; -} - -.toggle-switch { - position: relative; - display: inline-block; - width: 30px; - height: 16px; -} - -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; -} - -.toggle-slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 8px; - transition: background 0.08s; -} - -.toggle-slider:before { - position: absolute; - content: ""; - height: 12px; - width: 12px; - left: 4px; - top: 2px; - background-color: var(--panel); - border-radius: 50%; - transition: transform 0.08s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); -} - -.toggle-switch input:checked + .toggle-slider { - background-color: var(--toggle-active, var(--red)); -} - -.toggle-switch input:checked + .toggle-slider:before { - transform: translateX(14px); -} - -.preset-buttons-row { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: wrap; -} - -.chat-input-form { - display: flex; - gap: 8px; - align-items: flex-end; -} - -#message { - flex: 1; - min-height: 34px; - max-height: 120px; - resize: none; - font-size: 13px !important; - overflow-y: auto !important; - line-height: 1.4 !important; - font-family: inherit !important; -} - -.action-button { - width: 34px; - height: 34px; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - margin: 0; - background: none; - border: 1px solid var(--border); - border-radius: 4px; - cursor: pointer; - color: var(--fg); - transition: all 0.2s ease; -} - -.action-button:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: var(--fg); -} - -.action-button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.action-button.recording { - background: var(--color-recording); - border-color: var(--color-recording); - animation: pulse 1.5s infinite; -} - -@keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } -} - -#stop-icon { - display: none; - width: 14px; - height: 14px; - background: var(--color-recording); - border-radius: 2px; -} - -/* Attachment strip — centered + max-width to match the chat-input-bar below, - otherwise the chip floats flush-left while the input is centered (visible on - desktop where the chat area is wider than 800px). */ -.attach-strip { - display: flex; - gap: 6px; - flex-wrap: wrap; - padding: 2px 8px; - max-width: 800px; - width: 100%; - margin-left: auto; - margin-right: auto; - box-sizing: border-box; -} -.attach-strip:empty { display: none; } - -/* Upload-in-progress feedback: the message bubble shows immediately, so while - the files are still uploading we put a whirlpool ON each attachment chip and - dim the chip's content — making it obvious that file is being sent, not stuck. */ -.attach-strip.attach-uploading .thumb { - position: relative; - pointer-events: none; -} -.attach-strip.attach-uploading .thumb > :not(.thumb-upload-spinner) { - opacity: 0.4; -} -.thumb-upload-spinner { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; - z-index: 3; -} - -.thumb { - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 11%, transparent); - padding: 3px 6px; - font-size: 12px; - display: flex; - gap: 6px; - align-items: center; - border-radius: 4px; - transition: all 0.2s ease; - max-width: 180px; -} -.thumb-img { - max-width: 60px; - max-height: 40px; - border-radius: 3px; - object-fit: cover; -} -.attach-image-preview { - margin: 4px 0; -} -.attach-image-preview img { - box-shadow: 0 1px 4px rgba(0,0,0,0.2); - /* Same border as the chat bubbles. */ - border: 1px solid var(--bubble-border, var(--border)); -} -/* Image chips: image fills the chip, X overlays as a corner accent badge. - Same on desktop and mobile — doc/text chips keep the beside-X layout. */ -.thumb.thumb-image { - position: relative; - padding: 0; -} -.thumb.thumb-image .thumb-img { - max-height: 56px; - display: block; -} -@media (max-width: 768px) { - .thumb.thumb-image .thumb-img { - cursor: pointer; - touch-action: manipulation; - } -} -.thumb.thumb-image button { - position: absolute; - /* Sit on the top-right corner edge as an accent badge. */ - top: -7px; - right: -7px; - width: 24px; - height: 24px; - min-width: 0; - padding: 0; - border: 2px solid var(--bg); - border-radius: 50%; - background: var(--accent-primary, var(--red)); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 15px; - line-height: 1; - z-index: 3; - transition: transform 0.12s ease, filter 0.12s ease, box-shadow 0.12s ease; -} -.thumb.thumb-image button:hover { - transform: scale(1.12); - filter: brightness(1.12); - box-shadow: 0 2px 8px rgba(0,0,0,0.25); -} -.thumb.thumb-image button:active { - transform: scale(0.96); -} - -.attach-crop-overlay { - position: fixed; - inset: 0; - z-index: var(--attach-crop-z, 100001); - background: rgba(0, 0, 0, 0.72); - display: flex; - align-items: center; - justify-content: center; - padding: 14px; - box-sizing: border-box; -} -.attach-crop-panel { - width: min(94vw, 520px); - max-height: min(86vh, 720px); - background: var(--panel, #111); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 18px 50px rgba(0,0,0,0.45); - overflow: hidden; - display: flex; - flex-direction: column; -} -.attach-crop-stage { - position: relative; - min-height: 280px; - height: min(66vh, 560px); - background: #050505; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - touch-action: none; -} -.attach-crop-img { - max-width: 100%; - max-height: 100%; - object-fit: contain; - user-select: none; - pointer-events: none; -} -.attach-crop-box { - position: absolute; - border: 2px solid var(--accent, var(--red)); - box-shadow: 0 0 0 9999px rgba(0,0,0,0.42); - cursor: move; - touch-action: none; - box-sizing: border-box; -} -.attach-crop-box::before, -.attach-crop-box::after { - content: ''; - position: absolute; - inset: 33.333% 0 auto; - border-top: 1px solid rgba(255,255,255,0.38); -} -.attach-crop-box::after { - inset: 66.666% 0 auto; -} -.attach-crop-handle { - position: absolute; - right: -9px; - bottom: -9px; - width: 20px; - height: 20px; - border-radius: 50%; - background: var(--accent, var(--red)); - border: 2px solid var(--bg); - box-sizing: border-box; - cursor: nwse-resize; -} -.attach-crop-actions { - display: flex; - gap: 8px; - padding: 10px; - background: color-mix(in srgb, var(--bg) 78%, transparent); -} -.attach-crop-btn { - flex: 1; - height: 34px; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.attach-crop-primary { - background: var(--accent, var(--red)); - color: #fff; - border-color: var(--accent, var(--red)); -} -@media (max-width: 768px) { - /* Collapsed "N files" badge: use the same corner-X accent badge as image thumbs. */ - .thumb-collapsed { position: relative; } - .thumb-collapsed .thumb-collapsed-x { - position: absolute; - top: -7px; - right: -7px; - width: 24px; - height: 24px; - min-width: 0; - padding: 0; - border: 2px solid var(--bg); - border-radius: 50%; - background: var(--accent-primary, var(--red)); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 15px; - line-height: 1; - z-index: 3; - opacity: 1; - } - /* Bigger remove-X tap target for non-image (doc/text) chips on mobile too. */ - .thumb button { - height: 28px; - min-width: 28px; - font-size: 15px; - } -} -.thumb span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.thumb:hover { - background: color-mix(in srgb, var(--fg) 16%, transparent); - border-color: var(--fg); -} - -.thumb button { - height: 24px; - padding: 0 5px; - font-size: 0.95em; - border-radius: 4px; - color: var(--accent-primary, var(--red)); -} - -.thumb-collapsed { - cursor: pointer; - color: var(--red); - border-color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - font-weight: 600; - gap: 8px; - border-radius: 999px; /* pill — rounder than the square file chips */ - padding-left: 12px; -} -.thumb-collapsed:hover { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.thumb-collapsed-label { white-space: nowrap; } -.thumb-collapsed-x { - height: 24px; padding: 0 7px; font-size: 13px; border-radius: 4px; - color: var(--accent-primary, var(--red)); - background: none; border: none; cursor: pointer; opacity: 0.6; -} -.thumb-collapsed-x:hover { opacity: 1; } - -/* Recording indicator */ -#recording-indicator { - position: fixed; - top: 10px; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin: 10px; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); -} - -#recording-indicator.hidden { - display: none !important; -} - -.recording-content { - display: flex; - align-items: center; - gap: 12px; - color: white; -} - -.recording-icon { - color: var(--color-recording); - font-size: 20px; - animation: pulse 1.5s infinite; -} - -.recording-text { - font-size: 16px; - font-weight: 500; -} - -.stop-recording-btn { - background: var(--color-recording); - color: white; - border: none; - border-radius: 6px; - padding: 6px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s ease; -} - -.stop-recording-btn:hover { - background: var(--color-recording-hover); -} - -#recording-indicator.error { - background: rgba(173, 26, 26, 0.9); -} - -.recording-error { - color: var(--color-recording); - font-size: 14px; - margin-top: 4px; -} - -/* Mermaid diagram containers */ -.mermaid-container { - margin: 12px 0; - padding: 16px; - background: color-mix(in srgb, var(--bg) 95%, var(--fg)); - border: 1px solid var(--border); - border-radius: 8px; - overflow-x: auto; - text-align: center; -} -.mermaid-container svg { max-width: 100%; height: auto; } - -/* KaTeX math overrides */ -.katex-display { margin: 0.8em 0; overflow-x: auto; overflow-y: hidden; } -.katex { font-size: 1.1em; } - -/* Hide thinking sections globally via settings toggle */ -body.hide-thinking .thinking-section { display: none !important; } - -/* Widen chat area via settings toggle */ -body.fullwidth-chat .chat-history { - padding-left: 0 !important; - padding-right: 12px !important; -} - -/* Thinking process styles — colors follow theme accent */ -.msg .body .stream-content { - width: 100%; -} -.thinking-section { - margin: 12px 0; - width: 100%; - max-width: 100%; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - overflow: hidden; - transition: all 0.3s ease; -} - -.thinking-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 6px 12px; - cursor: pointer; - user-select: none; - background: color-mix(in srgb, var(--red) 8%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - transition: background 0.2s ease; -} - -.thinking-header > .token-new { - display: none; -} - -.thinking-header > div:last-child { - flex-shrink: 0; -} - -.thinking-header:hover { - background: color-mix(in srgb, var(--red) 12%, transparent); -} - -.thinking-header-left { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9em; - color: var(--red); - font-weight: 500; - overflow: hidden; - min-width: 0; -} -.thinking-header-left span { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - transition: opacity 0.2s ease; -} - -.thinking-icon { - font-size: 1.1em; -} - -.thinking-toggle { - font-size: 0.9em; - color: var(--red); - transition: transform 0.3s ease; -} -.thinking-toggle::after { - content: '\25BC'; /* ▼ */ -} - -.thinking-toggle.expanded { - transform: rotate(180deg); -} - -.thinking-content { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; - padding: 0 12px; -} - -.thinking-content.expanded { - max-height: 300px; - overflow-y: auto; - padding: 12px; -} - -.thinking-content-inner { - font-size: 0.85em; - color: var(--fg); - opacity: 0.9; - line-height: 1.5; -} -.live-reply-content { - animation: fadeSlideIn 0.3s ease-out; -} -@keyframes fadeSlideIn { - from { opacity: 0; transform: translateY(4px); } - to { opacity: 1; transform: translateY(0); } -} - -/* Thinking indicator animation */ -.thinking-indicator { - display: flex; - align-items: center; - gap: 4px; - color: var(--red); - font-style: italic; - padding: 8px 0; -} - -.thinking-dots::after { - content: '...'; - animation: thinking-dots 1.5s infinite; - display: inline-block; - width: 20px; - text-align: left; -} - -@keyframes thinking-dots { - 0%, 20% { content: '.'; } - 40% { content: '..'; } - 60%, 100% { content: '...'; } -} - -.thinking-complete { - color: var(--red); - font-size: 0.9em; - padding: 4px 0; - opacity: 0.8; -} - -.research-context-continue-hint { - display: inline-flex; - align-items: center; - gap: 4px; - margin: 5px 2px 0; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - opacity: 0.55; -} -.research-context-continue-hint svg { - flex: 0 0 auto; - opacity: 0.8; -} - -/* ── Sources section — collapsible source citations ── */ -.sources-section { - margin: 8px 0 12px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - overflow: hidden; - transition: all 0.3s ease; -} -.sources-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 12px; - cursor: pointer; - background: color-mix(in srgb, var(--red) 8%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - transition: background 0.2s ease; - user-select: none; -} -.sources-header:hover { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.sources-header-left { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.85em; - font-weight: 500; - color: var(--red); -} -.sources-header-left svg { - width: 14px; - height: 14px; - flex-shrink: 0; - opacity: 0.7; -} -.sources-toggle { - font-size: 0.8em; - color: var(--red); - opacity: 0.7; - transition: none; -} -.sources-toggle::after { - content: '\25B6'; /* ▶ right arrow */ -} -.sources-toggle[data-arrow="down"]::after { - content: '\25BC'; /* ▼ down arrow */ -} -.sources-content { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; - padding: 0 10px; -} -.sources-content.expanded { - max-height: 3000px; - padding: 8px 10px; -} -.sources-content-inner { - display: flex; - flex-direction: column; - gap: 4px; -} -.source-link { - display: flex; - align-items: center; - gap: 8px; - padding: 5px 8px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - text-decoration: none; - color: var(--fg); - transition: background 0.15s ease; -} -.source-link:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.source-num { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 20px; - height: 20px; - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 15%, transparent); - color: var(--fg); - font-size: 0.7em; - font-weight: 600; - flex-shrink: 0; -} -.source-title { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 0.82em; -} -.source-domain { - font-size: 0.72em; - opacity: 0.45; - flex-shrink: 0; -} - -.ai-spinner { - color: var(--red); -} -/* Nudge the Tidy button 2px left. */ -#memory-tidy-btn { position: relative; left: -2px; } -/* Tidy button's whirlpool nudge — sits 1px lower so it visually centers on - the Tidy label baseline. */ -#memory-tidy-btn .ai-spinner-whirlpool, -#memory-tidy-btn .spinner-whirlpool { - position: relative; - top: 1px; -} -.list-item.stream-complete { - animation: none; -} -.session-run-state { - flex: 0 0 auto; - margin-left: 5px; - font-size: 10px; - line-height: 1; - color: var(--fg-dim, #888); - opacity: 0.9; -} -.session-run-state.is-working { - color: var(--accent, var(--red)); -} -.session-run-state .session-run-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 !important; - display: inline-flex; - align-items: center; -} -.session-run-state .session-run-whirlpool .ai-spinner-whirlpool { - display: inline-flex; - width: 12px; - height: 12px; -} -.session-run-state.is-done { - color: var(--color-success, #4caf50); - font-weight: 600; -} -.cookbook-notif-active svg { opacity: 1 !important; } - -/* Rail notification dot — pulsing indicator on icon-rail buttons */ -.icon-rail-btn.rail-notify { - opacity: 1 !important; - position: relative; -} -.icon-rail-btn.rail-notify::before { - content: ''; - position: absolute; - top: 5px; - right: 4px; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--accent, var(--red)); - animation: rail-notif-pulse 2s ease-in-out infinite; - z-index: 1; -} -.icon-rail-btn.rail-notify.rail-notify-success::before { - background: var(--color-success, #4caf50); -} -@keyframes rail-notif-pulse { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.4; transform: scale(0.8); } -} -@keyframes stream-complete-pulse { - 0%, 100% { box-shadow: none; } - 50% { box-shadow: inset 0 0 0 1.5px var(--accent); } -} - -/* ===== SLASH COMMAND RESPONSES ===== */ -.msg.msg-system { - padding: 6px 12px; margin: 4px auto 4px 8px; - max-width: 85%; background: none; border-left: 2px solid var(--border); -} -.msg.msg-system .body { padding: 0; } -.msg.msg-system pre { margin: 4px 0; white-space: pre-wrap; font-size: 0.85em; } -.dropdown-divider { - height: 1px; - margin: 3px 6px; - background: color-mix(in srgb, var(--border) 60%, transparent); -} -.msg.stream-done-toast { - cursor: pointer; - border-left-color: var(--accent, var(--border)); - border-radius: 6px; - background: color-mix(in srgb, var(--accent) 6%, transparent); - transition: background 0.15s; -} -.msg.stream-done-toast:hover { - background: color-mix(in srgb, var(--accent) 12%, transparent); -} -.msg.stream-done-toast .body { - display: flex; - align-items: center; - gap: 8px; -} -.stream-done-indicator { - font-family: monospace; - font-size: 1.1em; - line-height: 1; - color: var(--accent); - flex-shrink: 0; - animation: bar-pulse 1.2s ease-in-out infinite; -} -@keyframes bar-pulse { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } -} - -/* ===== AGENT MULTI-BUBBLE ===== */ -.msg.msg-tool { - display: none; /* legacy — hidden, replaced by agent-thread */ -} -.msg.msg-continuation { - margin-top: 2px; -} - -/* ===== AGENT THREAD TIMELINE ===== */ -.agent-thread { - position: relative; - margin: 2px 0 2px 28px; - padding: 4px 0 4px 22px; - max-width: calc(85% - 20px); - box-sizing: border-box; -} -.agent-thread::before { - content: ''; - position: absolute; - left: 5px; - top: 14px; - bottom: 14px; - width: 2px; - background: color-mix(in srgb, var(--red) 18%, transparent); - border-radius: 1px; -} -/* Extend line to connect to chat bubble above/below */ -.agent-thread.has-top::before { - top: -6.5px; -} -.agent-thread.has-bottom::before { - bottom: -5px; -} -.agent-thread.has-ask-user-bottom::before { - bottom: -12px; -} -/* Terminating dot at bottom when no bubble below and last node is expanded */ -.agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { - content: ''; - position: absolute; - /* Centre the 6px dot on the rail: the rail sits at left:5px + 2px wide, - so its centre is 6px from the thread's left edge and the dot's left - edge belongs at 3px — i.e. 3px - padding-left. Keep this in step with - .agent-thread-dot and with the mobile padding override below. */ - left: -19px; - bottom: 5px; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--red); - box-shadow: 0 0 4px 1px color-mix(in srgb, var(--red) 55%, transparent), - 0 0 0 3px color-mix(in srgb, var(--red) 18%, transparent); -} - -/* Synapse pulse — a bright dot traveling down the line */ -.agent-thread::after { - content: ''; - position: absolute; - left: 4px; - width: 4px; - height: 4px; - border-radius: 50%; - background: var(--red); - box-shadow: 0 0 3px 1px color-mix(in srgb, var(--red) 50%, transparent); - pointer-events: none; - top: 0%; - opacity: 0; -} -.agent-thread.streaming::after { - animation: synapse-capped-short 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-top::after { - animation: synapse-capped 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-bottom::after { - animation: synapse-travel-short 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-top.has-bottom::after { - animation: synapse-travel 0.8s ease-in-out infinite; -} -@keyframes synapse-travel { - 0% { top: 0%; opacity: 0; } - 5% { opacity: 0.5; } - 85% { opacity: 0.35; } - 100% { top: 100%; opacity: 0; } -} -@keyframes synapse-capped { - 0% { top: 0%; opacity: 0; } - 5% { opacity: 0.5; } - 70% { opacity: 0.35; top: calc(100% - 20px); } - 100% { opacity: 0; top: calc(100% - 20px); } -} -@keyframes synapse-travel-short { - 0% { top: 14px; opacity: 0; } - 5% { opacity: 0.5; } - 85% { opacity: 0.35; } - 100% { top: 100%; opacity: 0; } -} -@keyframes synapse-capped-short { - 0% { top: 14px; opacity: 0; } - 5% { opacity: 0.5; } - 70% { opacity: 0.35; top: calc(100% - 20px); } - 100% { opacity: 0; top: calc(100% - 20px); } -} -.agent-thread-node { - position: relative; - padding: 5px 0; -} -.agent-thread-node + .agent-thread-node { - margin-top: 2px; -} -/* Supervisor ladder cards — same chrome as tool cards but tinted so the - user can tell at a glance "this is the agent recovering" vs "this is - the agent doing work". Stop rung gets the red accent. */ -.agent-thread-node.supervisor-step .agent-thread-tool { - color: color-mix(in srgb, var(--accent, #c08a3e) 80%, var(--fg)); - font-style: italic; -} -.agent-thread-node.supervisor-step .agent-thread-dot { - background: color-mix(in srgb, var(--accent, #c08a3e) 60%, transparent); -} -.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-tool { - color: var(--red, #d65a5a); -} -.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-dot { - background: color-mix(in srgb, var(--red, #d65a5a) 60%, transparent); -} -.agent-thread-dot { - position: absolute; - left: -20px; - top: 10px; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--red); - border: 2px solid var(--bg); - z-index: 1; -} -.agent-thread-node.running .agent-thread-dot { - background: var(--red); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 25%, transparent); - animation: thread-pulse 1.5s ease-in-out infinite; - top: 10px; -} -@keyframes thread-pulse { - 0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 20%, transparent); } - 50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--red) 10%, transparent); } -} -.agent-thread-node.error .agent-thread-dot { - background: var(--color-error); -} -.agent-thread-header { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 70%, transparent); - user-select: none; - padding: 2px 0; -} -.agent-thread-header:hover { - color: var(--fg); -} -.agent-thread-icon { - font-size: 0.9em; - color: var(--red); -} -.agent-thread-tool-icon { - width: 14px; - height: 14px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - color: color-mix(in srgb, var(--fg) 64%, transparent); - opacity: 0.85; -} -.agent-thread-tool-icon svg { - display: block; - width: 12px; - height: 12px; -} -.agent-thread-tool-icon-link { - color: inherit; - text-decoration: none; - cursor: pointer; -} -.agent-thread-tool-icon-link:hover { - color: var(--red); - opacity: 1; -} -.agent-thread-node.error .agent-thread-icon { - color: var(--color-error); -} -.agent-thread-tool { - font-weight: 600; - color: var(--red); - text-transform: uppercase; - letter-spacing: 0.3px; - font-size: 0.9em; -} -.agent-thread-header-link { - width: 17px; - height: 17px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 17px; - margin-left: -3px; - color: var(--red); - opacity: 0.72; - text-decoration: none; - border: 0; - background: transparent; -} -.agent-thread-header-link:hover { - opacity: 1; - color: var(--red); -} -.agent-thread-header-link svg { - display: block; -} -.agent-thread-header-link-calendar { - transform: translateY(-2px); -} -.msg-ai .body a[href^="#event-"], -.msg-ai .body a[href^="#email-"], -.msg-ai .body a[href^="#document-"], -.msg-ai .body a[href^="#note-"], -.msg-ai .body a[href^="#task-"], -.msg-ai .body a[href^="#skill-"], -.msg-ai .body a[href^="#memory-"], -.msg-ai .body a[href^="#session-"] { - display: inline-flex; - align-items: baseline; - max-width: 100%; - padding: 1px 5px; - border-radius: 5px; - background: color-mix(in srgb, var(--red) 12%, transparent); - color: var(--red); - font-weight: 600; - text-decoration: none; - box-decoration-break: clone; - -webkit-box-decoration-break: clone; -} -.msg-ai .body a[href^="#event-"], -.msg-ai .body a[href^="#note-"], -.msg-ai .body a[href^="#skill-"], -.msg-ai .body a[href^="#memory-"], -.msg-ai .body a[href^="#session-"] { - font-size: 11px; - line-height: 1.35; -} -.msg-ai .body p:has(> .emoji:first-child):has(> a.chat-link[href^="#note-"]) { - margin-top: 0.15em; - margin-bottom: 0.15em; - line-height: 1.35; -} -.msg-ai .body a[href^="#event-"]:hover, -.msg-ai .body a[href^="#email-"]:hover, -.msg-ai .body a[href^="#document-"]:hover, -.msg-ai .body a[href^="#note-"]:hover, -.msg-ai .body a[href^="#task-"]:hover, -.msg-ai .body a[href^="#skill-"]:hover, -.msg-ai .body a[href^="#memory-"]:hover, -.msg-ai .body a[href^="#session-"]:hover { - background: color-mix(in srgb, var(--red) 18%, transparent); - text-decoration: underline; -} -.agent-thread-status { - font-size: 0.85em; - opacity: 0.5; -} -.agent-thread-chevron { - width: 12px; - height: 12px; - flex: 0 0 12px; - display: inline-grid; - place-items: center; - opacity: 0.4; -} -.agent-thread-chevron::before { - content: ""; - width: 0; - height: 0; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 6px solid currentColor; - transform-origin: 50% 50%; - transition: transform 0.2s ease; -} -.agent-thread-node.open .agent-thread-chevron::before { - transform: rotate(90deg); -} -.agent-thread-node.browser-preview-node .agent-thread-chevron { - display: none; -} -.agent-thread-wave { - font-family: monospace; - font-size: 0.85em; - color: var(--red); - letter-spacing: -1px; -} -/* Live "cooking" timer on a running tool — prominent (accent, tabular) so a - long-running command always reads as alive, not frozen. */ -.agent-thread-elapsed { - margin: 0 6px 0 4px; - font-size: 11px; - font-weight: 600; - font-variant-numeric: tabular-nums; - color: var(--accent, var(--red)); -} -/* Stall watchdog banner — shown when the stream has been silent for ~1min. */ -.stall-banner { - display: flex; - align-items: center; - gap: 8px; - margin: 8px auto; - padding: 8px 12px; - max-width: 90%; - font-size: 12px; - border-radius: 8px; - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, var(--bg)); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 40%, transparent); -} -.stall-banner-txt { flex: 1; opacity: 0.85; } -.stall-banner-btn { - font-size: 11px; - font-weight: 600; - padding: 4px 10px; - border-radius: 6px; - border: none; - background: var(--accent, var(--red)); - color: #fff; - cursor: pointer; - flex-shrink: 0; -} -.stall-banner-stop { - background: none; - color: var(--fg); - border: 1px solid var(--border); -} -.agent-thread-content { - display: none; - padding: 4px 0 2px 0; - overflow-x: auto; - overflow-y: hidden; -} -.agent-thread-node.open .agent-thread-content { - display: block; -} -.agent-thread-cmd { - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - padding: 8px 12px; - margin: 4px 0; - color: var(--fg); - font-size: 0.85em; - white-space: pre-wrap; - word-break: break-word; - line-height: 1.4; - overflow-x: auto; -} -.agent-thread-summary { - display: inline-flex; - align-items: center; - gap: 6px; - max-width: 100%; - margin: 4px 0; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 26%, var(--border)); - border-radius: 6px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - color: color-mix(in srgb, var(--fg) 84%, var(--accent, var(--red))); - font-size: 12px; - font-variant-numeric: tabular-nums; - line-height: 1.35; -} -.agent-thread-summary-link { - cursor: pointer; - text-decoration: none; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-thread-summary-link:hover { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} - -.agent-image-progress { - margin: 6px 0 0; -} -.agent-image-progress-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - font-size: 11px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 72%, transparent); -} -.agent-image-progress-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-image-progress-value { - flex-shrink: 0; - font-variant-numeric: tabular-nums; - color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); -} -.agent-image-progress-track { - display: none !important; -} - -.private-browser-preview { - width: 100%; - max-height: min(300px, 36vh); - margin: 6px 0 2px; - display: flex; - flex-direction: column; - overflow: hidden; - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - color: var(--fg); - box-shadow: none; -} -.private-browser-preview-header { - height: 32px; - min-height: 32px; - display: flex; - align-items: center; - gap: 8px; - padding: 0 6px 0 10px; - border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); - background: color-mix(in srgb, var(--red) 10%, transparent); - font-size: 0.85em; - font-weight: 500; - cursor: pointer; -} -.private-browser-preview-title { - flex-shrink: 0; - color: var(--red); - font-size: inherit; - font-weight: inherit; - line-height: 1; - letter-spacing: 0; -} -.private-browser-preview-status { - min-width: 0; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-size: inherit; - line-height: 1; -} -.private-browser-preview-fold { - width: 18px; - height: 18px; - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - left: 2px; - flex-shrink: 0; - padding: 0; - border: 0; - border-radius: 0; - background: transparent; - color: var(--red); - cursor: pointer; - font-size: 0.95em; - font-family: inherit; - font-weight: 500; - line-height: 1; - text-shadow: none; - -webkit-text-stroke: 0; -} -.private-browser-preview-fold:hover { - background: transparent; - color: var(--red); -} -.private-browser-preview-frame { - position: relative; - min-height: 130px; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - background: color-mix(in srgb, #000 72%, var(--bg)); - box-shadow: - inset 0 1px 0 color-mix(in srgb, #fff 8%, transparent), - inset 0 0 0 1px color-mix(in srgb, #000 35%, transparent), - 0 8px 18px color-mix(in srgb, #000 18%, transparent); -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame { - cursor: zoom-in; -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame::after { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - opacity: 0.55; - transition: opacity 150ms ease; - background: - radial-gradient(circle at center, transparent 42%, color-mix(in srgb, #000 30%, transparent) 100%), - color-mix(in srgb, var(--bg) 10%, transparent); -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame:hover::after { - opacity: 0.9; -} -.private-browser-preview.folded .private-browser-preview-frame { - display: none; -} -.private-browser-preview.folded .private-browser-preview-header { - border-bottom: 0; -} -.private-browser-preview-frame img { - width: 100%; - height: auto; - max-height: calc(min(300px, 36vh) - 32px); - display: block; - object-fit: contain; - transform: scale(1.1); - transform-origin: center center; - animation: private-browser-image-in 180ms ease-out both; -} -.private-browser-preview-frame img:not([src]), -.private-browser-preview-frame img[src=""] { - display: none; -} -.private-browser-preview-empty { - padding: 22px 14px; - width: auto; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 7px; - color: color-mix(in srgb, var(--fg) 48%, transparent); - font-size: 11px; - line-height: 1.35; -} -.private-browser-preview-spinner { - width: 13px; - height: 13px; - flex: 0 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.private-browser-preview-spinner .spinner-whirlpool, -.private-browser-preview-spinner .ai-spinner-whirlpool { - margin: 0 !important; -} -@keyframes private-browser-image-in { - from { opacity: 0; transform: scale(1.08); } - to { opacity: 1; transform: scale(1.1); } -} - -@media (max-width: 700px) { - .private-browser-preview-frame img { - max-height: calc(36vh - 32px); - } -} - -/* Mobile: constrain thread content */ -@media (max-width: 768px) { - .agent-thread { - margin-left: 16px; - padding-left: 18px; - max-width: calc(90% - 16px); - } - .agent-thread-cmd { - font-size: 0.78em; - padding: 6px 8px; - max-width: 100%; - overflow-x: auto; - } - .agent-thread-content { - max-width: calc(100vw - 90px); - } - .agent-tool-output pre { - font-size: 0.8em; - max-width: 100%; - overflow-x: auto; - } - .agent-thread-header { - font-size: 0.8em; - } - .agent-thread-dot { - left: -16px; - top: 10px; - } - /* padding-left drops to 18px here, so the terminating dot needs its own - offset (3px - 18px) to stay centred on the rail. */ - .agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { - left: -15px; - } -} - -/* ===== AGENT TOOL OUTPUT (inside thread nodes) ===== */ -.agent-tool-output { - margin-top: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 8px; - overflow: hidden; -} -.agent-tool-output summary { - color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); - cursor: pointer; - font-size: 0.85em; - user-select: none; - padding: 6px 10px; - font-weight: 500; - /* Chevron on the right (like the thinking section) instead of the default - left-side disclosure triangle. */ - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - list-style: none; -} -.agent-tool-output summary::-webkit-details-marker { display: none; } -/* File-write diff — neutral chrome (not the red error tint) + colored lines */ -.agent-tool-diff { - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.agent-tool-diff summary { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-bottom-color: color-mix(in srgb, var(--fg) 12%, transparent); -} -.agent-tool-diff .diff-stat { - font-weight: 600; - opacity: 0.7; - font-family: var(--mono, monospace); -} -/* Collapsed diff summary: filename + +adds/−dels (theme green/red). */ -.agent-tool-diff summary { - display: flex; - align-items: center; - gap: 8px; -} -.agent-tool-diff .diff-file { - font-family: var(--mono, monospace); - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-tool-diff .diff-summary-stats { - margin-left: auto; - font-family: var(--mono, monospace); - font-weight: 600; - flex-shrink: 0; -} -.agent-tool-diff .diff-summary-stats .diff-stat-add { color: var(--green, #2ecc71); } -.agent-tool-diff .diff-summary-stats .diff-stat-del { color: var(--red, #e74c3c); } -.agent-tool-diff .diff-summary-stats .diff-stat-new { color: var(--accent, var(--red)); opacity: 0.85; } -.diff-pre { - margin: 0; - padding: 8px 10px; - overflow-x: auto; - font-family: var(--mono, monospace); - font-size: 0.82em; - line-height: 1.45; -} -.diff-pre span { display: block; white-space: pre; } -.diff-pre .diff-add { background: color-mix(in srgb, #2ecc71 22%, transparent); } -.diff-pre .diff-del { background: color-mix(in srgb, #e74c3c 22%, transparent); } -.diff-pre .diff-hunk { color: var(--accent); opacity: 0.85; } -.diff-pre .diff-meta { opacity: 0.55; } -.diff-pre .diff-ctx { opacity: 0.8; } -/* Suppress the global `summary::before { content: '▶' }` left arrow — this - section uses a right-side chevron instead. */ -.agent-tool-output summary::before { content: none; } -.agent-tool-output summary::after { - content: '\25BC'; /* ▼ */ - color: var(--red); - font-size: 0.9em; - transition: transform 0.3s ease; -} -.agent-tool-output[open] > summary::after { - transform: rotate(180deg); -} -.agent-tool-output summary:hover { - background: color-mix(in srgb, var(--red) 15%, transparent); -} -.agent-thinking-dots .ai-spinner { - font-size: 12px; - letter-spacing: 0.5px; -} - -.agent-tool-output[open] { - background: color-mix(in srgb, var(--red) 6%, transparent); -} -.agent-tool-output[open] > :not(summary) { - animation: detail-reveal 0.25s ease-out both; -} -@keyframes detail-reveal { - from { opacity: 0; transform: translateY(-4px); } - to { opacity: 1; transform: translateY(0); } -} -.agent-tool-output pre { - background: transparent; - border: none; - border-radius: 0; - padding: 10px 14px; - margin-top: 0; - max-height: 300px; - overflow-y: auto; - font-size: 0.95em; - color: var(--fg); - opacity: 0.85; - white-space: pre-wrap; - word-break: break-all; - line-height: 1.5; -} - -/* Mobile responsive styles */ -@media (max-width: 768px) { - #recording-indicator { - margin: 8px; - padding: 10px; - } - - .recording-text { - font-size: 14px; - } - - .stop-recording-btn { - padding: 6px 10px; - font-size: 12px; - } -} -/* ===== PANE STYLES (shared by compare) ===== */ - -.pane-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 0 4px; - border-bottom: 1px solid var(--border); -} -.close-split-btn, -.pane-close-btn { - background: none; - border: none; - color: var(--color-error); - font-size: 18px; - cursor: pointer; - padding: 0 4px; - line-height: 1; - opacity: 0; - transition: opacity 0.15s; -} -.pane-header:hover .close-split-btn, -.pane-header:hover .pane-close-btn { - opacity: 1; -} -.close-split-btn:hover, -.pane-close-btn:hover { - color: var(--color-error-light); -} - -/* Compare pane close controls use the same shared circular close treatment - as modal/title close buttons. */ -.compare-pane .pane-close-btn.close-btn { - width: 24px; - height: 24px; - min-width: 24px; - padding: 0; - border: 1px solid transparent; - border-radius: 999px; - background: transparent; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 0; - opacity: 0.5; - display: inline-flex; - align-items: center; - justify-content: center; - transition: background 0.15s, color 0.15s, opacity 0.15s; -} -.compare-pane .pane-close-btn.close-btn:hover, -.compare-pane .pane-close-btn.close-btn:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); - opacity: 1; - outline: none; -} -.compare-pane .pane-action-btn[data-action="expand"] { - width: 24px; - height: 24px; - min-width: 24px; - padding: 0; - border: 1px solid transparent; - border-radius: 999px; - justify-content: center; - transition: background 0.15s, color 0.15s, opacity 0.15s; -} -.compare-pane .pane-action-btn[data-action="expand"]:hover, -.compare-pane .pane-action-btn[data-action="expand"]:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); - opacity: 1; - outline: none; -} - - -/* ============================================ */ -/* RESEARCH DETAILS EXPANDABLE SECTION - UPDATED */ -/* ============================================ */ - -/* Style the details element */ -details { - background: color-mix(in srgb, var(--hl-string) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--hl-string) 30%, transparent); - border-radius: 8px; - margin: 12px 0; - padding: 0; - overflow: hidden; - transition: all 0.3s ease; -} - -details[open] { - background: color-mix(in srgb, var(--hl-string) 8%, transparent); -} -details[open] > :not(summary) { - animation: detail-reveal 0.25s ease-out both; -} - -/* Style the summary (clickable header) - NO CURSIVE, NORMAL SIZE */ -summary { - cursor: pointer; - padding: 10px 14px; - background: color-mix(in srgb, var(--hl-string) 10%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--hl-string) 20%, transparent); - font-weight: 500; - font-size: 0.95em; - font-style: normal; - font-family: inherit; - color: var(--hl-string); - user-select: none; - list-style: none; - display: flex; - align-items: center; - gap: 8px; - transition: background 0.2s ease; -} - -summary:hover { - background: color-mix(in srgb, var(--hl-string) 15%, transparent); -} - -/* Add custom arrow */ -summary::before { - content: '▶'; - display: inline-block; - transition: transform 0.3s ease; - font-size: 0.75em; /* Smaller arrow */ -} - -details[open] summary::before { - transform: rotate(90deg); -} - -/* Hide default marker in webkit browsers */ -summary::-webkit-details-marker { - display: none; -} - -/* Style the content inside details - SMALLER, MORE COMPACT */ -details > div, -details > p, -details > ul, -details > ol { - padding: 12px 14px; /* Less padding */ - animation: fadeIn 0.3s ease; - font-size: 0.9em; /* Smaller text */ - line-height: 1.5; -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Style research findings inside details - SMALLER HEADINGS */ -details h3 { - margin-top: 12px; - margin-bottom: 6px; - color: var(--hl-string); - font-size: 0.95em; - font-weight: 500; -} - -details h4 { - margin-top: 10px; - margin-bottom: 5px; - color: var(--hl-string); - font-size: 0.9em; - font-weight: 500; -} - -details ul { - margin-left: 18px; - margin-bottom: 10px; -} - -details li { - margin-bottom: 5px; - line-height: 1.4; - font-size: 0.85em; /* Smaller list items */ -} - -details strong { - color: var(--hl-string); - font-weight: 500; -} - -details a { - color: var(--accent); - text-decoration: none; - transition: color 0.2s ease; -} - -details a:hover { - color: var(--color-link-hover); - text-decoration: underline; -} - -/* Research metadata - SMALLER */ -details .research-meta { - font-size: 0.8em; - color: var(--fg); - opacity: 0.8; - margin-top: 4px; -} - -/* Source links - SMALLER */ -details .source-link { - display: inline-block; - margin-top: 3px; - padding: 2px 5px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); - border-radius: 4px; - font-size: 0.68em; - color: var(--accent); -} - -details .source-link:hover { - background: color-mix(in srgb, var(--accent) 20%, transparent); - border-color: var(--accent); -} - -/* Research report links - clean and subtle */ -details a { - color: var(--accent); - text-decoration: none; - font-weight: normal; /* Remove bold */ - font-size: 0.85em; - opacity: 0.9; - transition: all 0.2s ease; - word-break: break-all; /* Break long URLs nicely */ -} - -details a:hover { - color: var(--color-link-hover); - text-decoration: underline; - opacity: 1; -} - -/* ============================================ */ -/* CUSTOM SYNTAX HIGHLIGHTING - Theme-Reactive */ -/* ============================================ */ -.hljs { - background: var(--code-bg, var(--hl-bg, var(--panel))); - color: var(--code-fg, var(--hl-fg, var(--fg))); - padding: 8px; - border-radius: 4px; -} - -/* Keywords & control flow — purple/magenta */ -.hljs-keyword, -.hljs-selector-tag { color: var(--hl-keyword); } - -/* Strings & regex — warm yellow/orange */ -.hljs-string, -.hljs-regexp, -.hljs-addition { color: var(--hl-string); } - -/* Comments & docs — muted. font-style intentionally omitted: italics shift - glyph widths in the highlight overlay relative to the transparent textarea - above it, which makes the caret drift away from the visible character. */ -.hljs-comment, -.hljs-quote, -.hljs-meta { color: var(--hl-comment); } - -/* Functions & method names — blue */ -.hljs-function, -.hljs-title, -.hljs-title.function_, -.hljs-section { color: var(--hl-function); } - -/* Numbers & constants — distinct from strings */ -.hljs-number, -.hljs-literal { color: var(--hl-number, var(--hl-string)); } - -/* Built-ins & types — teal/cyan tint */ -.hljs-built_in, -.hljs-type, -.hljs-class, -.hljs-title.class_ { color: var(--hl-builtin, var(--hl-function)); } - -/* Variables & identifiers — fg with slight distinction */ -.hljs-variable, -.hljs-template-variable, -.hljs-attr { color: var(--hl-variable, var(--hl-fg, var(--fg))); } - -/* Operators & punctuation — slightly dimmed fg */ -.hljs-operator, -.hljs-punctuation { color: var(--hl-fg, var(--fg)); opacity: 0.8; } - -/* Parameters */ -.hljs-params { color: var(--hl-params, var(--hl-fg, var(--fg))); } - -/* Property access, attributes in HTML/XML */ -.hljs-property, -.hljs-selector-class, -.hljs-selector-id { color: var(--hl-variable, var(--hl-function)); } - -/* Tags (HTML) */ -.hljs-tag { color: var(--hl-keyword); } -.hljs-name { color: var(--hl-keyword); } - -/* Deletion/diff */ -.hljs-deletion { color: var(--red); } - -/* Symbol, special */ -.hljs-symbol { color: var(--hl-string); } -.hljs-link { color: var(--hl-function); text-decoration: underline; } - -/* Emphasis — applied globally (chat messages, rendered markdown in docs - preview, etc.) but explicitly NEUTRALIZED in the doc editor's overlay so - the textarea-and-overlay caret alignment stays bit-perfect. Bold / italic - shift glyph widths and that drifts the click-to-row mapping. */ -.hljs-emphasis { font-style: italic; } -.hljs-strong { font-weight: bold; } -.doc-editor-highlight .hljs-emphasis, -.doc-editor-highlight .hljs-strong { - font-style: normal !important; - font-weight: inherit !important; -} diff --git a/static/css/style-part-04.css b/static/css/style-part-04.css deleted file mode 100644 index ce6a9add4..000000000 --- a/static/css/style-part-04.css +++ /dev/null @@ -1,6011 +0,0 @@ -/* Fragment 5/9 of the former static/style.css. - * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. - * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. - */ -.admin-ep-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} - -/* Status messages */ -.admin-error { color: var(--color-error); font-size: 11px; margin-top: 4px; } -.admin-success { color: var(--color-success); font-size: 11px; margin-top: 4px; } -.admin-empty { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 12px; - padding: 10px 0; - text-align: center; -} -/* Endpoint-list empty states ("No endpoints configured" / "None") get a - left-aligned, accent-colored treatment so they read as "this row is your - placeholder, click Add" rather than a centered "nothing here" grey blob. */ -#adm-epList-local .admin-empty, -#adm-epList-api .admin-empty { - text-align: left; - padding: 8px 4px; - color: var(--accent-primary, var(--accent, var(--red))); -} - -/* RAG upload zone */ -.admin-rag-upload-zone { - border: 2px dashed var(--border); - border-radius: 8px; - padding: 14px; - text-align: center; - cursor: pointer; - transition: border-color 0.2s, background 0.2s; - margin-bottom: 8px; -} -.admin-rag-upload-zone:hover, -.admin-rag-upload-zone.dragover { - border-color: var(--red); - background: color-mix(in srgb, var(--red) 7%, transparent); -} -.admin-rag-upload-zone p { - color: color-mix(in srgb, var(--fg) 50%, transparent); - font-size: 11px; - margin-top: 4px; -} -.admin-rag-icon { font-size: 18px; } -.admin-rag-dir-row { - display: flex; - gap: 6px; - margin-bottom: 8px; -} -.admin-rag-dir-row input { - flex: 1; - padding: 5px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-rag-dir-row input:focus { outline: none; border-color: var(--red); } -.admin-rag-dir-row button { - padding: 5px 8px; - border: none; - border-radius: 6px; - background: var(--red); - color: #fff; - cursor: pointer; - font-size: 12px; - white-space: nowrap; -} -.admin-rag-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: 6px; -} -.admin-rag-section-label { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin: 8px 0 4px; -} -.admin-rag-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 8px; - background: var(--bg); - border-radius: 6px; - margin-bottom: 4px; - font-size: 12px; -} -.admin-rag-item-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--fg); -} -.admin-rag-item-meta { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 10px; - margin: 0 6px; - flex-shrink: 0; -} -.admin-rag-status { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-top: 6px; -} - -/* Token reveal */ -.admin-token-reveal { - margin-top: 8px; - padding: 8px; - background: color-mix(in srgb, var(--red) 8%, var(--bg)); - border: 1px solid color-mix(in srgb, var(--red) 25%, var(--border)); - border-radius: 8px; -} - -/* Selects in admin cards */ -.admin-card select { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-card input { - font-family: inherit; - font-size: 12px; -} -.admin-card label { - font-size: 12px; -} - -/* ---- Document Library ---- */ - -.doclib-modal-content { - width: min(600px, 92vw); - max-height: 85vh; - font-size: 12px; - background: var(--bg); -} -/* Anchor doclib modal to top so only the bottom edge moves on resize */ -#doclib-modal { - align-items: flex-start; - padding-top: 8vh; -} -.doclib-modal-content .modal-header h4 { - font-size: 1rem; -} -/* The "open in new tab" email modal inherited the 1rem doclib header, which - read way bigger than the email subject in the regular (inline) reader. Pin - it smaller so the two views are consistent. */ -.email-reader-tab-modal .modal-header h4 { - font-size: 13px; - font-weight: 600; -} -/* Match the sticky modal-header to the doclib modal-content body color - (var(--bg)) instead of the global var(--panel) default — otherwise the - header reads as a darker stripe above the email list. */ -.doclib-modal-content > .modal-header { - background: var(--bg); -} -.doclib-stats { - font-size: 11px; - color: var(--fg-muted); - margin-bottom: 8px; -} -.doclib-toolbar { - display: flex; - gap: 8px; - margin-bottom: 8px; - align-items: center; -} -.doclib-search { - flex: 1; - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - outline: none; - transition: border-color 0.15s; -} -#archive-select-btn { height: auto; padding: 5px 8px; } -.doclib-search:focus { - border-color: var(--red); -} -.doclib-sort { - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - outline: none; - cursor: pointer; -} -.doclib-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 8px; -} -.doclib-chip { - padding: 2px 10px; - border-radius: 12px; - font-size: 10px; - border: 1px solid var(--border); - background: transparent; - color: var(--fg-muted); - cursor: pointer; - user-select: none; - transition: background 0.15s, border-color 0.15s; -} -.doclib-chip:hover { - border-color: var(--red); -} -.doclib-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Document library — language chip row */ -.doclib-lang-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - padding: 2px 0; - max-height: 52px; - overflow-y: auto; -} -.doclib-lang-chips:empty { display: none; } -/* Mobile: keep tag chips on ONE row and scroll horizontally instead of wrapping - into a tall multi-line block (Serve, library — anywhere .doclib-lang-chips is used). */ -@media (max-width: 768px) { - .doclib-lang-chips { - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - max-height: none; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .doclib-lang-chips::-webkit-scrollbar { display: none; } - .doclib-lang-chips > * { flex-shrink: 0; } -} -#doclib-tidy-btn, #doclib-select-btn, #doclib-chats-tidy-btn { - position: relative; - top: -3px; -} -/* Document library — list layout */ -.doclib-grid { - display: flex; - flex-direction: column; - gap: 4px; - max-height: 400px; - overflow-y: auto; - padding: 0; - position: relative; -} - -/* Gallery grid domino-in cascade on open — same recipe as the document/email - libraries. Applied to #gallery-grid in gallery.js on the first render after - each open, removed after the longest delay so re-renders feel instant. */ -.gallery-just-opened > .gallery-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.gallery-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.gallery-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.gallery-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.gallery-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.gallery-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.gallery-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.gallery-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.gallery-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.gallery-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.gallery-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.gallery-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.gallery-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.gallery-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.gallery-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.gallery-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.gallery-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.gallery-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.gallery-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.gallery-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.gallery-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.gallery-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Tasks list domino-in cascade on open — mirrors gallery / doclib. Applied to - #tasks-list in tasks.js on the first render after each open, removed after - the longest delay so re-renders feel instant. */ -.tasks-just-opened > .task-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.tasks-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.tasks-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.tasks-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.tasks-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.tasks-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.tasks-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.tasks-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.tasks-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.tasks-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.tasks-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.tasks-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.tasks-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.tasks-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.tasks-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.tasks-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.tasks-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.tasks-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.tasks-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.tasks-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.tasks-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.tasks-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Document library cascade — same recipe as email library, applied per-tab - the first time content loads (chats / archive / research / documents). - Module-level Set in documentLibrary.js prevents re-firing on tab swaps - or filter/sort re-renders within the same page session. */ -.doclib-just-opened > .memory-item, -.doclib-just-opened > .doclib-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.doclib-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.doclib-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.doclib-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.doclib-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.doclib-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.doclib-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.doclib-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.doclib-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.doclib-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.doclib-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.doclib-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.doclib-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.doclib-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.doclib-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.doclib-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.doclib-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.doclib-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.doclib-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.doclib-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.doclib-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.doclib-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Domino cascade on first open of the email library — mirrors the sidebar - .section-just-expanded recipe so list items spring in staggered. Class - is applied to #email-lib-grid in emailLibrary.js on the first render - only, then removed after the longest delay so re-renders feel instant. */ -.email-lib-just-opened :is(.doclib-card, .date-section-header) { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(1) { animation-delay: 0.02s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(2) { animation-delay: 0.04s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(3) { animation-delay: 0.06s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(4) { animation-delay: 0.08s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(5) { animation-delay: 0.10s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(6) { animation-delay: 0.12s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(7) { animation-delay: 0.14s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(8) { animation-delay: 0.16s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(9) { animation-delay: 0.18s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(10) { animation-delay: 0.20s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(11) { animation-delay: 0.22s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(12) { animation-delay: 0.24s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(13) { animation-delay: 0.26s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(14) { animation-delay: 0.28s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(15) { animation-delay: 0.30s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(16) { animation-delay: 0.32s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(17) { animation-delay: 0.34s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(18) { animation-delay: 0.36s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(19) { animation-delay: 0.38s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(20) { animation-delay: 0.40s; } -/* Cap the cascade at 20 — beyond that they animate together so opening a - long inbox doesn't take forever to settle. */ -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(n+21) { animation-delay: 0.42s; } - -/* Inside the email library modal, the grid needs to grow with the modal — - but `flex: 1 1 0` collapses to 0 when the parent isn't height-constrained - (which is the non-fullscreen default). Use `auto` basis + a sensible - `min-height` floor so it shows ~400px naturally and absorbs more when - the modal is resized / fullscreened. */ -#email-lib-modal .doclib-grid { - max-height: none; - height: auto; - flex: 1 1 auto; - min-height: 400px; -} - -/* ── Mobile compose FAB (email) ── - Replaces the top-right "New" button on phones with a round mail-icon button - bottom-right that collapses to a circle while the list scrolls and springs - back out to "New" when scrolling stops. Desktop is unchanged. */ -.email-lib-fab { display: none; } -@media (max-width: 768px) { - /* The absolute FAB anchors to the email panel card. */ - #email-lib-modal .admin-card { position: relative; } - /* Hide the toolbar "New" button — the FAB is the mobile entry point. */ - #email-lib-compose-btn { display: none; } - - #email-lib-modal .email-lib-fab { - --fab-size: 56px; - display: flex; - align-items: center; - gap: 9px; - /* Hidden until the email list has rendered; JS adds .fab-revealed to pop - it in (scale-from-center). Avoids the button flashing at the top and - sliding down before _positionFab() places it. */ - transform: scale(0); - opacity: 0; - transform-origin: center center; - position: absolute; - right: calc(16px + env(safe-area-inset-right, 0px)); - bottom: calc(18px + env(safe-area-inset-bottom, 0px)); - height: var(--fab-size); - min-width: var(--fab-size); - padding: 0 20px 0 18px; - border: none; - border-radius: 999px; - background: var(--accent-primary, var(--red)); - color: #fff; - font-family: inherit; - font-size: 15px; - font-weight: 600; - line-height: 1; - cursor: pointer; - box-shadow: 0 6px 18px rgba(0,0,0,.38), 0 2px 6px rgba(0,0,0,.28); - z-index: 30; - pointer-events: auto; - /* This (base) transition governs the EXPAND — slower + graceful. */ - transition: - padding 420ms cubic-bezier(0.22, 1, 0.36, 1), - box-shadow 240ms ease, - transform 140ms ease, - background 140ms ease; - will-change: padding, transform; - } - #email-lib-modal .email-lib-fab svg { flex: 0 0 auto; display: block; } - #email-lib-modal .email-lib-fab .email-lib-fab-label { - overflow: hidden; - white-space: nowrap; - max-width: 120px; - opacity: 1; - /* EXPAND timing — label eases out a touch after the width opens up. */ - transition: - max-width 420ms cubic-bezier(0.22, 1, 0.36, 1), - opacity 300ms 60ms cubic-bezier(0.22, 1, 0.36, 1), - margin 420ms cubic-bezier(0.22, 1, 0.36, 1); - } - /* Collapsed (while scrolling) → pure circle, icon only. We DON'T set an - explicit width: the container is auto-width and shrinks smoothly as the - padding + label max-width animate (min-width keeps it circular). Setting a - fixed width here made the collapse snap, since auto↔px width can't tween. - This (.collapsed) transition governs the COLLAPSE — kept snappy. */ - #email-lib-modal .email-lib-fab.collapsed { - padding: 0; - justify-content: center; - transition: - padding 230ms cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 200ms ease, - transform 140ms ease; - } - #email-lib-modal .email-lib-fab.collapsed .email-lib-fab-label { - max-width: 0; - opacity: 0; - margin-left: -9px; - transition: - max-width 230ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), - margin 230ms cubic-bezier(0.4, 0, 0.2, 1); - } - /* Entrance: circle-expand-from-middle pop, played once when revealed. No - `forwards` fill so the resting transform (scale 1) comes from this rule — - that keeps the :active press transform working afterwards. */ - #email-lib-modal .email-lib-fab.fab-revealed { - transform: scale(1); - opacity: 1; - animation: emailFabPop 380ms cubic-bezier(0.34, 1.56, 0.64, 1); - } - #email-lib-modal .email-lib-fab:active { - transform: scale(0.93); - filter: brightness(0.92); - box-shadow: 0 3px 10px rgba(0,0,0,.4); - } -} -@keyframes emailFabPop { - from { transform: scale(0); opacity: 0; } - 60% { transform: scale(1.06); opacity: 1; } - to { transform: scale(1); opacity: 1; } -} -@media (prefers-reduced-motion: reduce) { - #email-lib-modal .email-lib-fab, - #email-lib-modal .email-lib-fab .email-lib-fab-label { transition-duration: 1ms; } - #email-lib-modal .email-lib-fab.fab-revealed { animation: none; } -} -/* When the modal-content has explicit height (fullscreen or user-resized), - drop the floor so the grid sizes purely from flex. */ -#email-lib-modal.email-lib-fullscreen .doclib-grid, -#email-lib-modal .modal-content[style*="height"] .doclib-grid { - min-height: 0; -} -/* Document library: same fix as the email library (#74). When fullscreened - the modal-content gets an inline `height: 100vh`, but the inner - .doclib-grid stays capped at the 400px default so the list looks like a - tiny strip floating in a giant empty modal. Mirror the email recipe: - make the modal a flex column, give the body/admin-card claim of the - remaining height, and let the grid take the rest. Scoped to the - fullscreen class so windowed-mode layout is unchanged. */ -/* Inner flex chain that lets the chats/documents grid claim the full - remaining height of the modal. Applies in BOTH fullscreen AND - right-docked states — without the docked state included, dragging a - fullscreen doclib to the right snap zone breaks the flex layout - because exitFullscreen removes .doclib-fullscreen, and the grid - falls back to its base max-height: 400px showing only ~8 items. - The CSS variable :is() selector lets both states share one rule. */ -#doclib-modal.doclib-fullscreen .doclib-modal-content, -#doclib-modal.modal-right-docked .doclib-modal-content, -#doclib-modal.modal-left-docked .doclib-modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -/* Fullscreen positioning — scoped to NOT apply when also right-docked. - Without this exclusion, dragging a fullscreen doclib to the right snap - zone keeps .doclib-fullscreen on the modal and these !important rules - override the dock's inline styles, leaving the modal stuck fullscreen - instead of becoming a side panel. */ -#doclib-modal.doclib-fullscreen:not(.modal-right-docked) .doclib-modal-content { - position: fixed !important; - top: 0 !important; - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; - right: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; -} -#doclib-modal.doclib-fullscreen .modal-header, -#doclib-modal.doclib-fullscreen .lib-tabs, -#doclib-modal.modal-right-docked .modal-header, -#doclib-modal.modal-right-docked .lib-tabs, -#doclib-modal.modal-left-docked .modal-header, -#doclib-modal.modal-left-docked .lib-tabs { - flex: 0 0 auto; -} -#doclib-modal.doclib-fullscreen .modal-body, -#doclib-modal.modal-right-docked .modal-body, -#doclib-modal.modal-left-docked .modal-body { - flex: 1 1 0; - min-height: 0; - overflow: hidden; - display: flex; - flex-direction: column; -} -#doclib-modal.doclib-fullscreen .admin-card, -#doclib-modal.modal-right-docked .admin-card, -#doclib-modal.modal-left-docked .admin-card { - flex: 1 1 0; - min-height: 0; - display: flex; - flex-direction: column; -} -#doclib-modal.doclib-fullscreen .doclib-grid:not(:has(.doclib-card-expanded)), -#doclib-modal.modal-right-docked .doclib-grid:not(:has(.doclib-card-expanded)), -#doclib-modal.modal-left-docked .doclib-grid:not(:has(.doclib-card-expanded)) { - max-height: none; - height: auto; - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* "Reply" docks the open email modal to the left as a sidebar so the doc - panel (which slides in from the right of the chat area) is visible - side-by-side. The .modal backdrop is already pointer-events:none for - email modals — only the content takes pointer events — so the chat / - doc panel underneath stays interactive. */ -.modal.email-snap-left { - align-items: stretch; - justify-content: flex-start; - left: 0 !important; - width: 100% !important; -} -.modal.email-snap-left .modal-content { - left: var(--email-doc-split-left-x, 0px) !important; - width: var(--email-doc-split-email-w, 420px) !important; - max-width: var(--email-doc-split-email-w, 420px) !important; - border-right: 1px solid var(--border); - box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; -} -@media (min-width: 769px) { - body.email-doc-split-active.doc-view #email-lib-modal, - body.email-doc-split-active.doc-view.email-front #email-lib-modal, - body.email-doc-split-active.doc-view .modal[id^="email-reader-"], - body.email-doc-split-active.doc-view.email-front .modal[id^="email-reader-"] { - z-index: 150 !important; - } - body.email-doc-split-active #email-lib-modal.email-snap-left, - body.email-doc-split-active #email-lib-modal.modal-left-docked { - left: var(--email-doc-split-left-x, 0px) !important; - width: var(--email-doc-split-email-w, 420px) !important; - overflow: hidden !important; - z-index: 155 !important; - } - body.email-doc-split-active #email-lib-modal.email-snap-left:not(.modal-dragging) .modal-content, - body.email-doc-split-active #email-lib-modal.modal-left-docked:not(.modal-dragging) .modal-content, - body.email-doc-split-active .modal[id^="email-reader-"].email-snap-left:not(.modal-dragging) .modal-content, - body.email-doc-split-active .modal[id^="email-reader-"].modal-left-docked:not(.modal-dragging) .modal-content { - position: absolute !important; - left: 0 !important; - top: 0 !important; - bottom: 0 !important; - right: auto !important; - width: var(--email-doc-split-email-w, 420px) !important; - max-width: var(--email-doc-split-email-w, 420px) !important; - height: 100vh !important; - max-height: 100vh !important; - transform: none !important; - margin: 0 !important; - } - body.email-doc-split-active.doc-view .doc-divider { - display: none !important; - } - body.email-doc-split-active.doc-view .doc-editor-pane { - position: fixed !important; - left: var(--email-doc-split-right-x, 420px) !important; - right: var(--right-dock-w, 0px) !important; - top: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - z-index: 260 !important; - margin-top: 0 !important; - transform: none !important; - border-left: none !important; - } -} - -/* Edge docking. Docked panels are fixed to the viewport edge; the workspace - panes reserve room with margins so left + right docks can be active at the - same time without skewing the entire body box. */ -body.right-dock-active { - padding-right: 0; -} -body.left-dock-active { - padding-left: 0; -} -body.left-dock-active:not(.email-doc-split-active) .chat-container { - margin-left: var(--left-dock-w, 0px); -} -body.right-dock-active .chat-container, -body.right-dock-active:not(.email-doc-split-active) .doc-editor-pane { - margin-right: var(--right-dock-w, 0px); -} -.modal.modal-right-docked { - align-items: stretch; - justify-content: flex-end; -} -.modal.modal-right-docked .modal-content { - border-left: 1px solid var(--border); - box-shadow: -4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; -} -.modal.modal-left-docked { - align-items: stretch; - justify-content: flex-start; -} -.modal.modal-left-docked .modal-content { - border-right: 1px solid var(--border); - box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; - /* Pin transitions OFF on the dock's positioning properties. The wide - sidebar collapse/expand changes --sidebar-w instantly, which means - the docked modal's `left: calc(...)` jumps by ~240px. Any CSS - transition on `left` (inherited or otherwise) would animate that - jump as a fly-across. Same defense for right-docked / fullscreen - panels in case future themes add a generic transition. */ - transition: none !important; -} -.modal.modal-right-docked .modal-content, -#email-lib-modal.email-lib-fullscreen .modal-content, -#doclib-modal.doclib-fullscreen .doclib-modal-content { - transition: none !important; -} -.modal.modal-right-docked .email-reader-header, -.modal.modal-left-docked .email-reader-header { - flex-direction: column; - /* Stretch the meta to the full header width so the absolute - action cluster's right:0 lands at the right edge (was sizing - meta to chip content width and centering it). */ - align-items: stretch; - gap: 6px; -} -/* Docked panes: 4px between From→To and 4px between To→Cc. - Meta has its own 2px gap (From→details) so an extra 2px on - details margin-top gives a 4px From→To distance, and gap:4 on - the details flex column gives 4px between To and Cc. */ -.modal.modal-right-docked .email-reader-meta-details, -.modal.modal-left-docked .email-reader-meta-details { - margin-top: 2px; - gap: 4px; -} -/* Docked: always show To/Cc — there's vertical room reserved for - the action cluster already and the chevron toggle just adds - friction. Force the hidden attribute open and hide the chevron. */ -.modal.modal-right-docked .email-reader-meta-details[hidden], -.modal.modal-left-docked .email-reader-meta-details[hidden] { - display: flex !important; -} -.modal.modal-right-docked .email-reader-meta-toggle, -.modal.modal-left-docked .email-reader-meta-toggle { - display: none; -} -.modal.modal-right-docked .email-reader-header > .email-reader-actions, -.modal.modal-left-docked .email-reader-header > .email-reader-actions { - position: static; - align-self: flex-end; - background: none; - padding-left: 0; -} -/* Docked modal: keep all meta rows as flex so From / To / Cc - labels align at the same x. Chips already overflow-scroll - inside each row when there are too many. No per-row margin-top - here — the spacing between rows comes entirely from - .email-reader-meta-details gap so From→To and To→Cc match. */ -.modal.modal-right-docked .email-reader-meta-row:not(.email-reader-meta-from), -.modal.modal-left-docked .email-reader-meta-row:not(.email-reader-meta-from) { - padding-right: 0; -} -.modal.modal-right-docked .email-reader-meta-row strong, -.modal.modal-left-docked .email-reader-meta-row strong { - min-width: 0; -} -.modal.modal-right-docked .recipient-chip, -.modal.modal-left-docked .recipient-chip { - max-width: 100%; -} -.archive-list { - margin-top: 8px; - border-top: 1px solid var(--border); - padding-top: 8px; -} -#archive-modal .memory-sort-select, -#archive-modal .memory-toolbar-btn { - height: 32px; -} -.archive-menu-btn { - position: relative; - top: 2px; -} -#archive-chips .doclib-chip { - height: 22px; - display: inline-flex; - align-items: center; - font-size: 10px; - padding: 0 8px; -} -/* Library tab panels — Chats / Archive / Research / Documents share the - same toolbar dimensions so the sort dropdown + Select button line up - identically across tabs. */ -#doclib-panel-chats .memory-sort-select, -#doclib-panel-archive .memory-sort-select, -#doclib-panel-research .memory-sort-select, -[data-doclib-panel="documents"] .memory-sort-select { - height: 24px; - font-size: 11px; - width: 86px; -} -#doclib-panel-chats .memory-toolbar-btn, -#doclib-panel-archive .memory-toolbar-btn, -#doclib-panel-research .memory-toolbar-btn, -[data-doclib-panel="documents"] .memory-toolbar-btn { - height: 24px; - font-size: 11px; - position: relative; - top: -3px; -} -/* Research's Recent (sort) + Select sat 1px lower than the other tabs — nudge up. */ -#doclib-panel-research .memory-sort-select { position: relative; top: 1.5px; } -#doclib-panel-research .memory-toolbar-btn { top: -4.5px; } -.library-sort-picker { - width: 108px; - flex: 0 0 108px; - top: 1px; -} -.library-sort-picker .library-sort-btn { - top: 0; - width: 100%; -} -.library-sort-picker .library-sort-menu { - top: calc(100% + 4px); - width: max(100%, 154px); -} -.library-sort-picker .library-sort-item[aria-selected="true"] { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); -} -#doclib-panel-chats #doclib-chats-tidy-btn, -#doclib-panel-chats #doclib-chats-select-btn, -#doclib-panel-archive #doclib-arc-select-btn { - top: 1px; -} -#doclib-panel-chats .doclib-chip-scroll-arrow { - top: calc(50% - 9px) !important; -} -[data-doclib-panel="documents"] .doclib-chip-scroll-arrow { - top: calc(50% - 9px) !important; -} -#doclib-modal .doclib-chip-scroll-arrow { - top: calc(50% - 19px) !important; -} -[data-doclib-panel="documents"] #doclib-tidy-btn, -[data-doclib-panel="documents"] #doclib-select-btn { - top: 1px; -} -#doclib-panel-research #doclib-research-tidy-btn, -#doclib-panel-research #doclib-research-select-btn { - top: -0.5px; -} -#doclib-panel-research .library-sort-picker { top: -0.5px; } -[data-doclib-panel] { padding-top: 6px !important; } -#doclib-panel-chats, #doclib-panel-archive { padding-top: 14px !important; } -#doclib-panel-chats .memory-desc, #doclib-panel-archive .memory-desc, #doclib-panel-research .memory-desc { margin-top: 7px; } -#doclib-panel-chats .memory-search-input, -#doclib-panel-archive .memory-search-input { - height: 30px !important; - min-height: 30px !important; - flex-shrink: 0; - font-size: 11px; -} -/* Unified search bar across Library tabs + Memory modal — same height, - same magnifying-glass icon at the start, consistent padding. */ -#doclib-panel-chats .memory-search-input, -#doclib-panel-archive .memory-search-input, -#doclib-panel-research .memory-search-input, -[data-doclib-panel="documents"] .memory-search-input, -#memory-modal .memory-search-input, -#tasks-modal .memory-search-input { - height: 30px; - min-height: 30px; - font-size: 11px; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: 9px center; - padding-left: 28px; -} -#doclib-panel-chats .doclib-chip { - height: 28px; -} -#doclib-panel-archive .doclib-chip { - height: 28px; -} -#doclib-panel-chats .doclib-chip, -#doclib-panel-archive .doclib-chip { - display: inline-flex; - align-items: center; - font-size: 9px; - padding: 0 8px; - border-radius: 10px; -} -.doclib-grid:has(.doclib-card-expanded) > .doclib-card:not(.doclib-card-expanded) { - display: none; -} -/* Hide everything except the grid when a card is expanded */ -.admin-card:has(.doclib-card-expanded) > *:not(.doclib-grid):not(.hwfit-cached-list) { - opacity: 0; - max-height: 0; - overflow: hidden; - pointer-events: none; - margin: 0 !important; - padding: 0 !important; - transition: opacity 0.12s ease, max-height 0.2s ease; -} -.admin-card:has(.doclib-card-expanded) > .memory-bulk-bar { - display: none; -} -.admin-card:has(.doclib-card-expanded) > .doclib-grid, -.admin-card:has(.doclib-card-expanded) > .hwfit-cached-list { - max-height: none !important; - min-height: 0 !important; - overflow: visible !important; -} -/* Firefox-mobile (no :has()) fallback: when reading an email, fully remove the - list-mode chrome (accounts row, toolbar, bulk bar, desc) — display:none so no - residual flex-gap leaves an empty strip above the reader — and zero the - admin-card gap so the grid/reader starts flush at the top. */ -#email-lib-modal.email-reading .admin-card > *:not(.doclib-grid):not(.hwfit-cached-list) { - display: none !important; -} -#email-lib-modal .admin-card:has(.doclib-card-expanded), -#email-lib-modal.email-reading .admin-card { - gap: 0 !important; - padding: 0 !important; - margin-bottom: 0 !important; - border: 0 !important; - border-radius: 0 !important; - background: transparent !important; -} -/* Override for chat-rows + research-rows: those expand inline with a - bounded preview (max-height: 60vh), so the grid must stay clipped - and scrollable. Without this scoping the overflow:visible above lets - the expanded chat preview escape the grid and overlap whatever's - underneath/beside the modal (e.g., a docked sibling panel) — what - the user calls "merging with other windows". */ -.admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - overflow: hidden auto !important; -} -/* Email modal needs the grid to STAY a constrained flex container when - expanded — overflow:visible (set above for cookbook) lets content escape - instead of letting the expanded reader fill via flex:1. We keep the same - max-height:none + min-height:0 but flip overflow back so the reader - actually fills the modal. */ -#email-lib-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid, -#email-lib-modal.email-reading .admin-card > .doclib-grid { - overflow: hidden !important; - display: flex !important; - flex: 1 1 auto !important; -} -#email-lib-modal.email-reading #email-lib-grid > .date-section-header { - display: none !important; -} -#email-lib-modal.email-reading .doclib-modal-content { - min-height: var(--email-reading-modal-min-h, auto); -} -#email-lib-modal .doclib-card.doclib-card-expanded { - flex: 1 1 auto !important; - height: 100% !important; - /* Neutral frame on the active email — the accent border + glow felt - overbearing on desktop; the size jump alone is enough signal. */ - border: 1px solid var(--border) !important; - box-shadow: 0 6px 18px rgba(0,0,0,0.12) !important; - animation: none !important; -} -/* Desktop-only, ONLY on the currently-expanded email card. Nudges the title - row down 6px / right 2px, bolds the subject, hides the timestamp from the - meta line (the reader header carries its own date), and pulls the prev/next - nav arrows in 4px. */ -@media (min-width: 769px) { - #email-lib-modal .doclib-card-expanded .email-card-titlerow, - #email-lib-modal .doclib-card-expanded .email-card-titlerow > * { - position: relative; - top: 6px; - left: 2px; - } - /* Smaller subject — the reader header below already carries weight. */ - #email-lib-modal .doclib-card-expanded .email-card-titlerow .memory-item-title { - font-weight: 700; - font-size: 12px; - } - /* A bit more breathing room around the title row so the smaller bold text - doesn't crowd the meta line below. (Timestamp kept — user changed mind.) */ - #email-lib-modal .doclib-card-expanded .email-card-titlerow { - padding: 4px 0 6px; - line-height: 1.35; - } - /* Nudge the date down/left in the meta row, and give the meta line +4px of - extra bottom space so the shifted date isn't clipped. */ - #email-lib-modal .doclib-card-expanded .email-meta-date { - position: relative; - top: 4px; - left: 0; - } - #email-lib-modal .doclib-card-expanded .memory-item-meta { - padding-bottom: 4px; - } - #email-lib-modal .doclib-card-expanded .email-card-nav-arrows { - position: relative; - left: -4px; - } -} -/* Skills modal: same mechanism as email above. The default expanded-grid - rule (overflow:visible, no flex) lets the card expand inline to ~content - height instead of filling — which is why the skill preview stopped at - partial height. Flip the grid back to a constrained flex container and - let the expanded card fill it, exactly like the email reader. */ -#memory-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid { - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - flex: 1 1 auto !important; -} -#memory-modal .doclib-card.doclib-card-expanded { - flex: 1 1 auto !important; - height: 100% !important; -} -#email-lib-modal .modal-content { - transition: width 0.22s ease-out, height 0.22s ease-out; -} -@media (min-width: 769px) { - body:not(.email-doc-split-active) #email-lib-modal:not(.email-lib-fullscreen):not(.modal-left-docked):not(.modal-right-docked) .modal-content:not([style*="height"]) { - min-height: min(560px, 85vh); - } -} - -/* Serve panel specifically: the admin-card inline style is - `overflow:hidden` (so the toolbar/header don't drift), and the list - inside has overflow:visible. On short windows that combination - clipped the cards off the bottom with no scrollbar. Make the list - itself the scroll surface so the rest of the card stays put. */ -.cookbook-group[data-backend-group="Serve"] > .admin-card { - min-height: 0; -} -.cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list, -.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list { - flex: 1 1 0; - min-height: 0; - overflow-y: auto !important; - overscroll-behavior: contain; -} -.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list .doclib-card.doclib-card-expanded { - flex: 0 0 auto !important; - overflow: visible !important; -} -/* Launch model cards use the same domino entrance as the other library lists - when a cached-model scan finishes. The class is removed after the one-shot - entrance, so filtering and sorting stay instantaneous. */ -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(1) { animation-delay: 0.04s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(2) { animation-delay: 0.08s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(3) { animation-delay: 0.12s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(4) { animation-delay: 0.16s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(5) { animation-delay: 0.20s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(6) { animation-delay: 0.24s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(7) { animation-delay: 0.28s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(8) { animation-delay: 0.32s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(9) { animation-delay: 0.36s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(10) { animation-delay: 0.40s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(11) { animation-delay: 0.44s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(12) { animation-delay: 0.48s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(n+13) { animation-delay: 0.48s; } -@media (prefers-reduced-motion: reduce) { - #cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { - animation: none; - } -} -/* Drag-and-drop visual hint for the email compose pane. Subtle accent - outline + tinted overlay so it's obvious files will attach if dropped. */ -.doc-editor-pane.email-dragover { - outline: 2px dashed var(--accent, #2563eb); - outline-offset: -8px; - background: color-mix(in srgb, var(--accent, #2563eb) 6%, transparent); -} -.doc-editor-pane.email-dragover::after { - content: 'Drop to attach'; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: var(--accent, #2563eb); - color: #fff; - padding: 8px 16px; - border-radius: 6px; - font-size: 12px; - pointer-events: none; - z-index: 1000; -} - -/* Email reader window — make the body always fill the available vertical - space regardless of how the window is positioned/resized. The user can - drag the bottom-right corner to resize; the body re-flexes automatically. */ -.email-window-modal .modal-content { - display: flex !important; - flex-direction: column !important; - resize: both; - overflow: hidden; - min-width: 320px; - min-height: 240px; - /* When user resizes/drags so the window covers a side, force the inner - body to fill all the way to the bottom. The default max-height: 85vh - (set inline by _makeDraggable's exitFullscreen) and 80vh from the - opener both cap at the *viewport* — if the browser window then changes - size (e.g. OS aero-snap), the modal stays at the old height and - truncates rows. Drop the cap whenever an explicit width OR height has - been written inline by the resize handle / drag handler. */ -} -.email-window-modal .modal-content[style*="width"], -.email-window-modal .modal-content[style*="height"] { - max-height: calc(100vh - 16px) !important; -} -.email-window-modal .email-window-body { - flex: 1 1 auto !important; - min-height: 0 !important; - /* Explicit basis of 0 makes the body grow to fill remaining space rather - than its content's intrinsic height (which was capping the thread). */ - flex-basis: 0 !important; - max-height: none !important; - overflow: auto !important; - overscroll-behavior: contain; -} - -/* Individual email reader window — fullscreen mode (drag-to-top snap or - double-click header). Same recipe as the library modal: pin to viewport - edges and let the inner body absorb the remaining height. */ -body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .modal-content { - position: fixed !important; - inset: 0 !important; - left: 0 !important; - top: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; - display: flex !important; - flex-direction: column !important; -} -body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .email-window-body { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; -} - -/* Email library modal: fullscreen mode + larger mobile sheet (mirrors the - cookbook treatment so the toolbar and bulk actions stay reachable). - Leaves whichever navigation strip is currently visible on the left - (the wide sidebar OR the icon rail — they're mutually exclusive, so - one of the two CSS vars is 0 at any given time, set by init.js). */ -body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { - position: fixed !important; - top: 0 !important; - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; - right: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; -} -/* Mobile: the /email route adds .email-lib-fullscreen, but the desktop full- - bleed rule above squares off the corners and offsets the email by the icon - rail. On phones the email should be the normal bottom-sheet — full width, - rounded top corners. Same specificity as the rule above + later in source, - so it wins on mobile. */ -@media (max-width: 768px) { - body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { - left: 0 !important; - right: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - height: 100dvh !important; - max-height: 100dvh !important; - border-radius: 14px 14px 0 0 !important; - border-top: 1px solid var(--border) !important; - } -} -/* Make the inner panes actually grow to fill the fullscreen container - instead of staying at their natural size. The body owns the remaining - height below the header; the admin-card + grid then expand into it. */ -#email-lib-modal.email-lib-fullscreen .modal-body { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; -} -#email-lib-modal.email-lib-fullscreen .modal-body > .admin-card { - flex: 1 1 auto !important; - min-height: 0 !important; -} -#email-lib-modal.email-lib-fullscreen .doclib-grid { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow: auto !important; -} -@media (max-width: 768px) { - /* Mobile email modal sizing — keep in sync with the rule earlier in the - file. 75dvh tall always so flex children (the expanded email reader) - have a real height to grow into. */ - #email-lib-modal .modal-content { - max-height: 90dvh !important; - max-height: 90vh !important; - height: 90dvh !important; - height: 90vh !important; - } - /* Inner panes grow to fill the modal-content — without flex:1 on the - body, the expanded email reader sits in a tiny box because there's - nothing pushing it to take the remaining height. overflow:hidden + - min-height:0 lets each layer pass its constraints down. */ - #email-lib-modal .modal-body, - #email-lib-modal .admin-card { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; - max-height: none !important; - } - #email-lib-modal .doclib-grid { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow-y: auto !important; - } - /* modal-content keeps its scroll OFF here — the inner flex children - (doclib-grid for the list view, email-reader-body for the expanded - reader) own the scroll surfaces. Without this, double-scroll layouts - trap touches and the reader can't claim full height. */ - #email-lib-modal .modal-content { - overflow: hidden !important; - -webkit-overflow-scrolling: touch; - overscroll-behavior: contain; - touch-action: pan-y; - } - /* Attachment row on phones: cap to ~2 rows so it never dominates the - reader. If more chips exist, the row scrolls vertically. Smaller chip - padding + max-width keeps each chip compact so two rows usually fit - everything anyway. */ - #email-lib-modal .email-reader-atts { - padding: 4px 10px !important; - gap: 3px !important; - max-height: calc(2 * 22px + 12px); /* 2 rows × chip height + padding */ - overflow-y: auto; - align-content: flex-start; - } -#email-lib-modal .email-attachment-chip { - padding: 1px 6px !important; - font-size: 10px !important; - max-width: min(240px, calc(100vw - 44px)) !important; - line-height: 18px !important; - } -} - - -/* Mobile: only ONE scroll surface inside the cookbook modal. The - modal-content is the scroller. Everything inside (cookbook-body, - cookbook-group, all the inner lists/panels) gets overflow: visible - so touch-pan never gets trapped in a nested scroller. - Without this, three levels of overflow:auto + max-height combinations - produce dead-zone areas where swipes do nothing. */ -@media (max-width: 768px) { - #cookbook-modal .modal-content { - overflow-y: auto !important; - -webkit-overflow-scrolling: touch; - overscroll-behavior: contain; - touch-action: pan-y; - } - #cookbook-modal .cookbook-body, - #cookbook-modal .cookbook-group, - #cookbook-modal .cookbook-section-body, - #cookbook-modal .hwfit-cached-list, - #cookbook-modal .doclib-grid, - #cookbook-modal .hwfit-list, - #cookbook-modal .hwfit-panel-cmd { - overflow: visible !important; - max-height: none !important; - min-height: 0 !important; - } - /* Running tmux output: cap how tall an expanded card gets (a long-lived job - can leave thousands of lines) so it doesn't balloon, but keep its own - scroll so you can read it. Outputs default collapsed on mobile now, so - you're usually looking at one expanded card at a time — no wall of nested - scrollers, and you collapse it to move past. */ - #cookbook-modal .cookbook-output-pre, - #cookbook-modal .cookbook-task .cookbook-output-pre { - max-height: 45vh !important; - min-height: 0 !important; - overflow-y: auto !important; - overflow-x: hidden !important; - overscroll-behavior: auto; /* chain to the modal at the scroll boundary */ - } - /* cookbook-body's flex:1 was needed when it owned scrolling — drop it - so the inner content drives modal-content's scroll height. */ - #cookbook-modal .cookbook-body { - flex: 0 0 auto !important; - height: auto !important; - } - /* Launch tab's cached-list normally has `flex: 1 1 0; min-height: 0` - (so it fills the modal on desktop). On mobile the parent now has - `height: auto`, which collapses `flex: 1 1 0` to ZERO PX — - models render but the list area is invisible because the flex - basis is 0 and there's no free space to grow into. Switch to - content-sized flex so the list grows with its children. */ - #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list, - #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list { - flex: 0 0 auto !important; - overflow: visible !important; - } -} -#cookbook-modal .hwfit-cached-list { - flex-shrink: 0; -} -.memory-toolbar { - transition: opacity 0.12s ease, max-height 0.2s ease; - max-height: 120px; -} -.doclib-card { - background: color-mix(in srgb, var(--fg) 3%, transparent); - border: 1px solid var(--border); - border-radius: 8px; - cursor: pointer; - transition: all 0.15s, opacity 0.3s, max-height 0.3s ease; - position: relative; -} -.doclib-card.doclib-card-deleting { - opacity: 0; - max-height: 0 !important; - overflow: hidden; - margin: 0; - padding: 0; - border-color: transparent; -} -.doclib-card.selected, -/* Universal selection highlight: any card-shaped row (documents, chats, - archive, research, skills, memories, tasks) gets the same accent outline - when its checkbox is checked. Done via :has() so the highlight follows - selection without per-renderer JS changes. The canonical checkbox class - is .memory-select-cb across every list. */ -.memory-item:has(.memory-select-cb:checked), -.doclib-card:has(.memory-select-cb:checked), -.doclib-chat-row:has(.memory-select-cb:checked) { - border-color: color-mix(in srgb, var(--red) 40%, transparent); - background: color-mix(in srgb, var(--red) 4%, transparent); - /* 2px accent outline overlaid on the 1px border — reads as a thicker - selected border without shifting layout (every card would otherwise - need a 2px transparent border to keep the same width). */ - outline: 2px solid var(--accent-primary, var(--red)); - outline-offset: -1px; -} -.doclib-card-header { - display: flex; - align-items: center; - padding: 7px 8px; - gap: 6px; - min-width: 0; -} -/* Mobile only: push the unexpanded email card's title-row text down 4px so - it sits visually centered with the surrounding card padding. Targets the - actual title-row class (.email-card-titlerow) — the earlier attempt used - .doclib-card-header which the email card builds differently. */ -@media (max-width: 768px) { - #email-lib-modal .doclib-card:not(.doclib-card-expanded) .email-card-titlerow { - margin-top: 4px; - } -} -.doclib-card-title { - font-size: 11px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; - flex: 1; -} -.doclib-card-ver { - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - color: var(--accent, var(--red)); - font-size: 9px; - flex-shrink: 0; - font-weight: 600; - letter-spacing: 0.3px; - min-width: 24px; - text-align: left; - box-sizing: border-box; -} -.doclib-card-ver-muted { - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.doclib-card-lang { - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - color: var(--accent); - font-size: 9px; - text-align: left; - flex-shrink: 0; - min-width: 52px; - box-sizing: border-box; -} -.doclib-card-session { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 60px; - width: 100px; - max-width: 100px; - font-size: 9px; - color: var(--fg-muted); - flex-shrink: 0; - text-align: left; -} -.doclib-card-time { - flex-shrink: 0; - opacity: 0.5; - font-size: 9px; - color: var(--fg-muted); - min-width: 50px; - text-align: left; -} -/* Footer — used by archive cards */ -.doclib-card-footer { - display: contents; -} -/* Preview — hidden by default, shown on expand */ -.doclib-card-preview { - display: none; - position: relative; - padding: 8px 12px; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.5; - color: var(--code-fg, var(--hl-fg, var(--fg))); - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin: 0; -} -.doclib-card-preview pre { - margin: 0; - white-space: pre-wrap; - word-break: break-all; -} -.doclib-card-expanded .doclib-card-preview pre { - flex: 1 1 0; - overflow-y: auto; - min-height: 0; -} -.doclib-card-preview code.hljs { - background: none; - padding: 0; -} -/* Expanded-only action bar — inside preview. Buttons shifted up 4px by - trimming the row's top margin so they sit closer to the preview text. */ -.doclib-card-expanded-actions { - display: none; - align-items: flex-start; - gap: 6px; - padding: 8px 0 2px; - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 4px; -} -.doclib-card-expanded-actions > .doclib-card-action-btn, -.doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - position: relative; - top: -4px; -} -.doclib-action-group { - display: flex; - flex-direction: column; - gap: 3px; -} -.doclib-action-btn-row { - display: flex; - gap: 6px; -} -.doclib-action-hint-row { - display: flex; - gap: 6px; -} -/* Match the chat/research footer buttons exactly: flat, bordered, - app font, accent on hover. */ -.doclib-card-action-btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - box-sizing: border-box; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - cursor: pointer; - /* These buttons live inside .doclib-card-preview, which forces a - monospace font. The chat/research footer buttons instead inherit the - modal font, which is the app font (Fira Code) on mobile and Inter on - desktop (.modal-content's Inter rule is inside @media min-width:769px). - Mirror that here so all three footers match in both contexts. */ - font-family: var(--font-family, 'Fira Code', monospace); - transition: border-color 0.15s, color 0.15s; -} -@media (min-width: 769px) { - .doclib-card-action-btn { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - letter-spacing: -0.015em; - } -} -.doclib-card-action-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -.doclib-card-text-btn-danger { - color: var(--color-danger, #e06c75); - border-color: var(--color-danger, #e06c75); -} -.doclib-card-text-btn-danger:hover { - color: #ff4d4d; - border-color: #ff4d4d; -} -.doclib-card-expanded-actions > .doclib-card-action-btn { - font-size: 10px; - padding: 3px 8px; -} -/* Push the Open/Clone group to the right edge of the action bar — Delete - anchors the left, primary actions sit opposite. */ -.doclib-card-expanded-actions > .doclib-action-group { - margin-left: auto; -} -.doclib-card-expanded-actions > .doclib-expanded-delete-btn, -.doclib-card-expanded-actions > .doclib-expanded-archive-btn { - left: 0 !important; -} -.doclib-expanded-mobile-more { display: none; } -.doclib-btn-hint { - font-weight: normal; - opacity: 0.35; - font-size: 8px; - min-width: 58px; - padding: 0 8px; - box-sizing: border-box; -} -@media (max-width: 768px) { - .doclib-btn-hint { display: none !important; } -} - -/* Chat row expand preview — matches the documents-tab expand style. */ -.doclib-card-chevron { - display: inline-flex; - align-items: center; - justify-content: center; - align-self: center; - flex-shrink: 0; - width: 14px; - height: 14px; - line-height: 0; - opacity: 0.3; - transform: rotate(0deg); - transition: transform 0.2s ease, opacity 0.15s; -} -.doclib-card-chevron svg { display: block; } -.doclib-card-expanded .doclib-card-chevron, -.task-card.expanded .doclib-card-chevron { - opacity: 0.8; - transform: rotate(180deg); -} -.doclib-card > .memory-item-actions { order: 1; } -.doclib-card > .doclib-card-chevron { order: 2; } -.doclib-chat-header .memory-item-actions, -.task-card .memory-item-actions { order: 2; } -.doclib-chat-header .doclib-card-chevron, -.task-card .doclib-card-chevron { order: 3; } -.hwfit-cached-list > .doclib-card > .memory-item-actions { order: 2; } -.hwfit-cached-list > .doclib-card > .doclib-card-chevron { order: 3; } -#email-lib-modal .doclib-card > .memory-item-actions { order: 1; align-self: center; } -#email-lib-modal .doclib-card > .doclib-card-chevron { display: none; } -.doclib-chat-row { flex-direction: column; align-items: stretch; } -.doclib-chat-row .doclib-card-chevron { - display: inline-flex; - opacity: 0.55; - transition: transform 0.2s ease, opacity 0.15s; - flex-shrink: 0; -} -.doclib-chat-row:not(.doclib-card-expanded) .doclib-card-chevron { - transform: rotate(0deg); -} -.doclib-chat-row.doclib-card-expanded .doclib-card-chevron { - opacity: 0.8; - transform: rotate(180deg); -} -/* Release .memory-item's base max-height: 200px when a chat row is - expanded. Without this, the row clips at 200px on desktop and the - preview's messages/actions visually spill past the row's box — - "colliding" with the next chat in the column. The mobile takeover - below already handles this via the same override; this rule is the - desktop-or-any-viewport equivalent. */ -.doclib-chat-row.doclib-card-expanded { - max-height: none; - overflow: visible; -} -/* When a chat row is expanded, take over the grid the same way documents - do: hide siblings, hide the admin-card toolbar, and let this row plus - its preview claim the full available height. .memory-item's default - max-height: 200px must be overridden or the preview will be tiny. */ -/* Mobile-only "card takeover" for an expanded chat — hides siblings, - hides the admin-card toolbar, and lets this row + its preview claim - the full available height. On desktop the expanded preview just - renders inline below the row with its messages constrained by - max-height + overflow:auto so the user gets a normal scroll. */ -@media (max-width: 820px) { - .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - flex: 1 1 auto !important; - max-height: none !important; - overflow-y: auto !important; - } - .doclib-chat-row.doclib-card-expanded { - flex: 1 1 auto !important; - max-height: none !important; - min-height: 0 !important; - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - align-items: stretch !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-header { - flex: 0 0 auto !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - padding-bottom: 4px !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { - -webkit-overflow-scrolling: touch; - max-height: none !important; - flex: 1 1 auto !important; - min-height: 0 !important; - } -} -.doclib-chat-preview { - font-size: 11px; - padding: 0 4px 6px; -} -.doclib-chat-preview .doclib-chat-open-btn { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - color: var(--fg-muted); - border: 1px solid var(--border); - background: none; - cursor: pointer; - font-family: inherit; - transition: border-color 0.15s, color 0.15s; -} -.doclib-chat-preview .task-completed-report-btn { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); -} -.doclib-chat-preview .doclib-chat-open-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -.doclib-chat-preview-messages { - margin-top: 6px; - max-height: 320px; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - padding-right: 4px; -} -/* Desktop expanded state — match the documents-tab behavior: hide - sibling chats, hide the admin-card toolbar/header, and let the - expanded row + its messages preview claim the full modal height. - Chat rows use `memory-item doclib-chat-row` (no `doclib-card` class) - so the document-tab's global rules at line 12284-12304 skip them — - these mirror those rules scoped to chat rows. The mobile media query - above (820px) keeps the same takeover behavior for phones. */ -@media (min-width: 821px) { - .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - flex: 1 1 auto !important; - max-height: none !important; - overflow: hidden auto !important; - } - .doclib-chat-row.doclib-card-expanded { - flex: 1 1 auto !important; - max-height: none !important; - min-height: 0 !important; - display: flex !important; - flex-direction: column !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { - flex: 1 1 auto !important; - min-height: 0 !important; - display: flex !important; - flex-direction: column !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow-y: auto !important; - } -} -.doclib-chat-preview-actions { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 8px; - padding: 2px 0 2px; - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 2px; - flex-shrink: 0; - /* Whole action footer nudged down 5px. */ - position: relative; - top: 5px; -} -.doclib-chat-delete-btn, -.doclib-chat-archive-btn, -.doclib-chat-restore-btn, -.doclib-chat-discuss-btn, -.doclib-chat-copy-btn { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - background: none; - cursor: pointer; - font-family: inherit; - transition: border-color 0.15s, color 0.15s; -} -.doclib-chat-archive-btn, -.doclib-chat-restore-btn, -.doclib-chat-discuss-btn, -.doclib-chat-copy-btn { - color: var(--fg-muted); - border: 1px solid var(--border); -} -.doclib-chat-restore-btn:hover { - color: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -.doclib-chat-archive-btn:hover, -.doclib-chat-discuss-btn:hover, -.doclib-chat-copy-btn:hover { - color: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -.doclib-chat-delete-btn { - color: var(--color-danger, #e06c75); - border: 1px solid var(--color-danger, #e06c75); -} -.doclib-chat-delete-btn:hover { - color: #ff4d4d; - border-color: #ff4d4d; -} -/* When a chat is archived there's no Archive button, so Delete becomes - the only left-side action and needs the auto margin to push Open right. */ -.doclib-chat-preview-actions:not(:has(.doclib-chat-archive-btn)) > .doclib-chat-delete-btn { - margin-right: auto; -} -/* Hide the "..." menu while the chat card is expanded — archive + - delete live in the preview footer instead. */ -.doclib-chat-row.doclib-card-expanded ._chat-menu { display: none; } -.doclib-chat-preview-actions.doclib-chat-preview-actions-top { - border-top: none; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 4px; - margin-bottom: 6px; - padding: 0 0 6px; - justify-content: flex-start; -} -.doclib-chat-preview-messages .doclib-chat-msg { - margin: 4px 0 10px; - padding-left: 8px; - border-left: 2px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.doclib-chat-preview-messages .doclib-chat-msg.user { - border-left-color: color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); -} -.doclib-chat-preview-messages .doclib-chat-msg-role { - font-size: 9px; - font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - opacity: 0.55; - margin-bottom: 2px; -} -.doclib-chat-preview-messages .doclib-chat-msg.user .doclib-chat-msg-role { - color: var(--accent, var(--red)); - opacity: 0.85; -} -.doclib-chat-preview-messages .doclib-chat-msg-body { - white-space: pre-wrap; - word-break: break-word; - opacity: 0.85; - line-height: 1.45; -} -/* Chat-bubble preview — mirrors the real chat layout. User bubbles hug - right with an accent tint; assistant bubbles hug left with a neutral - panel tint and a small model tag at the top. */ -.doclib-chat-bubble-row { - display: flex; - margin: 6px 0; -} -.doclib-chat-bubble-row.user { justify-content: flex-end; } -.doclib-chat-bubble-row.assistant { justify-content: flex-start; } -.doclib-chat-bubble { - max-width: 85%; - padding: 6px 10px 8px; - border-radius: 14px; - font-size: 11px; - line-height: 1.45; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.doclib-chat-bubble-row.user .doclib-chat-bubble { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); - border-bottom-right-radius: 4px; -} -.doclib-chat-bubble-row.assistant .doclib-chat-bubble { - border-bottom-left-radius: 4px; -} -.doclib-chat-msg-model { - display: inline-block; - font-size: 8px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; - opacity: 0.55; - margin-bottom: 3px; - padding: 1px 6px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg-muted, var(--fg)); -} -.doclib-chat-bubble-body { word-break: break-word; } -.doclib-chat-bubble-body p { margin: 4px 0; } -.doclib-chat-bubble-body p:first-child { margin-top: 0; } -.doclib-chat-bubble-body p:last-child { margin-bottom: 0; } -.doclib-chat-bubble-body pre { - font-size: 10px; - margin: 4px 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--bg) 60%, transparent); - border-radius: 6px; - overflow-x: auto; -} -.doclib-chat-bubble-body code { - font-size: 10px; - padding: 1px 4px; - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.doclib-chat-bubble-body pre code { background: none; padding: 0; } -.doclib-chat-bubble-body ul, -.doclib-chat-bubble-body ol { - margin: 4px 0; - padding-left: 18px; -} -.doclib-card-collapse-chevron { - display: none; - align-items: center; - justify-content: center; - width: 24px; - height: 22px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: transparent; - color: var(--color-muted, var(--fg)); - font: inherit; - opacity: 0.3; - cursor: pointer; - flex-shrink: 0; - margin: 0 0 0 4px; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.doclib-card-collapse-chevron:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); -} -.cookbook-serve-title { - display: flex; - align-items: center; - min-width: 0; -} -.cookbook-serve-title .doclib-card-collapse-chevron { - display: inline-flex; - margin-left: auto; - margin-top: 0; -} -.doclib-card-expanded .doclib-card-collapse-chevron { - display: inline-flex; -} -/* Expanded card — fills the whole grid */ -.doclib-card.doclib-card-expanded { - flex: 1; - min-height: 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); - border: 1px solid var(--border); - border-radius: 8px; - display: flex; - flex-direction: column; - overflow: hidden; - animation: doclib-expand 0.2s ease-out; -} -.doclib-card.doclib-card-expanded { - flex-direction: column !important; - align-items: stretch !important; - max-height: none !important; - gap: 4px !important; -} -.doclib-card.doclib-card-expanded > .memory-item-actions { display: none; } -/* Non-preview children of an expanded doc card carry an inline flex:1 from - the row layout. With the card now flex-column those children would steal - half the height and shrink the preview — pin them at auto height. */ -.doclib-card.doclib-card-expanded > div:not(.doclib-card-preview):not(.doclib-card-header):not(.memory-item-actions):not(.email-card-reader) { - flex: 0 0 auto !important; -} -/* The email reader IS the scroll container — keep its flex:1 so its - internal body can claim the rest of the card's height and scroll. */ -.doclib-card.doclib-card-expanded > .email-card-reader { - flex: 1 1 auto !important; - min-height: 0 !important; -} -.doclib-card.doclib-card-expanded .doclib-card-preview { - display: flex; - flex-direction: column; - flex: 1 1 auto !important; - /* The preview matches chat rows: document content scrolls, while the - footer stays in the card rather than becoming the last scroll item. */ - overflow: hidden; - min-height: 0; -} -.doclib-card.doclib-card-expanded .doclib-card-expanded-actions { - display: flex; - flex-shrink: 0; -} -/* Document cards can be expanded in the normal windowed library, where the - grid has no definite height. A zero-basis then collapses to nothing - while the footer remains visible. Keep the document text's intrinsic height - in that layout; bounded/fullscreen layouts can still shrink it and scroll. */ -#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview > pre { - flex: 1 1 auto; - min-height: 1.5em; -} - -/* Collapsible skills section headers (Your skills / Built-in). */ -.skills-section-header { - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - user-select: none; - padding: 6px 4px 4px; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.06em; - opacity: 0.6; - font-weight: 600; -} -.skills-section-header:hover { opacity: 0.9; } -.skills-section-chevron { transition: transform 0.15s ease; flex-shrink: 0; } -.skills-section-header.collapsed .skills-section-chevron { transform: rotate(-90deg); } -.skills-section-count { - margin-left: auto; - opacity: 0.6; - font-variant-numeric: tabular-nums; - font-weight: normal; -} -.skill-card-section-hidden { display: none !important; } -/* Warning banner shown when previewing/editing a built-in capability. */ -.skill-builtin-warn { - font-size: 9.5px; - line-height: 1.45; - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 35%, transparent); - border-radius: 6px; - padding: 6px 9px; - margin: 0 0 8px; - flex-shrink: 0; -} - -/* #skills-list wears both .memory-list and .doclib-grid. doclib-grid's - max-height:400px would cap the list AND clamp an expanded card — keep - the memory-list fill-the-modal behaviour instead. */ -#skills-list.doclib-grid { - max-height: none; - flex: 1; - min-height: 0; -} -#skills-list.doclib-grid:has(.doclib-card-expanded) { - overflow: hidden; /* expanded card owns its own scroll */ -} -/* When a skill is expanded, hide the sibling Add-Skill form card so the - expanded SKILL.md uses the full panel. Otherwise the two admin-cards - split the height ~50/50 and the expanded skill is stuck in its half - while the Add form idles in the other. */ -.memory-tab-panel[data-memory-panel="skills"]:has(.doclib-card-expanded) > .admin-card:nth-of-type(2) { - display: none !important; -} - -/* Skills cards reuse the doclib expand/footer machinery. The SKILL.md - preview + the edit textarea need to fill the expanded card and own - their own scroll, same as a document preview. */ -/* Collapsed skill rows used a smaller (0.9em code) title, so the click row - read slimmer than document/chat/library cards. Give the header a min-height - so the tap target matches the other library items. */ -.skill-card > .skill-card-header { - min-height: 53px; - box-sizing: border-box; - display: flex; - align-items: center; - gap: 6px; -} -/* Skill rows share Tasks' card hover colors. Draft state belongs in the - status badge, not in a second opacity layer across the whole row. */ -/* Name + description column. Name wraps to 2 lines (skill slugs are long — - use the space rather than truncating to "check-model-downl…"). */ -.skill-card-textcol { - flex: 1 1 auto; - min-width: 0; - overflow: hidden; -} -.skill-card-title-row { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.skill-card-name { - font-family: inherit; - font-weight: 500; - font-size: 12px; - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - word-break: normal; - line-height: normal; -} -.skill-card-title-row .skill-card-name { min-width: 0; } -.skill-status-published { - width: 20px; - height: 20px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 15%, transparent); - border-color: color-mix(in srgb, var(--color-success, #4ade80) 32%, transparent); - border-radius: 50%; -} -.skill-stats .skill-status-published { - width: 15px; - height: 15px; - margin-left: 3px; - vertical-align: middle; -} -.skill-stats .skill-status-published svg { - width: 9px; - height: 9px; -} -.skill-card-desc { - font-size: 10px; - opacity: 0.4; - margin-top: -1px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.skills-audit-model-field { - display: flex; - align-items: center; - gap: 8px; - margin-top: 10px; - font-size: 11px; -} -.skills-audit-model-field > span { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 68%, transparent); -} -.skills-audit-model-field select { - min-width: 0; - flex: 1; - height: 26px; - padding: 0 6px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.skill-card-tags { - display: flex; - gap: 4px; - flex-wrap: wrap; - margin-top: 4px; -} -.skill-tag-pill { - padding: 1px 5px; - font-size: 9px; -} -.skill-usage-pill { - margin-left: auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.skill-tag-filter-chip { cursor: pointer; } - -@media (max-width: 768px) { - .skill-card > .skill-card-header { - align-items: flex-start; - } - .skill-card-textcol { - min-width: 0; - } - .skill-card-tags { - max-width: 100%; - overflow: hidden; - } - .skill-card-tags .skill-tag-pill { - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -} -/* Right cluster — pills (right-aligned), stats, then the menu/chevron. */ -.skill-card-right { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; - margin-left: auto; -} -.skill-card-right .memory-cat-badge { flex-shrink: 0; } -.skill-status-bin { - background: color-mix(in srgb, var(--color-danger, #d9534f) 20%, transparent); - color: var(--color-danger, #d9534f); -} -.skill-model-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-model-student { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.skill-audit-pass { - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 16%, transparent); -} -.skill-audit-needs_work { - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 16%, transparent); -} -.skill-audit-fail { - color: var(--color-danger, #e06c75); - background: color-mix(in srgb, var(--color-danger, #e06c75) 16%, transparent); -} -.skill-audit-inconclusive, -.skill-audit-skipped, -.skill-audit-unknown { - color: color-mix(in srgb, var(--fg) 64%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-model-teacher { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 24%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-necessity-duplicate { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 22%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-trivial { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 62%, transparent); - border-color: color-mix(in srgb, var(--fg) 16%, transparent); -} -.skill-necessity-irrelevant { - background: color-mix(in srgb, var(--color-danger, #e06c75) 18%, transparent); - color: var(--color-danger, #e06c75); -} -.skills-summary-strip { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - margin: -3px 0 8px; -} -.skills-summary-strip::-webkit-scrollbar { display: none; } -.skills-summary-strip:empty { - display: none; -} -.skills-summary-chip { - display: inline-flex; - align-items: center; - gap: 5px; - height: 23px; - padding: 0 8px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 10px; - line-height: 1; - cursor: pointer; - flex: 0 0 auto; -} -.skills-summary-chip:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.skills-summary-chip.active { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - color: var(--fg); -} -.skills-summary-chip strong { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10px; - color: var(--fg); -} - -/* Shared filter/tag strip contract. These rows all sit immediately below - their screen's search or primary toolbar and stay one line at every width. */ -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -) { - align-items: center; - flex-wrap: nowrap !important; - gap: 6px; - width: 100%; - max-width: 100%; - min-width: 0; - max-height: none; - overflow-x: auto !important; - overflow-y: hidden !important; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - box-sizing: border-box; - padding: 0; - margin: -3px 0 8px; -} -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -)::-webkit-scrollbar { display: none; } -:is( - .skills-summary-strip > .skills-summary-chip, - #memory-category-filters > .skills-summary-chip, - .memory-category-filters > .memory-cat-chip, - .doclib-lang-chips > :is(.memory-cat-chip, .skills-summary-chip), - .doclib-chips > .doclib-chip, - .notes-labels-bar > .notes-label-chip, - .tasks-activity-filters > .memory-cat-chip, - .gallery-tag-chips > .gallery-chip, - .gallery-album-chips > .gallery-chip, - .gallery-ai-tags > .gallery-ai-chip, - .cal-filters > .cal-filter-item -) { - flex: 0 0 auto; - box-sizing: border-box; - height: 23px !important; - min-height: 23px; - padding: 0 8px !important; - border-radius: 6px !important; - font-size: 10px !important; - line-height: 1; - white-space: nowrap; -} -.doclib-lang-chips { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.doclib-chip-scroll-frame { - position: relative; - display: flex; - align-items: center; - flex: 1 1 100%; - min-width: 0; - max-width: 100%; -} -.doclib-chip-scroll-frame > .chip-scroll-strip { - flex: 1 1 auto; - min-width: 0; - margin-left: 0; - margin-right: 0; -} -.doclib-chip-scroll-arrow { - display: none; - position: absolute; - top: calc(50% - 1px) !important; - transform: translateY(-50%); - margin-top: 0; - z-index: 2; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - box-sizing: border-box; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 18px; - cursor: pointer; - opacity: 0.84; - transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease; -} -.doclib-chip-scroll-arrow-glyph { - display: inline-block; - position: relative; - top: -1px; - line-height: 1; -} -.doclib-chip-scroll-arrow:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); -} -@media (max-width: 768px) { - .doclib-chip-scroll-arrow { display: none !important; } -} -.doclib-chip-scroll-frame.has-overflow > .doclib-chip-scroll-arrow:not([hidden]) { - display: inline-flex; -} -.doclib-chip-scroll-arrow.left { left: 0; } -.doclib-chip-scroll-arrow.right { right: 0; } -.doclib-chip-scroll-frame.has-overflow > .chip-scroll-strip { - padding-left: 0 !important; - padding-right: 22px !important; -} -.doclib-chip-scroll-frame > .chip-scroll-strip.is-pointer-dragging { - cursor: grabbing; - user-select: none; -} -.doclib-chip-scroll-frame > .chip-scroll-strip:not(.is-pointer-dragging) { - cursor: grab; -} -/* Calendar tags are wrapped by chipScroll.js after render. Keep that wrapper - content-sized inside the calendar's column layout; the default flex growth - otherwise turns the one-line tag strip into a large empty panel. */ -#cal-body > .doclib-chip-scroll-frame:has(> .cal-filters) { - flex: 0 0 auto; - min-height: 0; -} -/* The completed-task filters are a compact toolbar, not a flexible panel. - Without this override the shared chip frame grows into the available - height of the card, which is especially visible the first time the mobile - view switches to Completed. */ -.tasks-completed-card > .doclib-chip-scroll-frame:has(> #tasks-completed-status-chips) { - flex: 0 0 auto; - min-height: 0; -} -/* The active Tasks filter row is also a compact toolbar. Keep its wrapper - content-sized so changing a filter cannot make the mobile panel grow a - large empty gap between the chips and the task cards. */ -.admin-card > .doclib-chip-scroll-frame:has(> #tasks-filter-chips) { - flex: 0 0 auto; - min-height: 0; -} -#memory-category-filters { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.cookbook-serve-tag-label { - display: inline-flex; - align-items: center; - min-width: 0; -} -#serve-search, -#hwfit-cache-select, -#serve-tags { - position: relative; - top: -2px; -} -#hwfit-cache-select { - top: -4px; -} -/* Calendar previously framed its tags as a separate card. It now follows the - same unframed placement as the other filter strips. */ -.cal-filters { - border: 0; - background: none; - box-shadow: none; -} -.skill-duplicate-keep { - background: color-mix(in srgb, var(--color-success, #4ade80) 22%, transparent); - color: var(--color-success, #4ade80); -} -.skill-duplicate-group { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - color: color-mix(in srgb, var(--fg) 62%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-duplicate-lower { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); - color: var(--color-danger, #e06c75); -} -.skill-stats { - font-size: 9px; - font-family: monospace; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.skill-turns-saved { - font-size: 9px; - opacity: 0.82; - font-variant-numeric: tabular-nums; - white-space: nowrap; - flex: 0 0 auto; -} -.skill-expanded-savings { - display: flex; - align-items: center; - min-height: 18px; - margin: 0 0 5px; -} -.skill-turns-positive { - color: color-mix(in srgb, var(--color-success, #4ade80) 82%, var(--fg)); -} -.skill-turns-zero, -.skill-turns-unknown { - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.skill-turns-negative { - color: color-mix(in srgb, var(--color-danger, #e06c75) 80%, var(--fg)); -} -.skill-conf { - display: inline-block; - min-width: 4ch; - text-align: right; - font-weight: 700; - color: var(--accent, var(--red)); -} -.skill-verified, -.skill-teachermark, -.skill-needsmark { - display: inline-flex; - align-items: center; - vertical-align: middle; - margin-right: 3px; -} -.skill-verified { color: var(--accent, #4ade80); } -.skill-teachermark { color: var(--color-warning, #f0ad4e); } -.skill-needsmark { color: var(--color-danger, #e06c75); cursor: help; } - -/* The card currently being processed by an "Audit now" run glows + pulses so - it's obvious which one the audit is on. */ -@keyframes skill-audit-pulse { - 0%, 100% { box-shadow: 0 0 6px 0 color-mix(in srgb, var(--accent, #4ade80) 55%, transparent); } - 50% { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent, #4ade80) 85%, transparent); } -} -.skill-card.skill-audit-active { - border-color: var(--accent, #4ade80) !important; - animation: skill-audit-pulse 1.3s ease-in-out infinite; -} - -/* A test is running for this skill — the app's whirlpool spinner is injected - next to the name from JS (see _setCardRunning) so the collapsed/folded card - still makes it obvious work is in progress. */ -/* Collapsed bar shows the kebab menu; expanded shows an up-chevron to - collapse. (Toggled by the .doclib-card-expanded class.) */ -.skill-kebab-btn { - display: inline-flex; - align-items: center; - justify-content: center; - background: none; - border: none; - padding: 4px; - margin: 0; - cursor: pointer; - color: var(--fg); - opacity: 0.5; - border-radius: 5px; - flex-shrink: 0; - position: relative; - top: -2px; -} -.skill-kebab-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-chevron-up { display: none; align-items: center; opacity: 0.5; flex-shrink: 0; } -/* Kebab dropdown */ -.skill-kebab-menu { - position: fixed; - /* z-index is set inline via topPortalZ() at open time (#4720); a static - value here loses once the modal bring-to-front counter climbs past it. */ - min-width: auto; - width: max-content; - padding: 4px; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 6px 20px rgba(0,0,0,0.22); - display: flex; - flex-direction: column; - gap: 1px; -} -.skill-kebab-item { - display: flex; - align-items: center; - gap: 10px; - width: 100%; - padding: 6px 8px; - background: none; - border: none; - border-radius: 8px; - cursor: pointer; - color: var(--fg); - font-size: 11px; - text-align: left; -} -.skill-kebab-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-kebab-item.danger { color: var(--color-error, #e55); } -.skill-kebab-item.danger:hover { background: color-mix(in srgb, var(--color-error, #e55) 15%, transparent); } -/* Section labels separating "Your skills" from the read-only "Built-in - capabilities" list. */ -.skills-section-label { - font-size: 10px; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.04em; - margin: 10px 2px 4px; - flex-shrink: 0; -} -.skills-section-label:first-child { margin-top: 0; } -/* When a learned skill is expanded the grid hides sibling cards; hide the - section labels too so they don't orphan above the collapsed list. */ -#skills-list:has(.doclib-card-expanded) .skills-section-label { display: none; } -/* Built-in cards are read-only — no expand/hover-pointer affordance. */ -.skill-builtin-card { cursor: default; } -.skill-builtin-card:hover { border-color: var(--border); } -.skill-card-preview .skill-md-pre { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - white-space: pre-wrap; - word-break: break-word; -} -.skill-preview-copy { - position: absolute; - top: 6px; - right: 8px; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - padding: 0; - opacity: 0; - color: var(--fg); - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - cursor: pointer; - transition: opacity .15s, color .15s, border-color .15s; -} -.skill-card-preview:hover .skill-preview-copy { opacity: 0.7; } -.skill-preview-copy:hover, -.skill-preview-copy:focus-visible { opacity: 1 !important; } -.skill-preview-copy.copied { - opacity: 1; - color: var(--color-save-green, #4caf50); - border-color: var(--color-save-green, #4caf50); -} -/* :has()-free expand layout. skills.js adds .skills-has-expanded to the - skills admin-card when a skill opens, so this works on engines that don't - support :has() (e.g. older Firefox mobile, where the expand stuck at - ~50%). Hide everything but the list so the grid fills the card; the - absolutely-positioned expanded card (mobile) then fills the grid. */ -.admin-card.skills-has-expanded > *:not(#skills-list) { - display: none !important; -} -.admin-card.skills-has-expanded > #skills-list.doclib-grid { - flex: 1 1 auto !important; - height: 100% !important; - min-height: 0 !important; - overflow: hidden !important; -} -/* Expand animation — a clean opacity fade. The mobile card snaps to a - full-screen overlay (position:absolute), so any translate/scale on it - reads as a jarring "explosion"; a pure fade just appears smoothly. (No - transform also keeps skills.js's height measurement accurate.) */ -@keyframes skill-card-expand { - from { opacity: 0; } - to { opacity: 1; } -} -.skill-card.doclib-card-expanded { - animation: skill-card-expand 0.18s ease-out; -} -/* Switching directly from one expanded card to another: no fade (it would - show the previous card collapsing through the semi-transparent new one). */ -.skill-card.doclib-card-expanded.skill-expand-instant { - animation: none !important; -} -@media (prefers-reduced-motion: reduce) { - .skill-card.doclib-card-expanded, - .skill-card.doclib-card-expanded .doclib-card-preview { - animation: none !important; - } -} -/* The preview WRAPPER inherits flex:1 (grow) from the generic doclib expand - rule — that's what stretched the card and left a void / pushed the footer - out. Size it to content (shrink + scroll only when long) so the footer - sits right under the preview and stays fully visible. */ -.skill-card.doclib-card-expanded .doclib-card-preview { - flex: 0 1 auto !important; - min-height: 0; - /* The footer lives INSIDE this wrapper. Don't let the wrapper scroll, or - the footer scrolls off with the content. Keep it clipped; the - inside is the scroller, so the footer stays pinned at the bottom. */ - overflow: hidden !important; -} -.skill-card.doclib-card-expanded .skill-md-pre { - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them - back down 4px so they're vertically centered in the skill card footer. */ -.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn, -.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - top: 0; -} -/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */ -#skills-bulk-bar .memory-toolbar-btn { - position: relative; - top: -2px; -} -/* ── Audit-all progress panel ── */ -.skills-audit-panel { - border: 1px solid var(--border); - border-radius: 8px; - padding: 8px 10px; - margin: 6px 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } -.skills-audit-title { font-size: 12px; font-weight: 600; } -.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; } -.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; } -.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; } -.skills-audit-log { - max-height: 22vh; overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10.5px; line-height: 1.5; opacity: 0.8; -} -/* ── Skill test monitor + AI eval verdict ── */ -.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; } -.skill-test-log { - flex: 1 1 auto; - min-height: 120px; - max-height: 48vh; - overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.45; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - white-space: pre-wrap; - word-break: break-word; -} -.skill-test-log > div { margin: 1px 0; } -.skill-test-meta { opacity: 0.5; } -.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; } -.skill-test-round { opacity: 0.6; margin-top: 6px !important; } -.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; } -.skill-test-out { opacity: 0.7; padding-left: 10px; } -.skill-test-say { white-space: pre-wrap; } -.skill-test-err { color: var(--color-danger, #e06c75); } -.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } -.skill-eval-badge { - font-size: 10px; font-weight: 700; letter-spacing: 0.03em; - padding: 2px 8px; border-radius: 10px; flex-shrink: 0; -} -.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); } -.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); } -.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); } -.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); } -.skill-eval-summary { font-size: 11px; opacity: 0.85; } -.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; } -.skill-eval-issues li { margin: 2px 0; } -.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } -.skill-eval-approve.suggested { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; -} -/* Already published — the button confirms the state (click to unpublish). */ -.skill-eval-approve.is-approved { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; - background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important; -} -/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title, - When to use, How, Tags) is accent-colored while the rest stays muted — real - placeholders can't be partially colored. The native placeholder is a single - space so :placeholder-shown still toggles the overlay. */ -.skill-ph-wrap { position: relative; } -.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; } -.skill-rich-ph { - position: absolute; - left: 11px; right: 11px; top: 50%; - transform: translateY(-50%); - pointer-events: none; - font-size: 12px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 40%, transparent); - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -/* Textarea: align the hint to the first line instead of vertical-centering. */ -.skill-rich-ph-top { top: 7px; transform: none; } -.skill-rich-ph .k { color: var(--accent, var(--red)); } -/* Keep the memory-input enter hint pinned to the field's right edge instead - of letting it drift after the example text. */ -#new-memory-input ~ .skill-rich-ph > svg { - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - margin-left: 0 !important; -} -/* Hide the overlay once the field has real content. */ -.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; } -.skill-md-editor { - flex: 1 1 auto; - min-height: 0; - width: 100%; - box-sizing: border-box; - resize: none; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); -} -.doclib-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -/* Unified loading row across every Library tab (Chats / Documents / Research / - Archive): one opacity, one size, with the whirlpool spinner next to it. */ -.lib-loading-row { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 28px 16px; - font-size: 12px; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.pdf-loading-state { - min-height: 120px; - margin: 0; - box-sizing: border-box; - flex-direction: column; - gap: 10px; - border: 0; - border-radius: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - box-shadow: none; - font-size: 12px; - font-weight: 600; - letter-spacing: 0.01em; -} -.pdf-loading-state .ai-spinner-whirlpool { - color: var(--accent, var(--red)); - opacity: 0.95; -} -.pdf-loading-state .ai-spinner-whirlpool canvas { - display: block; -} -.doclib-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg-muted); - font-size: 11px; - cursor: pointer; - transition: border-color 0.15s, color 0.15s; - flex-shrink: 0; - position: relative; - top: 10px; -} -/* Soft dark gradient above the button — blends the list content into the - load-more strip instead of a hard cutoff. Spans the modal width and - sits behind/above the button. */ -.doclib-load-more::before { - content: ""; - position: absolute; - left: 50%; - transform: translateX(-50%); - bottom: 100%; - margin-bottom: 4px; - width: min(600px, 92vw); - height: 34px; - pointer-events: none; - background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent); -} -.doclib-load-more:hover { - border-color: var(--red); - color: var(--red); -} -.doclib-inline-load-more { - border: 0; - border-radius: 0; - padding: 0; - background: none; - top: 0; - font-size: 9px !important; - line-height: 1.2 !important; -} - -/* Document library toolbar buttons */ -.doclib-toolbar-btn { - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - font-size: 11px; - padding: 5px 10px; - border-radius: 6px; - cursor: pointer; - transition: all 0.15s; - white-space: nowrap; - font-family: inherit; - display: inline-flex; - align-items: center; - gap: 3px; -} -.doclib-toolbar-btn:hover { - color: var(--fg); - border-color: var(--fg); -} -.doclib-toolbar-btn.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -.doclib-toolbar-btn.danger { - color: var(--color-error, #e55); -} -.doclib-toolbar-btn.danger:hover:not(:disabled) { - border-color: var(--color-error, #e55); -} -.doclib-toolbar-btn:disabled { - opacity: 0.4; - cursor: default; -} - -/* Bulk action bar */ -.doclib-bulk-bar { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--red) 5%, transparent); - font-size: 10px; - margin-bottom: 8px; -} -.doclib-bulk-bar.hidden { - display: none; -} -.doclib-bulk-check-all { - display: flex; - align-items: center; - gap: 3px; - cursor: pointer; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-size: 10px; -} -#doclib-bulk-bar .memory-toolbar-btn { - position: relative; - top: -3px; -} - -/* Custom checkboxes */ -.doclib-select-cb, -.doclib-bulk-check-all input { - -webkit-appearance: none; - appearance: none; - width: 13px; - height: 13px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - cursor: pointer; - flex-shrink: 0; - margin: 0; - position: relative; - transition: all 0.15s; -} -.doclib-select-cb:hover, -.doclib-bulk-check-all input:hover { - border-color: var(--red); -} -.doclib-select-cb:checked, -.doclib-bulk-check-all input:checked { - background: var(--red); - border-color: var(--red); -} -.doclib-select-cb:checked::after, -.doclib-bulk-check-all input:checked::after { - content: ''; - position: absolute; - left: 3px; - top: 0px; - width: 4px; - height: 8px; - border: solid var(--bg); - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); -} - - -/* + button outside scroll area (when doc is on right side) */ - -@media (max-width: 600px) { - .doclib-toolbar { - flex-direction: column; - } - .doclib-card-session, - .doclib-card-time { - display: none; - } - .doclib-card-preview { - max-height: 40vh; - } -} - -/* Collapse card tags before they compete with attachment/favorite controls. */ -.email-card-tags .email-tags-more { - color: var(--email-tags-more-color, var(--fg)); - background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent); -} -.email-tags-more-single, -[data-email-tags-count-mini] { display: none; } -.email-tags-more-single svg { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag, -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra { - display: none; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single, -.email-card-tags.email-tags-expanded .email-tags-more-single { - display: inline-flex; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini], -.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; } - -/* Keep the reader actions in drafting order. */ -.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; } -.email-reader-actions-inline > [data-act="reply"] { order: 2; } -.email-reader-actions-inline > [data-act="reply-all"] { order: 3; } -.email-reader-actions-inline > [data-act="forward"] { order: 4; } -.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed { - display: none !important; -} - -/* ── Archive browser ── */ -.archive-list { - grid-template-columns: 1fr !important; - gap: 4px !important; -} -.archive-row { - flex-direction: row !important; -} -.archive-row .doclib-card-header { - flex: 1; - padding: 8px 10px; -} -.archive-row .doclib-card-title { - flex: 1; - min-width: 0; -} -/* Archive column layout */ -.archive-header { - display: flex; - align-items: center; - padding: 4px 10px; - gap: 6px; - font-size: 9px; - font-weight: 600; - opacity: 0.35; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.archive-col-title { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: -4px; -} -.archive-col-msgs { - width: 40px; - flex-shrink: 0; - text-align: left; - margin-left: -8px; -} -.archive-col-model { - width: 90px; - flex-shrink: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.archive-col-time { - width: 55px; - flex-shrink: 0; - text-align: right; -} -.archive-col-menu { - width: 24px; - flex-shrink: 0; -} -.archive-menu-btn { - background: transparent; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - padding: 4px; - line-height: 0; - border-radius: 4px; - transition: background .15s, opacity .15s; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - margin-left: 4px; - vertical-align: middle; - height: 100%; -} -.archive-menu-btn svg { - display: block; - margin-top: -6px; -} -.archive-menu-btn:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - opacity: 1; -} - -/* ── Gallery (image library) ── */ - -.gallery-modal-content { - width: min(820px, 94vw); - max-height: 92vh; - font-size: 12px; -} -/* Keep the Gallery frame anchored while tabs or bulk-selection controls - change the content height. Centering the frame made every shorter tab move - the whole window vertically. */ -@media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal, - #cookbook-modal { - align-items: flex-start; - padding-top: 4vh; - box-sizing: border-box; - } -} -/* While the Edit tab is active the editor needs a *definite* height so - the right-side tool panel (`.ge-right-panel { overflow-y:auto }`) - scrolls INTERNALLY. Without this the modal only has `max-height`, so - `.gallery-editor { height:100% }` can't resolve — the editor grows to - its full content height, overflows the modal body, and the modal-body - scrollbar that appears can't be wheel-scrolled because the editor's - `overscroll-behavior:contain` blocks the chain. Pinning a real height - here makes the panel bounded and scrollable as designed. Scoped to - the editor view (matched via the container's inline `display:flex`) - so the Photos/Albums views keep sizing to their content. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) { - height: 92vh; -} -/* The editor tab owns the modal body's scroll context. Keep the landing - chooser and the mounted editor inside the available sheet height so a long - saved-project list cannot push the launch controls below the viewport. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body { - display: flex; - min-height: 0; - overflow: hidden; -} -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container { - min-height: 0; - overflow: hidden; -} -/* Photo-detail view sizing (issue #314). - The detail view is rendered as a `position:absolute; inset:0` overlay - *inside* `.gallery-images-container`, painted over the photo grid. Because - it's absolutely positioned it can't contribute to the container's height — - the container (and therefore the overlay's `inset:0` box) collapses to the - height of the grid sitting behind it. When the library only has a few - photos that grid is short, so the detail view is crushed: the image is - clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a - tiny, internally-scrolling strip. (With a large library the grid is tall, - which is why it looked fine in the demo video but cramped for users with - few photos.) - Fix: when the detail view is open, hide the grid-view siblings and drop the - overlay into normal flow. The container — and the window, up to its 92vh - max-height — then sizes to the detail's own content (image + metadata), so - nothing is clipped or squeezed regardless of how many photos exist. Scoped - via the detail element's inline `display:flex` so the grid / albums views - keep sizing to their own content. Works on both desktop and the mobile - full-screen sheet. */ -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) { - display: none !important; -} -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail { - position: static; -} -/* Containing block for the photo-detail overlay — keeps it inside the body - so it sits below the modal header and the tab strip instead of covering them. */ -.gallery-images-container { - position: relative; -} -.gallery-modal-content .modal-header h4 { - font-size: 1rem; -} -.gallery-stats { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-bottom: 4px; -} -.gallery-toolbar { - display: flex; - gap: 6px; - margin-bottom: 4px; - align-items: center; -} -.gallery-toolbar-break { display: none; } -/* Keep the hint anchored while independently seating the input in the row. */ -.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; } -.gallery-search { - position: relative; - top: 4px; - flex: 1; - width: 100%; - padding: 4px 8px; - padding-right: 78px; /* room for the enter hint */ - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - font: inherit; - font-size: 12px; - outline: none; -} -.gallery-search-enter-hint { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - font-weight: 600; - color: var(--accent, var(--red)); - pointer-events: none; - opacity: 0; - transition: opacity 0.12s; - white-space: nowrap; -} -/* Show the hint only once the user has typed something (placeholder gone). */ -.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; } -.gallery-search:focus { - border-color: var(--red); -} -.gallery-model-filter, -.gallery-sort { - padding: 4px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - position: relative; - top: 4px; -} -.gallery-selection-bar { - margin-bottom: 4px; - position: relative; - top: 2px; - padding-right: 18px; -} -.gallery-tag-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 4px; -} -.gallery-chip { - height: 28px; - box-sizing: border-box; - padding: 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - border-radius: 14px; - font-size: 12px; - line-height: 1; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: border-color 0.15s, background 0.15s; -} -.gallery-chip:hover { - border-color: var(--red); -} -.gallery-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Album chips row */ -.gallery-album-chips { - display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px; -} -.gallery-chip-fav { color: var(--red); } -.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); } -.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; } -/* Active-album indicator — appears when the Photos grid is filtered to one album. */ -.gallery-chip-active-album, -.gallery-active-filter-pill { - display: inline-flex; align-items: center; gap: 4px; - background: color-mix(in srgb, var(--red) 14%, transparent); - border-color: color-mix(in srgb, var(--red) 45%, transparent); - position: relative; - top: 6px; -} -.gallery-active-filter-pill { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - color: color-mix(in srgb, var(--fg) 86%, transparent); -} -.gallery-clear-all-filters { - height: 28px; - border-style: dashed; - opacity: 0.72; -} -.gallery-clear-all-filters:hover { - opacity: 1; - color: var(--red); -} -.gallery-chip-clear { - background: none; - border: none; - color: inherit; - opacity: 0.6; - cursor: pointer; - padding: 0; - margin-left: 1px; - width: 12px; - height: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 12px; - line-height: 1; - position: relative; - top: -4px; -} -.gallery-chip-clear:hover { opacity: 1; } - - - -/* Favorite button on card */ -/* Video cards: fills the same slot as , plus a small play badge - so it's clear the thumbnail represents a video. */ -.gallery-card video { - width: 100%; - aspect-ratio: 1 / 1; - object-fit: cover; - background: #000; - display: block; -} -.gallery-card-play { - position: absolute; - left: 50%; top: 50%; - transform: translate(-50%, -50%); - width: 36px; height: 36px; - display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.55); - color: #fff; - border-radius: 50%; - pointer-events: none; - z-index: 1; -} -.gallery-card-play svg { margin-left: 2px; } - -.gallery-fav-btn { - position: absolute; top: 0px; right: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; font-size: 14px; - color: rgba(255,255,255,0.6); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s; - padding: 0; -} -.gallery-fav-btn.gallery-fav-active { opacity: 1; color: var(--red); } - -.gallery-dl-btn { - position: absolute; top: 0px; left: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; - color: rgba(255,255,255,0.75); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s, background 0.15s; - padding: 0; -} -.gallery-dl-btn:hover { background: rgba(0,0,0,0.7); color: #fff; } -/* In select mode the per-thumbnail hover buttons (favorite + download) just - get in the way of picking — hide them so the card is a clean select target. - `body.gallery-selecting` is the authoritative signal (one class for the - whole grid) — the per-card .gallery-card-selectable is kept as a backup - in case the body class is missed. */ -body.gallery-selecting .gallery-fav-btn, -body.gallery-selecting .gallery-dl-btn, -.gallery-card-selectable .gallery-fav-btn, -.gallery-card-selectable .gallery-dl-btn { - display: none !important; -} - -/* AI tag chips in detail view */ -.gallery-ai-tags { display: flex; gap: 4px; flex-wrap: wrap; } -.gallery-ai-chip { - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); - border-radius: 10px; padding: 3px 8px; font-size: 10px; - color: var(--fg); opacity: 0.8; - font: inherit; font-size: 10px; - cursor: pointer; - transition: background 0.12s, opacity 0.12s, border-color 0.12s; -} -.gallery-ai-chip:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); -} -/* AI-generated tags: muted neutral with a tiny sparkle marker so the - user can tell at a glance these came from the model, not them. */ -.gallery-aitag-chip { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-aitag-chip:hover { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.gallery-aitag-mark { - font-size: 8px; - opacity: 0.55; - color: var(--accent, var(--red)); -} -/* User-applied tags keep the accent-colored chip — they're the personal/ - curated tags, so they get the strongest visual weight. */ -.gallery-user-chip { - font-weight: 600; -} -/* × to remove a user tag (appears inside the chip). */ -.gallery-tag-x { - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 5px; - width: 13px; - height: 13px; - border-radius: 50%; - font-size: 12px; - line-height: 1; - opacity: 0.55; - transition: opacity 0.12s, background 0.12s, color 0.12s; -} -.gallery-tag-x:hover { - opacity: 1; - background: color-mix(in srgb, var(--red, #ff5555) 22%, transparent); - color: var(--red, #ff5555); -} - -.gallery-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - align-content: start; - gap: 8px; - max-height: calc(92vh - 165px); - overflow-y: auto; - padding: 2px; - transition: border-color 0.15s, background 0.15s; -} -.gallery-grid.gallery-dragover { - border: 2px dashed var(--red); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.gallery-card { - position: relative; - aspect-ratio: 1; - border-radius: 6px; - overflow: hidden; - border: 1px solid var(--border); - cursor: pointer; - transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s; -} -.gallery-card.gallery-card-focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 36%, transparent); -} -/* Upload-affordance tile pinned to the top-left of the Photos grid. Matches - the Upload album tile in the Albums tab — dashed border, centered icon. */ -.gallery-card-upload { - border-style: dashed; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - opacity: 0.75; - transition: opacity 0.12s, border-color 0.15s, transform 0.15s; -} -/* The "Start AI tag" button turns into a Cancel control during a tag run. */ -.gallery-tag-cancelling { - color: var(--red) !important; - border-color: color-mix(in srgb, var(--red) 45%, var(--border)) !important; -} -/* Skeleton/shimmer placeholder tiles shown while the first page of photos loads. */ -.gallery-card-skeleton { - cursor: default; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.gallery-card-skeleton:hover { transform: none; box-shadow: none; border-color: var(--border); } -/* Chat attachment image: shimmer skeleton + centered whirlpool shown while a - sent photo uploads / its thumbnail loads (see chatRenderer buildAttachCards). */ -.attach-image-skeleton { - position: relative; - width: 160px; height: 120px; - border-radius: 6px; - display: flex; align-items: center; justify-content: center; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.attach-image-skeleton > .spinner-whirlpool { margin: 0 !important; } - -/* Corner "Aa" button on a chat photo thumbnail — opens the vision/OCR editor - so the user can correct what the vision model fed to the LLM. */ -.attach-image-preview { position: relative; } -.attach-vision-model, -.attach-image-name { - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.attach-vision-model { - font-size: 10px; - color: var(--accent, var(--red)); - opacity: 0.85; - margin-top: 3px; -} -.attach-image-name { - font-size: 10px; - opacity: 0.5; - margin-top: 1px; -} -.attach-ocr-btn { - position: absolute; top: 4px; right: 4px; - z-index: 5; /* sit above msg-action overlays so the click lands here */ - height: 22px; - background: rgba(0,0,0,0.55); color: #fff; - border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; - padding: 0 8px 0 6px; cursor: pointer; - display: inline-flex; align-items: center; justify-content: center; gap: 5px; - font-size: 11px; font-weight: 500; line-height: 1; - opacity: 0.75; transition: opacity 0.15s; -} -.attach-ocr-btn:hover { opacity: 1; } -.attach-ocr-btn svg { display: block; } -/* On mobile keep just the (larger) edit icon — the "Caption" label crowds - the corner of the small chat photo. */ -@media (max-width: 768px) { - .attach-ocr-btn .attach-ocr-label { display: none; } - .attach-ocr-btn { - width: 28px; height: 28px; - padding: 0; - } - .attach-ocr-btn svg { width: 16px; height: 16px; } -} - -/* Vision/OCR text editor modal — opened from the "Aa" button above. */ -.vision-editor-overlay { - position: fixed; inset: 0; z-index: 9999; - background: rgba(0,0,0,0.55); - display: flex; align-items: center; justify-content: center; - padding: 16px; -} -.vision-editor-panel { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 14px 16px; - width: min(560px, 92vw); - max-height: 88vh; - display: flex; flex-direction: column; gap: 8px; - box-shadow: 0 14px 40px rgba(0,0,0,0.4); -} -.vision-editor-title { - font-size: 13px; font-weight: 600; color: var(--fg); - display: flex; align-items: center; gap: 6px; -} -.vision-editor-desc { font-size: 11px; opacity: 0.6; line-height: 1.4; } -.vision-editor-text { - width: 100%; min-height: 180px; - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 8px 10px; - font-family: inherit; font-size: 12px; line-height: 1.5; - resize: vertical; outline: none; -} -.vision-editor-text:focus { border-color: var(--accent-primary, var(--red)); } -.vision-editor-hint { font-size: 10.5px; opacity: 0.55; line-height: 1.4; } -.vision-editor-actions { - display: flex; gap: 8px; justify-content: flex-end; - margin-top: 4px; -} -.vision-editor-btn { - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; - transition: border-color 0.15s, background 0.15s; - display: inline-flex; align-items: center; gap: 5px; -} -.vision-editor-btn svg { display: block; } -/* Optical nudge: the button text sits 1px higher than the SVG glyphs - alongside it. Drop the text down 1px so they line up. */ -.vision-editor-btn .vision-btn-label { position: relative; top: 1px; } -.vision-editor-btn:hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); } -.vision-editor-btn-primary { - background: var(--accent-primary, var(--red)); - color: #fff; border-color: transparent; -} -.vision-editor-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } - -/* Quick full-size lightbox shown when the user taps a chat photo thumbnail. */ -.attach-lightbox { - position: fixed; inset: 0; z-index: 9998; - background: rgba(0, 0, 0, 0.85); - display: flex; align-items: center; justify-content: center; - padding: 16px; cursor: zoom-out; - animation: attach-lightbox-fade 0.12s ease-out; -} -.attach-lightbox img { - max-width: 100%; max-height: 100%; - border-radius: 6px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); - object-fit: contain; -} -.attach-lightbox-err { - position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); - background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 10px; font-size: 12px; -} -@keyframes attach-lightbox-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -.gallery-card-upload:hover { - opacity: 1; - border-color: var(--red); - transform: translateY(-1px); -} -.gallery-card-upload-inner { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 6px; - color: var(--fg); -} -.gallery-card-upload-label { - font-size: 12px; - font-weight: 500; - opacity: 0.8; -} -.gallery-card:hover { - border-color: var(--red); - box-shadow: 0 2px 8px color-mix(in srgb, var(--red) 15%, transparent); - transform: translateY(-1px); -} -.gallery-card-overlay { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 8px; - background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 34%, rgba(0,0,0,0.22)); - color: #fff; - opacity: 0; - pointer-events: none; - transition: opacity 0.15s; -} -.gallery-card-badges { - display: flex; - gap: 4px; - flex-wrap: wrap; - align-items: flex-end; - justify-content: flex-end; - margin-top: auto; - margin-bottom: 34px; -} -.gallery-card-badge { - padding: 2px 6px; - border-radius: 999px; - background: rgba(0, 0, 0, 0.55); - border: 1px solid rgba(255, 255, 255, 0.14); - color: rgba(255, 255, 255, 0.88); - font-size: 9px; - font-weight: 600; - line-height: 1.1; -} -.gallery-card-badge-fav { - color: #ff5f6f; -} -.gallery-select-dot { - position: absolute; top: 6px; left: 6px; z-index: 2; - display: block; - width: 10px; height: 10px; border-radius: 50%; cursor: pointer; - background: color-mix(in srgb, var(--fg) 30%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 50%, transparent); - transition: background 0.15s; -} -.gallery-select-dot.selected { background: var(--red); border-color: var(--red); } -.gallery-select-dot:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -.gallery-select-btn { - padding: 9px 10px 11px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; transition: all 0.15s; - margin-top: -4px !important; -} -.gallery-select-btn:hover { opacity: 1; } -/* Match the library Select toggle's red tint (.memory-toolbar-btn.active). - Bumped contrast so the toggled state actually reads on mobile — the prior - 15% red on transparent was nearly invisible against the page background. */ -.gallery-select-btn.active { - background: color-mix(in srgb, var(--red) 28%, transparent); - color: var(--red); - border-color: var(--red); - font-weight: 600; - opacity: 1; -} -/* Toolbar action buttons sit 2px lower than the generic select-btn so - they baseline-align with the toolbar's selects/inputs instead of - floating above them. */ -.gallery-toolbar-action { - margin-top: 0 !important; - display: inline-flex; - align-items: center; - gap: 2px; -} -.gallery-bulk-bar { - display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 12px; - justify-content: flex-end; -} -.gallery-bulk-bar.hidden { display: none; } -.gallery-bulk-all { - display: inline-flex; align-items: center; gap: 4px; - font-size: 11px; opacity: 0.75; cursor: pointer; margin-right: auto; -} -.gallery-bulk-all input { accent-color: var(--accent, var(--red)); cursor: pointer; } -.gallery-bulk-delete { - padding: 3px 10px; background: transparent; color: var(--red); - border: 1px solid var(--red); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; -} -.gallery-bulk-delete:hover { background: var(--red); color: #fff; } -.gallery-bulk-cancel { - padding: 3px 10px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; -} -.gallery-bulk-cancel:hover { opacity: 1; } -.gallery-card img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} -.gallery-card-info { - position: absolute; - bottom: 0; - left: 0; - right: 0; - padding: 6px 8px; - background: linear-gradient(transparent, rgba(0,0,0,0.75)); - color: #fff; -} -.gallery-card-prompt { - font-size: 10px; - line-height: 1.3; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.gallery-card-meta { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - font-size: 9px; - opacity: 0.8; -} -.gallery-card-model { - padding: 1px 5px; - border-radius: 6px; - background: rgba(255,255,255,0.15); -} -.gallery-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -.gallery-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: none; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-load-more:hover { - border-color: var(--red); - color: var(--red); -} - -/* Gallery detail overlay */ -.gallery-detail { - position: absolute; - inset: 0; - background: var(--panel); - z-index: 10; - display: flex; - flex-direction: column; - overflow-y: auto; -} -.gallery-detail-header { - display: flex; - align-items: center; - gap: 6px; - padding: 2px 8px 4px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - /* Pin to top so a tall (portrait) photo can't cover the Back / ⋮ - menu when the detail body grows past the visible area. */ - position: sticky; - top: 0; - background: var(--panel); - /* Above the image's rotate/nav buttons (z-index:3) so they scroll cleanly - behind the header instead of colliding with it. */ - z-index: 5; - min-height: 0; -} -.gallery-detail-title { - max-width: min(360px, 40vw); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 12px; - font-weight: 600; - opacity: 0.82; - position: relative; - top: 1px; -} -.gallery-detail-back { - background: none; - border: none; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 12px; - padding: 4px 8px; - border-radius: 4px; - position: relative; - top: -2px; -} -/* Slightly larger header icons (back/edit/heart/⋮) — override the inline 14px. */ -.gallery-detail-header svg { width: 16px; height: 16px; } -.gallery-detail-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.gallery-detail-action { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - padding: 3px 8px; - border-radius: 4px; - transition: border-color 0.15s; -} -.gallery-detail-action:hover { - border-color: var(--fg); -} -.gallery-detail-action.danger { - color: var(--color-error, #e55); -} -/* Overflow menu — replaces the seven individual action buttons that used - to crowd the right side of the detail header. */ -.gallery-detail-menu-wrap { position: relative; } -.gallery-detail-menu-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 26px; - padding: 0; - position: relative; - top: -2px; -} -.gallery-detail-menu { - position: absolute; - top: calc(100% + 4px); - right: 0; - min-width: 150px; - padding: 3px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 12; - display: flex; - flex-direction: column; - gap: 0; -} -.gallery-detail-menu[hidden] { display: none; } -.gallery-detail-menu .dropdown-item-compact { - width: 100%; - background: none; - border: none; - text-align: left; - font: inherit; - /* Tighter than the global default — the photo-detail menu has 8 - items so every pixel of vertical padding adds up. */ - padding: 4px 8px; - font-size: 11px; - gap: 8px; - line-height: 1.2; -} -.gallery-detail-menu .dropdown-item-compact .dropdown-icon, -.gallery-detail-menu .dropdown-item-compact .dropdown-icon svg { - width: 12px; - height: 12px; -} -.gallery-detail-body { - display: flex; - gap: 16px; - padding: 12px; - flex: 1; - min-height: 0; - overflow: hidden; -} -.gallery-detail-image { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - min-width: 0; - min-height: 0; - height: 100%; - padding-inline: 56px; - box-sizing: border-box; - overflow: hidden; -} -/* Shrink-wraps the actual image so overlay children (heart, face boxes) - anchor to the IMAGE bounds, not the wider letterboxed wrapper. */ -.gallery-detail-img-frame { - position: relative; - display: inline-flex; - max-width: 100%; - max-height: 100%; -} -.gallery-detail-nav { - position: absolute; - top: 50%; - transform: translateY(-50%); - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-nav:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-nav-prev { left: 8px; } -.gallery-detail-nav-next { right: 8px; } - -/* Rotate buttons — same pattern as the prev/next arrows but pinned to the - top corners. Hover-revealed so they don't clutter the image. */ -.gallery-detail-rotate { - position: absolute; - top: 8px; - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-rotate:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-rotate-ccw { left: 8px; } -.gallery-detail-rotate-cw { right: 8px; } -/* Inline heart that lives next to the "Date" label in the sidebar. - Tiny outline icon by default; fills red when favorited. */ -.gallery-detail-date-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; -} -.gallery-detail-fav-inline { - background: none; - border: none; - padding: 0; - /* Nudge up 8px so the heart sits visually above the Date label - baseline rather than centred on it. */ - margin-top: -8px; - border-radius: 4px; - color: var(--fg-muted); - opacity: 0.6; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; - transition: opacity 0.15s, color 0.15s; -} -/* Shrink the SVG so the heart matches the height of the small uppercase - Date label sitting next to it. */ -.gallery-detail-fav-inline svg { - width: 12px; - height: 12px; - display: block; -} -.gallery-detail-fav-inline:hover { - opacity: 1; -} -.gallery-detail-fav-inline.active { - opacity: 1; - color: var(--red); -} -.gallery-detail-sidebar { - width: 290px; - flex-shrink: 0; - /* Vertical scroll for long metadata, but never horizontal — overflow:auto - alone would let a wide child (e.g. long album name in the select) push - the sidebar into horizontal-scroll mode. */ - overflow-x: hidden; - overflow-y: auto; - min-width: 0; -} -.gallery-detail-inspector-head { - padding-bottom: 10px; - margin-bottom: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.gallery-detail-eyebrow { - font-size: 9px; - font-weight: 700; - color: var(--accent, var(--red)); - text-transform: uppercase; - letter-spacing: 0.08em; - margin-bottom: 3px; -} -.gallery-detail-inspector-title { - font-size: 14px; - font-weight: 650; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-meta-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 6px; - margin: 4px 0 12px; -} -.gallery-detail-meta-card { - min-width: 0; - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-radius: 7px; - padding: 7px 8px; -} -.gallery-detail-meta-card span { - display: block; - font-size: 9px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 48%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 3px; -} -.gallery-detail-meta-card strong { - display: block; - min-width: 0; - font-size: 11px; - font-weight: 600; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-inline-link { - appearance: none; - background: none; - border: none; - color: var(--accent, var(--red)); - padding: 0; - font: inherit; - font-weight: inherit; - cursor: pointer; - text-align: left; -} -.gallery-detail-mini-actions { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 7px; -} -.gallery-detail-mini-btn { - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 8px; - font: inherit; - font-size: 10.5px; - cursor: pointer; - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-detail-mini-btn:hover { - border-color: color-mix(in srgb, var(--fg) 42%, var(--border)); -} -/* Constrain any child that could otherwise stretch the sidebar wider than - its declared width (selects with long option text, long URLs, etc.). */ -.gallery-detail-sidebar select, -.gallery-detail-sidebar input, -.gallery-detail-sidebar .gallery-detail-prompt { - max-width: 100%; - box-sizing: border-box; -} -.gallery-date-rel { - opacity: 0.55; - margin-left: 4px; - font-size: 11px; - font-style: italic; -} -.gallery-detail-section label { - display: block; - font-size: 10px; - font-weight: 600; - opacity: 0.6; - margin-bottom: 3px; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.gallery-detail-section div { - font-size: 12px; - word-break: break-word; -} -.gallery-detail-prompt { - white-space: pre-wrap; - line-height: 1.4; -} -.gallery-tag-input, -.gallery-detail-name-input { - width: 100%; - padding: 5px 8px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - font: inherit; - font-size: 11px; - margin-bottom: 4px; -} -.gallery-tag-input:focus, -.gallery-detail-name-input:focus { - border-color: var(--red); - outline: none; -} -/* ↵ enter hint inside the Add-a-tag field (accent), replacing the "press Enter" - placeholder text. */ -.gallery-tag-input-wrap { position: relative; } -.gallery-tag-enter-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; /* the input has 4px bottom margin; center on the box */ - color: var(--accent, var(--red)); - font-size: 13px; - line-height: 1; - pointer-events: none; - opacity: 0.8; -} -.gallery-detail-name-input { font-size: 13px; font-weight: 500; } -/* ↵ enter hint on the image name field — accent, shown while editing to signal - that Enter saves. */ -.gallery-name-wrap { position: relative; } -.gallery-name-enter { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; - color: var(--accent, var(--red)); - opacity: 0.85; - pointer-events: none; - display: none; -} -.gallery-tag-save { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 10px; - padding: 3px 10px; - border-radius: 4px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-tag-save:hover { - border-color: var(--red); - color: var(--red); -} - -@media (max-width: 600px) { - .gallery-grid { - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - } - /* Single-row toolbar on mobile: search + Sources + Recent (sort) all share - one row. The search bar shrinks so the dropdowns fit beside it. */ - .gallery-toolbar { - flex-wrap: nowrap; - gap: 4px; - } - .gallery-search-wrap { - flex: 1 1 0; - min-width: 0; - } - .gallery-search { - /* Drop the desktop right-padding reserved for the "enter to tag" hint — - hide the hint on mobile (below) so the input can use its full width. */ - padding-right: 8px; - } - .gallery-search-enter-hint { display: none; } - /* Disable the row break so the filters stay inline with search. */ - .gallery-toolbar-break { display: none; } - .gallery-model-filter, - .gallery-sort { - flex: 0 0 auto; - /* Cap each dropdown so search keeps a usable amount of width. */ - max-width: 100px; - font-size: 11px; - padding: 4px 6px; - /* Keep the same vertical alignment as the desktop toolbar. */ - position: relative; - top: 4px; - } - .gallery-select-btn { - padding: 6px 8px 8px; - font-size: 9px; - flex-shrink: 0; - /* Push Select to the far right of the single-row toolbar, regardless - of its DOM position (sits between search and the dropdowns). */ - order: 99; - margin-left: auto; - } - /* Tabs scroll horizontally rather than wrapping when narrow. */ - .gallery-tabs { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .gallery-tabs::-webkit-scrollbar { display: none; } - .gallery-tab { flex-shrink: 0; } - /* Album chips also scroll horizontally so they don't stack. */ - .gallery-album-chips { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .gallery-album-chips::-webkit-scrollbar { display: none; } - .gallery-chip { flex-shrink: 0; } - /* Tasks filter chips — same horizontal-swipe behaviour as the gallery - albums / library chips on mobile: one row, slide-to-see-more. */ - .tasks-activity-filters { - overflow-x: auto; - flex-wrap: nowrap !important; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .tasks-activity-filters::-webkit-scrollbar { display: none; } - .tasks-activity-filters > * { flex-shrink: 0; } - /* Detail view */ - .gallery-detail-body { - flex-direction: column; - overflow: visible; - } - .gallery-detail-image { - flex: 0 0 auto; - width: 100%; - height: clamp(280px, 58vh, 520px); - padding-inline: 48px; - } - .gallery-detail-sidebar { - width: 100%; - } - .gallery-detail-title { - display: none; - } - .gallery-detail-meta-grid { - grid-template-columns: 1fr; - } - /* Album grid: smaller tile minimum on mobile. */ - .gallery-albums-grid { - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 8px; - } -} - - -/* ── Scoreboard table ── */ -.scoreboard-table { - width: 100%; - border-collapse: collapse; - font-size: 0.88em; -} -.scoreboard-table th { - text-align: left; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 55%, transparent); - text-transform: uppercase; - letter-spacing: 0.5px; -} -.scoreboard-table th:not(:first-child) { - text-align: center; -} -.scoreboard-table td { - padding: 5px 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; -} -.scoreboard-table td.scoreboard-model { - text-align: left; - font-weight: 500; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.scoreboard-table td.scoreboard-pct { - font-weight: 600; - color: var(--red); -} -.scoreboard-table tbody tr:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} - -#scoreboard-overlay svg { - color: var(--accent, var(--red)); -} - -/* ── Compare search results ── */ -.compare-search-results { - display: flex; - flex-direction: column; - gap: 2px; -} -.compare-search-result { - padding: 6px 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); -} -.compare-search-result:last-child { - border-bottom: none; -} -.search-result-title { - color: var(--color-accent); - text-decoration: none; - font-weight: 500; - font-size: 0.9em; - display: block; - line-height: 1.3; -} -.search-result-title:hover { - color: var(--color-link-hover); - text-decoration: underline; -} -.search-result-snippet { - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.82em; - line-height: 1.4; - margin-top: 2px; -} -.search-result-url { - color: color-mix(in srgb, var(--fg) 35%, transparent); - font-size: 0.75em; - margin-top: 3px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - - -/* ── Cookbook Tool ── */ - -/* ── Cookbook ── */ -.cookbook-serve-preset { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - margin-bottom: 3px; - cursor: pointer; - transition: background 0.15s; -} -.cookbook-serve-preset:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cookbook-serve-preset-name { - font-size: 11px; - font-weight: 500; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-serve-preset-meta { - font-size: 9px; - font-family: 'Fira Code', monospace; - color: var(--fg-muted); - opacity: 0.5; -} -.cookbook-serve-preset-launch { - background: none; - border: none; - color: #50fa7b; - cursor: pointer; - font-size: 10px; - opacity: 0.6; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-launch:hover { opacity: 1; } -.cookbook-serve-preset-rm { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 10px; - opacity: 0.3; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-rm:hover { opacity: 0.8; color: var(--red); } -.cookbook-body { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - flex: 1; - min-height: 0; - scrollbar-width: thin; -} -#cookbook-modal .modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -#cookbook-modal .modal-header { - flex: 0 0 auto; -} -#cookbook-modal .cookbook-body { - min-height: 0; - overflow-y: auto; - overflow-x: hidden; -} -#cookbook-modal .cookbook-group { - min-height: 0; -} -#cookbook-modal .cookbook-group > .admin-card { - min-height: 0; - /* Let .cookbook-body be the SINGLE scroll surface. Nesting another - overflow:auto here trapped the wheel inside the cached-list when a - serve panel expanded — the page couldn't scroll past the panel's - bottom (Launch button got hidden). */ - overflow: visible !important; -} -#cookbook-modal .cookbook-section-body { - min-height: 0; -} -.cookbook-group { - display: flex; - flex-direction: column; - gap: 8px; - flex: 1; - min-height: 0; -} - -/* Cards — match admin-card */ -.cookbook-card { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-card-header { - display: flex; - align-items: center; - justify-content: space-between; -} -.cookbook-card-header-actions { display: flex; gap: 4px; } -.cookbook-card-title { font-size: 13px; font-weight: 600; } -.cookbook-card-desc { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} - -/* Fields grid */ -.cookbook-fields { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 6px; - margin-top: 4px; -} -.cookbook-field-label { - display: flex; - flex-direction: column; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - gap: 3px; -} - -/* Inputs — match admin-add-form input */ -.cookbook-field-input { - padding: 5px 8px; - font-size: 12px; - font-family: inherit; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - outline: none; -} -.cookbook-field-input:focus { border-color: var(--red); } -#hwfit-dl-server, #hwfit-usecase, #hwfit-server-select, #hwfit-cache-server, #serve-sort { height: 28px; width: 88px; } -/* Serve tab — match Documents sizing, but leave room for the active "Cancel" - label and center it vertically so the descenders don't clip. */ -#hwfit-cache-select { - min-width: 58px; - height: 28px; - position: relative; - top: -3px; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; -} - -/* Command preview */ -.cookbook-cmd-preview { - margin: 4px 0 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - color: var(--fg); -} - -/* Buttons — match admin-btn-sm */ -.cookbook-actions { display: flex; gap: 6px; margin-top: 2px; } -.cookbook-btn { - padding: 4px 10px; - min-width: 54px; - text-align: center; - font-size: 11px; - font-family: inherit; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - cursor: pointer; - transition: all 0.15s; -} -.cookbook-btn:hover { - background: var(--border); - border-color: var(--accent); -} -.cookbook-btn:disabled { opacity: 0.5; cursor: not-allowed; } -.cookbook-run-btn { - background: var(--accent); - color: var(--panel); - border-color: var(--accent); - font-weight: 600; -} -.cookbook-run-btn:hover { opacity: 0.85; } -.cookbook-stop-btn { - background: var(--color-error); - border-color: var(--color-error); -} - -/* Output */ -.cookbook-output-wrap { - position: relative; - margin: 0; -} -.cookbook-output-kill { - position: absolute; - top: 4px; - right: 34px; - width: 22px; - height: 22px; - padding: 0; - background: none; - border: 1px solid transparent; - border-radius: 4px; - color: var(--fg-muted); - font-size: 16px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity .15s, color .15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-output-kill:hover { color: var(--color-error, var(--warn)); border-color: var(--border); } -.cookbook-output-wrap .copy-code { - position: absolute; - top: 6px; - right: 6px; -} -.cookbook-output-wrap:hover .copy-code { opacity: 0.7; } -.cookbook-output-pre { - margin: 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - max-height: 180px; - overflow-y: auto; -} -.cookbook-output-error { color: var(--color-error); } - -/* Downloaded/cached models */ -.hwfit-cached-item { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 6px; - padding: 6px 8px; - margin: 2px 0; - border-radius: 6px; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 3%, transparent); - cursor: default; - font-size: 12px; -} -.hwfit-cached-item .hwfit-serve-panel { - flex-basis: 100%; - margin-top: 4px; -} -.hwfit-cached-item:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); -} -.hwfit-cached-header { - font-size: 10px; - opacity: 0.4; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - border: none !important; - background: none !important; - padding: 2px 8px; - cursor: default !important; -} -.hwfit-cached-header:hover { background: none !important; } -.hwfit-cached-name { - font-weight: 600; - flex-shrink: 0; -} -.hwfit-cached-repo { - flex: 1; - color: var(--fg-muted); - font-size: 11px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-cached-size, -.hwfit-cached-files { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; -} -.hwfit-cached-serve, -.hwfit-cached-delete { - flex-shrink: 0; -} -.hwfit-cached-delete { - opacity: 0.4; - font-size: 10px; -} -.hwfit-cached-delete:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} -.hwfit-cached-badge { - font-size: 9px; - padding: 1px 6px; - border-radius: 3px; - font-weight: 600; - white-space: nowrap; -} -.hwfit-cached-ready { - background: color-mix(in srgb, var(--color-success, #4caf50) 20%, transparent); - color: var(--color-success, #4caf50); -} -.hwfit-cached-dl { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 20%, transparent); - color: var(--color-warning, #f0ad4e); -} - -/* Cookbook notification dot */ -#tool-cookbook-btn { - position: relative; -} -#rail-cookbook { - position: relative; -} -.cookbook-open-loading { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - flex-shrink: 0; - margin-left: 2px; - color: var(--accent, var(--red)); - opacity: 0.9; -} -.cookbook-open-loading .ai-spinner, -.cookbook-open-loading .ai-spinner-whirlpool { - display: inline-flex !important; - width: 14px; - height: 14px; - margin: 0 !important; - gap: 0 !important; -} -.cookbook-open-loading canvas { - display: block; -} -.cookbook-section-loading-wp, -.cookbook-section-loading-wp canvas { - width: 22px !important; - height: 22px !important; - display: block !important; -} -#rail-cookbook .cookbook-open-loading { - position: absolute; - right: 2px; - top: -2px; - width: 12px; - height: 12px; - margin-left: 0; -} -/* The serve-list 'downloading' / 'download stalled' status text sits a hair - low against the rest of the meta line on mobile — nudge it up 2px. */ -@media (max-width: 768px) { - .cookbook-dl-status { - position: relative; - top: -2px; - } -} -.cookbook-notif-dot { - width: 6px; - height: 6px; - flex-shrink: 0; - border-radius: 50%; - background: var(--color-success, #4caf50); - /* Drives the breathing glow in the keyframes (matches email's dot). */ - --notif-glow: var(--color-success, #4caf50); - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -.cookbook-notif-dot.cookbook-notif-error { - background: var(--color-error, #f44); - --notif-glow: var(--color-error, #f44); - position: relative; - left: -2px; - top: -1px; -} -.rail-notify-error { color: var(--color-error, #f44) !important; } -.cookbook-tab-error-dot { - display: inline-block; width: 5px; height: 5px; border-radius: 50%; - background: var(--color-error, #f44); margin-left: 4px; vertical-align: middle; - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -@keyframes cookbook-notif-pulse { - 0%, 100% { - opacity: 1; - box-shadow: 0 0 0 0 color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 60%, transparent); - } - 50% { - opacity: 0.85; - box-shadow: 0 0 6px 2px color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 55%, transparent); - } -} -.cookbook-clear-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-clear-btn:hover { - color: var(--red) !important; - border-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 8%, transparent) !important; -} -.cookbook-bulk-bar { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - margin-bottom: 4px; - background: color-mix(in srgb, var(--red) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 6px; - font-size: 11px; -} -.cookbook-bulk-bar.hidden { display: none; } -.serve-select-cb { - width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; cursor: pointer; - background: var(--border); transition: background 0.15s; - align-self: center; - position: relative; - top: -2px; -} -#serve-bulk-bar { - position: relative; - top: -6px; -} -#serve-bulk-bar #serve-bulk-cancel { - top: -2px !important; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 28px; - height: 24px; - line-height: 1; -} -#serve-bulk-bar #serve-bulk-cancel svg { - top: 0; -} -.serve-select-cb.selected { background: var(--red); } -.serve-select-cb:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -#hwfit-cache-select.active { - background: var(--red); - color: #fff; - border-color: var(--red); -} -.cookbook-serve-dirs { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.cookbook-serve-dir-pill { - font-size: 10px; - font-family: 'Fira Code', monospace; - padding: 2px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); - color: var(--fg-muted); - letter-spacing: 0.2px; -} -/* Status pills shown inline in a Serve-tab card title (next to the model - name) when the model has a live serve / download task. Shared base - class so "running" and "downloading" sit on the same row with the - same chrome; only color varies. */ -.cookbook-serve-running-pill, -.cookbook-serve-downloading-pill { - display: inline-block; - margin-left: 6px; - padding: 1px 7px; - border-radius: 10px; - font-size: 9px; - font-weight: 500; - text-transform: lowercase; - letter-spacing: 0.3px; - vertical-align: 2px; - position: relative; - top: 1px; /* nudged down 2px from the old -1px so it sits - flush with the cap-height of the title text */ -} -.cookbook-serve-running-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); -} -.cookbook-serve-downloading-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); - opacity: 0.85; -} -.cookbook-serve-downloading-pill.is-stalled { - /* Stalled downloads stay visible but read as warning, not progress. */ - color: var(--fg-muted, #888); - background: color-mix(in srgb, var(--fg-muted, #888) 10%, transparent); - border-color: color-mix(in srgb, var(--fg-muted, #888) 30%, transparent); - opacity: 1; -} -.cookbook-serve-running-pill.is-clickable { - cursor: pointer; - transition: background 0.12s, border-color 0.12s; -} -.cookbook-serve-running-pill.is-clickable:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -.cookbook-serve-dir-edit { - font-size: 9px; - color: var(--fg-muted); - opacity: 0.4; - cursor: pointer; - text-decoration: underline; - text-underline-offset: 2px; -} -.cookbook-serve-dir-edit:hover { - opacity: 0.7; - color: var(--accent, var(--red)); -} -.cookbook-gpu-group { - display: flex; - gap: 3px; - margin-top: -4px; -} -.cookbook-gpu-btn { - width: 30px; height: 30px; - font-size: 12px; - font-family: 'Fira Code', monospace; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - color: var(--fg-muted); - cursor: pointer; - opacity: 0.4; - transition: all 0.15s; - padding: 0; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-gpu-btn:hover { - opacity: 0.7; - border-color: var(--fg-muted); -} -.cookbook-gpu-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 55%, transparent); - color: var(--fg); - font-weight: 600; -} -/* Probe annotation states (set by "Free?" button) */ -.cookbook-gpu-btn.gpu-free { - opacity: 1; - border-color: #4ade80; - color: #4ade80; -} -.cookbook-gpu-btn.gpu-busy { - opacity: 0.85; - border-color: color-mix(in srgb, var(--red) 70%, transparent); - color: var(--red); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-btn.gpu-missing { - opacity: 0.2; - text-decoration: line-through; - cursor: not-allowed; -} -/* "Clear Server" lives in the actions row next to "Probe GPUs". - Inherits .cookbook-btn sizing; this rule just adds the destructive - red tint on hover so the "this kills processes" cue stays. */ -.cookbook-gpu-clear:hover:not(:disabled) { - color: var(--red); - border-color: color-mix(in srgb, var(--red) 60%, var(--border)); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -/* GPU probe popup — per-GPU process list with kill buttons */ -.cookbook-gpu-popup { - /* Fixed positioning (relative to viewport) so we never get pulled into - a scrolling/transform stacking context from an ancestor. Z-index has - to clear the cookbook modal (260) and the rest of the high-z UI - layers (themed-confirm and various overlays sit around 9000-10000). */ - position: fixed; - z-index: 10010; - min-width: 280px; - max-width: 420px; - background: var(--panel, #1a1a1a); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 8px 24px rgba(0,0,0,0.4); - font-family: 'Fira Code', monospace; - font-size: 11px; - color: var(--fg); -} -.cookbook-gpu-popup-head { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; -} -.cookbook-gpu-popup-stats { - flex: 1; - font-weight: 400; - color: var(--fg-muted); - font-size: 10px; -} -.cookbook-gpu-popup-close { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 14px; - padding: 0 4px; -} -.cookbook-gpu-popup-close:hover { color: var(--fg); } -.cookbook-gpu-popup-body { - padding: 4px 0; - max-height: 320px; - overflow-y: auto; -} -.cookbook-gpu-popup-empty { - padding: 10px; - color: var(--fg-muted); - font-style: italic; -} -.cookbook-gpu-proc { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; -} -.cookbook-gpu-proc:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); } -.cookbook-gpu-proc-info { - flex: 1; - display: flex; - gap: 8px; - align-items: center; - min-width: 0; -} -.cookbook-gpu-proc-pid { - color: var(--fg-muted); - width: 56px; - flex-shrink: 0; -} -.cookbook-gpu-proc-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.cookbook-gpu-proc-mem { - color: var(--fg-muted); - flex-shrink: 0; -} -.cookbook-gpu-proc-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} -.cookbook-gpu-kill { - border: 1px solid color-mix(in srgb, var(--red) 60%, transparent); - background: none; - color: var(--red); - border-radius: 3px; - padding: 2px 8px; - cursor: pointer; - font-family: inherit; - font-size: 10px; -} -.cookbook-gpu-kill[data-sig="KILL"] { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.cookbook-gpu-kill:hover:not(:disabled) { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.cookbook-gpu-kill:disabled { opacity: 0.4; cursor: wait; } -.cookbook-serve-title { - display: flex; - align-items: center; - gap: 4px; - min-width: 0; -} -.cookbook-serve-title-name { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-hf-link { - font-size: 9px; - text-decoration: none; - color: var(--fg-muted); - opacity: 0.5; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - vertical-align: 1px; - letter-spacing: 0.3px; - font-weight: 600; - flex-shrink: 0; -} -.cookbook-hf-link:hover { - opacity: 0.8; - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -/* In the Trending models list the HF pill is the row's main affordance — - tint it accent so it stands out next to the model name. */ -#cookbook-hf-latest-list .cookbook-hf-link { - color: var(--accent, var(--red)); - opacity: 0.85; - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); -} - -/* Running tab sections */ -.cookbook-saved-section, -.cookbook-serve-section, -.cookbook-dl-section { - border: 1px solid var(--border); - border-radius: 6px; - margin-bottom: 6px; - overflow: hidden; -} -/* Divider between server-group sections in the Active tab so the - transition between e.g. local + remote-host server blocks reads - visually instead of running into each other. */ -.cookbook-group[data-backend-group="Running"] > [class*="cookbook-server-section-"] + [class*="cookbook-server-section-"] { - margin-top: 14px; - padding-top: 14px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.cookbook-section-header { - display: flex; - align-items: center; - gap: 6px; - padding: 7px 10px; - cursor: pointer; - user-select: none; - /* Persistent surface + border so it reads as a clickable bar instead of - blending into the panel background. */ - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - transition: background 0.15s, border-color 0.15s; -} -.cookbook-section-header:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: color-mix(in srgb, var(--fg) 22%, transparent); -} -.cookbook-section-header.has-server-color { - color: var(--fg); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 45%, var(--border)); - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 4%, var(--bg)); - box-shadow: inset 6px 0 0 var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color:hover { - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 7%, var(--bg)); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 60%, var(--border)); -} -.cookbook-section-header.has-server-color .cookbook-section-title, -.cookbook-section-header.has-server-color .cookbook-section-chevron { - color: var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color .cookbook-srv-status.ok { - background: var(--cookbook-server-accent, var(--cookbook-server-color)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 55%, transparent); -} -.cookbook-section-header .cookbook-clear-btn { - margin-left: 0; - position: relative; - top: -3px; -} -@media (max-width: 768px) { - .cookbook-section-header .cookbook-clear-btn { - top: -3px; - } - .cookbook-task-check { - top: 4px; - } -} -/* "Stop all" sits just left of "Clear finished"; it carries the auto margin so - the pair is pushed together to the right of the section title. */ -.cookbook-section-header .cookbook-stop-all-btn { - margin-left: auto; - margin-right: 6px; - position: relative; - top: -3px; -} -.cookbook-stop-all-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-stop-all-btn:hover { - color: var(--warn, #f0ad4e) !important; - border-color: var(--warn, #f0ad4e) !important; - background: color-mix(in srgb, var(--warn, #f0ad4e) 8%, transparent) !important; -} -.cookbook-section-chevron { - flex-shrink: 0; - opacity: 0.6; - transition: transform 0.15s ease, opacity 0.15s ease; -} -.cookbook-section-body { - padding: 0; -} -.cookbook-saved-toggle-header { - font-size: 10px; - font-weight: 600; - color: var(--fg-muted); - text-transform: uppercase; - letter-spacing: 0.3px; - margin-bottom: 4px; -} -.cookbook-saved-toggle:hover { color: var(--fg); } -.cookbook-saved-item { - margin: 2px 0; - border-radius: 4px; - font-size: 11px; -} -.cookbook-saved-header { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 6px; - border-radius: 4px; - transition: background 0.08s; -} -.cookbook-saved-header:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.cookbook-saved-item .hwfit-serve-panel { - margin: 4px 0 4px 6px; -} -.cookbook-saved-host { - font-size: 10px; - color: var(--fg-muted); - font-family: 'Fira Code', monospace; -} -.cookbook-saved-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-saved-launch { - font-size: 10px; - padding: 2px 8px; -} -.cookbook-saved-del { - background: none; - border: none; - color: var(--fg-muted); - opacity: 0.3; - cursor: pointer; - font-size: 11px; - padding: 0 2px; -} -.cookbook-saved-del:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} - -/* Serve config panel */ -.hwfit-serve-panel { - margin-top: 6px; - padding: 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex: 1; - display: flex; - flex-direction: column; - position: relative; -} -.cookbook-serve-slots { - display: flex; - gap: 3px; - justify-content: flex-end; - margin-bottom: 4px; -} -/* When the Save split sits inside Row 1 (next to GPUs), align it with the - input baseline (the row's grid cells stretch top-down; without this the - Save buttons sit above the GPU button group). */ -.hwfit-serve-row .cookbook-serve-slots { - align-self: end; - margin-bottom: 4px; -} -.cookbook-slot-btn { - min-width: 22px; height: 28px; - padding: 0 6px; - font-size: 10px; font-weight: 600; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - white-space: nowrap; - max-width: 80px; - overflow: hidden; - text-overflow: ellipsis; - color: var(--fg-muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.5; - transition: all 0.15s; -} -.cookbook-slot-btn:hover { opacity: 0.9; border-color: var(--fg-muted); } -/* Saved-configs split button: "Save" + dropdown-arrow joined into one control, - but each segment keeps its own style — Save uses the filled accent - "Done"-button look; the arrow stays a subtle outlined menu trigger. */ -.cookbook-saved-split { gap: 0; } -.cookbook-saved-save, -.cookbook-saved-arrow { max-width: none; } -.cookbook-saved-save { - padding: 0 10px; - gap: 4px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - font-weight: 600; - opacity: 1; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.cookbook-saved-save:hover { - background: var(--border); - border-color: var(--accent); - opacity: 1; -} -.cookbook-saved-arrow { - padding: 0 6px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - opacity: 1; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: none; -} -.cookbook-saved-menu .cookbook-saved-favorite { - border-left: 3px solid var(--red); - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-saved-fav-btn { - width: 20px; - height: 20px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: none; - color: var(--fg-muted); - opacity: 0.55; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -.cookbook-saved-fav-btn:hover, -.cookbook-saved-fav-btn.active { - color: var(--red); - opacity: 1; - background: color-mix(in srgb, var(--red) 10%, transparent); -} -.cookbook-saved-fav-badge { - flex-shrink: 0; - background: transparent; - padding: 0; -} -.cookbook-serve-favorite-model { - border-left-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-serve-fav-badge { - display: inline-flex; - align-items: center; - vertical-align: 1px; - margin-left: 5px; - margin-right: 2px; - background: transparent; - padding: 0; -} -.cookbook-slot-saved { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); } -.cookbook-slot-saved:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); } -.cookbook-slot-btn.active { opacity: 1; background: var(--accent); color: #fff; border-color: var(--accent); -} -.cookbook-slot-wrap { - position: relative; - display: inline-flex; -} -.cookbook-slot-del { - position: absolute; - top: -5px; - right: -5px; - width: 14px; - height: 14px; - padding: 0; - border: 1px solid var(--border); - border-radius: 50%; - background: var(--bg, #1a1a1a); - color: var(--fg-muted); - font-size: 12px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, color 0.15s, border-color 0.15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-slot-del:hover { - color: #f44; - border-color: #f44; -} -.cookbook-card-backend { display: none; } -/* Dependencies row — aligned tags */ -.cookbook-dep-row { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel); -} -/* Let the deps list fill the available window height instead of being - capped at the default .doclib-grid 400-px box. */ -[data-backend-group="Dependencies"] #cookbook-deps-list { - flex: 1; - max-height: none; -} - -/* Settings tab: stack the HF token + Servers cards as distinct blocks - (matches the Download tab's block-per-section layout). */ -.cookbook-settings-stack { - display: flex; - flex-direction: column; - gap: 10px; - flex: 1; - /* Scroll the whole settings panel so the Servers card can grow to hold every - server (it used to be a cramped internal scroll box that clipped them). */ - overflow-y: auto; - min-height: 0; -} -.cookbook-settings-stack.hidden { display: none; } -.cookbook-dep-row.cookbook-dep-blocked { opacity: 0.4; } -.cookbook-dep-info { flex: 1; min-width: 0; } -.cookbook-dep-section { - display: flex; - align-items: baseline; - gap: 8px; - margin: 12px 2px 4px; -} -.cookbook-dep-section-title { - font-size: 11px; - font-weight: 600; - letter-spacing: 0.03em; -} -.cookbook-dep-section-note { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-subgroup { - margin: 6px 0 8px 12px; - padding: 7px 8px 8px; - border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - border-radius: 0 6px 6px 0; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-dep-subgroup-title { - display: flex; - align-items: baseline; - gap: 7px; - margin: 0 1px 1px; - font-size: 10.5px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 78%, transparent); -} -.cookbook-dep-subgroup-title em { - font-size: 9.5px; - font-style: normal; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.cookbook-dep-tag { - font-size: 9px; - padding: 0 8px; - border-radius: 4px; - white-space: nowrap; - text-align: center; - min-width: 62px; - box-sizing: border-box; - line-height: 1; - /* Explicit height so the tags (Install / Installed) are the EXACT - same height as the sibling tags — Firefox gives buttons a taller - native box otherwise. Mirrors the server-row tag rule (height:24px). */ - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.cookbook-dep-target { - border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-cat { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -/* Rebuild tag — same look as the LLM category tag, sits to its left. */ -.cookbook-dep-rebuild { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 75%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent); - cursor: pointer; - font-family: inherit; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - position: relative; - top: -2px; -} -.cookbook-dep-reinstall { - top: -3px; -} -.cookbook-dep-rebuild:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); -} -.cookbook-dep-installed { - background: color-mix(in srgb, var(--green, #50fa7b) 18%, transparent); - color: var(--green, #50fa7b); - border: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); - /* Match Install + Installed ▾ so all three variants align in mixed rows. */ - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-na { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); - cursor: help; - /* Match other dep tag widths so N/A rows line up with Install / Installed. */ - min-width: 75.85px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-install { - background: var(--accent, var(--red)); - color: #fff; - border: none; - cursor: pointer; - font-family: inherit; - font-weight: 500; - position: relative; - top: -3px; - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - /* Strip the native button box so it's the same height as the sibling tags - (Firefox renders taller otherwise); height comes from .cookbook-dep-tag. */ - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-build-deps { - width: auto; - min-width: 87.7px; - max-width: 100%; - white-space: normal; - overflow-wrap: anywhere; - line-height: 1.15; - text-align: center; - height: auto; - min-height: 24px; -} -/* The install state is the terminal control in a dependency row. Keep it on - the far edge even when optional rebuild/category controls are present. */ -.cookbook-dep-row > .cookbook-dep-installed, -.cookbook-dep-row > .cookbook-dep-na, -.cookbook-dep-row > .cookbook-dep-install:not(.cookbook-dep-build-deps) { - margin-left: auto; -} -/* Center the "?" glyph inside the help chip. Without text-align it sits 0.5px - left of true center because of the character's natural baseline offset. */ -.hwfit-help-chip { - text-align: center; - padding-left: 0.5px; -} -.cookbook-dep-install:hover { opacity: 0.85; } -/* Installed split button: "Installed" label + separator + ▾ caret; clicking it - opens the actions menu (Update). Replaces the old ⋮ button. */ -.cookbook-dep-installed-btn { - padding: 0; - width: 87.7px; - min-width: 87.7px; - cursor: pointer; - font-family: inherit; - overflow: hidden; - position: relative; - top: -3px; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-installed-btn .cookbook-dep-caret { - display: inline-flex; - align-items: center; - align-self: stretch; - padding: 0 5px; - font-size: 14px; - line-height: 1; - position: relative; - top: 1px; - left: 4px; - opacity: 0.85; - border-left: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); -} -.hwfit-serve-row { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - margin-bottom: 6px; -} -/* Tighter min on the core-knobs row only so Dtype / TP / GPU Mem / Max - Seqs fit on one line without crushing Row 1's GPU button strip or - the Backend picker. */ -.hwfit-serve-row-core { - grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); -} -/* Save / saved-config split aligned to the right end of Row 1 and - nudged up 4px so it sits flush with the rest of the row. */ -.hwfit-serve-row .cookbook-serve-slots { - justify-self: end; - position: relative; - top: -4px; -} -/* The Settings label wraps the Save split — right-align its title text - so the word "Settings" sits above the right-aligned Save button - instead of floating off to the left of its grid cell. 8px right - padding now (16px → 8px) per follow-up tweak, brings the title back - over the actual Save button instead of overshooting it. */ -.hwfit-serve-row label:has(> .cookbook-serve-slots) { - grid-column: -2 / -1; - justify-self: end; - text-align: right; - padding-right: 0; -} -.hwfit-serve-row label:has(> .cookbook-serve-slots) > span { - display: inline-block; - position: relative; - left: -33px; -} -.hwfit-serve-topline { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - margin: 0 0 6px; -} -.hwfit-serve-topline .hwfit-serve-runtime-text { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-serve-preset-row { - display: flex; - justify-content: flex-end; - margin: 0; - flex: 0 0 auto; - position: relative; - top: -6px; -} -.hwfit-serve-row label { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; - letter-spacing: 0.3px; -} -.hwfit-serve-row-core .hwfit-context-label { - grid-column: span 2; - width: auto; - min-width: 0; - max-width: none; - justify-self: start; -} -.hwfit-context-control { - display: block; - position: relative; - align-items: center; - margin-top: 2px; - width: 100px; -} -.hwfit-context-control .hwfit-sf[data-field="ctx"] { - min-width: 0; - width: 100%; - padding-right: 40px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]) { - position: relative; - left: -66px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - position: relative; - left: -64px; -} -.hwfit-serve-row-core .hwfit-gpus-label { - position: relative; - left: -70px; -} -.hwfit-context-calc-btn { - position: absolute; - right: 3px; - top: -4px; - width: 34px; - height: 28px; - min-width: 34px; - padding: 0; - font-size: 10px; - line-height: 1; - display: inline-flex; - align-items: center; - justify-content: center; - text-align: center; - overflow: hidden; - white-space: nowrap; -} -.hwfit-context-calc-btn .spinner-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 auto !important; -} -.hwfit-context-calc-btn .ai-spinner-whirlpool { - width: 12px !important; - height: 12px !important; -} -@media (min-width: 769px) { - [data-llama-mode-toggle].mode-toggle.mode-toggle-three .mode-toggle-btn > span { - top: -6px !important; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { - font-size: 0; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { - content: 'Unif'; - font-size: 11px; - } -} -@media (max-width: 768px) { - .hwfit-serve-row-core .hwfit-context-label { - display: flex; - flex-direction: column; - align-items: flex-start; - } - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]), - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - left: 0; - } - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_env_preset"]), - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="extra_env"]) { - grid-column: 1 / -1 !important; - } - .hwfit-context-control { - display: inline-flex; - align-items: center; - gap: 4px; - width: auto; - } - .hwfit-context-control .hwfit-sf[data-field="ctx"] { - width: 100px; - padding-right: 6px; - } - .hwfit-context-calc-btn { - position: relative; - top: -2px; - left: 2px; - right: auto; - flex: 0 0 34px; - width: 34px; - min-width: 34px; - } -} -.hwfit-auto-ctx-note { - display: block; - margin-top: 3px; - font-size: 9px; - opacity: 0.6; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Ctx slider — ported from origin/main. Sits in the Scan/Download toolbar - next to the quant dropdown. Drives _ctxValue() in cookbook-hwfit.js. */ -.hwfit-ctx-control { - height: 28px; - min-width: 134px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - gap: 5px; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - font-size: 11px; /* slightly smaller than .cookbook-field-input so Context reads quieter next to Engine/Quant */ -} -.hwfit-ctx-control span { - /* Match Quant/Engine select label style: no uppercase, no letter-spacing. */ - text-transform: none; - letter-spacing: 0; - opacity: 0.9; -} -.hwfit-ctx-control > span:first-child { - font-size: 9.5px; -} -/* Editor-style slider (same look as the gallery editor sliders): thin pill - rail that fattens on interaction, circular red thumb that grows on hover. */ -.hwfit-ctx-control input[type="range"] { - width: 64px; - min-width: 64px; - height: 4px; - padding: 0; - border: 0; - -webkit-appearance: none; - appearance: none; - /* Hard-coded grey so the rail is GUARANTEED visible regardless of theme — - every theme-derived color we tried (--fg-muted, --border, accent-bg mix) - kept blending into the panel background on at least one theme. */ - background: rgba(150, 150, 150, 0.65); - border-radius: 999px; - accent-color: var(--red); - cursor: pointer; - transition: height 0.15s ease, background 0.15s ease; -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - background: var(--fg); -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - height: 10px; -} -.hwfit-ctx-control input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]::-moz-range-thumb { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]:hover::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:focus::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:active::-webkit-slider-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control input[type="range"]:hover::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:focus::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:active::-moz-range-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control output { - min-width: 28px; - text-align: right; - color: var(--fg); - font-weight: 600; -} -.hwfit-sf { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - font-family: inherit; - font-size: 12px; - padding: 0 6px; - height: 28px; - box-sizing: border-box; -} -.hwfit-sf[data-field="backend"], -.hwfit-sf[data-field="dtype"], -.hwfit-sf[data-field="tp"] { - height: 32px; - box-sizing: border-box; - width: 100%; -} -.hwfit-sf:focus { - border-color: var(--accent, var(--red)); - outline: none; -} -.hwfit-sf[type="text"] { width: 100%; } -.hwfit-sf.hwfit-sf-wide { width: 100%; } -.hwfit-serve-checks { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 6px; -} -.hwfit-sf-cb { - display: flex; - flex-direction: row; - align-items: center; - gap: 6px; - font-size: 11px; - color: var(--fg-muted); - cursor: pointer; - padding: 3px 0; -} diff --git a/static/index.html b/static/index.html index cba90c7c9..ad04daf89 100644 --- a/static/index.html +++ b/static/index.html @@ -246,15 +246,24 @@ real request is discarded and the font fetched a second time. --> - - - - - - - - - + + + + + + + + + + + + + + + + + +
then collapses to nothing + while the footer remains visible. Keep the document text's intrinsic height + in that layout; bounded/fullscreen layouts can still shrink it and scroll. */ +#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview > pre { + flex: 1 1 auto; + min-height: 1.5em; +} + +/* Collapsible skills section headers (Your skills / Built-in). */ +.skills-section-header { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; + user-select: none; + padding: 6px 4px 4px; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.06em; + opacity: 0.6; + font-weight: 600; +} +.skills-section-header:hover { opacity: 0.9; } +.skills-section-chevron { transition: transform 0.15s ease; flex-shrink: 0; } +.skills-section-header.collapsed .skills-section-chevron { transform: rotate(-90deg); } +.skills-section-count { + margin-left: auto; + opacity: 0.6; + font-variant-numeric: tabular-nums; + font-weight: normal; +} +.skill-card-section-hidden { display: none !important; } +/* Warning banner shown when previewing/editing a built-in capability. */ +.skill-builtin-warn { + font-size: 9.5px; + line-height: 1.45; + color: var(--color-warning, #f0ad4e); + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 35%, transparent); + border-radius: 6px; + padding: 6px 9px; + margin: 0 0 8px; + flex-shrink: 0; +} + +/* #skills-list wears both .memory-list and .doclib-grid. doclib-grid's + max-height:400px would cap the list AND clamp an expanded card — keep + the memory-list fill-the-modal behaviour instead. */ +#skills-list.doclib-grid { + max-height: none; + flex: 1; + min-height: 0; +} +#skills-list.doclib-grid:has(.doclib-card-expanded) { + overflow: hidden; /* expanded card owns its own scroll */ +} +/* When a skill is expanded, hide the sibling Add-Skill form card so the + expanded SKILL.md uses the full panel. Otherwise the two admin-cards + split the height ~50/50 and the expanded skill is stuck in its half + while the Add form idles in the other. */ +.memory-tab-panel[data-memory-panel="skills"]:has(.doclib-card-expanded) > .admin-card:nth-of-type(2) { + display: none !important; +} + +/* Skills cards reuse the doclib expand/footer machinery. The SKILL.md + preview + the edit textarea need to fill the expanded card and own + their own scroll, same as a document preview. */ +/* Collapsed skill rows used a smaller (0.9em code) title, so the click row + read slimmer than document/chat/library cards. Give the header a min-height + so the tap target matches the other library items. */ +.skill-card > .skill-card-header { + min-height: 53px; + box-sizing: border-box; + display: flex; + align-items: center; + gap: 6px; +} +/* Skill rows share Tasks' card hover colors. Draft state belongs in the + status badge, not in a second opacity layer across the whole row. */ +/* Name + description column. Name wraps to 2 lines (skill slugs are long — + use the space rather than truncating to "check-model-downl…"). */ +.skill-card-textcol { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; +} +.skill-card-title-row { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.skill-card-name { + font-family: inherit; + font-weight: 500; + font-size: 12px; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + word-break: normal; + line-height: normal; +} +.skill-card-title-row .skill-card-name { min-width: 0; } +.skill-status-published { + width: 20px; + height: 20px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--color-success, #4ade80); + background: color-mix(in srgb, var(--color-success, #4ade80) 15%, transparent); + border-color: color-mix(in srgb, var(--color-success, #4ade80) 32%, transparent); + border-radius: 50%; +} +.skill-stats .skill-status-published { + width: 15px; + height: 15px; + margin-left: 3px; + vertical-align: middle; +} +.skill-stats .skill-status-published svg { + width: 9px; + height: 9px; +} +.skill-card-desc { + font-size: 10px; + opacity: 0.4; + margin-top: -1px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.skills-audit-model-field { + display: flex; + align-items: center; + gap: 8px; + margin-top: 10px; + font-size: 11px; +} +.skills-audit-model-field > span { + flex: 0 0 auto; + color: color-mix(in srgb, var(--fg) 68%, transparent); +} +.skills-audit-model-field select { + min-width: 0; + flex: 1; + height: 26px; + padding: 0 6px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + color: var(--fg); + font: inherit; +} +.skill-card-tags { + display: flex; + gap: 4px; + flex-wrap: wrap; + margin-top: 4px; +} +.skill-tag-pill { + padding: 1px 5px; + font-size: 9px; +} +.skill-usage-pill { + margin-left: auto; + color: color-mix(in srgb, var(--fg) 62%, transparent); +} +.skill-tag-filter-chip { cursor: pointer; } + +@media (max-width: 768px) { + .skill-card > .skill-card-header { + align-items: flex-start; + } + .skill-card-textcol { + min-width: 0; + } + .skill-card-tags { + max-width: 100%; + overflow: hidden; + } + .skill-card-tags .skill-tag-pill { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} +/* Right cluster — pills (right-aligned), stats, then the menu/chevron. */ +.skill-card-right { + display: flex; + align-items: center; + gap: 6px; + flex-shrink: 0; + margin-left: auto; +} +.skill-card-right .memory-cat-badge { flex-shrink: 0; } +.skill-status-bin { + background: color-mix(in srgb, var(--color-danger, #d9534f) 20%, transparent); + color: var(--color-danger, #d9534f); +} +.skill-model-pill { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.skill-model-student { + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.skill-audit-pass { + color: var(--color-success, #4ade80); + background: color-mix(in srgb, var(--color-success, #4ade80) 16%, transparent); +} +.skill-audit-needs_work { + color: var(--color-warning, #f0ad4e); + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 16%, transparent); +} +.skill-audit-fail { + color: var(--color-danger, #e06c75); + background: color-mix(in srgb, var(--color-danger, #e06c75) 16%, transparent); +} +.skill-audit-inconclusive, +.skill-audit-skipped, +.skill-audit-unknown { + color: color-mix(in srgb, var(--fg) 64%, transparent); + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.skill-model-teacher { + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 24%, transparent); + color: var(--color-warning, #f0ad4e); +} +.skill-necessity-pill { + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.skill-necessity-duplicate { + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 22%, transparent); + color: var(--color-warning, #f0ad4e); +} +.skill-necessity-trivial { + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: color-mix(in srgb, var(--fg) 62%, transparent); + border-color: color-mix(in srgb, var(--fg) 16%, transparent); +} +.skill-necessity-irrelevant { + background: color-mix(in srgb, var(--color-danger, #e06c75) 18%, transparent); + color: var(--color-danger, #e06c75); +} +.skills-summary-strip { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + overscroll-behavior-x: contain; + touch-action: pan-x; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + margin: -3px 0 8px; +} +.skills-summary-strip::-webkit-scrollbar { display: none; } +.skills-summary-strip:empty { + display: none; +} +.skills-summary-chip { + display: inline-flex; + align-items: center; + gap: 5px; + height: 23px; + padding: 0 8px; + border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + color: color-mix(in srgb, var(--fg) 70%, transparent); + font-size: 10px; + line-height: 1; + cursor: pointer; + flex: 0 0 auto; +} +.skills-summary-chip:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: color-mix(in srgb, var(--fg) 18%, transparent); +} +.skills-summary-chip.active { + background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + color: var(--fg); +} +.skills-summary-chip strong { + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 10px; + color: var(--fg); +} + +/* Shared filter/tag strip contract. These rows all sit immediately below + their screen's search or primary toolbar and stay one line at every width. */ +:is( + .skills-summary-strip, + #memory-category-filters, + .memory-category-filters:has(> .memory-cat-chip), + .doclib-lang-chips, + .doclib-chips, + .notes-labels-bar, + .tasks-activity-filters, + .gallery-tag-chips, + .gallery-album-chips, + .gallery-ai-tags, + .cal-filters +) { + align-items: center; + flex-wrap: nowrap !important; + gap: 6px; + width: 100%; + max-width: 100%; + min-width: 0; + max-height: none; + overflow-x: auto !important; + overflow-y: hidden !important; + overscroll-behavior-x: contain; + touch-action: pan-x; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + box-sizing: border-box; + padding: 0; + margin: -3px 0 8px; +} +:is( + .skills-summary-strip, + #memory-category-filters, + .memory-category-filters:has(> .memory-cat-chip), + .doclib-lang-chips, + .doclib-chips, + .notes-labels-bar, + .tasks-activity-filters, + .gallery-tag-chips, + .gallery-album-chips, + .gallery-ai-tags, + .cal-filters +)::-webkit-scrollbar { display: none; } +:is( + .skills-summary-strip > .skills-summary-chip, + #memory-category-filters > .skills-summary-chip, + .memory-category-filters > .memory-cat-chip, + .doclib-lang-chips > :is(.memory-cat-chip, .skills-summary-chip), + .doclib-chips > .doclib-chip, + .notes-labels-bar > .notes-label-chip, + .tasks-activity-filters > .memory-cat-chip, + .gallery-tag-chips > .gallery-chip, + .gallery-album-chips > .gallery-chip, + .gallery-ai-tags > .gallery-ai-chip, + .cal-filters > .cal-filter-item +) { + flex: 0 0 auto; + box-sizing: border-box; + height: 23px !important; + min-height: 23px; + padding: 0 8px !important; + border-radius: 6px !important; + font-size: 10px !important; + line-height: 1; + white-space: nowrap; +} +.doclib-lang-chips { + flex: 0 0 auto; + min-height: 25px; + padding: 1px 0 !important; + scroll-padding-inline: 1px; +} +.doclib-chip-scroll-frame { + position: relative; + display: flex; + align-items: center; + flex: 1 1 100%; + min-width: 0; + max-width: 100%; +} +.doclib-chip-scroll-frame > .chip-scroll-strip { + flex: 1 1 auto; + min-width: 0; + margin-left: 0; + margin-right: 0; +} +.doclib-chip-scroll-arrow { + display: none; + position: absolute; + top: calc(50% - 1px) !important; + transform: translateY(-50%); + margin-top: 0; + z-index: 2; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + border-radius: 50%; + box-sizing: border-box; + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + font-size: 18px; + line-height: 18px; + cursor: pointer; + opacity: 0.84; + transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease; +} +.doclib-chip-scroll-arrow-glyph { + display: inline-block; + position: relative; + top: -1px; + line-height: 1; +} +.doclib-chip-scroll-arrow:hover { + opacity: 1; + background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); +} +@media (max-width: 768px) { + .doclib-chip-scroll-arrow { display: none !important; } +} +.doclib-chip-scroll-frame.has-overflow > .doclib-chip-scroll-arrow:not([hidden]) { + display: inline-flex; +} +.doclib-chip-scroll-arrow.left { left: 0; } +.doclib-chip-scroll-arrow.right { right: 0; } +.doclib-chip-scroll-frame.has-overflow > .chip-scroll-strip { + padding-left: 0 !important; + padding-right: 22px !important; +} +.doclib-chip-scroll-frame > .chip-scroll-strip.is-pointer-dragging { + cursor: grabbing; + user-select: none; +} +.doclib-chip-scroll-frame > .chip-scroll-strip:not(.is-pointer-dragging) { + cursor: grab; +} +/* Calendar tags are wrapped by chipScroll.js after render. Keep that wrapper + content-sized inside the calendar's column layout; the default flex growth + otherwise turns the one-line tag strip into a large empty panel. */ +#cal-body > .doclib-chip-scroll-frame:has(> .cal-filters) { + flex: 0 0 auto; + min-height: 0; +} +/* The completed-task filters are a compact toolbar, not a flexible panel. + Without this override the shared chip frame grows into the available + height of the card, which is especially visible the first time the mobile + view switches to Completed. */ +.tasks-completed-card > .doclib-chip-scroll-frame:has(> #tasks-completed-status-chips) { + flex: 0 0 auto; + min-height: 0; +} +/* The active Tasks filter row is also a compact toolbar. Keep its wrapper + content-sized so changing a filter cannot make the mobile panel grow a + large empty gap between the chips and the task cards. */ +.admin-card > .doclib-chip-scroll-frame:has(> #tasks-filter-chips) { + flex: 0 0 auto; + min-height: 0; +} +#memory-category-filters { + flex: 0 0 auto; + min-height: 25px; + padding: 1px 0 !important; + scroll-padding-inline: 1px; +} +.cookbook-serve-tag-label { + display: inline-flex; + align-items: center; + min-width: 0; +} +#serve-search, +#hwfit-cache-select, +#serve-tags { + position: relative; + top: -2px; +} +#hwfit-cache-select { + top: -4px; +} +/* Calendar previously framed its tags as a separate card. It now follows the + same unframed placement as the other filter strips. */ +.cal-filters { + border: 0; + background: none; + box-shadow: none; +} +.skill-duplicate-keep { + background: color-mix(in srgb, var(--color-success, #4ade80) 22%, transparent); + color: var(--color-success, #4ade80); +} +.skill-duplicate-group { + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + color: color-mix(in srgb, var(--fg) 62%, transparent); + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.skill-duplicate-lower { + background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); + color: var(--color-danger, #e06c75); +} +.skill-stats { + font-size: 9px; + font-family: monospace; + white-space: nowrap; + color: color-mix(in srgb, var(--fg) 45%, transparent); +} +.skill-turns-saved { + font-size: 9px; + opacity: 0.82; + font-variant-numeric: tabular-nums; + white-space: nowrap; + flex: 0 0 auto; +} +.skill-expanded-savings { + display: flex; + align-items: center; + min-height: 18px; + margin: 0 0 5px; +} +.skill-turns-positive { + color: color-mix(in srgb, var(--color-success, #4ade80) 82%, var(--fg)); +} +.skill-turns-zero, +.skill-turns-unknown { + color: color-mix(in srgb, var(--fg) 48%, transparent); +} +.skill-turns-negative { + color: color-mix(in srgb, var(--color-danger, #e06c75) 80%, var(--fg)); +} +.skill-conf { + display: inline-block; + min-width: 4ch; + text-align: right; + font-weight: 700; + color: var(--accent, var(--red)); +} +.skill-verified, +.skill-teachermark, +.skill-needsmark { + display: inline-flex; + align-items: center; + vertical-align: middle; + margin-right: 3px; +} +.skill-verified { color: var(--accent, #4ade80); } +.skill-teachermark { color: var(--color-warning, #f0ad4e); } +.skill-needsmark { color: var(--color-danger, #e06c75); cursor: help; } + +/* The card currently being processed by an "Audit now" run glows + pulses so + it's obvious which one the audit is on. */ +@keyframes skill-audit-pulse { + 0%, 100% { box-shadow: 0 0 6px 0 color-mix(in srgb, var(--accent, #4ade80) 55%, transparent); } + 50% { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent, #4ade80) 85%, transparent); } +} +.skill-card.skill-audit-active { + border-color: var(--accent, #4ade80) !important; + animation: skill-audit-pulse 1.3s ease-in-out infinite; +} + +/* A test is running for this skill — the app's whirlpool spinner is injected + next to the name from JS (see _setCardRunning) so the collapsed/folded card + still makes it obvious work is in progress. */ +/* Collapsed bar shows the kebab menu; expanded shows an up-chevron to + collapse. (Toggled by the .doclib-card-expanded class.) */ +.skill-kebab-btn { + display: inline-flex; + align-items: center; + justify-content: center; + background: none; + border: none; + padding: 4px; + margin: 0; + cursor: pointer; + color: var(--fg); + opacity: 0.5; + border-radius: 5px; + flex-shrink: 0; + position: relative; + top: -2px; +} +.skill-kebab-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 10%, transparent); } +.skill-chevron-up { display: none; align-items: center; opacity: 0.5; flex-shrink: 0; } +/* Kebab dropdown */ +.skill-kebab-menu { + position: fixed; + /* z-index is set inline via topPortalZ() at open time (#4720); a static + value here loses once the modal bring-to-front counter climbs past it. */ + min-width: auto; + width: max-content; + padding: 4px; + background: var(--panel, var(--bg)); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 6px 20px rgba(0,0,0,0.22); + display: flex; + flex-direction: column; + gap: 1px; +} +.skill-kebab-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 6px 8px; + background: none; + border: none; + border-radius: 8px; + cursor: pointer; + color: var(--fg); + font-size: 11px; + text-align: left; +} +.skill-kebab-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); } +.skill-kebab-item.danger { color: var(--color-error, #e55); } +.skill-kebab-item.danger:hover { background: color-mix(in srgb, var(--color-error, #e55) 15%, transparent); } +/* Section labels separating "Your skills" from the read-only "Built-in + capabilities" list. */ +.skills-section-label { + font-size: 10px; + opacity: 0.5; + text-transform: uppercase; + letter-spacing: 0.04em; + margin: 10px 2px 4px; + flex-shrink: 0; +} +.skills-section-label:first-child { margin-top: 0; } +/* When a learned skill is expanded the grid hides sibling cards; hide the + section labels too so they don't orphan above the collapsed list. */ +#skills-list:has(.doclib-card-expanded) .skills-section-label { display: none; } +/* Built-in cards are read-only — no expand/hover-pointer affordance. */ +.skill-builtin-card { cursor: default; } +.skill-builtin-card:hover { border-color: var(--border); } +.skill-card-preview .skill-md-pre { + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 11.5px; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; +} +.skill-preview-copy { + position: absolute; + top: 6px; + right: 8px; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + opacity: 0; + color: var(--fg); + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + cursor: pointer; + transition: opacity .15s, color .15s, border-color .15s; +} +.skill-card-preview:hover .skill-preview-copy { opacity: 0.7; } +.skill-preview-copy:hover, +.skill-preview-copy:focus-visible { opacity: 1 !important; } +.skill-preview-copy.copied { + opacity: 1; + color: var(--color-save-green, #4caf50); + border-color: var(--color-save-green, #4caf50); +} +/* :has()-free expand layout. skills.js adds .skills-has-expanded to the + skills admin-card when a skill opens, so this works on engines that don't + support :has() (e.g. older Firefox mobile, where the expand stuck at + ~50%). Hide everything but the list so the grid fills the card; the + absolutely-positioned expanded card (mobile) then fills the grid. */ +.admin-card.skills-has-expanded > *:not(#skills-list) { + display: none !important; +} +.admin-card.skills-has-expanded > #skills-list.doclib-grid { + flex: 1 1 auto !important; + height: 100% !important; + min-height: 0 !important; + overflow: hidden !important; +} +/* Expand animation — a clean opacity fade. The mobile card snaps to a + full-screen overlay (position:absolute), so any translate/scale on it + reads as a jarring "explosion"; a pure fade just appears smoothly. (No + transform also keeps skills.js's height measurement accurate.) */ +@keyframes skill-card-expand { + from { opacity: 0; } + to { opacity: 1; } +} +.skill-card.doclib-card-expanded { + animation: skill-card-expand 0.18s ease-out; +} +/* Switching directly from one expanded card to another: no fade (it would + show the previous card collapsing through the semi-transparent new one). */ +.skill-card.doclib-card-expanded.skill-expand-instant { + animation: none !important; +} +@media (prefers-reduced-motion: reduce) { + .skill-card.doclib-card-expanded, + .skill-card.doclib-card-expanded .doclib-card-preview { + animation: none !important; + } +} +/* The preview WRAPPER inherits flex:1 (grow) from the generic doclib expand + rule — that's what stretched the card and left a void / pushed the footer + out. Size it to content (shrink + scroll only when long) so the footer + sits right under the preview and stays fully visible. */ +.skill-card.doclib-card-expanded .doclib-card-preview { + flex: 0 1 auto !important; + min-height: 0; + /* The footer lives INSIDE this wrapper. Don't let the wrapper scroll, or + the footer scrolls off with the content. Keep it clipped; the + inside is the scroller, so the footer stays pinned at the bottom. */ + overflow: hidden !important; +} +.skill-card.doclib-card-expanded .skill-md-pre { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; +} +/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them + back down 4px so they're vertically centered in the skill card footer. */ +.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn, +.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { + top: 0; +} +/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */ +#skills-bulk-bar .memory-toolbar-btn { + position: relative; + top: -2px; +} +/* ── Audit-all progress panel ── */ +.skills-audit-panel { + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px 10px; + margin: 6px 0; + background: color-mix(in srgb, var(--fg) 3%, transparent); +} +.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } +.skills-audit-title { font-size: 12px; font-weight: 600; } +.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; } +.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; } +.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; } +.skills-audit-log { + max-height: 22vh; overflow-y: auto; + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 10.5px; line-height: 1.5; opacity: 0.8; +} +/* ── Skill test monitor + AI eval verdict ── */ +.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; } +.skill-test-log { + flex: 1 1 auto; + min-height: 120px; + max-height: 48vh; + overflow-y: auto; + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 9.5px; + line-height: 1.45; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + white-space: pre-wrap; + word-break: break-word; +} +.skill-test-log > div { margin: 1px 0; } +.skill-test-meta { opacity: 0.5; } +.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; } +.skill-test-round { opacity: 0.6; margin-top: 6px !important; } +.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; } +.skill-test-out { opacity: 0.7; padding-left: 10px; } +.skill-test-say { white-space: pre-wrap; } +.skill-test-err { color: var(--color-danger, #e06c75); } +.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } +.skill-eval-badge { + font-size: 10px; font-weight: 700; letter-spacing: 0.03em; + padding: 2px 8px; border-radius: 10px; flex-shrink: 0; +} +.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); } +.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); } +.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); } +.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); } +.skill-eval-summary { font-size: 11px; opacity: 0.85; } +.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; } +.skill-eval-issues li { margin: 2px 0; } +.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } +.skill-eval-approve.suggested { + border-color: var(--color-success, #4ade80) !important; + color: var(--color-success, #4ade80) !important; +} +/* Already published — the button confirms the state (click to unpublish). */ +.skill-eval-approve.is-approved { + border-color: var(--color-success, #4ade80) !important; + color: var(--color-success, #4ade80) !important; + background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important; +} +/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title, + When to use, How, Tags) is accent-colored while the rest stays muted — real + placeholders can't be partially colored. The native placeholder is a single + space so :placeholder-shown still toggles the overlay. */ +.skill-ph-wrap { position: relative; } +.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; } +.skill-rich-ph { + position: absolute; + left: 11px; right: 11px; top: 50%; + transform: translateY(-50%); + pointer-events: none; + font-size: 12px; + line-height: 1.2; + color: color-mix(in srgb, var(--fg) 40%, transparent); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +/* Textarea: align the hint to the first line instead of vertical-centering. */ +.skill-rich-ph-top { top: 7px; transform: none; } +.skill-rich-ph .k { color: var(--accent, var(--red)); } +/* Keep the memory-input enter hint pinned to the field's right edge instead + of letting it drift after the example text. */ +#new-memory-input ~ .skill-rich-ph > svg { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + margin-left: 0 !important; +} +/* Hide the overlay once the field has real content. */ +.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; } +.skill-md-editor { + flex: 1 1 auto; + min-height: 0; + width: 100%; + box-sizing: border-box; + resize: none; + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 11.5px; + line-height: 1.5; + padding: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); +} +.doclib-empty { + text-align: center; + color: color-mix(in srgb, var(--fg) 35%, transparent); + padding: 32px 16px; + font-size: 12px; + font-style: italic; +} +/* Unified loading row across every Library tab (Chats / Documents / Research / + Archive): one opacity, one size, with the whirlpool spinner next to it. */ +.lib-loading-row { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 28px 16px; + font-size: 12px; + color: color-mix(in srgb, var(--fg) 45%, transparent); +} +.pdf-loading-state { + min-height: 120px; + margin: 0; + box-sizing: border-box; + flex-direction: column; + gap: 10px; + border: 0; + border-radius: 0; + background: transparent; + color: color-mix(in srgb, var(--fg) 68%, transparent); + box-shadow: none; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.01em; +} +.pdf-loading-state .ai-spinner-whirlpool { + color: var(--accent, var(--red)); + opacity: 0.95; +} +.pdf-loading-state .ai-spinner-whirlpool canvas { + display: block; +} +.doclib-load-more { + display: block; + margin: 10px auto 0; + padding: 6px 16px; + background: transparent; + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg-muted); + font-size: 11px; + cursor: pointer; + transition: border-color 0.15s, color 0.15s; + flex-shrink: 0; + position: relative; + top: 10px; +} +/* Soft dark gradient above the button — blends the list content into the + load-more strip instead of a hard cutoff. Spans the modal width and + sits behind/above the button. */ +.doclib-load-more::before { + content: ""; + position: absolute; + left: 50%; + transform: translateX(-50%); + bottom: 100%; + margin-bottom: 4px; + width: min(600px, 92vw); + height: 34px; + pointer-events: none; + background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent); +} +.doclib-load-more:hover { + border-color: var(--red); + color: var(--red); +} +.doclib-inline-load-more { + border: 0; + border-radius: 0; + padding: 0; + background: none; + top: 0; + font-size: 9px !important; + line-height: 1.2 !important; +} + +/* Document library toolbar buttons */ +.doclib-toolbar-btn { + background: none; + border: 1px solid var(--border); + color: var(--fg-muted); + font-size: 11px; + padding: 5px 10px; + border-radius: 6px; + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; + font-family: inherit; + display: inline-flex; + align-items: center; + gap: 3px; +} +.doclib-toolbar-btn:hover { + color: var(--fg); + border-color: var(--fg); +} +.doclib-toolbar-btn.active { + background: color-mix(in srgb, var(--red) 15%, transparent); + border-color: color-mix(in srgb, var(--red) 40%, transparent); + color: var(--red); +} +.doclib-toolbar-btn.danger { + color: var(--color-error, #e55); +} +.doclib-toolbar-btn.danger:hover:not(:disabled) { + border-color: var(--color-error, #e55); +} +.doclib-toolbar-btn:disabled { + opacity: 0.4; + cursor: default; +} + +/* Bulk action bar */ +.doclib-bulk-bar { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 8px; + border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); + border-radius: 6px; + background: color-mix(in srgb, var(--red) 5%, transparent); + font-size: 10px; + margin-bottom: 8px; +} +.doclib-bulk-bar.hidden { + display: none; +} +.doclib-bulk-check-all { + display: flex; + align-items: center; + gap: 3px; + cursor: pointer; + color: color-mix(in srgb, var(--fg) 60%, transparent); + font-size: 10px; +} +#doclib-bulk-bar .memory-toolbar-btn { + position: relative; + top: -3px; +} + +/* Custom checkboxes */ +.doclib-select-cb, +.doclib-bulk-check-all input { + -webkit-appearance: none; + appearance: none; + width: 13px; + height: 13px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + cursor: pointer; + flex-shrink: 0; + margin: 0; + position: relative; + transition: all 0.15s; +} +.doclib-select-cb:hover, +.doclib-bulk-check-all input:hover { + border-color: var(--red); +} +.doclib-select-cb:checked, +.doclib-bulk-check-all input:checked { + background: var(--red); + border-color: var(--red); +} +.doclib-select-cb:checked::after, +.doclib-bulk-check-all input:checked::after { + content: ''; + position: absolute; + left: 3px; + top: 0px; + width: 4px; + height: 8px; + border: solid var(--bg); + border-width: 0 1.5px 1.5px 0; + transform: rotate(45deg); +} + + +/* + button outside scroll area (when doc is on right side) */ + +@media (max-width: 600px) { + .doclib-toolbar { + flex-direction: column; + } + .doclib-card-session, + .doclib-card-time { + display: none; + } + .doclib-card-preview { + max-height: 40vh; + } +} + +/* Collapse card tags before they compete with attachment/favorite controls. */ +.email-card-tags .email-tags-more { + color: var(--email-tags-more-color, var(--fg)); + background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent); +} +.email-tags-more-single, +[data-email-tags-count-mini] { display: none; } +.email-tags-more-single svg { display: none; } +.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag, +.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra { + display: none; +} +.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single, +.email-card-tags.email-tags-expanded .email-tags-more-single { + display: inline-flex; +} +.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; } +.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini], +.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; } + +/* Keep the reader actions in drafting order. */ +.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; } +.email-reader-actions-inline > [data-act="reply"] { order: 2; } +.email-reader-actions-inline > [data-act="reply-all"] { order: 3; } +.email-reader-actions-inline > [data-act="forward"] { order: 4; } diff --git a/static/css/09-gallery.css b/static/css/09-gallery.css new file mode 100644 index 000000000..6554a5813 --- /dev/null +++ b/static/css/09-gallery.css @@ -0,0 +1,1382 @@ +/* 09-gallery - 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. + */ +.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed { + display: none !important; +} + +/* ── Archive browser ── */ +.archive-list { + grid-template-columns: 1fr !important; + gap: 4px !important; +} +.archive-row { + flex-direction: row !important; +} +.archive-row .doclib-card-header { + flex: 1; + padding: 8px 10px; +} +.archive-row .doclib-card-title { + flex: 1; + min-width: 0; +} +/* Archive column layout */ +.archive-header { + display: flex; + align-items: center; + padding: 4px 10px; + gap: 6px; + font-size: 9px; + font-weight: 600; + opacity: 0.35; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.archive-col-title { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-left: -4px; +} +.archive-col-msgs { + width: 40px; + flex-shrink: 0; + text-align: left; + margin-left: -8px; +} +.archive-col-model { + width: 90px; + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.archive-col-time { + width: 55px; + flex-shrink: 0; + text-align: right; +} +.archive-col-menu { + width: 24px; + flex-shrink: 0; +} +.archive-menu-btn { + background: transparent; + border: none; + color: var(--fg); + opacity: 0.3; + cursor: pointer; + padding: 4px; + line-height: 0; + border-radius: 4px; + transition: background .15s, opacity .15s; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + margin-left: 4px; + vertical-align: middle; + height: 100%; +} +.archive-menu-btn svg { + display: block; + margin-top: -6px; +} +.archive-menu-btn:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); + opacity: 1; +} + +/* ── Gallery (image library) ── */ + +.gallery-modal-content { + width: min(820px, 94vw); + max-height: 92vh; + font-size: 12px; +} +/* Keep the Gallery frame anchored while tabs or bulk-selection controls + change the content height. Centering the frame made every shorter tab move + the whole window vertically. */ +@media (min-width: 769px) { + #calendar-modal, + #gallery-modal, + #tasks-modal, + #memory-modal, + #doclib-modal, + #compare-model-overlay, + #research-overlay, + #theme-modal, + #settings-modal, + #email-lib-modal, + #cookbook-modal { + align-items: flex-start; + padding-top: 4vh; + box-sizing: border-box; + } +} +/* While the Edit tab is active the editor needs a *definite* height so + the right-side tool panel (`.ge-right-panel { overflow-y:auto }`) + scrolls INTERNALLY. Without this the modal only has `max-height`, so + `.gallery-editor { height:100% }` can't resolve — the editor grows to + its full content height, overflows the modal body, and the modal-body + scrollbar that appears can't be wheel-scrolled because the editor's + `overscroll-behavior:contain` blocks the chain. Pinning a real height + here makes the panel bounded and scrollable as designed. Scoped to + the editor view (matched via the container's inline `display:flex`) + so the Photos/Albums views keep sizing to their content. */ +.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) { + height: 92vh; +} +/* The editor tab owns the modal body's scroll context. Keep the landing + chooser and the mounted editor inside the available sheet height so a long + saved-project list cannot push the launch controls below the viewport. */ +.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body { + display: flex; + min-height: 0; + overflow: hidden; +} +.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container { + min-height: 0; + overflow: hidden; +} +/* Photo-detail view sizing (issue #314). + The detail view is rendered as a `position:absolute; inset:0` overlay + *inside* `.gallery-images-container`, painted over the photo grid. Because + it's absolutely positioned it can't contribute to the container's height — + the container (and therefore the overlay's `inset:0` box) collapses to the + height of the grid sitting behind it. When the library only has a few + photos that grid is short, so the detail view is crushed: the image is + clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a + tiny, internally-scrolling strip. (With a large library the grid is tall, + which is why it looked fine in the demo video but cramped for users with + few photos.) + Fix: when the detail view is open, hide the grid-view siblings and drop the + overlay into normal flow. The container — and the window, up to its 92vh + max-height — then sizes to the detail's own content (image + metadata), so + nothing is clipped or squeezed regardless of how many photos exist. Scoped + via the detail element's inline `display:flex` so the grid / albums views + keep sizing to their own content. Works on both desktop and the mobile + full-screen sheet. */ +#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) { + display: none !important; +} +#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail { + position: static; +} +/* Containing block for the photo-detail overlay — keeps it inside the body + so it sits below the modal header and the tab strip instead of covering them. */ +.gallery-images-container { + position: relative; +} +.gallery-modal-content .modal-header h4 { + font-size: 1rem; +} +.gallery-stats { + font-size: 10px; + color: color-mix(in srgb, var(--fg) 50%, transparent); + margin-bottom: 4px; +} +.gallery-toolbar { + display: flex; + gap: 6px; + margin-bottom: 4px; + align-items: center; +} +.gallery-toolbar-break { display: none; } +/* Keep the hint anchored while independently seating the input in the row. */ +.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; } +.gallery-search { + position: relative; + top: 4px; + flex: 1; + width: 100%; + padding: 4px 8px; + padding-right: 78px; /* room for the enter hint */ + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + font: inherit; + font-size: 12px; + outline: none; +} +.gallery-search-enter-hint { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10px; + font-weight: 600; + color: var(--accent, var(--red)); + pointer-events: none; + opacity: 0; + transition: opacity 0.12s; + white-space: nowrap; +} +/* Show the hint only once the user has typed something (placeholder gone). */ +.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; } +.gallery-search:focus { + border-color: var(--red); +} +.gallery-model-filter, +.gallery-sort { + padding: 4px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; + position: relative; + top: 4px; +} +.gallery-selection-bar { + margin-bottom: 4px; + position: relative; + top: 2px; + padding-right: 18px; +} +.gallery-tag-chips { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-bottom: 4px; +} +.gallery-chip { + height: 28px; + box-sizing: border-box; + padding: 0 13px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + border-radius: 14px; + font-size: 12px; + line-height: 1; + border: 1px solid var(--border); + background: none; + color: var(--fg); + cursor: pointer; + transition: border-color 0.15s, background 0.15s; +} +.gallery-chip:hover { + border-color: var(--red); +} +.gallery-chip.active { + background: color-mix(in srgb, var(--red) 15%, transparent); + border-color: color-mix(in srgb, var(--red) 40%, transparent); + color: var(--red); +} +/* Album chips row */ +.gallery-album-chips { + display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px; +} +.gallery-chip-fav { color: var(--red); } +.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); } +.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; } +/* Active-album indicator — appears when the Photos grid is filtered to one album. */ +.gallery-chip-active-album, +.gallery-active-filter-pill { + display: inline-flex; align-items: center; gap: 4px; + background: color-mix(in srgb, var(--red) 14%, transparent); + border-color: color-mix(in srgb, var(--red) 45%, transparent); + position: relative; + top: 6px; +} +.gallery-active-filter-pill { + background: color-mix(in srgb, var(--fg) 7%, transparent); + border-color: color-mix(in srgb, var(--fg) 18%, transparent); + color: color-mix(in srgb, var(--fg) 86%, transparent); +} +.gallery-clear-all-filters { + height: 28px; + border-style: dashed; + opacity: 0.72; +} +.gallery-clear-all-filters:hover { + opacity: 1; + color: var(--red); +} +.gallery-chip-clear { + background: none; + border: none; + color: inherit; + opacity: 0.6; + cursor: pointer; + padding: 0; + margin-left: 1px; + width: 12px; + height: 12px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 12px; + line-height: 1; + position: relative; + top: -4px; +} +.gallery-chip-clear:hover { opacity: 1; } + + + +/* Favorite button on card */ +/* Video cards: fills the same slot as , plus a small play badge + so it's clear the thumbnail represents a video. */ +.gallery-card video { + width: 100%; + aspect-ratio: 1 / 1; + object-fit: cover; + background: #000; + display: block; +} +.gallery-card-play { + position: absolute; + left: 50%; top: 50%; + transform: translate(-50%, -50%); + width: 36px; height: 36px; + display: flex; align-items: center; justify-content: center; + background: rgba(0, 0, 0, 0.55); + color: #fff; + border-radius: 50%; + pointer-events: none; + z-index: 1; +} +.gallery-card-play svg { margin-left: 2px; } + +.gallery-fav-btn { + position: absolute; top: 0px; right: 4px; z-index: 2; + background: rgba(0,0,0,0.4); border: none; border-radius: 50%; + width: 26px; height: 26px; font-size: 14px; + color: rgba(255,255,255,0.6); cursor: pointer; + display: flex; align-items: center; justify-content: center; + opacity: 0; transition: opacity 0.15s; + padding: 0; +} +.gallery-fav-btn.gallery-fav-active { opacity: 1; color: var(--red); } + +.gallery-dl-btn { + position: absolute; top: 0px; left: 4px; z-index: 2; + background: rgba(0,0,0,0.4); border: none; border-radius: 50%; + width: 26px; height: 26px; + color: rgba(255,255,255,0.75); cursor: pointer; + display: flex; align-items: center; justify-content: center; + opacity: 0; transition: opacity 0.15s, background 0.15s; + padding: 0; +} +.gallery-dl-btn:hover { background: rgba(0,0,0,0.7); color: #fff; } +/* In select mode the per-thumbnail hover buttons (favorite + download) just + get in the way of picking — hide them so the card is a clean select target. + `body.gallery-selecting` is the authoritative signal (one class for the + whole grid) — the per-card .gallery-card-selectable is kept as a backup + in case the body class is missed. */ +body.gallery-selecting .gallery-fav-btn, +body.gallery-selecting .gallery-dl-btn, +.gallery-card-selectable .gallery-fav-btn, +.gallery-card-selectable .gallery-dl-btn { + display: none !important; +} + +/* AI tag chips in detail view */ +.gallery-ai-tags { display: flex; gap: 4px; flex-wrap: wrap; } +.gallery-ai-chip { + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); + border-radius: 10px; padding: 3px 8px; font-size: 10px; + color: var(--fg); opacity: 0.8; + font: inherit; font-size: 10px; + cursor: pointer; + transition: background 0.12s, opacity 0.12s, border-color 0.12s; +} +.gallery-ai-chip:hover { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); +} +/* AI-generated tags: muted neutral with a tiny sparkle marker so the + user can tell at a glance these came from the model, not them. */ +.gallery-aitag-chip { + background: color-mix(in srgb, var(--fg) 6%, transparent); + border-color: color-mix(in srgb, var(--fg) 18%, transparent); + display: inline-flex; + align-items: center; + gap: 4px; +} +.gallery-aitag-chip:hover { + background: color-mix(in srgb, var(--fg) 12%, transparent); + border-color: color-mix(in srgb, var(--fg) 35%, transparent); +} +.gallery-aitag-mark { + font-size: 8px; + opacity: 0.55; + color: var(--accent, var(--red)); +} +/* User-applied tags keep the accent-colored chip — they're the personal/ + curated tags, so they get the strongest visual weight. */ +.gallery-user-chip { + font-weight: 600; +} +/* × to remove a user tag (appears inside the chip). */ +.gallery-tag-x { + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 5px; + width: 13px; + height: 13px; + border-radius: 50%; + font-size: 12px; + line-height: 1; + opacity: 0.55; + transition: opacity 0.12s, background 0.12s, color 0.12s; +} +.gallery-tag-x:hover { + opacity: 1; + background: color-mix(in srgb, var(--red, #ff5555) 22%, transparent); + color: var(--red, #ff5555); +} + +.gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + align-content: start; + gap: 8px; + max-height: calc(92vh - 165px); + overflow-y: auto; + padding: 2px; + transition: border-color 0.15s, background 0.15s; +} +.gallery-grid.gallery-dragover { + border: 2px dashed var(--red); + border-radius: 8px; + background: color-mix(in srgb, var(--red) 5%, transparent); +} +.gallery-card { + position: relative; + aspect-ratio: 1; + border-radius: 6px; + overflow: hidden; + border: 1px solid var(--border); + cursor: pointer; + transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s; +} +.gallery-card.gallery-card-focus { + border-color: var(--accent); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 36%, transparent); +} +/* Upload-affordance tile pinned to the top-left of the Photos grid. Matches + the Upload album tile in the Albums tab — dashed border, centered icon. */ +.gallery-card-upload { + border-style: dashed; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + opacity: 0.75; + transition: opacity 0.12s, border-color 0.15s, transform 0.15s; +} +/* The "Start AI tag" button turns into a Cancel control during a tag run. */ +.gallery-tag-cancelling { + color: var(--red) !important; + border-color: color-mix(in srgb, var(--red) 45%, var(--border)) !important; +} +/* Skeleton/shimmer placeholder tiles shown while the first page of photos loads. */ +.gallery-card-skeleton { + cursor: default; + background: linear-gradient(100deg, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, + color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); + background-size: 200% 100%; + animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; +} +.gallery-card-skeleton:hover { transform: none; box-shadow: none; border-color: var(--border); } +/* Chat attachment image: shimmer skeleton + centered whirlpool shown while a + sent photo uploads / its thumbnail loads (see chatRenderer buildAttachCards). */ +.attach-image-skeleton { + position: relative; + width: 160px; height: 120px; + border-radius: 6px; + display: flex; align-items: center; justify-content: center; + background: linear-gradient(100deg, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, + color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); + background-size: 200% 100%; + animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; +} +.attach-image-skeleton > .spinner-whirlpool { margin: 0 !important; } + +/* Corner "Aa" button on a chat photo thumbnail — opens the vision/OCR editor + so the user can correct what the vision model fed to the LLM. */ +.attach-image-preview { position: relative; } +.attach-vision-model, +.attach-image-name { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.attach-vision-model { + font-size: 10px; + color: var(--accent, var(--red)); + opacity: 0.85; + margin-top: 3px; +} +.attach-image-name { + font-size: 10px; + opacity: 0.5; + margin-top: 1px; +} +.attach-ocr-btn { + position: absolute; top: 4px; right: 4px; + z-index: 5; /* sit above msg-action overlays so the click lands here */ + height: 22px; + background: rgba(0,0,0,0.55); color: #fff; + border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; + padding: 0 8px 0 6px; cursor: pointer; + display: inline-flex; align-items: center; justify-content: center; gap: 5px; + font-size: 11px; font-weight: 500; line-height: 1; + opacity: 0.75; transition: opacity 0.15s; +} +.attach-ocr-btn:hover { opacity: 1; } +.attach-ocr-btn svg { display: block; } +/* On mobile keep just the (larger) edit icon — the "Caption" label crowds + the corner of the small chat photo. */ +@media (max-width: 768px) { + .attach-ocr-btn .attach-ocr-label { display: none; } + .attach-ocr-btn { + width: 28px; height: 28px; + padding: 0; + } + .attach-ocr-btn svg { width: 16px; height: 16px; } +} + +/* Vision/OCR text editor modal — opened from the "Aa" button above. */ +.vision-editor-overlay { + position: fixed; inset: 0; z-index: 9999; + background: rgba(0,0,0,0.55); + display: flex; align-items: center; justify-content: center; + padding: 16px; +} +.vision-editor-panel { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 10px; + padding: 14px 16px; + width: min(560px, 92vw); + max-height: 88vh; + display: flex; flex-direction: column; gap: 8px; + box-shadow: 0 14px 40px rgba(0,0,0,0.4); +} +.vision-editor-title { + font-size: 13px; font-weight: 600; color: var(--fg); + display: flex; align-items: center; gap: 6px; +} +.vision-editor-desc { font-size: 11px; opacity: 0.6; line-height: 1.4; } +.vision-editor-text { + width: 100%; min-height: 180px; + background: var(--panel); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; + padding: 8px 10px; + font-family: inherit; font-size: 12px; line-height: 1.5; + resize: vertical; outline: none; +} +.vision-editor-text:focus { border-color: var(--accent-primary, var(--red)); } +.vision-editor-hint { font-size: 10.5px; opacity: 0.55; line-height: 1.4; } +.vision-editor-actions { + display: flex; gap: 8px; justify-content: flex-end; + margin-top: 4px; +} +.vision-editor-btn { + background: var(--panel); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 14px; font-size: 12px; cursor: pointer; + transition: border-color 0.15s, background 0.15s; + display: inline-flex; align-items: center; gap: 5px; +} +.vision-editor-btn svg { display: block; } +/* Optical nudge: the button text sits 1px higher than the SVG glyphs + alongside it. Drop the text down 1px so they line up. */ +.vision-editor-btn .vision-btn-label { position: relative; top: 1px; } +.vision-editor-btn:hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); } +.vision-editor-btn-primary { + background: var(--accent-primary, var(--red)); + color: #fff; border-color: transparent; +} +.vision-editor-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } + +/* Quick full-size lightbox shown when the user taps a chat photo thumbnail. */ +.attach-lightbox { + position: fixed; inset: 0; z-index: 9998; + background: rgba(0, 0, 0, 0.85); + display: flex; align-items: center; justify-content: center; + padding: 16px; cursor: zoom-out; + animation: attach-lightbox-fade 0.12s ease-out; +} +.attach-lightbox img { + max-width: 100%; max-height: 100%; + border-radius: 6px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + object-fit: contain; +} +.attach-lightbox-err { + position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); + background: var(--bg); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 10px; font-size: 12px; +} +@keyframes attach-lightbox-fade { + from { opacity: 0; } + to { opacity: 1; } +} + +.gallery-card-upload:hover { + opacity: 1; + border-color: var(--red); + transform: translateY(-1px); +} +.gallery-card-upload-inner { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + color: var(--fg); +} +.gallery-card-upload-label { + font-size: 12px; + font-weight: 500; + opacity: 0.8; +} +.gallery-card:hover { + border-color: var(--red); + box-shadow: 0 2px 8px color-mix(in srgb, var(--red) 15%, transparent); + transform: translateY(-1px); +} +.gallery-card-overlay { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 8px; + background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 34%, rgba(0,0,0,0.22)); + color: #fff; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s; +} +.gallery-card-badges { + display: flex; + gap: 4px; + flex-wrap: wrap; + align-items: flex-end; + justify-content: flex-end; + margin-top: auto; + margin-bottom: 34px; +} +.gallery-card-badge { + padding: 2px 6px; + border-radius: 999px; + background: rgba(0, 0, 0, 0.55); + border: 1px solid rgba(255, 255, 255, 0.14); + color: rgba(255, 255, 255, 0.88); + font-size: 9px; + font-weight: 600; + line-height: 1.1; +} +.gallery-card-badge-fav { + color: #ff5f6f; +} +.gallery-select-dot { + position: absolute; top: 6px; left: 6px; z-index: 2; + display: block; + width: 10px; height: 10px; border-radius: 50%; cursor: pointer; + background: color-mix(in srgb, var(--fg) 30%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 50%, transparent); + transition: background 0.15s; +} +.gallery-select-dot.selected { background: var(--red); border-color: var(--red); } +.gallery-select-dot:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } +.gallery-select-btn { + padding: 9px 10px 11px; background: transparent; color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; cursor: pointer; + font-size: 11px; font-family: inherit; opacity: 0.6; transition: all 0.15s; + margin-top: -4px !important; +} +.gallery-select-btn:hover { opacity: 1; } +/* Match the library Select toggle's red tint (.memory-toolbar-btn.active). + Bumped contrast so the toggled state actually reads on mobile — the prior + 15% red on transparent was nearly invisible against the page background. */ +.gallery-select-btn.active { + background: color-mix(in srgb, var(--red) 28%, transparent); + color: var(--red); + border-color: var(--red); + font-weight: 600; + opacity: 1; +} +/* Toolbar action buttons sit 2px lower than the generic select-btn so + they baseline-align with the toolbar's selects/inputs instead of + floating above them. */ +.gallery-toolbar-action { + margin-top: 0 !important; + display: inline-flex; + align-items: center; + gap: 2px; +} +.gallery-bulk-bar { + display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 12px; + justify-content: flex-end; +} +.gallery-bulk-bar.hidden { display: none; } +.gallery-bulk-all { + display: inline-flex; align-items: center; gap: 4px; + font-size: 11px; opacity: 0.75; cursor: pointer; margin-right: auto; +} +.gallery-bulk-all input { accent-color: var(--accent, var(--red)); cursor: pointer; } +.gallery-bulk-delete { + padding: 3px 10px; background: transparent; color: var(--red); + border: 1px solid var(--red); border-radius: 4px; cursor: pointer; + font-size: 11px; font-family: inherit; +} +.gallery-bulk-delete:hover { background: var(--red); color: #fff; } +.gallery-bulk-cancel { + padding: 3px 10px; background: transparent; color: var(--fg); + border: 1px solid var(--border); border-radius: 4px; cursor: pointer; + font-size: 11px; font-family: inherit; opacity: 0.6; +} +.gallery-bulk-cancel:hover { opacity: 1; } +.gallery-card img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +.gallery-card-info { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 6px 8px; + background: linear-gradient(transparent, rgba(0,0,0,0.75)); + color: #fff; +} +.gallery-card-prompt { + font-size: 10px; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.gallery-card-meta { + display: flex; + align-items: center; + gap: 6px; + margin-top: 2px; + font-size: 9px; + opacity: 0.8; +} +.gallery-card-model { + padding: 1px 5px; + border-radius: 6px; + background: rgba(255,255,255,0.15); +} +.gallery-empty { + text-align: center; + color: color-mix(in srgb, var(--fg) 35%, transparent); + padding: 32px 16px; + font-size: 12px; + font-style: italic; +} +.gallery-load-more { + display: block; + margin: 10px auto 0; + padding: 6px 16px; + background: none; + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 11px; + transition: border-color 0.15s, color 0.15s; +} +.gallery-load-more:hover { + border-color: var(--red); + color: var(--red); +} + +/* Gallery detail overlay */ +.gallery-detail { + position: absolute; + inset: 0; + background: var(--panel); + z-index: 10; + display: flex; + flex-direction: column; + overflow-y: auto; +} +.gallery-detail-header { + display: flex; + align-items: center; + gap: 6px; + padding: 2px 8px 4px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + /* Pin to top so a tall (portrait) photo can't cover the Back / ⋮ + menu when the detail body grows past the visible area. */ + position: sticky; + top: 0; + background: var(--panel); + /* Above the image's rotate/nav buttons (z-index:3) so they scroll cleanly + behind the header instead of colliding with it. */ + z-index: 5; + min-height: 0; +} +.gallery-detail-title { + max-width: min(360px, 40vw); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 12px; + font-weight: 600; + opacity: 0.82; + position: relative; + top: 1px; +} +.gallery-detail-back { + background: none; + border: none; + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 12px; + padding: 4px 8px; + border-radius: 4px; + position: relative; + top: -2px; +} +/* Slightly larger header icons (back/edit/heart/⋮) — override the inline 14px. */ +.gallery-detail-header svg { width: 16px; height: 16px; } +.gallery-detail-back:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.gallery-detail-action { + background: none; + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 11px; + padding: 3px 8px; + border-radius: 4px; + transition: border-color 0.15s; +} +.gallery-detail-action:hover { + border-color: var(--fg); +} +.gallery-detail-action.danger { + color: var(--color-error, #e55); +} +/* Overflow menu — replaces the seven individual action buttons that used + to crowd the right side of the detail header. */ +.gallery-detail-menu-wrap { position: relative; } +.gallery-detail-menu-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 26px; + padding: 0; + position: relative; + top: -2px; +} +.gallery-detail-menu { + position: absolute; + top: calc(100% + 4px); + right: 0; + min-width: 150px; + padding: 3px; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); + z-index: 12; + display: flex; + flex-direction: column; + gap: 0; +} +.gallery-detail-menu[hidden] { display: none; } +.gallery-detail-menu .dropdown-item-compact { + width: 100%; + background: none; + border: none; + text-align: left; + font: inherit; + /* Tighter than the global default — the photo-detail menu has 8 + items so every pixel of vertical padding adds up. */ + padding: 4px 8px; + font-size: 11px; + gap: 8px; + line-height: 1.2; +} +.gallery-detail-menu .dropdown-item-compact .dropdown-icon, +.gallery-detail-menu .dropdown-item-compact .dropdown-icon svg { + width: 12px; + height: 12px; +} +.gallery-detail-body { + display: flex; + gap: 16px; + padding: 12px; + flex: 1; + min-height: 0; + overflow: hidden; +} +.gallery-detail-image { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + min-width: 0; + min-height: 0; + height: 100%; + padding-inline: 56px; + box-sizing: border-box; + overflow: hidden; +} +/* Shrink-wraps the actual image so overlay children (heart, face boxes) + anchor to the IMAGE bounds, not the wider letterboxed wrapper. */ +.gallery-detail-img-frame { + position: relative; + display: inline-flex; + max-width: 100%; + max-height: 100%; +} +.gallery-detail-nav { + position: absolute; + top: 50%; + transform: translateY(-50%); + background: rgba(0, 0, 0, 0.45); + color: rgba(255, 255, 255, 0.85); + border: none; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s, background 0.15s; + z-index: 3; + padding: 0; +} +.gallery-detail-nav:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } +.gallery-detail-nav-prev { left: 8px; } +.gallery-detail-nav-next { right: 8px; } + +/* Rotate buttons — same pattern as the prev/next arrows but pinned to the + top corners. Hover-revealed so they don't clutter the image. */ +.gallery-detail-rotate { + position: absolute; + top: 8px; + background: rgba(0, 0, 0, 0.45); + color: rgba(255, 255, 255, 0.85); + border: none; + border-radius: 50%; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s, background 0.15s; + z-index: 3; + padding: 0; +} +.gallery-detail-rotate:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } +.gallery-detail-rotate-ccw { left: 8px; } +.gallery-detail-rotate-cw { right: 8px; } +/* Inline heart that lives next to the "Date" label in the sidebar. + Tiny outline icon by default; fills red when favorited. */ +.gallery-detail-date-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; +} +.gallery-detail-fav-inline { + background: none; + border: none; + padding: 0; + /* Nudge up 8px so the heart sits visually above the Date label + baseline rather than centred on it. */ + margin-top: -8px; + border-radius: 4px; + color: var(--fg-muted); + opacity: 0.6; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; + transition: opacity 0.15s, color 0.15s; +} +/* Shrink the SVG so the heart matches the height of the small uppercase + Date label sitting next to it. */ +.gallery-detail-fav-inline svg { + width: 12px; + height: 12px; + display: block; +} +.gallery-detail-fav-inline:hover { + opacity: 1; +} +.gallery-detail-fav-inline.active { + opacity: 1; + color: var(--red); +} +.gallery-detail-sidebar { + width: 290px; + flex-shrink: 0; + /* Vertical scroll for long metadata, but never horizontal — overflow:auto + alone would let a wide child (e.g. long album name in the select) push + the sidebar into horizontal-scroll mode. */ + overflow-x: hidden; + overflow-y: auto; + min-width: 0; +} +.gallery-detail-inspector-head { + padding-bottom: 10px; + margin-bottom: 10px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); +} +.gallery-detail-eyebrow { + font-size: 9px; + font-weight: 700; + color: var(--accent, var(--red)); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 3px; +} +.gallery-detail-inspector-title { + font-size: 14px; + font-weight: 650; + line-height: 1.25; + word-break: break-word; +} +.gallery-detail-meta-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 6px; + margin: 4px 0 12px; +} +.gallery-detail-meta-card { + min-width: 0; + border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); + background: color-mix(in srgb, var(--fg) 4%, transparent); + border-radius: 7px; + padding: 7px 8px; +} +.gallery-detail-meta-card span { + display: block; + font-size: 9px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 48%, transparent); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 3px; +} +.gallery-detail-meta-card strong { + display: block; + min-width: 0; + font-size: 11px; + font-weight: 600; + line-height: 1.25; + word-break: break-word; +} +.gallery-detail-inline-link { + appearance: none; + background: none; + border: none; + color: var(--accent, var(--red)); + padding: 0; + font: inherit; + font-weight: inherit; + cursor: pointer; + text-align: left; +} +.gallery-detail-mini-actions { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 7px; +} +.gallery-detail-mini-btn { + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 4px 8px; + font: inherit; + font-size: 10.5px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 4px; +} +.gallery-detail-mini-btn:hover { + border-color: color-mix(in srgb, var(--fg) 42%, var(--border)); +} +/* Constrain any child that could otherwise stretch the sidebar wider than + its declared width (selects with long option text, long URLs, etc.). */ +.gallery-detail-sidebar select, +.gallery-detail-sidebar input, +.gallery-detail-sidebar .gallery-detail-prompt { + max-width: 100%; + box-sizing: border-box; +} +.gallery-date-rel { + opacity: 0.55; + margin-left: 4px; + font-size: 11px; + font-style: italic; +} +.gallery-detail-section label { + display: block; + font-size: 10px; + font-weight: 600; + opacity: 0.6; + margin-bottom: 3px; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.gallery-detail-section div { + font-size: 12px; + word-break: break-word; +} +.gallery-detail-prompt { + white-space: pre-wrap; + line-height: 1.4; +} +.gallery-tag-input, +.gallery-detail-name-input { + width: 100%; + padding: 5px 8px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; + font: inherit; + font-size: 11px; + margin-bottom: 4px; +} +.gallery-tag-input:focus, +.gallery-detail-name-input:focus { + border-color: var(--red); + outline: none; +} +/* ↵ enter hint inside the Add-a-tag field (accent), replacing the "press Enter" + placeholder text. */ +.gallery-tag-input-wrap { position: relative; } +.gallery-tag-enter-hint { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + margin-top: -2px; /* the input has 4px bottom margin; center on the box */ + color: var(--accent, var(--red)); + font-size: 13px; + line-height: 1; + pointer-events: none; + opacity: 0.8; +} +.gallery-detail-name-input { font-size: 13px; font-weight: 500; } +/* ↵ enter hint on the image name field — accent, shown while editing to signal + that Enter saves. */ +.gallery-name-wrap { position: relative; } +.gallery-name-enter { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + margin-top: -2px; + color: var(--accent, var(--red)); + opacity: 0.85; + pointer-events: none; + display: none; +} +.gallery-tag-save { + background: none; + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 10px; + padding: 3px 10px; + border-radius: 4px; + transition: border-color 0.15s, color 0.15s; +} +.gallery-tag-save:hover { + border-color: var(--red); + color: var(--red); +} + +@media (max-width: 600px) { + .gallery-grid { + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 6px; + } + /* Single-row toolbar on mobile: search + Sources + Recent (sort) all share + one row. The search bar shrinks so the dropdowns fit beside it. */ + .gallery-toolbar { + flex-wrap: nowrap; + gap: 4px; + } + .gallery-search-wrap { + flex: 1 1 0; + min-width: 0; + } + .gallery-search { + /* Drop the desktop right-padding reserved for the "enter to tag" hint — + hide the hint on mobile (below) so the input can use its full width. */ + padding-right: 8px; + } + .gallery-search-enter-hint { display: none; } + /* Disable the row break so the filters stay inline with search. */ + .gallery-toolbar-break { display: none; } + .gallery-model-filter, + .gallery-sort { + flex: 0 0 auto; + /* Cap each dropdown so search keeps a usable amount of width. */ + max-width: 100px; + font-size: 11px; + padding: 4px 6px; + /* Keep the same vertical alignment as the desktop toolbar. */ + position: relative; + top: 4px; + } + .gallery-select-btn { + padding: 6px 8px 8px; + font-size: 9px; + flex-shrink: 0; + /* Push Select to the far right of the single-row toolbar, regardless + of its DOM position (sits between search and the dropdowns). */ + order: 99; + margin-left: auto; + } + /* Tabs scroll horizontally rather than wrapping when narrow. */ + .gallery-tabs { + overflow-x: auto; + flex-wrap: nowrap; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + } + .gallery-tabs::-webkit-scrollbar { display: none; } + .gallery-tab { flex-shrink: 0; } + /* Album chips also scroll horizontally so they don't stack. */ + .gallery-album-chips { + overflow-x: auto; + flex-wrap: nowrap; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-bottom: 4px; + } + .gallery-album-chips::-webkit-scrollbar { display: none; } + .gallery-chip { flex-shrink: 0; } + /* Tasks filter chips — same horizontal-swipe behaviour as the gallery + albums / library chips on mobile: one row, slide-to-see-more. */ + .tasks-activity-filters { + overflow-x: auto; + flex-wrap: nowrap !important; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-bottom: 4px; + } + .tasks-activity-filters::-webkit-scrollbar { display: none; } + .tasks-activity-filters > * { flex-shrink: 0; } + /* Detail view */ + .gallery-detail-body { + flex-direction: column; + overflow: visible; + } + .gallery-detail-image { + flex: 0 0 auto; + width: 100%; + height: clamp(280px, 58vh, 520px); + padding-inline: 48px; + } + .gallery-detail-sidebar { + width: 100%; + } + .gallery-detail-title { + display: none; + } + .gallery-detail-meta-grid { + grid-template-columns: 1fr; + } + /* Album grid: smaller tile minimum on mobile. */ + .gallery-albums-grid { + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; + } +} + + +/* ── Scoreboard table ── */ +.scoreboard-table { + width: 100%; + border-collapse: collapse; + font-size: 0.88em; +} +.scoreboard-table th { + text-align: left; + padding: 6px 10px; + border-bottom: 1px solid var(--border); + font-weight: 600; + font-size: 0.85em; + color: color-mix(in srgb, var(--fg) 55%, transparent); + text-transform: uppercase; + letter-spacing: 0.5px; +} +.scoreboard-table th:not(:first-child) { + text-align: center; +} +.scoreboard-table td { + padding: 5px 10px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); + text-align: center; +} +.scoreboard-table td.scoreboard-model { + text-align: left; + font-weight: 500; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.scoreboard-table td.scoreboard-pct { + font-weight: 600; + color: var(--red); +} diff --git a/static/css/style-part-05.css b/static/css/10-cookbook.css similarity index 55% rename from static/css/style-part-05.css rename to static/css/10-cookbook.css index 5008414f9..8acded779 100644 --- a/static/css/style-part-05.css +++ b/static/css/10-cookbook.css @@ -1,14 +1,1639 @@ -/* Fragment 6/9 of the former static/style.css. +/* 10-cookbook - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ +.scoreboard-table tbody tr:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} + +#scoreboard-overlay svg { + color: var(--accent, var(--red)); +} + +/* ── Compare search results ── */ +.compare-search-results { + display: flex; + flex-direction: column; + gap: 2px; +} +.compare-search-result { + padding: 6px 0; + border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); +} +.compare-search-result:last-child { + border-bottom: none; +} +.search-result-title { + color: var(--color-accent); + text-decoration: none; + font-weight: 500; + font-size: 0.9em; + display: block; + line-height: 1.3; +} +.search-result-title:hover { + color: var(--color-link-hover); + text-decoration: underline; +} +.search-result-snippet { + color: color-mix(in srgb, var(--fg) 70%, transparent); + font-size: 0.82em; + line-height: 1.4; + margin-top: 2px; +} +.search-result-url { + color: color-mix(in srgb, var(--fg) 35%, transparent); + font-size: 0.75em; + margin-top: 3px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + + +/* ── Cookbook Tool ── */ + +/* ── Cookbook ── */ +.cookbook-serve-preset { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + margin-bottom: 3px; + cursor: pointer; + transition: background 0.15s; +} +.cookbook-serve-preset:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } +.cookbook-serve-preset-name { + font-size: 11px; + font-weight: 500; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.cookbook-serve-preset-meta { + font-size: 9px; + font-family: 'Fira Code', monospace; + color: var(--fg-muted); + opacity: 0.5; +} +.cookbook-serve-preset-launch { + background: none; + border: none; + color: #50fa7b; + cursor: pointer; + font-size: 10px; + opacity: 0.6; + padding: 0 2px; + position: relative; + top: -4px; +} +.cookbook-serve-preset-launch:hover { opacity: 1; } +.cookbook-serve-preset-rm { + background: none; + border: none; + color: var(--fg-muted); + cursor: pointer; + font-size: 10px; + opacity: 0.3; + padding: 0 2px; + position: relative; + top: -4px; +} +.cookbook-serve-preset-rm:hover { opacity: 0.8; color: var(--red); } +.cookbook-body { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + flex: 1; + min-height: 0; + scrollbar-width: thin; +} +#cookbook-modal .modal-content { + display: flex; + flex-direction: column; + overflow: hidden; +} +#cookbook-modal .modal-header { + flex: 0 0 auto; +} +#cookbook-modal .cookbook-body { + min-height: 0; + overflow-y: auto; + overflow-x: hidden; +} +#cookbook-modal .cookbook-group { + min-height: 0; +} +#cookbook-modal .cookbook-group > .admin-card { + min-height: 0; + /* Let .cookbook-body be the SINGLE scroll surface. Nesting another + overflow:auto here trapped the wheel inside the cached-list when a + serve panel expanded — the page couldn't scroll past the panel's + bottom (Launch button got hidden). */ + overflow: visible !important; +} +#cookbook-modal .cookbook-section-body { + min-height: 0; +} +.cookbook-group { + display: flex; + flex-direction: column; + gap: 8px; + flex: 1; + min-height: 0; +} + +/* Cards — match admin-card */ +.cookbook-card { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + display: flex; + flex-direction: column; + gap: 6px; +} +.cookbook-card-header { + display: flex; + align-items: center; + justify-content: space-between; +} +.cookbook-card-header-actions { display: flex; gap: 4px; } +.cookbook-card-title { font-size: 13px; font-weight: 600; } +.cookbook-card-desc { + font-size: 11px; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} + +/* Fields grid */ +.cookbook-fields { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 6px; + margin-top: 4px; +} +.cookbook-field-label { + display: flex; + flex-direction: column; + font-size: 11px; + color: color-mix(in srgb, var(--fg) 60%, transparent); + gap: 3px; +} + +/* Inputs — match admin-add-form input */ +.cookbook-field-input { + padding: 5px 8px; + font-size: 12px; + font-family: inherit; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + outline: none; +} +.cookbook-field-input:focus { border-color: var(--red); } +#hwfit-dl-server, #hwfit-usecase, #hwfit-server-select, #hwfit-cache-server, #serve-sort { height: 28px; width: 88px; } +/* Serve tab — match Documents sizing, but leave room for the active "Cancel" + label and center it vertically so the descenders don't clip. */ +#hwfit-cache-select { + min-width: 58px; + height: 28px; + position: relative; + top: -3px; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +/* Command preview */ +.cookbook-cmd-preview { + margin: 4px 0 0; + padding: 8px 10px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + font-family: 'Fira Code', monospace; + font-size: 11px; + white-space: pre-wrap; + word-break: break-all; + color: var(--fg); +} + +/* Buttons — match admin-btn-sm */ +.cookbook-actions { display: flex; gap: 6px; margin-top: 2px; } +.cookbook-btn { + padding: 4px 10px; + min-width: 54px; + text-align: center; + font-size: 11px; + font-family: inherit; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); + cursor: pointer; + transition: all 0.15s; +} +.cookbook-btn:hover { + background: var(--border); + border-color: var(--accent); +} +.cookbook-btn:disabled { opacity: 0.5; cursor: not-allowed; } +.cookbook-run-btn { + background: var(--accent); + color: var(--panel); + border-color: var(--accent); + font-weight: 600; +} +.cookbook-run-btn:hover { opacity: 0.85; } +.cookbook-stop-btn { + background: var(--color-error); + border-color: var(--color-error); +} + +/* Output */ +.cookbook-output-wrap { + position: relative; + margin: 0; +} +.cookbook-output-kill { + position: absolute; + top: 4px; + right: 34px; + width: 22px; + height: 22px; + padding: 0; + background: none; + border: 1px solid transparent; + border-radius: 4px; + color: var(--fg-muted); + font-size: 16px; + line-height: 1; + cursor: pointer; + opacity: 0; + transition: opacity .15s, color .15s; + display: flex; + align-items: center; + justify-content: center; +} +.cookbook-output-kill:hover { color: var(--color-error, var(--warn)); border-color: var(--border); } +.cookbook-output-wrap .copy-code { + position: absolute; + top: 6px; + right: 6px; +} +.cookbook-output-wrap:hover .copy-code { opacity: 0.7; } +.cookbook-output-pre { + margin: 0; + padding: 8px 10px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + font-family: 'Fira Code', monospace; + font-size: 11px; + white-space: pre-wrap; + word-break: break-all; + max-height: 180px; + overflow-y: auto; +} +.cookbook-output-error { color: var(--color-error); } + +/* Downloaded/cached models */ +.hwfit-cached-item { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + padding: 6px 8px; + margin: 2px 0; + border-radius: 6px; + border: 1px solid var(--border); + background: color-mix(in srgb, var(--fg) 3%, transparent); + cursor: default; + font-size: 12px; +} +.hwfit-cached-item .hwfit-serve-panel { + flex-basis: 100%; + margin-top: 4px; +} +.hwfit-cached-item:hover { + background: color-mix(in srgb, var(--fg) 6%, transparent); +} +.hwfit-cached-header { + font-size: 10px; + opacity: 0.4; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + border: none !important; + background: none !important; + padding: 2px 8px; + cursor: default !important; +} +.hwfit-cached-header:hover { background: none !important; } +.hwfit-cached-name { + font-weight: 600; + flex-shrink: 0; +} +.hwfit-cached-repo { + flex: 1; + color: var(--fg-muted); + font-size: 11px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hwfit-cached-size, +.hwfit-cached-files { + font-size: 10px; + color: var(--fg-muted); + white-space: nowrap; +} +.hwfit-cached-serve, +.hwfit-cached-delete { + flex-shrink: 0; +} +.hwfit-cached-delete { + opacity: 0.4; + font-size: 10px; +} +.hwfit-cached-delete:hover { + opacity: 1; + color: var(--color-error, var(--warn)); +} +.hwfit-cached-badge { + font-size: 9px; + padding: 1px 6px; + border-radius: 3px; + font-weight: 600; + white-space: nowrap; +} +.hwfit-cached-ready { + background: color-mix(in srgb, var(--color-success, #4caf50) 20%, transparent); + color: var(--color-success, #4caf50); +} +.hwfit-cached-dl { + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 20%, transparent); + color: var(--color-warning, #f0ad4e); +} + +/* Cookbook notification dot */ +#tool-cookbook-btn { + position: relative; +} +#rail-cookbook { + position: relative; +} +.cookbook-open-loading { + display: inline-flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + flex-shrink: 0; + margin-left: 2px; + color: var(--accent, var(--red)); + opacity: 0.9; +} +.cookbook-open-loading .ai-spinner, +.cookbook-open-loading .ai-spinner-whirlpool { + display: inline-flex !important; + width: 14px; + height: 14px; + margin: 0 !important; + gap: 0 !important; +} +.cookbook-open-loading canvas { + display: block; +} +.cookbook-section-loading-wp, +.cookbook-section-loading-wp canvas { + width: 22px !important; + height: 22px !important; + display: block !important; +} +#rail-cookbook .cookbook-open-loading { + position: absolute; + right: 2px; + top: -2px; + width: 12px; + height: 12px; + margin-left: 0; +} +/* The serve-list 'downloading' / 'download stalled' status text sits a hair + low against the rest of the meta line on mobile — nudge it up 2px. */ +@media (max-width: 768px) { + .cookbook-dl-status { + position: relative; + top: -2px; + } +} +.cookbook-notif-dot { + width: 6px; + height: 6px; + flex-shrink: 0; + border-radius: 50%; + background: var(--color-success, #4caf50); + /* Drives the breathing glow in the keyframes (matches email's dot). */ + --notif-glow: var(--color-success, #4caf50); + animation: cookbook-notif-pulse 2s ease-in-out infinite; +} +.cookbook-notif-dot.cookbook-notif-error { + background: var(--color-error, #f44); + --notif-glow: var(--color-error, #f44); + position: relative; + left: -2px; + top: -1px; +} +.rail-notify-error { color: var(--color-error, #f44) !important; } +.cookbook-tab-error-dot { + display: inline-block; width: 5px; height: 5px; border-radius: 50%; + background: var(--color-error, #f44); margin-left: 4px; vertical-align: middle; + animation: cookbook-notif-pulse 2s ease-in-out infinite; +} +@keyframes cookbook-notif-pulse { + 0%, 100% { + opacity: 1; + box-shadow: 0 0 0 0 color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 60%, transparent); + } + 50% { + opacity: 0.85; + box-shadow: 0 0 6px 2px color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 55%, transparent); + } +} +.cookbook-clear-btn { + font-size: 10px !important; + padding: 0 8px !important; + height: 22px !important; + border-radius: 6px !important; + border: 1px solid var(--border) !important; + color: color-mix(in srgb, var(--fg) 40%, transparent) !important; + opacity: 1 !important; + background: none !important; +} +.cookbook-clear-btn:hover { + color: var(--red) !important; + border-color: var(--red) !important; + background: color-mix(in srgb, var(--red) 8%, transparent) !important; +} +.cookbook-bulk-bar { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + margin-bottom: 4px; + background: color-mix(in srgb, var(--red) 6%, transparent); + border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); + border-radius: 6px; + font-size: 11px; +} +.cookbook-bulk-bar.hidden { display: none; } +.serve-select-cb { + width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; cursor: pointer; + background: var(--border); transition: background 0.15s; + align-self: center; + position: relative; + top: -2px; +} +#serve-bulk-bar { + position: relative; + top: -6px; +} +#serve-bulk-bar #serve-bulk-cancel { + top: -2px !important; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 28px; + height: 24px; + line-height: 1; +} +#serve-bulk-bar #serve-bulk-cancel svg { + top: 0; +} +.serve-select-cb.selected { background: var(--red); } +.serve-select-cb:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } +#hwfit-cache-select.active { + background: var(--red); + color: #fff; + border-color: var(--red); +} +.cookbook-serve-dirs { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} +.cookbook-serve-dir-pill { + font-size: 10px; + font-family: 'Fira Code', monospace; + padding: 2px 8px; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); + color: var(--fg-muted); + letter-spacing: 0.2px; +} +/* Status pills shown inline in a Serve-tab card title (next to the model + name) when the model has a live serve / download task. Shared base + class so "running" and "downloading" sit on the same row with the + same chrome; only color varies. */ +.cookbook-serve-running-pill, +.cookbook-serve-downloading-pill { + display: inline-block; + margin-left: 6px; + padding: 1px 7px; + border-radius: 10px; + font-size: 9px; + font-weight: 500; + text-transform: lowercase; + letter-spacing: 0.3px; + vertical-align: 2px; + position: relative; + top: 1px; /* nudged down 2px from the old -1px so it sits + flush with the cap-height of the title text */ +} +.cookbook-serve-running-pill { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); +} +.cookbook-serve-downloading-pill { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); + opacity: 0.85; +} +.cookbook-serve-downloading-pill.is-stalled { + /* Stalled downloads stay visible but read as warning, not progress. */ + color: var(--fg-muted, #888); + background: color-mix(in srgb, var(--fg-muted, #888) 10%, transparent); + border-color: color-mix(in srgb, var(--fg-muted, #888) 30%, transparent); + opacity: 1; +} +.cookbook-serve-running-pill.is-clickable { + cursor: pointer; + transition: background 0.12s, border-color 0.12s; +} +.cookbook-serve-running-pill.is-clickable:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); +} + +.cookbook-serve-dir-edit { + font-size: 9px; + color: var(--fg-muted); + opacity: 0.4; + cursor: pointer; + text-decoration: underline; + text-underline-offset: 2px; +} +.cookbook-serve-dir-edit:hover { + opacity: 0.7; + color: var(--accent, var(--red)); +} +.cookbook-gpu-group { + display: flex; + gap: 3px; + margin-top: -4px; +} +.cookbook-gpu-btn { + width: 30px; height: 30px; + font-size: 12px; + font-family: 'Fira Code', monospace; + border: 1px solid var(--border); + border-radius: 4px; + background: none; + color: var(--fg-muted); + cursor: pointer; + opacity: 0.4; + transition: all 0.15s; + padding: 0; + display: flex; + align-items: center; + justify-content: center; +} +.cookbook-gpu-btn:hover { + opacity: 0.7; + border-color: var(--fg-muted); +} +.cookbook-gpu-btn.active { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent); + border-color: color-mix(in srgb, var(--fg) 55%, transparent); + color: var(--fg); + font-weight: 600; +} +/* Probe annotation states (set by "Free?" button) */ +.cookbook-gpu-btn.gpu-free { + opacity: 1; + border-color: #4ade80; + color: #4ade80; +} +.cookbook-gpu-btn.gpu-busy { + opacity: 0.85; + border-color: color-mix(in srgb, var(--red) 70%, transparent); + color: var(--red); + background: color-mix(in srgb, var(--red) 8%, transparent); +} +.cookbook-gpu-btn.gpu-missing { + opacity: 0.2; + text-decoration: line-through; + cursor: not-allowed; +} +/* "Clear Server" lives in the actions row next to "Probe GPUs". + Inherits .cookbook-btn sizing; this rule just adds the destructive + red tint on hover so the "this kills processes" cue stays. */ +.cookbook-gpu-clear:hover:not(:disabled) { + color: var(--red); + border-color: color-mix(in srgb, var(--red) 60%, var(--border)); + background: color-mix(in srgb, var(--red) 8%, transparent); +} +.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } +.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } +/* GPU probe popup — per-GPU process list with kill buttons */ +.cookbook-gpu-popup { + /* Fixed positioning (relative to viewport) so we never get pulled into + a scrolling/transform stacking context from an ancestor. Z-index has + to clear the cookbook modal (260) and the rest of the high-z UI + layers (themed-confirm and various overlays sit around 9000-10000). */ + position: fixed; + z-index: 10010; + min-width: 280px; + max-width: 420px; + background: var(--panel, #1a1a1a); + border: 1px solid var(--border); + border-radius: 6px; + box-shadow: 0 8px 24px rgba(0,0,0,0.4); + font-family: 'Fira Code', monospace; + font-size: 11px; + color: var(--fg); +} +.cookbook-gpu-popup-head { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + border-bottom: 1px solid var(--border); + font-weight: 600; +} +.cookbook-gpu-popup-stats { + flex: 1; + font-weight: 400; + color: var(--fg-muted); + font-size: 10px; +} +.cookbook-gpu-popup-close { + background: none; + border: none; + color: var(--fg-muted); + cursor: pointer; + font-size: 14px; + padding: 0 4px; +} +.cookbook-gpu-popup-close:hover { color: var(--fg); } +.cookbook-gpu-popup-body { + padding: 4px 0; + max-height: 320px; + overflow-y: auto; +} +.cookbook-gpu-popup-empty { + padding: 10px; + color: var(--fg-muted); + font-style: italic; +} +.cookbook-gpu-proc { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 10px; +} +.cookbook-gpu-proc:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); } +.cookbook-gpu-proc-info { + flex: 1; + display: flex; + gap: 8px; + align-items: center; + min-width: 0; +} +.cookbook-gpu-proc-pid { + color: var(--fg-muted); + width: 56px; + flex-shrink: 0; +} +.cookbook-gpu-proc-name { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.cookbook-gpu-proc-mem { + color: var(--fg-muted); + flex-shrink: 0; +} +.cookbook-gpu-proc-actions { + display: flex; + gap: 4px; + flex-shrink: 0; +} +.cookbook-gpu-kill { + border: 1px solid color-mix(in srgb, var(--red) 60%, transparent); + background: none; + color: var(--red); + border-radius: 3px; + padding: 2px 8px; + cursor: pointer; + font-family: inherit; + font-size: 10px; +} +.cookbook-gpu-kill[data-sig="KILL"] { + background: color-mix(in srgb, var(--red) 12%, transparent); +} +.cookbook-gpu-kill:hover:not(:disabled) { + background: color-mix(in srgb, var(--red) 20%, transparent); +} +.cookbook-gpu-kill:disabled { opacity: 0.4; cursor: wait; } +.cookbook-serve-title { + display: flex; + align-items: center; + gap: 4px; + min-width: 0; +} +.cookbook-serve-title-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.cookbook-hf-link { + font-size: 9px; + text-decoration: none; + color: var(--fg-muted); + opacity: 0.5; + padding: 1px 5px; + border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); + border-radius: 3px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + vertical-align: 1px; + letter-spacing: 0.3px; + font-weight: 600; + flex-shrink: 0; +} +.cookbook-hf-link:hover { + opacity: 0.8; + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); +} +/* In the Trending models list the HF pill is the row's main affordance — + tint it accent so it stands out next to the model name. */ +#cookbook-hf-latest-list .cookbook-hf-link { + color: var(--accent, var(--red)); + opacity: 0.85; + border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); +} + +/* Running tab sections */ +.cookbook-saved-section, +.cookbook-serve-section, +.cookbook-dl-section { + border: 1px solid var(--border); + border-radius: 6px; + margin-bottom: 6px; + overflow: hidden; +} +/* Divider between server-group sections in the Active tab so the + transition between e.g. local + remote-host server blocks reads + visually instead of running into each other. */ +.cookbook-group[data-backend-group="Running"] > [class*="cookbook-server-section-"] + [class*="cookbook-server-section-"] { + margin-top: 14px; + padding-top: 14px; + border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); +} +.cookbook-section-header { + display: flex; + align-items: center; + gap: 6px; + padding: 7px 10px; + cursor: pointer; + user-select: none; + /* Persistent surface + border so it reads as a clickable bar instead of + blending into the panel background. */ + background: color-mix(in srgb, var(--fg) 5%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + transition: background 0.15s, border-color 0.15s; +} +.cookbook-section-header:hover { + background: color-mix(in srgb, var(--fg) 9%, transparent); + border-color: color-mix(in srgb, var(--fg) 22%, transparent); +} +.cookbook-section-header.has-server-color { + color: var(--fg); + border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 45%, var(--border)); + background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 4%, var(--bg)); + box-shadow: inset 6px 0 0 var(--cookbook-server-accent, var(--cookbook-server-color)); +} +.cookbook-section-header.has-server-color:hover { + background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 7%, var(--bg)); + border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 60%, var(--border)); +} +.cookbook-section-header.has-server-color .cookbook-section-title, +.cookbook-section-header.has-server-color .cookbook-section-chevron { + color: var(--cookbook-server-accent, var(--cookbook-server-color)); +} +.cookbook-section-header.has-server-color .cookbook-srv-status.ok { + background: var(--cookbook-server-accent, var(--cookbook-server-color)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 18%, transparent), + 0 0 8px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 55%, transparent); +} +.cookbook-section-header .cookbook-clear-btn { + margin-left: 0; + position: relative; + top: -3px; +} +@media (max-width: 768px) { + .cookbook-section-header .cookbook-clear-btn { + top: -3px; + } + .cookbook-task-check { + top: 4px; + } +} +/* "Stop all" sits just left of "Clear finished"; it carries the auto margin so + the pair is pushed together to the right of the section title. */ +.cookbook-section-header .cookbook-stop-all-btn { + margin-left: auto; + margin-right: 6px; + position: relative; + top: -3px; +} +.cookbook-stop-all-btn { + font-size: 10px !important; + padding: 0 8px !important; + height: 22px !important; + border-radius: 6px !important; + border: 1px solid var(--border) !important; + color: color-mix(in srgb, var(--fg) 40%, transparent) !important; + opacity: 1 !important; + background: none !important; +} +.cookbook-stop-all-btn:hover { + color: var(--warn, #f0ad4e) !important; + border-color: var(--warn, #f0ad4e) !important; + background: color-mix(in srgb, var(--warn, #f0ad4e) 8%, transparent) !important; +} +.cookbook-section-chevron { + flex-shrink: 0; + opacity: 0.6; + transition: transform 0.15s ease, opacity 0.15s ease; +} +.cookbook-section-body { + padding: 0; +} +.cookbook-saved-toggle-header { + font-size: 10px; + font-weight: 600; + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: 0.3px; + margin-bottom: 4px; +} +.cookbook-saved-toggle:hover { color: var(--fg); } +.cookbook-saved-item { + margin: 2px 0; + border-radius: 4px; + font-size: 11px; +} +.cookbook-saved-header { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 6px; + border-radius: 4px; + transition: background 0.08s; +} +.cookbook-saved-header:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.cookbook-saved-item .hwfit-serve-panel { + margin: 4px 0 4px 6px; +} +.cookbook-saved-host { + font-size: 10px; + color: var(--fg-muted); + font-family: 'Fira Code', monospace; +} +.cookbook-saved-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.cookbook-saved-launch { + font-size: 10px; + padding: 2px 8px; +} +.cookbook-saved-del { + background: none; + border: none; + color: var(--fg-muted); + opacity: 0.3; + cursor: pointer; + font-size: 11px; + padding: 0 2px; +} +.cookbook-saved-del:hover { + opacity: 1; + color: var(--color-error, var(--warn)); +} + +/* Serve config panel */ +.hwfit-serve-panel { + margin-top: 6px; + padding: 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + flex: 1; + display: flex; + flex-direction: column; + position: relative; +} +.cookbook-serve-slots { + display: flex; + gap: 3px; + justify-content: flex-end; + margin-bottom: 4px; +} +/* When the Save split sits inside Row 1 (next to GPUs), align it with the + input baseline (the row's grid cells stretch top-down; without this the + Save buttons sit above the GPU button group). */ +.hwfit-serve-row .cookbook-serve-slots { + align-self: end; + margin-bottom: 4px; +} +.cookbook-slot-btn { + min-width: 22px; height: 28px; + padding: 0 6px; + font-size: 10px; font-weight: 600; + border: 1px solid var(--border); + border-radius: 4px; + background: none; + white-space: nowrap; + max-width: 80px; + overflow: hidden; + text-overflow: ellipsis; + color: var(--fg-muted); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.5; + transition: all 0.15s; +} +.cookbook-slot-btn:hover { opacity: 0.9; border-color: var(--fg-muted); } +/* Saved-configs split button: "Save" + dropdown-arrow joined into one control, + but each segment keeps its own style — Save uses the filled accent + "Done"-button look; the arrow stays a subtle outlined menu trigger. */ +.cookbook-saved-split { gap: 0; } +.cookbook-saved-save, +.cookbook-saved-arrow { max-width: none; } +.cookbook-saved-save { + padding: 0 10px; + gap: 4px; + background: var(--bg); + color: var(--fg); + border-color: var(--border); + font-weight: 600; + opacity: 1; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.cookbook-saved-save:hover { + background: var(--border); + border-color: var(--accent); + opacity: 1; +} +.cookbook-saved-arrow { + padding: 0 6px; + background: var(--bg); + color: var(--fg); + border-color: var(--border); + opacity: 1; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: none; +} +.cookbook-saved-menu .cookbook-saved-favorite { + border-left: 3px solid var(--red); + background: color-mix(in srgb, var(--red) 4%, transparent); +} +.cookbook-saved-fav-btn { + width: 20px; + height: 20px; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + background: none; + color: var(--fg-muted); + opacity: 0.55; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +.cookbook-saved-fav-btn:hover, +.cookbook-saved-fav-btn.active { + color: var(--red); + opacity: 1; + background: color-mix(in srgb, var(--red) 10%, transparent); +} +.cookbook-saved-fav-badge { + flex-shrink: 0; + background: transparent; + padding: 0; +} +.cookbook-serve-favorite-model { + border-left-color: var(--red) !important; + background: color-mix(in srgb, var(--red) 4%, transparent); +} +.cookbook-serve-fav-badge { + display: inline-flex; + align-items: center; + vertical-align: 1px; + margin-left: 5px; + margin-right: 2px; + background: transparent; + padding: 0; +} +.cookbook-slot-saved { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); } +.cookbook-slot-saved:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); } +.cookbook-slot-btn.active { opacity: 1; background: var(--accent); color: #fff; border-color: var(--accent); +} +.cookbook-slot-wrap { + position: relative; + display: inline-flex; +} +.cookbook-slot-del { + position: absolute; + top: -5px; + right: -5px; + width: 14px; + height: 14px; + padding: 0; + border: 1px solid var(--border); + border-radius: 50%; + background: var(--bg, #1a1a1a); + color: var(--fg-muted); + font-size: 12px; + line-height: 1; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s, color 0.15s, border-color 0.15s; + display: flex; + align-items: center; + justify-content: center; +} +.cookbook-slot-del:hover { + color: #f44; + border-color: #f44; +} +.cookbook-card-backend { display: none; } +/* Dependencies row — aligned tags */ +.cookbook-dep-row { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel); +} +/* Let the deps list fill the available window height instead of being + capped at the default .doclib-grid 400-px box. */ +[data-backend-group="Dependencies"] #cookbook-deps-list { + flex: 1; + max-height: none; +} + +/* Settings tab: stack the HF token + Servers cards as distinct blocks + (matches the Download tab's block-per-section layout). */ +.cookbook-settings-stack { + display: flex; + flex-direction: column; + gap: 10px; + flex: 1; + /* Scroll the whole settings panel so the Servers card can grow to hold every + server (it used to be a cramped internal scroll box that clipped them). */ + overflow-y: auto; + min-height: 0; +} +.cookbook-settings-stack.hidden { display: none; } +.cookbook-dep-row.cookbook-dep-blocked { opacity: 0.4; } +.cookbook-dep-info { flex: 1; min-width: 0; } +.cookbook-dep-section { + display: flex; + align-items: baseline; + gap: 8px; + margin: 12px 2px 4px; +} +.cookbook-dep-section-title { + font-size: 11px; + font-weight: 600; + letter-spacing: 0.03em; +} +.cookbook-dep-section-note { + font-size: 10px; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.cookbook-dep-subgroup { + margin: 6px 0 8px 12px; + padding: 7px 8px 8px; + border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); + background: color-mix(in srgb, var(--fg) 3%, transparent); + border-radius: 0 6px 6px 0; + display: flex; + flex-direction: column; + gap: 6px; +} +.cookbook-dep-subgroup-title { + display: flex; + align-items: baseline; + gap: 7px; + margin: 0 1px 1px; + font-size: 10.5px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 78%, transparent); +} +.cookbook-dep-subgroup-title em { + font-size: 9.5px; + font-style: normal; + font-weight: 500; + color: color-mix(in srgb, var(--fg) 48%, transparent); +} +.cookbook-dep-tag { + font-size: 9px; + padding: 0 8px; + border-radius: 4px; + white-space: nowrap; + text-align: center; + min-width: 62px; + box-sizing: border-box; + line-height: 1; + /* Explicit height so the tags (Install / Installed) are the EXACT + same height as the sibling tags — Firefox gives buttons a taller + native box otherwise. Mirrors the server-row tag rule (height:24px). */ + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; +} +.cookbook-dep-target { + border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.cookbook-dep-cat { + background: color-mix(in srgb, var(--fg) 10%, transparent); + color: color-mix(in srgb, var(--fg) 60%, transparent); +} +/* Rebuild tag — same look as the LLM category tag, sits to its left. */ +.cookbook-dep-rebuild { + background: color-mix(in srgb, var(--fg) 10%, transparent); + color: color-mix(in srgb, var(--fg) 75%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent); + cursor: pointer; + font-family: inherit; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + position: relative; + top: -2px; +} +.cookbook-dep-reinstall { + top: -3px; +} +.cookbook-dep-rebuild:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); +} +.cookbook-dep-installed { + background: color-mix(in srgb, var(--green, #50fa7b) 18%, transparent); + color: var(--green, #50fa7b); + border: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); + /* Match Install + Installed ▾ so all three variants align in mixed rows. */ + width: 87.7px; + min-width: 87.7px; + padding: 0 10px; + box-sizing: border-box; +} +.cookbook-dep-na { + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); + cursor: help; + /* Match other dep tag widths so N/A rows line up with Install / Installed. */ + min-width: 75.85px; + padding: 0 10px; + box-sizing: border-box; +} +.cookbook-dep-install { + background: var(--accent, var(--red)); + color: #fff; + border: none; + cursor: pointer; + font-family: inherit; + font-weight: 500; + position: relative; + top: -3px; + width: 87.7px; + min-width: 87.7px; + padding: 0 10px; + /* Strip the native button box so it's the same height as the sibling tags + (Firefox renders taller otherwise); height comes from .cookbook-dep-tag. */ + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; +} +.cookbook-dep-build-deps { + width: auto; + min-width: 87.7px; + max-width: 100%; + white-space: normal; + overflow-wrap: anywhere; + line-height: 1.15; + text-align: center; + height: auto; + min-height: 24px; +} +/* The install state is the terminal control in a dependency row. Keep it on + the far edge even when optional rebuild/category controls are present. */ +.cookbook-dep-row > .cookbook-dep-installed, +.cookbook-dep-row > .cookbook-dep-na, +.cookbook-dep-row > .cookbook-dep-install:not(.cookbook-dep-build-deps) { + margin-left: auto; +} +/* Center the "?" glyph inside the help chip. Without text-align it sits 0.5px + left of true center because of the character's natural baseline offset. */ +.hwfit-help-chip { + text-align: center; + padding-left: 0.5px; +} +.cookbook-dep-install:hover { opacity: 0.85; } +/* Installed split button: "Installed" label + separator + ▾ caret; clicking it + opens the actions menu (Update). Replaces the old ⋮ button. */ +.cookbook-dep-installed-btn { + padding: 0; + width: 87.7px; + min-width: 87.7px; + cursor: pointer; + font-family: inherit; + overflow: hidden; + position: relative; + top: -3px; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; +} +.cookbook-dep-installed-btn .cookbook-dep-caret { + display: inline-flex; + align-items: center; + align-self: stretch; + padding: 0 5px; + font-size: 14px; + line-height: 1; + position: relative; + top: 1px; + left: 4px; + opacity: 0.85; + border-left: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); +} +.hwfit-serve-row { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 6px; + margin-bottom: 6px; +} +/* Tighter min on the core-knobs row only so Dtype / TP / GPU Mem / Max + Seqs fit on one line without crushing Row 1's GPU button strip or + the Backend picker. */ +.hwfit-serve-row-core { + grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); +} +/* Save / saved-config split aligned to the right end of Row 1 and + nudged up 4px so it sits flush with the rest of the row. */ +.hwfit-serve-row .cookbook-serve-slots { + justify-self: end; + position: relative; + top: -4px; +} +/* The Settings label wraps the Save split — right-align its title text + so the word "Settings" sits above the right-aligned Save button + instead of floating off to the left of its grid cell. 8px right + padding now (16px → 8px) per follow-up tweak, brings the title back + over the actual Save button instead of overshooting it. */ +.hwfit-serve-row label:has(> .cookbook-serve-slots) { + grid-column: -2 / -1; + justify-self: end; + text-align: right; + padding-right: 0; +} +.hwfit-serve-row label:has(> .cookbook-serve-slots) > span { + display: inline-block; + position: relative; + left: -33px; +} +.hwfit-serve-topline { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; + margin: 0 0 6px; +} +.hwfit-serve-topline .hwfit-serve-runtime-text { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hwfit-serve-preset-row { + display: flex; + justify-content: flex-end; + margin: 0; + flex: 0 0 auto; + position: relative; + top: -6px; +} +.hwfit-serve-row label { + font-size: 10px; + color: var(--fg-muted); + white-space: nowrap; + letter-spacing: 0.3px; +} +.hwfit-serve-row-core .hwfit-context-label { + grid-column: span 2; + width: auto; + min-width: 0; + max-width: none; + justify-self: start; +} +.hwfit-context-control { + display: block; + position: relative; + align-items: center; + margin-top: 2px; + width: 100px; +} +.hwfit-context-control .hwfit-sf[data-field="ctx"] { + min-width: 0; + width: 100%; + padding-right: 40px; +} +.hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]) { + position: relative; + left: -66px; +} +.hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { + position: relative; + left: -64px; +} +.hwfit-serve-row-core .hwfit-gpus-label { + position: relative; + left: -70px; +} +.hwfit-context-calc-btn { + position: absolute; + right: 3px; + top: -4px; + width: 34px; + height: 28px; + min-width: 34px; + padding: 0; + font-size: 10px; + line-height: 1; + display: inline-flex; + align-items: center; + justify-content: center; + text-align: center; + overflow: hidden; + white-space: nowrap; +} +.hwfit-context-calc-btn .spinner-whirlpool { + width: 12px !important; + height: 12px !important; + margin: 0 auto !important; +} +.hwfit-context-calc-btn .ai-spinner-whirlpool { + width: 12px !important; + height: 12px !important; +} +@media (min-width: 769px) { + [data-llama-mode-toggle].mode-toggle.mode-toggle-three .mode-toggle-btn > span { + top: -6px !important; + } + .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { + font-size: 0; + } + .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { + content: 'Unif'; + font-size: 11px; + } +} +@media (max-width: 768px) { + .hwfit-serve-row-core .hwfit-context-label { + display: flex; + flex-direction: column; + align-items: flex-start; + } + .hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]), + .hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { + left: 0; + } + details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_env_preset"]), + details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="extra_env"]) { + grid-column: 1 / -1 !important; + } + .hwfit-context-control { + display: inline-flex; + align-items: center; + gap: 4px; + width: auto; + } + .hwfit-context-control .hwfit-sf[data-field="ctx"] { + width: 100px; + padding-right: 6px; + } + .hwfit-context-calc-btn { + position: relative; + top: -2px; + left: 2px; + right: auto; + flex: 0 0 34px; + width: 34px; + min-width: 34px; + } +} +.hwfit-auto-ctx-note { + display: block; + margin-top: 3px; + font-size: 9px; + opacity: 0.6; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* Ctx slider — ported from origin/main. Sits in the Scan/Download toolbar + next to the quant dropdown. Drives _ctxValue() in cookbook-hwfit.js. */ +.hwfit-ctx-control { + height: 28px; + min-width: 134px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + gap: 5px; + padding: 0 7px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + font-size: 11px; /* slightly smaller than .cookbook-field-input so Context reads quieter next to Engine/Quant */ +} +.hwfit-ctx-control span { + /* Match Quant/Engine select label style: no uppercase, no letter-spacing. */ + text-transform: none; + letter-spacing: 0; + opacity: 0.9; +} +.hwfit-ctx-control > span:first-child { + font-size: 9.5px; +} +/* Editor-style slider (same look as the gallery editor sliders): thin pill + rail that fattens on interaction, circular red thumb that grows on hover. */ +.hwfit-ctx-control input[type="range"] { + width: 64px; + min-width: 64px; + height: 4px; + padding: 0; + border: 0; + -webkit-appearance: none; + appearance: none; + /* Hard-coded grey so the rail is GUARANTEED visible regardless of theme — + every theme-derived color we tried (--fg-muted, --border, accent-bg mix) + kept blending into the panel background on at least one theme. */ + background: rgba(150, 150, 150, 0.65); + border-radius: 999px; + accent-color: var(--red); + cursor: pointer; + transition: height 0.15s ease, background 0.15s ease; +} +.hwfit-ctx-control input[type="range"]:hover, +.hwfit-ctx-control input[type="range"]:focus, +.hwfit-ctx-control input[type="range"]:active { + background: var(--fg); +} +.hwfit-ctx-control input[type="range"]:hover, +.hwfit-ctx-control input[type="range"]:focus, +.hwfit-ctx-control input[type="range"]:active { + height: 10px; +} +.hwfit-ctx-control input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--red); + border: none; + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.hwfit-ctx-control input[type="range"]::-moz-range-thumb { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--red); + border: none; + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.hwfit-ctx-control input[type="range"]:hover::-webkit-slider-thumb, +.hwfit-ctx-control input[type="range"]:focus::-webkit-slider-thumb, +.hwfit-ctx-control input[type="range"]:active::-webkit-slider-thumb { + width: 18px; + height: 18px; +} +.hwfit-ctx-control input[type="range"]:hover::-moz-range-thumb, +.hwfit-ctx-control input[type="range"]:focus::-moz-range-thumb, +.hwfit-ctx-control input[type="range"]:active::-moz-range-thumb { + width: 18px; + height: 18px; +} +.hwfit-ctx-control output { + min-width: 28px; + text-align: right; + color: var(--fg); + font-weight: 600; +} +.hwfit-sf { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--fg); + font-family: inherit; + font-size: 12px; + padding: 0 6px; + height: 28px; + box-sizing: border-box; +} +.hwfit-sf[data-field="backend"], +.hwfit-sf[data-field="dtype"], +.hwfit-sf[data-field="tp"] { + height: 32px; + box-sizing: border-box; + width: 100%; +} +.hwfit-sf:focus { + border-color: var(--accent, var(--red)); + outline: none; +} +.hwfit-sf[type="text"] { width: 100%; } +.hwfit-sf.hwfit-sf-wide { width: 100%; } +.hwfit-serve-checks { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 6px; +} +.hwfit-sf-cb { + display: flex; + flex-direction: row; + align-items: center; + gap: 6px; + font-size: 11px; + color: var(--fg-muted); + cursor: pointer; + padding: 3px 0; +} .hwfit-sf-cb:hover { color: var(--fg); } /* Speculative method + tokens controls — render inline beside the checkbox */ .hwfit-spec-group .hwfit-spec-method, @@ -2697,3315 +4322,3 @@ font-size: 11px; } } - -/* Snapped/narrow Settings window: move the tab rail to the top. Viewport media - alone misses desktop right-half snapping, where the modal is narrow but the - browser window is not. */ -@container settings-modal (max-width: 620px) { - .settings-layout { - flex-direction: column; - } - .settings-sidebar { - width: auto; - max-height: none; - flex-direction: row; - border-right: none; - border-bottom: 1px solid var(--border); - overflow-x: auto; - overflow-y: hidden; - padding: 6px; - scrollbar-width: none; - } - .settings-sidebar::-webkit-scrollbar { - display: none; - } - .settings-sidebar-divider, - .settings-sidebar-label { - display: none; - } - .settings-nav-item { - flex: 0 0 auto; - padding: 6px 10px; - font-size: 11px; - } - .settings-panels { - padding: 12px 14px; - } -} - -/* ── Entrance Animations ── */ - -/* Welcome name — left-to-right wipe with a touch of horizontal stretch. - Fires on initial render via the .welcome-name rule; restarts on Nobody⇄ - Odysseus toggle via JS reflow trick. */ -.welcome-name { - transform-origin: left center; - animation: welcome-name-reveal 0.55s cubic-bezier(0.34, 1.32, 0.55, 1) both; -} -/* Hold the welcome-screen entrance animations until the app has finished its - initial load (fonts loaded + layout settled). Running them mid-load made the - splash jump/flicker as the page reflowed ("haywire"). Until body.welcome-ready - is set by JS, the splash sits in its final, static state (no flash, since the - non-animated state IS the resting state); adding the class then plays the - entrance once, cleanly. */ -body:not(.welcome-ready) #welcome-screen, -body:not(.welcome-ready) .welcome-name { - animation: none !important; -} -/* Hold the splash invisible (the entrance's start state) until ready, so when - the animation is released it fades/wipes in smoothly instead of flashing - from fully-visible to the animation's hidden first frame. */ -body:not(.welcome-ready) #welcome-screen { - opacity: 0; -} -@keyframes welcome-name-reveal { - from { - clip-path: inset(0 100% 0 0); - transform: scaleX(0.96); - opacity: 0.5; - } - to { - clip-path: inset(0 0 0 0); - transform: scaleX(1); - opacity: 1; - } -} - -@keyframes welcome-enter { - from { - opacity: 0; - transform: translate(-50%, -45%); - } - to { - opacity: 1; - transform: translate(-50%, -50%); - } -} - -@keyframes msg-enter { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes picker-roll-up { - from { - opacity: 0; - transform: scaleY(0.4) translateY(8px); - } - to { - opacity: 1; - transform: scaleY(1) translateY(0); - } -} -@keyframes picker-roll-down { - from { - opacity: 1; - transform: scaleY(1) translateY(0); - } - to { - opacity: 0; - transform: scaleY(0.4) translateY(8px); - } -} -@keyframes model-picker-refresh-spin { - to { transform: rotate(360deg); } -} - -@keyframes modal-enter { - from { - opacity: 0; - transform: scale(0.95) translateY(8px); - } - to { - opacity: 1; - transform: scale(1) translateY(0); - } -} -@keyframes modal-exit { - from { - opacity: 1; - transform: scale(1) translateY(0); - } - to { - opacity: 0; - transform: scale(0.97) translateY(6px); - } -} -.modal-content.modal-closing { - animation: modal-exit 0.18s ease-in both; -} - -#cookbook-modal .modal-content.cookbook-modal-entering { - animation: cookbook-modal-enter 0.28s cubic-bezier(0.22, 1.35, 0.36, 1) both; -} -/* Mobile: real bottom-sheet slide-up — the existing 12px translate is - too subtle on a phone, the modal effectively just appeared. */ -@media (max-width: 768px) { - #cookbook-modal .modal-content.cookbook-modal-entering { - animation: cookbook-modal-enter-mobile 0.32s cubic-bezier(0.22, 1, 0.36, 1) both; - } -} -#cookbook-modal .modal-content:not(.cookbook-modal-entering):not(.modal-closing) { - animation: none; -} -#cookbook-modal .modal-content.modal-closing { - animation: modal-exit 0.18s cubic-bezier(0.4, 0, 1, 1) both; -} - -/* Per-token streaming fade — new words materialize */ -.token-new { - animation: token-fade 0.4s ease-out both; -} - -@keyframes token-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -/* Disable entrance animations when loading chat history (bulk render) */ -.chat-history.no-animate .msg { - animation: none; -} - -/* Prefer reduced motion */ -@media (prefers-reduced-motion: reduce) { - .msg, - .modal-content, - #welcome-screen, - .toast, - .token-new { - animation: none !important; - transition: none !important; - } -} - -@media (min-width: 769px) { - #tool-tasks-btn #assistant-notif-dot.sidebar-notif-dot, - #tool-tasks-btn.task-failure-pending::after, - #tool-tasks-btn.task-completion-pending::after { - transform: translateX(-2px); - } -} -.tasks-modal-content { max-width: 600px; width: min(600px, 92vw); background: var(--bg); font-size: 12px; } -.task-completed-preview-row { - cursor: default; - margin-bottom: 6px; - padding: 8px 10px 10px; - max-height: none; - overflow: visible; -} -.task-completed-preview-head { - display: flex; - align-items: center; - gap: 7px; - min-height: 20px; - font-size: 11px; -} -.task-completed-chat-preview { - padding: 0 0 4px; -} -.task-completed-chat-preview .doclib-chat-preview-messages { - max-height: 220px; - margin-top: 7px; -} -.task-completed-chat-preview .doclib-chat-bubble { - max-width: 92%; -} -.task-completed-chat-preview .doclib-chat-preview-actions { - top: 2px; - margin-top: 4px; -} -.task-completed-preview-row .task-log-time { - margin-left: 8px; -} - -/* Tasks tabs reuse the .memory-tab look. The Brain window's tab bar is - full-bleed (its underline spans the whole modal width). The Tasks bar is a - direct child of .modal-content (padding:10px), so cancel that padding with - negative side margins to span edge-to-edge, then re-inset the tabs by 10px - so they line up with the rest of the modal content — matching the Brain bar. */ -.tasks-modal-content .tasks-tabs { - margin: -2px -10px 8px; - padding: 0 10px; -} - -/* Activity log — compact by default. Click the row to expand body+actions. */ -.task-log-row { - --cat-hue: 220; - border: 1px solid var(--border); - border-radius: 8px; - padding: 4px 10px; - margin-bottom: 3px; - background: color-mix(in srgb, var(--fg) 2%, transparent); - position: relative; - cursor: pointer; - transition: padding .12s ease; -} -.task-log-row:hover { - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.task-log-row.task-log-row-error { - border-color: color-mix(in srgb, var(--red, #f87171) 35%, var(--border)); - background: color-mix(in srgb, var(--red, #f87171) 4%, transparent); -} -.task-log-row.task-log-row-error:hover { - background: color-mix(in srgb, var(--red, #f87171) 7%, transparent); -} -.task-log-row.expanded { padding: 8px 10px 6px; } -.task-log-row-head { - display: flex; - align-items: center; - gap: 8px; - font-size: 11px; - margin-bottom: 0; -} -.task-log-row.expanded .task-log-row-head { margin-bottom: 4px; } -/* Collapsed: body + footer hidden. Expanded: visible. */ -.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-body, -.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-actions, -.task-log-row:not(.expanded):not(.is-skipped) .task-log-prompt { - display: none; -} -.task-log-name { - font-weight: 600; - /* Pull saturation from the per-row hue but mix with the foreground so the - title still reads in dark mode. Lightness stays adaptive. */ - color: hsl(var(--cat-hue) 60% 60%); -} -.task-log-failed-tag { - color: var(--red, #f87171); - font-size: 10px; - font-weight: 700; - line-height: 1; - white-space: nowrap; - margin-left: -2px; -} -.task-log-task-icon { - display: inline-flex; - align-items: center; - flex: 0 0 auto; - margin-right: -3px; -} -.task-log-row.task-log-row-error .task-log-name, -.task-log-row.task-log-row-error .task-log-task-icon { - color: var(--red, #f87171); -} -.task-log-task-icon svg { - top: 0 !important; - opacity: 0.46 !important; -} -.task-log-row-head .task-ai-mark { - top: 0; - margin-left: -4px; -} -.task-log-repeat { - font-size: 10px; - font-weight: 500; - line-height: 1; - color: color-mix(in srgb, var(--fg) 62%, transparent); - border-radius: 999px; - padding: 1px 0; - white-space: nowrap; -} -@media (prefers-color-scheme: light) { - .task-log-name { color: hsl(var(--cat-hue) 65% 38%); } -} -/* Per-account prefix in fan-out results — e.g. "[Default] No recent emails" - becomes a compact accent chip + plain message. Makes multi-account activity - rows readable instead of a bracket soup. */ -/* Running / queued status line in the Activity tab — whirlpool + label. */ -.task-log-running { - display: inline-flex; - align-items: center; - gap: 0; - font-size: 11px; - opacity: 0.75; -} -.task-log-running-label { font-style: normal; } - -/* New right-side placement: "Running " sits where the timestamp - normally would, on the head row's right edge. */ -.task-log-running-inline { - display: inline-flex; - align-items: center; - gap: 0; - font-size: 11px; - opacity: 0.75; -} -.task-log-force-run { - appearance: none; - box-sizing: border-box; - border: 0; - background: color-mix(in srgb, var(--fg) 7%, transparent); - box-shadow: none; - color: inherit; - opacity: .82; - margin-left: 7px; - padding: 1px 6px 1px 4px; - height: 18px; - min-height: 16px; - max-height: 18px; - border-radius: 999px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 3px; - font-family: inherit; - font-size: 10px; - line-height: 1; - cursor: pointer; - position: relative; - top: -3px; -} -.task-log-force-run svg, -.task-log-stop svg { - display: block; - flex-shrink: 0; - transform: translateY(1px); -} -.task-log-force-run:hover { - opacity: 1; - background: color-mix(in srgb, var(--green, #50fa7b) 16%, transparent); - color: var(--green, #50fa7b); -} -.task-log-stop { - border: 0; - background: transparent; - color: inherit; - opacity: .72; - padding: 0; - margin-left: 6px; - width: 12px; - height: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - position: relative; - top: -3px; -} -.task-log-stop:hover { - opacity: 1; - color: var(--red, #f87171); -} - -/* Slim single-line row for skipped (noop) runs — body/actions stripped, font - shrunk, opacity dropped. Distinguishes "task ran but had nothing to do" - from a "real" entry without flooding the feed visually. */ -.task-log-row.is-skipped { - padding: 4px 8px; - opacity: 0.45; - font-size: 11px; - background: transparent; -} -.task-log-row.is-skipped .task-log-row-head { padding: 0; } -.task-log-row.is-skipped .task-log-name { font-weight: 500; } -.task-log-row.is-skipped .task-log-skipped-reason { - flex: 1 1 auto; - margin-left: 6px; - font-style: italic; - opacity: 0.85; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.task-log-row.is-skipped:hover { opacity: 0.7; } - -.task-log-account-tag { - display: inline-block; - font-size: 10px; - font-weight: 600; - letter-spacing: 0.02em; - padding: 1px 7px; - margin-right: 4px; - border-radius: 10px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); - vertical-align: 1px; -} -.task-log-status { - width: 7px; - height: 7px; - border-radius: 50%; - flex-shrink: 0; - background: color-mix(in srgb, var(--fg) 30%, transparent); -} -.task-log-status-ok { background: #4ade80; box-shadow: 0 0 6px #4ade80, 0 0 3px #4ade80; } -.task-log-status-error { background: var(--red, #f87171); box-shadow: 0 0 6px var(--red, #f87171), 0 0 3px var(--red, #f87171); } -.task-log-status-info { background: color-mix(in srgb, var(--fg) 25%, transparent); } -.task-log-status-queued { background: #fbbf24; box-shadow: 0 0 0 2px color-mix(in srgb, #fbbf24 30%, transparent); } -.task-log-status-running { background: #60a5fa; animation: task-log-pulse 1.4s ease-in-out infinite; } -.task-log-status-skipped { background: color-mix(in srgb, var(--fg) 20%, transparent); } -.task-log-status-aborted { background: color-mix(in srgb, var(--fg) 30%, transparent); border: 1px dashed color-mix(in srgb, var(--fg) 50%, transparent); } -.task-log-row-body { - font-size: 12px; - line-height: 1.5; - color: color-mix(in srgb, var(--fg) 85%, transparent); - overflow-wrap: anywhere; - word-break: break-word; -} -.task-log-row-body p { margin: 0 0 6px 0; } -.task-log-row-body p:last-child { margin-bottom: 0; } -.task-log-row-body pre { - margin: 4px 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - border-radius: 4px; - white-space: pre-wrap; - word-break: break-word; - font-size: 11px; - max-height: 180px; - overflow: auto; -} -.task-log-row-body code { - font-size: 11px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - padding: 1px 4px; - border-radius: 3px; -} -.task-log-row-body ul, .task-log-row-body ol { - margin: 4px 0; - padding-left: 20px; -} -/* Markdown headings inside a task result — without these, ## headings - render at browser-default huge sizes and blow out the cramped row. */ -.task-log-row-body h1, -.task-log-row-body h2, -.task-log-row-body h3, -.task-log-row-body h4 { - margin: 10px 0 4px; - line-height: 1.3; - font-weight: 650; - color: var(--fg); -} -.task-log-row-body h1 { font-size: 14px; } -.task-log-row-body h2 { font-size: 13px; } -.task-log-row-body h3, -.task-log-row-body h4 { font-size: 12px; opacity: 0.9; } -.task-log-row-body h1:first-child, -.task-log-row-body h2:first-child, -.task-log-row-body h3:first-child { margin-top: 0; } -.task-log-row-body blockquote { - margin: 4px 0; - padding: 2px 0 2px 10px; - border-left: 2px solid color-mix(in srgb, var(--fg) 20%, transparent); - opacity: 0.85; -} -.task-log-row-body a { color: var(--accent, #60a5fa); text-decoration: none; } -.task-log-row-body a:hover { text-decoration: underline; } -.task-log-row-body strong { font-weight: 650; color: var(--fg); } -/* Email-summary tables — render compactly instead of default browser ugliness. */ -.task-log-row-body table { - border-collapse: collapse; - width: 100%; - margin: 6px 0; - font-size: 11px; -} -.task-log-row-body th, -.task-log-row-body td { - border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); - padding: 3px 6px; - text-align: left; - vertical-align: top; -} -.task-log-row-body th { - background: color-mix(in srgb, var(--fg) 5%, transparent); - font-weight: 600; -} -.task-log-row-actions { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-top: 4px; -} -.task-log-open-chat, -.task-log-open-report, -.task-log-copy, -.task-log-clear-cache, -.task-log-run-again, -.task-log-force-run-action { - display: inline-flex; - align-items: center; - gap: 3px; - background: none; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 5px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-family: inherit; - font-size: 10px; - padding: 1px 6px; - cursor: pointer; - transition: background .15s, color .15s, border-color .15s; - line-height: 1.4; -} -.task-log-open-chat:hover, -.task-log-open-report:hover, -.task-log-copy:hover, -.task-log-clear-cache:hover, -.task-log-run-again:hover, -.task-log-force-run-action:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--fg) 30%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.task-log-row-actions > .task-log-open-chat, -.task-log-row-actions > .task-log-copy { - margin-left: auto; -} -.task-log-clear-cache svg { - position: relative; - top: 0; -} -/* Activity filter chips — toggle-out model: ON by default (solid), - click to toggle OFF (dimmed + strikethrough) to hide that group. */ -.tasks-af-chip { - font-size: 11px; - padding: 3px 10px; - border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); - border-radius: 12px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: var(--fg); - cursor: pointer; - font-family: inherit; - transition: opacity .12s, background .12s, border-color .12s; -} -.tasks-af-chip:hover { border-color: color-mix(in srgb, var(--fg) 32%, transparent); } -.tasks-af-chip.off { - opacity: 0.4; - text-decoration: line-through; - background: transparent; -} -.tasks-af-chip.active { - border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - opacity: 1; -} -.tasks-af-chip-error { border-color: color-mix(in srgb, var(--red, #f87171) 45%, transparent); } -.tasks-af-chip-error:not(.off) { color: var(--red, #f87171); } - -/* "Open in Deep Research" is now a regular clickable chat link (a - `#research-` markdown anchor in the assistant's message), rendered as - an a.chat-link. Nudge it 4px right so it sits slightly inset. */ -a.chat-link[href^="#research-"] { - margin-left: 4px; -} -.task-log-row.is-long .task-log-row-body { - max-height: 8.5em; - overflow: hidden; - position: relative; - mask-image: linear-gradient(to bottom, #000 70%, transparent); - -webkit-mask-image: linear-gradient(to bottom, #000 70%, transparent); -} -.task-log-row.is-long.expanded .task-log-row-body { - max-height: none; - overflow: visible; - mask-image: none; - -webkit-mask-image: none; -} -.task-log-row-toggle { - margin-top: 6px; - background: none; - border: none; - font-family: inherit; - font-size: 11px; - cursor: pointer; - padding: 2px 0; - color: transparent; /* hide raw "Show more" text */ -} -.task-log-row:not(.expanded) .task-log-row-toggle::before { content: 'Show more'; } -.task-log-row.expanded .task-log-row-toggle::before { content: 'Show less'; } -.task-log-prompt { - margin-top: 6px; - font-size: 11px; -} -.task-log-prompt summary { - cursor: pointer; - opacity: 0.5; - user-select: none; -} -.task-log-prompt summary:hover { opacity: 0.8; } -.task-log-prompt pre { - margin: 4px 0 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-radius: 4px; - white-space: pre-wrap; - word-break: break-word; - font-size: 11px; - opacity: 0.75; - max-height: 200px; - overflow: auto; -} - -/* Tasks is the shared theme reference for procedural cards. Keep the tint - together so Skills cannot drift when theme colors change. Hover and - selection remain owned by the shared card state rules above. */ -.task-card, -.skill-card { - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -.task-card { - border: 1px solid var(--border); - border-radius: 8px; - padding: 7px 8px; - margin-bottom: 0; - min-height: 46px; - box-sizing: border-box; - align-items: center; -} -/* When expanded, height grows to fit the detail panel. */ -.task-card.expanded { align-items: flex-start; } -.task-card .memory-item-title { - position: relative; - top: -2px; -} -/* Always show the per-card ⋮ kebab on task cards (the default opacity:0 + - hover-reveal is unreliable on touch and easy to miss on desktop). - pointer-events / z-index belt-and-braces against any sibling overlay, - margin reset undoes the global `.modal-body button { margin-top:6px }` - that was punting the hit-target down. */ -.task-card .memory-item-actions { - opacity: 0.55; - pointer-events: auto; - position: relative; - z-index: 5; -} -.task-card:hover .memory-item-actions, -.task-card .memory-item-actions:hover { opacity: 1; } -.task-card .memory-item-actions .memory-item-btn { - margin: 0 !important; - pointer-events: auto; - position: relative; - z-index: 5; - width: 28px; - min-width: 28px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; -} -/* Make the SVG inside the kebab transparent to pointer events so the click - always lands on the BUTTON itself (some browsers / nested SVG events lose - the click when hitting the inner glyph). */ -.task-card .memory-item-actions .memory-item-btn svg { pointer-events: none; } -.task-ai-mark { - flex: 0 0 auto; - color: var(--accent, var(--red)); - opacity: 0.78; -} -/* Per-card select checkbox rides up to the title line. The "All" checkbox is - #tasks-select-all (not .memory-select-cb), so it stays put. */ -.task-card .memory-select-cb { position: relative; } -.task-card.task-card-deleting { - opacity: 0.68; - pointer-events: none; - position: relative; -} -.task-card-delete-busy { - position: absolute; - right: 11px; - top: 50%; - transform: translateY(-50%); - display: inline-flex; - align-items: center; - gap: 5px; - height: 22px; - padding: 0 8px; - border-radius: 999px; - color: var(--red); - background: color-mix(in srgb, var(--red) 12%, var(--panel, #111)); - border: 1px solid color-mix(in srgb, var(--red) 24%, transparent); - font-size: 10px; - font-weight: 600; - line-height: 1; - z-index: 7; -} -.task-card-delete-busy-spin { - width: 10px; - height: 10px; - border-radius: 999px; - border: 1.5px solid color-mix(in srgb, currentColor 28%, transparent); - border-top-color: currentColor; - animation: spin 0.8s linear infinite; -} -#tasks-pause-all-btn { position: relative; top: -2px; } -#tasks-pause-all-btn:has(input:disabled) { opacity: 0.4; } -/* Bigger ⋮ dropdown on mobile (its buttons carry inline styles → !important). */ -@media (max-width: 768px) { - .task-card .task-status-badge { - touch-action: manipulation; - -webkit-tap-highlight-color: transparent; - } - .task-card .task-state-badge svg, - .task-card .task-card-run-btn svg { - width: 10px; - height: 10px; - } -} - -.task-card-header { - display: flex; - align-items: center; - gap: 4px; - flex-wrap: wrap; -} - -.task-card-name { - font-size: 12px; - font-weight: 600; -} - -.task-card-schedule { - font-size: 10.5px; - opacity: 0.5; - margin-left: auto; -} - -.task-card-output { - font-size: 10px; - opacity: 0.45; -} - -.task-card-prompt { - font-size: 11px; - opacity: 0.55; - margin: 4px 0; - line-height: 1.4; -} - -.task-card-meta { - font-size: 10.5px; - opacity: 0.45; - margin-bottom: 6px; -} - -.task-card-actions { - display: flex; - gap: 4px; - flex-wrap: wrap; -} - -.task-btn { - font-family: inherit; - font-size: 11px; - padding: 4px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: none; - color: var(--fg); - cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s, background 0.15s; -} -.task-btn:hover { opacity: 1; } -.task-btn:disabled { opacity: 0.3; cursor: not-allowed; } - -.task-btn-primary { - background: var(--red); - color: #fff; - border-color: transparent; - opacity: 1; -} -.task-btn-primary:hover { opacity: 0.85; } - -.task-btn-danger { color: var(--red); border-color: color-mix(in srgb, var(--red) 30%, transparent); } -.task-btn-danger:hover { opacity: 1; border-color: var(--red); } - -.tasks-clock { - font-size: 10px; - opacity: 0.35; - text-align: center; - padding: 6px 0 8px; -} - -.task-preset-grid { - display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; -} -.task-preset-card { - display: flex; flex-direction: column; align-items: flex-start; gap: 2px; - padding: 12px; border: 1px solid var(--border); border-radius: 8px; - background: var(--bg); cursor: pointer; transition: all 0.15s; text-align: left; - color: var(--fg); -} -.task-preset-card:hover { - border-color: var(--accent, #cc6a3a); background: color-mix(in srgb, var(--accent, #cc6a3a) 8%, var(--bg)); -} -.task-preset-icon { font-size: 20px; margin-bottom: 2px; } -.task-preset-label { font-size: 12px; font-weight: 600; } -.task-preset-desc { font-size: 10px; opacity: 0.5; line-height: 1.3; } - -.task-form { display: flex; flex-direction: column; gap: 4px; } -.task-form-label { - font-size: 11px; - opacity: 0.55; - font-weight: 500; - display: block; - /* Consistent rhythm: 8px above each label (section gap), 4px below to the - field — same spacing for Name/Prompt/Trigger/Output/Model/Chain. */ - margin: 8px 0 4px; -} -.task-form-input { - font-family: inherit; - font-size: 11px; - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - transition: border-color 0.15s; - box-sizing: border-box; - width: 100%; - max-width: 100%; - min-width: 0; -} -.task-form-input:focus { - outline: none; - border-color: var(--red); -} -.task-form-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } - -.task-form-notif-toggle { - display: flex; - align-items: center; - gap: 8px; - margin: 9px 0 3px; - cursor: pointer; - color: var(--fg); -} -.task-form-notif-toggle input { - position: absolute; - opacity: 0; - pointer-events: none; -} -.task-form-notif-switch { - width: 30px; - height: 16px; - border-radius: 999px; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 8%, transparent); - position: relative; - flex: 0 0 auto; - transition: background 0.15s, border-color 0.15s; -} -.task-form-notif-toggle input:checked + .task-form-notif-switch { - border-color: color-mix(in srgb, var(--accent, var(--red)) 58%, transparent); - background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); -} -.task-form-notif-copy { - display: flex; - flex-direction: column; - gap: 1px; - font-size: 11px; - font-weight: 500; -} -.task-form-notif-copy span:last-child { - font-size: 10px; - font-weight: 400; - opacity: 0.52; -} - -.task-form-toggle { - display: flex; gap: 4px; -} -.task-toggle-btn { - flex: 1; padding: 4px 8px; font-size: 10px; font-family: inherit; - border: 1px solid var(--border); border-radius: 6px; background: transparent; - color: var(--fg); opacity: 0.4; cursor: pointer; transition: all 0.15s; -} -.task-toggle-btn:hover { opacity: 0.7; border-color: var(--fg); } -.task-toggle-btn.active { - opacity: 1; border-color: var(--red); color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); -} - -.task-time-picker, .task-date-picker { - display: flex; - align-items: center; - gap: 4px; -} -.task-time-select, .task-date-select { - width: auto; - min-width: 48px; - font-size: 11px; -} -.task-time-sep { opacity: 0.4; font-size: 11px; } - -.task-history-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 8px; -} - -.task-runs-list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; } -.task-run-item { - border: 1px solid var(--border); - border-radius: 6px; - padding: 8px 10px; -} -.task-run-item-header { - display: flex; - align-items: center; - gap: 4px; - font-size: 11px; -} -.task-run-time { margin-left: auto; font-size: 10px; opacity: 0.45; } -.task-run-result { - font-size: 11px; - opacity: 0.6; - margin-top: 4px; - line-height: 1.4; - white-space: pre-wrap; - word-break: break-word; -} - -/* ── Settings Form Utilities ── */ - -.settings-row { - display: flex; - align-items: center; - gap: 8px; -} -.settings-col { - display: flex; - flex-direction: column; - gap: 6px; -} -.settings-label { - font-size: 12px; - min-width: 70px; -} -.settings-select { - flex: 1; - /* Allow the select to shrink below its longest option's width inside a flex - row — without this a long model name (e.g. the Vision picker) overflowed - the card off-screen on mobile. */ - min-width: 0; - max-width: 100%; - padding: 5px 8px; - font-family: inherit; - font-size: 12px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - transition: border-color 0.15s; - appearance: none; - -webkit-appearance: none; - -moz-appearance: textfield; - outline: none; -} -input.settings-select::-webkit-outer-spin-button, -input.settings-select::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -input.settings-select::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } -.settings-select:focus { - outline: none; - border-color: var(--red); -} - -/* Deep Research uses long labels and previously fixed-width fields. Keep - every control constrained by the card at intermediate desktop widths too. */ -.admin-card:has(#set-researchSearch) .settings-row { min-width: 0; } -.admin-card:has(#set-researchSearch) .settings-row > .settings-label { flex: 0 0 112px; min-width: 0; } -#set-researchSearch, -#set-researchMaxTokens, -#set-researchRunTimeout, -.settings-row div:has(> #set-researchExtractTimeout), -.settings-row div:has(> #set-researchExtractConcurrency) { - width: auto !important; - max-width: 100% !important; - min-width: 0 !important; - flex: 1 1 0 !important; -} - -@media (max-width: 768px) { - /* Deep Research settings contain desktop-sized inline widths. On mobile, - let each control claim the card width instead of escaping the admin block. */ - .settings-row:has(#set-researchSearch), - .settings-row:has(#set-researchMaxTokens), - .settings-row:has(#set-researchExtractTimeout), - .settings-row:has(#set-researchExtractConcurrency), - .settings-row:has(#set-researchRunTimeout) { - flex-wrap: wrap; - align-items: stretch; - } - .settings-row:has(#set-researchSearch) .settings-label, - .settings-row:has(#set-researchMaxTokens) .settings-label, - .settings-row:has(#set-researchExtractTimeout) .settings-label, - .settings-row:has(#set-researchExtractConcurrency) .settings-label, - .settings-row:has(#set-researchRunTimeout) .settings-label { - flex: 1 1 auto; - } - #set-researchSearch, - #set-researchMaxTokens, - #set-researchRunTimeout, - .settings-row div:has(> #set-researchExtractTimeout), - .settings-row div:has(> #set-researchExtractConcurrency) { - width: 100% !important; - max-width: 100% !important; - flex: 1 1 100% !important; - margin-left: 0 !important; - } -} - -/* Default-chat fallback chain editor. Each row mirrors the primary - endpoint/model selectors, indented under them to read as a chain. */ -.settings-fallbacks { - display: flex; - flex-direction: column; - gap: 6px; -} -.settings-fallbacks:not(:empty) { margin-top: 2px; } -.settings-fallback-row { - display: flex; - align-items: center; - gap: 6px; - padding-left: 12px; - border-left: 2px solid color-mix(in srgb, var(--fg) 12%, transparent); - max-width: 100%; - box-sizing: border-box; -} -.settings-fallback-num { - font-size: 11px; - opacity: 0.4; - min-width: 14px; - text-align: right; -} -.settings-fallback-row .settings-select { - width: 0; - min-width: 0; - min-inline-size: 0; - box-sizing: border-box; - overflow: hidden; - text-overflow: ellipsis; -} -.settings-fallback-row .settings-select:first-of-type { - flex: 0 1 128px; - width: 128px; - max-width: 34%; -} -.settings-fallback-row .settings-select:nth-of-type(2) { - flex: 1 1 0; - width: 0; - max-width: 100%; -} -details.hwfit-serve-advanced > .hwfit-serve-row, -details.hwfit-serve-advanced > .hwfit-serve-checks, -details.hwfit-serve-advanced > .hwfit-serve-cmd-wrap, -details.hwfit-serve-advanced > .hwfit-serve-extra { - margin-top: 0; - margin-bottom: 0; -} -/* Inside Advanced, tighten the per-row grid gap and the label → - select gap so rows of single-line dropdowns don't visually sit - on top of half a row of empty space. Outside Advanced (Core, - etc.) the original ~6px breathing room stays. */ -details.hwfit-serve-advanced > .hwfit-serve-row { - gap: 4px; - row-gap: 4px; -} -details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp, -details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-llamacpp { - margin-top: -20px; - position: relative; - left: 4px; -} -details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp { - column-gap: 8px; - row-gap: 2px; -} -details.hwfit-serve-advanced > .hwfit-llama-placement-row { - margin-top: -6px !important; -} -details.hwfit-serve-advanced > .hwfit-llama-memory-row { - margin-top: -20px !important; -} -.cookbook-llama-cpu-mode details.hwfit-serve-advanced > .hwfit-llama-memory-row { - margin-top: -8px !important; -} -details.hwfit-serve-advanced > .hwfit-llama-batch-row, -details.hwfit-serve-advanced > .hwfit-llama-monitor-row, -details.hwfit-serve-advanced > .hwfit-llama-checks-row { - margin-top: -18px !important; -} -details.hwfit-serve-advanced > .hwfit-diff-settings-row { - row-gap: 2px; -} -details.hwfit-serve-advanced > .hwfit-diff-checks-row, -details.hwfit-serve-advanced > .hwfit-diff-harmonize-row { - margin-top: -10px !important; -} -details.hwfit-serve-advanced > .hwfit-diff-checks-row { - row-gap: 2px; -} -details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-vram-monitor { - gap: 4px !important; - grid-template-columns: max-content minmax(0, 1fr); - white-space: nowrap; -} -details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_block_size"] { - width: calc(100% - 6px); -} -details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_kv_cache_dtype"] { - width: 60px; - min-width: 60px; -} -details.hwfit-serve-advanced > .hwfit-serve-checks { - gap: 4px; - row-gap: 4px; -} -/* Pull the vLLM/SGLang checks row, Extra args, and the trailing - model-specific (Speculative) checks row up tight against the row - above — the previous 4px gap plus per-row baseline padding left a - ~8px gap that read as too airy in the Advanced fold. */ -details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-vllm, -details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-sglang, -details.hwfit-serve-advanced > .hwfit-serve-extra { - margin-top: -8px; -} -details.hwfit-serve-advanced > .hwfit-serve-extra { - margin-top: -18px; -} -details.hwfit-serve-advanced > .hwfit-serve-row:last-of-type, -details.hwfit-serve-advanced > .hwfit-serve-checks:last-of-type { - margin-bottom: 0; -} - -.settings-fallback-remove { - flex-shrink: 0; - width: 32px; - height: 32px; - display: inline-flex; - align-items: center; - justify-content: center; - border: 1px solid var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 55%, transparent); - cursor: pointer; - transition: border-color 0.12s, color 0.12s, background 0.12s; -} -.settings-fallback-remove:hover { - border-color: var(--red); - color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); -} -.settings-fallback-add { - align-self: flex-start; - margin-top: 2px; - font-size: 11px; - padding: 3px 9px; - border: 1px dashed var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 65%, transparent); - cursor: pointer; - transition: border-color 0.12s, color 0.12s; -} -.settings-fallback-add:hover { - border-color: var(--red); - color: var(--red); -} -.settings-input { - flex: 1; - padding: 5px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-family: inherit; - font-size: 12px; - transition: border-color 0.15s; -} -.settings-input:focus { - outline: none; - border-color: var(--red); -} -/* ── Contacts manager (Settings → Integrations → CardDAV) ── */ -.contacts-add-row { - display: flex; - gap: 6px; - margin-bottom: 8px; - align-items: center; -} -.contacts-list { - display: flex; - flex-direction: column; - gap: 2px; - max-height: 360px; - overflow-y: auto; -} -.contact-row { - padding: 6px 4px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 35%, transparent); -} -.contact-row:last-child { border-bottom: none; } -.contact-row-edit { margin-top: 4px; } - -/* ── Sort Dropdown ── */ - -.sort-dropdown { - position: absolute; - right: 0; - top: 100%; - z-index: 1000; - min-width: 120px !important; - width: max-content; - padding: 4px !important; - margin-top: 4px; - background: var(--panel) !important; - border: 1px solid var(--border) !important; - border-radius: 8px !important; - box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; - backdrop-filter: none !important; -} -.sort-dropdown-item { - cursor: pointer; - padding: 6px 8px 6px 6px !important; - font-size: 11px !important; - border-radius: 6px !important; - border-bottom: none !important; - white-space: nowrap; - transition: background 0.1s; -} -.sort-dropdown-item:hover { - background: color-mix(in srgb, var(--accent) 10%, transparent) !important; -} -.sort-dropdown-item.ai-sort-option { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); -} -.sort-dropdown-item.ai-sort-option .auto-sort-icon { - color: var(--accent, var(--red)); - display: inline-flex; - align-items: center; -} -#auto-sort-sessions-btn { - transform: translateX(-4px); -} -.sort-dropdown-item.ai-sort-option:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent) !important; -} -.sort-dropdown-sep { - border-top: 1px solid var(--border); - margin-top: 2px; - padding-top: 6px; -} - -/* Mobile: bigger taps for the chats sort/funnel menu and the select-mode - bulk-action bar. Also drop the "Rearrange" item — it's a touch-finicky - feature better left to desktop. */ -@media (max-width: 768px) { - /* Nudge the funnel/sort button right on mobile so it doesn't hug the - chevron next to it. */ - #session-sort-btn { transform: translateX(13px); } - #session-sort-dropdown.sort-dropdown { - min-width: 200px !important; - padding: 6px !important; - } - #session-sort-dropdown .sort-dropdown-item { - padding: 12px 14px !important; - font-size: 14px !important; - } - #session-rearrange-toggle { display: none !important; } - - /* Select-mode bar — make Archive / Delete / Cancel buttons + the - Select-all toggle finger-sized. */ - .session-bulk-bar { - padding: 8px 10px; - font-size: 14px; - gap: 10px; - } - .session-bulk-btn { - padding: 10px; - min-width: 44px; - min-height: 44px; - } - .session-bulk-btn svg { width: 20px; height: 20px; } - #session-select-all-dot { font-size: 22px !important; padding: 6px; } - #session-select-all-label { font-size: 14px !important; padding: 4px; } -} - - -/* ── Admin: Built-in tools ── */ -.admin-tool-category { - border: 1px solid var(--border); - border-radius: 6px; - margin-bottom: 6px; - overflow: hidden; - transition: border-color 0.15s; -} -.admin-tool-category:hover { - border-color: color-mix(in srgb, var(--fg) 20%, var(--border)); -} -.admin-tool-cat-header { - font-size: 12px; - font-weight: 500; - padding: 8px 10px; - transition: background 0.15s; -} -.admin-tool-cat-header:hover { - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.admin-tool-cat-body { - border-top: 1px solid var(--border); - padding: 4px 0; -} -.admin-tool-cat-body.hidden { - display: none; -} -.admin-tool-row { - display: flex; - align-items: center; - gap: 8px; - padding: 5px 10px; - padding-right: 28px; /* align toggles with header toggle (chevron 12px + gap 6px + padding) */ - transition: background 0.08s; -} -.admin-tool-row:hover { - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.admin-tool-info { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 1px; -} -.admin-tool-name { - font-size: 11px; - font-weight: 500; -} -.admin-tool-desc { - font-size: 10px; - opacity: 0.4; -} -.admin-tool-ctx { - font-size: 9px; - opacity: 0.3; - flex-shrink: 0; - min-width: 35px; - text-align: right; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; -} - -/* Cookbook serve param hints */ -.hwfit-hint { - display: inline-flex; - align-items: center; - justify-content: center; - width: 12px; - height: 12px; - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - font-size: 8px; - font-weight: 600; - opacity: 0.35; - cursor: help; - margin-left: 2px; - vertical-align: middle; - transition: opacity 0.1s; -} -.hwfit-hint:hover { - opacity: 0.8; - background: color-mix(in srgb, var(--accent) 15%, transparent); -} -.hwfit-dl-dot { - color: var(--green); - font-size: 8px; - margin-left: 4px; - opacity: 0.7; -} -.hwfit-parser-tag { - font-size: 9px; - opacity: 0.4; - background: color-mix(in srgb, var(--fg) 8%, transparent); - padding: 1px 5px; - border-radius: 3px; - margin-left: 4px; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; -} - -/* Cookbook output copy — show only on hover */ -.cookbook-output-wrap .cookbook-output-copy { - opacity: 0; - transition: opacity 0.1s; -} - -/* Cached model menu button */ -.hwfit-cached-menu-btn { - background: none; - border: none; - color: var(--fg); - font-size: 16px; - cursor: pointer; - opacity: 0; - padding: 2px 4px; - border-radius: 4px; - transition: opacity 0.1s; - flex-shrink: 0; - position: relative; - top: -2px; -} -.memory-item:hover .hwfit-cached-menu-btn, -.hwfit-cached-item:hover .hwfit-cached-menu-btn { opacity: 0.4; } -.hwfit-cached-menu-btn:hover { opacity: 1 !important; } -.hwfit-cached-menu-btn svg { pointer-events: none; } -@media (max-width: 768px) { - #cookbook-modal .hwfit-cached-menu-btn { - opacity: 0.72 !important; - width: 32px; - height: 32px; - min-width: 32px; - padding: 0; - justify-content: center; - top: -4px; - } - #cookbook-modal .hwfit-cached-menu-btn:active { - opacity: 1 !important; - background: color-mix(in srgb, var(--fg) 9%, transparent); - } -} - -/* Quick run button */ -.cookbook-run-btn { - background: var(--accent, var(--red)) !important; - color: var(--panel) !important; - border-color: var(--accent, var(--red)) !important; - font-weight: 600; -} -.cookbook-run-btn:hover { - opacity: 0.9; -} -#hwfit-cache-scan { - position: relative; - top: 1px; - width: 26px; - height: 24px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -#hwfit-cache-scan.spinning svg { - animation: modelPickerRefreshSpin 0.75s linear infinite; -} -#serve-search { - height: 28px; - min-height: 28px; - font-family: inherit; - font-size: 11px; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: 9px center; - padding-left: 28px; -} -.cookbook-sort-select-wrap { - position: relative; - display: inline-flex; - flex-shrink: 0; - align-items: center; -} -.cookbook-sort-select-wrap > svg { - position: absolute; - left: 9px; - z-index: 1; - pointer-events: none; - opacity: 0.65; - transform: translateY(4px); -} - -/* Cookbook model directory tags */ -.cookbook-modeldir-tag { - font-size: 11px; - padding: 3px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - display: inline-flex; - align-items: center; - gap: 4px; - font-family: 'Berkeley Mono', 'SF Mono', monospace; -} -.cookbook-modeldir-default { - opacity: 0.5; -} -.cookbook-server-bottom-actions { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 4px; - margin-top: 5px; -} -.cookbook-server-bottom-actions > button { - height: 22px; - box-sizing: border-box; - display: inline-flex; - align-items: center; -} -/* Default-server radio-check in a Settings server title (same look as the - model-dir download target). Active = accent-tinted. */ -.cookbook-srv-default { - cursor: pointer; - opacity: 0.35; - display: inline-flex; - align-items: center; - gap: 3px; - line-height: 1; - transition: opacity 0.12s, color 0.12s; -} -.cookbook-srv-default:hover { opacity: 0.8; } -.cookbook-srv-default.active { opacity: 1; color: var(--accent, var(--red)); } -.cookbook-srv-default-label { font-size: 10px; font-weight: 600; letter-spacing: 0.02em; } -.cookbook-srv-default-icon { - display: inline-flex; - align-items: center; - line-height: 0; -} -.cookbook-srv-default-icon svg { - width: 13px; - height: 13px; -} -.cookbook-srv-default-icon, -.cookbook-srv-default-label { - position: relative; - top: 2px; -} -.email-settings-default, -.email-settings-default .cookbook-srv-default-icon, -.email-settings-default .cookbook-srv-default-label { - position: relative; - top: -2px; -} -/* Download-target toggle inside a model-dir tag */ -.cookbook-modeldir-dl { - cursor: pointer; - opacity: 0.35; - display: inline-flex; - align-items: center; - line-height: 0; - transition: opacity 0.12s, color 0.12s; -} -.cookbook-modeldir-dl:hover { opacity: 0.8; } -.cookbook-modeldir-dl.active { opacity: 1; color: var(--accent, var(--red)); } -/* The tag currently flagged as the download target — clearly highlighted so - it's obvious where downloads land. */ -.cookbook-modeldir-target { - opacity: 1; - color: var(--accent, var(--red)); - font-weight: 600; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); -} -.cookbook-modeldir-rm { - cursor: pointer; - opacity: 0.4; - font-size: 10px; -} -.cookbook-modeldir-rm:hover { - opacity: 1; -} -.cookbook-modeldir-add { - background: none; - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - font-size: 10px; - font-weight: 500; - height: 19.5px; - box-sizing: border-box; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 2px; - position: relative; - top: -3px; - padding: 0 9px; - transition: border-color 0.12s, background 0.12s; -} -.cookbook-modeldir-add:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); } - -/* Cookbook serve optimizations */ -.hwfit-serve-opts { - display: flex; - align-items: center; - gap: 8px; - margin: 4px 0; -} -.hwfit-apply-opts { - font-size: 11px !important; - min-width: auto !important; - white-space: nowrap; -} -.hwfit-opts-desc { - font-size: 9px; - opacity: 0.4; - font-style: italic; -} - -/* Library modal tabs */ -.lib-tabs, -.admin-tabs { - display: flex; - gap: 0; - margin-bottom: 8px; - border-bottom: 1px solid var(--border); -} -.lib-tab, -.admin-tab { - background: none; - border: none; - border-bottom: 2px solid transparent; - color: var(--fg-muted); - font-size: 12px; - font-family: inherit; - padding: 6px 14px; - cursor: pointer; - transition: color 0.1s, border-color 0.1s; -} -.lib-tab:hover, -.admin-tab:hover { color: var(--fg); } -.lib-tab.active, -.admin-tab.active { - color: var(--accent, var(--red)); - border-bottom-color: var(--accent, var(--red)); -} - -/* Cookbook tab count badge */ -.cookbook-tab-count { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 16px; - height: 16px; - padding: 0 4px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); - color: var(--accent, var(--red)); - font-size: 9px; - font-weight: 700; - margin-left: 4px; - line-height: 1; -} - -/* ── Generate Visual Report button (deep research) ── */ -.view-report-btn { - display: inline-flex; - align-items: center; - gap: 6px; - margin-top: 12px; - padding: 8px 16px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 10%, transparent); - color: var(--fg); - font-family: inherit; - font-size: 0.88em; - font-weight: 500; - transition: background 0.15s ease; - cursor: pointer; -} -.view-report-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--red) 18%, transparent); -} -.view-report-btn svg { - flex-shrink: 0; -} -.report-btn-wrap { - margin-top: 12px; - display: flex; - flex-wrap: wrap; - gap: 8px; -} -.view-report-btn.chat-about-btn { - border-color: color-mix(in srgb, var(--fg) 22%, transparent); - background: color-mix(in srgb, var(--fg) 6%, transparent); -} -.view-report-btn.chat-about-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--fg) 12%, transparent); -} -.view-report-btn:disabled { - opacity: 0.6; - cursor: progress; -} -.report-spinner { - display: flex; - align-items: center; - gap: 6px; - font-size: 0.88em; - color: var(--fg); - opacity: 0.7; -} -.report-spinner-text { - font-weight: 500; -} -.report-spinner-dots::after { - content: ''; - animation: report-dots 1.4s steps(4, end) infinite; -} -@keyframes report-dots { - 0% { content: ''; } - 25% { content: '.'; } - 50% { content: '..'; } - 75% { content: '...'; } - 100% { content: ''; } -} - -/* ── Continue research hint ────────────────────────── */ -.continue-research-wrap { - margin-top: 8px; -} -.continue-research-hint { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 0.78em; - color: var(--fg-dim, var(--fg)); - opacity: 0.5; - padding: 4px 0; -} -.continue-research-hint svg { - flex-shrink: 0; - opacity: 0.6; -} - -/* ── Research reconnect & timer ────────────────────── */ -.msg.research-reconnect { - border-left: 3px solid var(--red); - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.research-timer { - font-size: 0.8em; - opacity: 0.6; - margin-top: 4px; - font-family: monospace; - color: var(--fg-dim, var(--fg)); -} - -/* ── Research synapse visualization ─────────────────────────────── - Live SVG graph of an in-flight deep-research run. The query is the - central node; sub-questions branch off per round; sources are leaf - nodes that pop in as they're captured. Pulses indicate activity. */ -.research-synapse { - margin: 6px 0 4px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 24%, var(--border)); - border-radius: 7px; - background: - linear-gradient(color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), - linear-gradient(90deg, color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), - radial-gradient(ellipse at 50% 48%, color-mix(in srgb, var(--accent, var(--red)) 13%, transparent) 0%, transparent 62%), - color-mix(in srgb, var(--panel) 68%, var(--bg)); - background-size: 18px 18px, 18px 18px, auto, auto; - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--fg) 5%, transparent); - overflow: hidden; -} -.research-synapse .rs-stage { - height: 400px; - position: relative; -} -.research-synapse .rs-meta-header { - position: absolute; - z-index: 4; - top: 0; - left: 0; - right: 0; - border-top: 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - padding: 5px 8px 6px; - background: color-mix(in srgb, var(--panel) 76%, transparent); -} -.research-synapse-ascii-filter > .rs-stage > svg { - display: none; -} -.research-synapse-ascii-filter .rs-ascii-overlay { - position: absolute; - z-index: 3; - inset: 28px 10px 10px; - margin: 0; - overflow: hidden; - pointer-events: none; - color: color-mix(in srgb, var(--accent, var(--red)) 76%, var(--fg)); - font: 600 10px/1.35 ui-monospace, SFMono-Regular, Consolas, monospace; - white-space: pre; - text-align: center; - opacity: 1; - text-shadow: 0 0 7px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); - mix-blend-mode: screen; -} -.research-synapse-compact .rs-meta { padding: 5px 8px 6px; font-size: 10px; } -.research-synapse-compact .rs-label-sub { font-size: 8px; } -.research-synapse .rs-stage-head { - position: absolute; - z-index: 2; - top: 7px; - left: 9px; - right: 9px; - display: flex; - align-items: center; - gap: 5px; - color: var(--fg-dim, var(--fg)); - font: 600 8px/1 ui-monospace, "JetBrains Mono", monospace; - text-transform: uppercase; - pointer-events: none; -} -.research-synapse .rs-live-dot { - width: 5px; - height: 5px; - border-radius: 50%; - background: var(--accent, var(--red)); - box-shadow: 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 75%, transparent); - animation: rs-live 1.8s ease-in-out infinite; -} -.research-synapse .rs-phase-chip { - margin-left: auto; - padding: 3px 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, var(--border)); - border-radius: 3px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, var(--bg)); - color: var(--accent, var(--red)); -} -.research-synapse svg { - display: block; - width: 100%; - height: 100%; -} -.research-synapse .rs-edge { - stroke: color-mix(in srgb, var(--accent, var(--red)) 32%, var(--border)); - stroke-width: 1.35; - fill: none; - opacity: 0.72; -} -.research-synapse .rs-node { - fill: var(--bg); - stroke: var(--accent, var(--red)); - stroke-width: 1.5; - transition: all 0.3s ease; - transform-box: fill-box; - transform-origin: center; -} -.research-synapse .rs-source-index { - fill: var(--fg); - font: 650 4.5px/1 ui-monospace, "JetBrains Mono", monospace; - pointer-events: none; - opacity: 0.88; -} -.research-synapse .rs-node-expanded { animation: rs-hub-expand 0.75s ease-out; } -.research-synapse .rs-node-new { - animation: rs-pop 0.6s ease-out; -} -.research-synapse .rs-pulse { - fill: var(--accent, var(--red)); - opacity: 0; - animation: rs-pulse 2.6s ease-out infinite; - transform-box: fill-box; - transform-origin: center; -} -.research-synapse .rs-label { - fill: var(--fg); - font-size: 10px; - font-family: ui-monospace, "JetBrains Mono", monospace; - pointer-events: none; - opacity: 0.85; -} -.research-synapse .rs-label-sub { - font-size: 9px; - opacity: 0.7; -} -.research-synapse .rs-meta { - display: grid; - grid-template-columns: minmax(0, 1fr) auto auto auto; - align-items: center; - gap: 10px; - padding: 6px 10px 8px; - font-family: ui-monospace, "JetBrains Mono", monospace; - font-size: 11px; - color: var(--fg-dim, var(--fg)); - opacity: 0.92; - border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - background: color-mix(in srgb, var(--panel) 76%, transparent); -} -.research-synapse .rs-meta .rs-status { - display: none; - color: var(--accent, var(--red)); - font-weight: 600; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.research-synapse .rs-meta b { - color: var(--fg); - font-weight: 600; -} -.research-synapse.rs-complete .rs-meta .rs-status { color: var(--color-success, #4caf50); } -.research-synapse.rs-error .rs-meta .rs-status { color: var(--red); } -.research-synapse.rs-phase-reading .rs-phase-chip, -.research-synapse.rs-phase-analyzing .rs-phase-chip { color: var(--gold, #d4a72c); } -.research-synapse.rs-phase-writing .rs-phase-chip { color: var(--color-success, #4caf50); } - -@media (prefers-reduced-motion: reduce) { - .research-synapse .rs-live-dot, - .research-synapse .rs-pulse, - .research-synapse .rs-node-new, - .research-synapse .rs-node-expanded, - .research-synapse .rs-edge-firing { animation: none !important; } -} - -/* ── Raw findings items (inside sources-style box) ── */ -.finding-item { - display: flex; - flex-direction: column; - gap: 4px; -} -.finding-item .source-link { - border-radius: 6px 6px 0 0; -} -.finding-summary { - padding: 6px 8px 8px; - font-size: 0.82em; - line-height: 1.5; - color: var(--fg-dim, var(--fg)); - opacity: 0.85; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); - margin-bottom: 4px; - white-space: pre-wrap; - word-break: break-word; -} -.finding-item:last-child .finding-summary { - border-bottom: none; - margin-bottom: 0; -} - -/* ═══════════════════════════════════════════════════════════ - Gallery Editor - ═══════════════════════════════════════════════════════════ */ - -/* Tab bar */ -.gallery-tabs { - display: flex; - gap: 0; - border-bottom: 1px solid var(--border); - padding: 0 16px; - background: var(--panel); -} -.gallery-tab { - padding: 8px 18px; - background: none; - border: none; - border-bottom: 2px solid transparent; - color: var(--fg); - opacity: 0.6; - cursor: pointer; - font-size: 13px; - font-weight: 500; - transition: opacity 0.15s, border-color 0.15s; -} -.gallery-tab:hover { opacity: 0.85; } -.gallery-tab.active { - opacity: 1; - border-bottom-color: var(--red); -} - -/* Narrow modal tab strips should stay on one row. Resized docked windows can - be much narrower than the viewport, so this cannot live only in mobile media - queries. */ -.cookbook-tabs, -.memory-tabs, -.admin-tabs, -.lib-tabs, -.gallery-tabs, -.preset-tabs { - flex-wrap: nowrap !important; - overflow-x: auto !important; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - overscroll-behavior-x: contain; - scrollbar-width: none; -} -.cookbook-tabs::-webkit-scrollbar, -.memory-tabs::-webkit-scrollbar, -.admin-tabs::-webkit-scrollbar, -.lib-tabs::-webkit-scrollbar, -.gallery-tabs::-webkit-scrollbar, -.preset-tabs::-webkit-scrollbar { - display: none; -} -.cookbook-tabs > *, -.memory-tabs > *, -.admin-tabs > *, -.lib-tabs > *, -.gallery-tabs > *, -.preset-tabs > * { - flex: 0 0 auto; -} -.cookbook-tab, -.memory-tab, -.admin-tab, -.lib-tab, -.gallery-tab, -.preset-tab { - display: inline-flex; - align-items: center; - justify-content: center; - white-space: nowrap; - line-height: 1; -} -.gallery-tab { - gap: 6px; -} - -@container (max-width: 360px) { - .cookbook-tab:has(svg), - .memory-tab:has(svg), - .admin-tab:has(svg), - .lib-tab:has(svg), - .gallery-tab:has(svg), - .preset-tab:has(svg) { - width: 34px; - min-width: 34px; - padding-left: 0; - padding-right: 0; - font-size: 0; - } - - .cookbook-tab:has(svg) svg, - .memory-tab:has(svg) svg, - .admin-tab:has(svg) svg, - .lib-tab:has(svg) svg, - .gallery-tab:has(svg) svg, - .preset-tab:has(svg) svg { - width: 14px; - height: 14px; - margin-right: 0 !important; - vertical-align: middle !important; - } - - .memory-tab:has(svg) .memory-count, - .gallery-tab:has(svg) .gallery-tab-label, - .gallery-tab:has(svg) .gallery-tab-close, - .cookbook-tab:has(svg) .cookbook-tab-count, - .preset-tab:has(svg) .preset-count { - display: none !important; - } -} -/* Icon + label layout inside each tab. */ -.gallery-tab { - display: inline-flex; - align-items: center; - gap: 6px; -} -.gallery-tab-icon { - display: inline-flex; - align-items: center; - opacity: 0.85; -} -.gallery-tab.active .gallery-tab-icon { opacity: 1; } -/* Close × on the Edit tab — appears on hover. */ -.gallery-tab-close { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - margin-left: 2px; - border-radius: 4px; - opacity: 0; - transition: opacity 0.12s, background 0.12s; - /* Use a slightly larger glyph at a larger line-height so the × sits - visually centered — the U+00D7 character has uneven em-box metrics. */ - font-size: 16px; - line-height: 16px; - font-weight: 500; - cursor: pointer; - padding-bottom: 2px; - box-sizing: border-box; -} -.gallery-tab:hover .gallery-tab-close, -.gallery-tab.active .gallery-tab-close { opacity: 0.65; } -/* Hide the close × entirely on the Edit tab when there's no edit open. */ -.gallery-tab[data-tab="editor"]:not(.has-edit) .gallery-tab-close { display: none !important; } -.gallery-tab-close:hover { opacity: 1 !important; background: color-mix(in srgb, var(--red) 25%, transparent); color: var(--red); } -/* Inline rename input shown when the Edit tab is double-clicked. */ -.gallery-tab-rename-input { - background: var(--bg); - border: 1px solid var(--red); - border-radius: 3px; - color: var(--fg); - font: inherit; - font-size: 13px; - padding: 1px 4px; - width: 140px; - outline: none; -} - -/* Albums tab — grid of album cards with cover thumbnails. */ -.gallery-albums-container { - padding: 12px 4px; - max-height: calc(92vh - 120px); - overflow-y: auto; - border: 2px dashed transparent; - border-radius: 8px; - transition: border-color 0.15s, background 0.15s; -} -.gallery-settings-container { - padding: 8px 4px; - max-height: calc(92vh - 120px); - overflow-y: auto; -} -.gallery-albums-container.gallery-dragover { - border-color: var(--red); - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.gallery-albums-empty { - display: flex; flex-direction: column; align-items: center; gap: 12px; - padding: 48px 16px; - opacity: 0.7; - font-size: 13px; -} -.gallery-albums-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 12px; -} -.gallery-album-card { - position: relative; - display: flex; - flex-direction: column; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - overflow: hidden; - cursor: pointer; - transition: transform 0.12s, border-color 0.12s, box-shadow 0.12s; -} -.gallery-album-card:hover { - transform: translateY(-2px); - border-color: var(--red); - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 12%, transparent); -} -.gallery-album-menu-btn { - position: absolute; - top: 6px; right: 6px; - width: 24px; height: 24px; - display: flex; align-items: center; justify-content: center; - background: color-mix(in srgb, var(--bg) 70%, transparent); - color: var(--fg); - border: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - border-radius: 6px; - cursor: pointer; - opacity: 0; - transition: opacity 0.12s, background 0.12s; - z-index: 2; - backdrop-filter: blur(4px); - /* Plain "…" text — the previous SVG was rendering as a flat black - block on some themes when its currentColor didn't contrast. */ - font-size: 16px; - line-height: 1; - font-weight: 600; - padding: 0 0 4px; -} -.gallery-album-card:hover .gallery-album-menu-btn, -.gallery-album-menu-btn:focus-visible { opacity: 1; } -.gallery-album-menu-btn:hover { background: var(--bg); } -.gallery-album-menu-pop { - position: absolute; - top: 34px; right: 6px; - min-width: 140px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); - display: flex; flex-direction: column; - padding: 4px; - z-index: 3; -} -.gallery-album-menu-pop[hidden] { display: none; } -.gallery-album-cover { - aspect-ratio: 4 / 3; - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - display: flex; align-items: center; justify-content: center; - overflow: hidden; -} -.gallery-album-placeholder { - opacity: 0.4; - font-size: 40px; - display: flex; align-items: center; justify-content: center; - width: 100%; height: 100%; -} -.gallery-album-info { - padding: 8px 10px; - display: flex; flex-direction: column; gap: 2px; -} -.gallery-album-name { - font-size: 13px; - font-weight: 500; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -.gallery-album-count { - font-size: 11px; - opacity: 0.55; -} -.gallery-album-card-add { - border-style: dashed; - opacity: 0.7; -} -.gallery-album-card-add:hover { opacity: 1; } -.gallery-album-action-tile { - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); -} - -/* Edit-tab empty state — shown before any image is loaded. */ -.gallery-editor-landing { - display: flex; flex-direction: column; align-items: center; justify-content: center; - gap: 10px; - padding: 28px 16px; - flex: 1; - text-align: center; - color: var(--fg); -} -.gallery-editor-landing h3 { - margin: 0; - font-size: 16px; - font-weight: 600; -} -.gallery-editor-heading { - display: inline-flex; - align-items: center; - gap: 7px; -} -.gallery-editor-heading > svg { opacity: 0.6; flex: 0 0 auto; } -/* "vision model" link in the AI-tagging description → opens Settings. */ -.ge-vision-link { - color: var(--accent, var(--red)); - text-decoration: underline; - text-underline-offset: 2px; - cursor: pointer; -} -.ge-vision-link:hover { opacity: 0.8; } -.ge-alpha-tag { - font-size: 9px; - font-weight: 700; - letter-spacing: 0.06em; - text-transform: uppercase; - vertical-align: middle; - padding: 1px 5px; - border-radius: 4px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - position: relative; - top: -1px; -} -.gallery-editor-landing-actions { - display: flex; gap: 10px; - margin-top: 8px; -} -/* Bigger primary action buttons specifically inside the editor landing — - the gallery-wide .gallery-select-btn is a compact toolbar style which is - too small for the empty-state hero. */ -.gallery-editor-landing-actions .gallery-select-btn { - padding: 7px 22px 17px; - font-size: 13px; - opacity: 0.85; -} -/* Visible template picker: every canvas ratio is inspectable without opening - a native menu, and its SVG outline previews the resulting canvas shape. */ -.gallery-editor-template-picker { - width: min(760px, 96%); - margin-top: 10px; -} -.gallery-editor-template-picker h4 { - margin: 0 0 7px; - font-size: 11px; - opacity: 0.55; - text-transform: uppercase; - letter-spacing: 0.06em; -} -.gallery-editor-template-options { - display: grid; - grid-template-columns: repeat(4, minmax(105px, 1fr)); - gap: 6px; -} -.gallery-editor-template-option { - height: 72px; - min-width: 0; - padding: 6px 7px; - display: grid; - grid-template-rows: 28px auto auto; - justify-items: center; - align-items: center; - background: var(--panel); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 5px; - font: inherit; - font-size: 11px; - cursor: pointer; - overflow: hidden; -} -.gallery-editor-template-option svg { color: var(--accent, var(--red)); } -.gallery-editor-template-option:hover, -.gallery-editor-template-option:focus-visible { - outline: none; - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 7%, var(--panel)); -} -.gallery-editor-template-option span { - width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-weight: 600; -} -.gallery-editor-template-option small { - width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 9px; - opacity: 0.52; -} -@media (max-width: 620px) { - .gallery-editor-landing { - justify-content: flex-start; - gap: 7px; - padding: 18px 12px; - overflow-y: auto; - } - .gallery-editor-landing-actions { - width: 100%; - gap: 6px; - justify-content: center; - } - .gallery-editor-landing-actions .gallery-select-btn { - flex: 1 1 0; - min-width: 0; - max-width: 180px; - padding: 6px 8px 10px; - font-size: 11px; - } - .gallery-editor-template-picker { - width: 100%; - margin-top: 4px; - } - .gallery-editor-template-options { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - /* The common square, widescreen, portrait, and Instagram presets are - enough to start on a phone. Keep the print and 4K presets on desktop. */ - .gallery-editor-template-option:nth-child(n + 5) { - display: none; - } -} -/* Saved-drafts grid on the editor landing. Surfaces every persisted - in-progress project so the user can resume without re-opening the - original photo. Each card: thumbnail + name + last-saved hint + ×. */ -.gallery-editor-drafts { - width: min(720px, 92%); - margin: 28px auto 0; /* centered */ - padding-top: 18px; - border-top: 1px solid var(--border); - display: flex; - flex-direction: column; - gap: 10px; -} -.gallery-editor-drafts { - position: relative; -} -.gallery-editor-drafts-loading { - position: absolute; - inset: 30px 0 0 0; - z-index: 4; - display: flex; - align-items: center; - justify-content: center; - background: var(--panel); - border-radius: 6px; - min-height: 80px; -} -.gallery-editor-drafts-header { - display: flex; - align-items: center; - justify-content: center; /* center the title / search / select */ - gap: 8px; - margin-bottom: 8px; -} -.gallery-editor-drafts-title { - margin: 0; - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.06em; - opacity: 0.55; - font-weight: 600; - text-align: left; - flex-shrink: 0; -} -.gallery-editor-drafts-search { - flex: 1 1 auto; - min-width: 0; - max-width: none; - height: 26px; /* same thickness as the Select button */ - box-sizing: border-box; - padding: 0 8px; - font-size: 12px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; -} -.gallery-editor-drafts-search:focus { outline: none; border-color: var(--red); } -/* The shared .gallery-select-btn has a -4px top margin + tall asymmetric padding - meant for the main gallery toolbar; in this header it makes the button a - different height / offset from the search box. Normalize so it lines up. */ -.gallery-editor-drafts-header .gallery-select-btn { - margin-top: 0 !important; - height: 26px; /* match the search bar */ - box-sizing: border-box; - padding: 0 10px; - font-size: 12px; - line-height: 1; - flex-shrink: 0; -} - -.cookbook-official-filter { - display: inline-flex; - align-items: center; - gap: 4px; - flex: 0 1 auto; - min-width: 0; - max-width: 100%; - color: var(--fg); - font-size: 10px; - opacity: 0.72; - white-space: nowrap; - position: relative; - top: 1px; -} -.cookbook-official-filter > span:first-child { - position: relative; - top: 1px; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-size: 9px; -} -.cookbook-official-filter .toggle { - width: 26px; - height: 14px; -} -.cookbook-official-filter .toggle .slider:before { - width: 10px; - height: 10px; - left: 2px; - bottom: 2px; -} -.cookbook-official-filter .toggle input:checked + .slider:before { - transform: translateX(12px); -} -#gallery-editor-drafts-select { margin-left: auto; flex-shrink: 0; } -#gallery-editor-drafts-select.active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -/* Graceful exit when a project card is deleted — fade + shrink before re-render. */ -.gallery-editor-draft-card.gallery-draft-removing { - opacity: 0; - transform: scale(0.92); - transition: opacity 0.24s ease, transform 0.24s ease; - pointer-events: none; -} -/* Draft select dot uses the standard small .gallery-select-dot look (same as the - photo grid) — no oversized white-ring checkbox. */ -.gallery-editor-drafts-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); - gap: 10px; -} -.gallery-drafts-just-opened > .gallery-editor-draft-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.gallery-drafts-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.gallery-drafts-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.gallery-drafts-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.gallery-drafts-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.gallery-drafts-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.gallery-drafts-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.gallery-drafts-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.gallery-drafts-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.gallery-drafts-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.gallery-drafts-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.gallery-drafts-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.gallery-drafts-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.gallery-drafts-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.gallery-drafts-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.gallery-drafts-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.gallery-drafts-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.gallery-drafts-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.gallery-drafts-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.gallery-drafts-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.gallery-drafts-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.gallery-drafts-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } -.gallery-drafts-view-toggle { - display: inline-flex; - position: relative; - top: 0; - height: 24px; - flex: 0 0 auto; - overflow: hidden; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); -} -.gallery-drafts-view-toggle button { - width: 25px; - height: 22px; - margin: 0; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - border: 0; - border-right: 1px solid var(--border); - background: transparent; - color: color-mix(in srgb, var(--fg) 55%, transparent); - cursor: pointer; - line-height: 0; -} -.gallery-drafts-view-toggle button:last-child { border-right: 0; } -.gallery-drafts-view-toggle button:hover { color: var(--fg); } -.gallery-drafts-view-toggle button.active { - height: 22px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-thumb { - width: 44px; - height: 44px; - aspect-ratio: auto; -} -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete { - position: static; - opacity: 0.55; - background: transparent; - color: var(--fg); -} -.gallery-editor-draft-delete-trash { display: none; } -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete-trash { - display: block; - width: 18px; - height: 18px; - position: relative; - top: -2px; -} -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete:hover { - opacity: 1; - color: var(--color-error, #f44); - background: color-mix(in srgb, var(--color-error, #f44) 10%, transparent); -} -.gallery-editor-draft-card { - position: relative; - display: flex; - flex-direction: column; - gap: 4px; - padding: 6px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 70%, transparent); - cursor: pointer; - transition: border-color 0.15s, background 0.15s, transform 0.15s; - text-align: left; -} -.gallery-editor-draft-card:hover { - border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--panel) 90%, transparent); -} -.gallery-editor-draft-thumb { - width: 100%; - aspect-ratio: 1; - object-fit: cover; - border-radius: 4px; - background: var(--bg); - display: block; -} -.gallery-editor-draft-thumb-empty { - background: repeating-linear-gradient( - 45deg, - color-mix(in srgb, var(--fg) 4%, transparent), - color-mix(in srgb, var(--fg) 4%, transparent) 6px, - transparent 6px, - transparent 12px - ); -} -.gallery-editor-draft-info { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; -} -.gallery-editor-draft-name { - font-size: 12px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.gallery-editor-draft-meta { - font-size: 10px; - opacity: 0.55; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.gallery-editor-draft-delete { - position: absolute; - top: -2px; - right: 8px; - width: 22px; - height: 22px; - border-radius: 50%; - border: none; - background: rgba(0, 0, 0, 0.55); - color: #fff; - font-size: 14px; - line-height: 1; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.12s, background 0.12s; -} -.gallery-editor-draft-delete { - padding-bottom: 2px; -} -.gallery-editor-draft-delete:hover { background: var(--red); } -/* On very narrow phones, give project search its own row instead of - compressing the title and view controls into an overflowing header. */ -@media (max-width: 360px) { - .gallery-editor-drafts-header { - flex-wrap: wrap; - justify-content: flex-start; - gap: 6px; - } - .gallery-editor-drafts-title { - flex: 1 1 auto; - } - .gallery-editor-drafts-search { - order: 10; - flex: 1 1 100%; - width: 100%; - max-width: none; - } - #gallery-editor-drafts-select { - margin-left: 0; - } -} -/* Editor layout */ -.gallery-editor { - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; - height: 100%; - overflow: hidden; - /* Prevent the editor's own scroll/touch from chaining out to the - gallery modal or the page body — critical on mobile so touching - the canvas doesn't slide the whole page. */ - overscroll-behavior: contain; - touch-action: pan-y; -} -/* Pin the topbar to the top of the editor so its action buttons remain - visible if the gallery modal body itself ends up scrolling. */ -.ge-topbar { position: sticky; top: 0; z-index: 5; } - -/* Top bar */ -.ge-topbar { - display: flex; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: thin; - align-items: center; - justify-content: space-between; - padding: 6px 10px; - background: var(--panel); - border-bottom: 1px solid var(--border); - flex-shrink: 0; - gap: 8px; -} -.ge-topbar-left, .ge-topbar-right { - display: flex; - flex: 0 0 auto; - align-items: center; - gap: 4px; -} -.ge-topbar-right > * { - flex-shrink: 0; -} -.ge-topbar .dropdown[popover] { - position: fixed; - margin: 0; - bottom: auto; - max-height: calc(100vh - 80px); - overflow-y: auto; -} -.ge-draft-status { - flex: 0 1 auto; - min-width: 0; - max-width: 92px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 52%, transparent); - font-size: 10px; - line-height: 1; - transition: color 0.15s, opacity 0.15s; -} -.ge-draft-status[data-state="pending"], -.ge-draft-status[data-state="saving"] { color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); } -.ge-draft-status[data-state="saved"] { color: color-mix(in srgb, var(--color-success, #4ade80) 78%, var(--fg)); } -.ge-draft-status[data-state="error"] { color: var(--color-error, #ef4444); } -/* "ALPHA" badge — flags the editor as in-development. */ -.ge-alpha-badge { - flex-shrink: 0; - font-size: 9px; - font-weight: 700; - letter-spacing: 0.1em; - line-height: 1; - padding: 3px 6px; - margin-right: 4px; - border-radius: 4px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - text-transform: uppercase; - user-select: none; - cursor: default; -} -@media (max-width: 768px) { - /* Keep the toolbar tight on mobile — badge stays but shrinks. */ - .ge-alpha-badge { font-size: 8px; padding: 2px 5px; margin-right: 2px; } -} -.ge-diffusion-status { - font-size: 11px; - cursor: pointer; - padding: 2px 8px; - border-radius: 4px; - transition: all 0.15s; - white-space: nowrap; -} -.ge-diffusion-status.online { - color: var(--color-success, #4ade80); -} -.ge-diffusion-status.offline { - color: var(--color-error, #ef4444); - opacity: 0.7; -} -.ge-diffusion-status.offline:hover { - opacity: 1; - background: color-mix(in srgb, var(--color-error) 10%, transparent); -} -.ge-topbar-fill { - display: inline-flex; - align-items: center; - gap: 4px; - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -.ge-topbar-fill:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: var(--accent, var(--red)); -} -/* Mask-color swatch in the topbar — small label + circular swatch so - the user can pick a contrasting overlay colour from anywhere. */ -.ge-topbar-mask-color-wrap { - display: inline-flex; - align-items: center; - gap: 4px; - cursor: pointer; -} -.ge-topbar-mask-color-label { - font-size: 10px; - opacity: 0.6; - text-transform: uppercase; - letter-spacing: 0.06em; -} -.ge-topbar-mask-color { - width: 18px; - height: 18px; - flex: 0 0 18px; -} -.ge-topbar-mask-color.cp-swatch-input { - width: 18px; - height: 18px; - flex: 0 0 18px; - border-radius: 50%; - padding: 0; -} -.ge-topbar-sep { - width: 1px; - height: 16px; - background: var(--border); - margin: 0 4px; -} - -/* Editor body (toolbar + canvas + panel) */ -.ge-editor-body { - display: flex; - flex: 1; - min-height: 0; - overflow: hidden; - position: relative; -} -.gallery-editor-container { - flex: 1; - min-height: 0; - display: none; -} - -.ge-toolbar { - display: flex; - flex-direction: column; - gap: 2px; - /* Asymmetric horizontal padding shifts the whole column 4 px left — - buttons, hover backgrounds, active highlight pills, and section - separators all move together. */ - padding: 8px 8px 8px 0; - background: var(--panel); - border-right: 1px solid var(--border); - width: 64px; - flex-shrink: 0; - min-height: 0; - overflow-x: hidden; - overflow-y: auto; - scrollbar-width: none; -} -.ge-toolbar::-webkit-scrollbar { display: none; } -.ge-tool-sep { - font-size: 8px; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--color-muted); - text-align: center; - padding: 8px 0 4px; - border-top: 1px solid var(--border); - margin-top: 4px; - opacity: 0.6; - flex-shrink: 0; -} -/* All tool buttons share a fixed height so the column reads as a clean - grid. Long labels (e.g. "Remove BG") truncate with ellipsis instead of - wrapping to two lines and making one button taller than the others. */ -.ge-tool-btn { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 2px; - padding: 0 2px; - height: 42px; - flex-shrink: 0; - border: none; - background: none; - color: var(--fg); - opacity: 0.6; - cursor: pointer; - border-radius: 6px; - transition: background 0.15s, opacity 0.15s; -} -.ge-tool-btn { position: relative; } -/* Hover background is now a pseudo so we can shift it 2 px left - independently from the button's own bounds (which contain the icon - and label at their already-shifted positions). */ -.ge-tool-btn:hover { opacity: 0.85; } -.ge-tool-btn.active { - opacity: 1; - color: var(--red); - background: none; - /* Padding stays the same as inactive buttons so the icon stays - horizontally aligned with its neighbors. The taller highlight is - drawn by ::before so it can extend up/down without pushing the - button or shifting the icon. */ -} -/* Tool button contents stay above the active highlight pseudo. Also - shifted an additional 2 px left so the icon + label sit further inside - the highlight pill (which still anchors to the button bounds). */ -.ge-tool-btn > * { position: relative; z-index: 1; left: -2px; } -.ge-tool-icon { font-size: 18px; line-height: 1; } -.ge-tool-label { font-size: 10px; line-height: 1.25; } -/* AI badge — same ✦ glyph the Enhance tool uses, pinned to the top- - left of any tool button marked `ai: true`. Same color as the icon - itself (inherits foreground) so it reads as part of the tool, not - a flag. */ -.ge-tool-ai { - position: absolute !important; - top: 1px; - z-index: 2; - color: inherit; - opacity: 0.7; - pointer-events: none; - font-size: 11px; - line-height: 1; - font-weight: 700; - left: 3px !important; -} -.ge-tool-btn.is-ai:hover .ge-tool-ai { opacity: 0.95; } -.ge-tool-btn.is-ai.active .ge-tool-ai { opacity: 1; } -/* Inline ✦ marker used on AI action buttons (Generate, Remove, etc.) - so they read as AI-backed at a glance — same glyph as .ge-tool-ai - in the toolbar, just sitting inline next to the label. */ -.ge-btn-ai-mark { - display: inline-block; - font-size: 11px; - line-height: 1; - font-weight: 700; - opacity: 0.75; - margin-right: 1px; -} - -/* Per-tool clear-selection badge — shows a tiny X in the top-right of - the Lasso / Wand button when each holds a selection. Click clears - the selection without switching tools (handler stops propagation). */ -.ge-tool-clear { - position: absolute !important; - top: 3px; - right: 3px; - width: 14px; - height: 14px; - display: none; - align-items: center; - justify-content: center; - border-radius: 50%; - color: #fff; - background: var(--red); - cursor: pointer; - opacity: 0.95; - z-index: 2; - box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 70%, transparent), - 0 1px 4px color-mix(in srgb, var(--red) 50%, transparent); - transition: opacity 0.12s, transform 0.12s, box-shadow 0.12s; - left: auto !important; -} -.ge-tool-clear:hover { - opacity: 1; - transform: scale(1.15); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 80%, transparent), - 0 2px 6px color-mix(in srgb, var(--red) 60%, transparent); -} - -/* Aspect-ratio placeholder shown while a draft is loading. */ -.ge-canvas-placeholder { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border: 1px dashed color-mix(in srgb, var(--fg) 20%, transparent); - border-radius: 4px; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 1; -} -/* Canvas area (center) */ -.ge-canvas-area { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - overflow: auto; - background: var(--bg); - position: relative; - min-width: 0; - /* Stop touch interactions inside the editor from dragging the whole - page (or the gallery modal). Touches on the canvas are owned by the - editor's drawing logic — no native pan/zoom. */ - overscroll-behavior: contain; -} -.ge-ruler { - position: absolute; - z-index: 16; - touch-action: none; - user-select: none; -} -.ge-ruler-horizontal { - top: 0; - left: 18px; - right: 0; - width: calc(100% - 18px); - height: 18px; - cursor: row-resize; -} -.ge-ruler-vertical { - top: 18px; - bottom: 0; - left: 0; - width: 18px; - height: calc(100% - 18px); - cursor: col-resize; -} -.ge-ruler-corner { - position: absolute; - top: 0; - left: 0; - z-index: 17; - width: 18px; - height: 18px; - background: var(--panel); - border-right: 1px solid var(--border); - border-bottom: 1px solid var(--border); - box-sizing: border-box; -} -.ge-wand-loading { - position: absolute; - inset: 0; - z-index: 25; - display: flex; - align-items: center; - justify-content: center; - background: color-mix(in srgb, var(--bg) 30%, transparent); - pointer-events: none; -} -.ge-ai-command { - position: absolute; - left: 50%; - bottom: 14px; - z-index: 18; - width: min(430px, calc(100% - 28px)); - transform: translateX(-50%); - padding: 7px; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 94%, transparent); - box-shadow: 0 8px 22px color-mix(in srgb, #000 26%, transparent); - backdrop-filter: blur(8px); - transition: bottom 0.16s ease, opacity 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; -} -.ge-ai-command-toggle { - width: 100%; - height: 28px; - display: none; - align-items: center; - justify-content: center; - gap: 5px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--accent, var(--red)); - font: inherit; - font-size: 11px; - font-weight: 600; - cursor: pointer; -} -.ge-ai-command-toggle-caret { - opacity: 0.7; - flex-shrink: 0; -} -.ge-ai-command-head { - display: flex; - align-items: center; - min-height: 24px; - padding: 0 2px 5px; - color: var(--fg); -} -.ge-ai-command-title { - margin-left: 5px; - font-size: 11px; - font-weight: 650; -} -.ge-ai-command-scope { - margin-left: 7px; - padding-left: 7px; - border-left: 1px solid var(--border); - color: var(--fg-muted); - font-size: 9px; - line-height: 1; - text-transform: uppercase; -} -.ge-ai-command-collapsed { - bottom: -16px; - width: auto; - min-width: 102px; - padding: 2px 9px; - opacity: 0.32; - border-color: color-mix(in srgb, var(--accent, var(--red)) 18%, var(--border)); - box-shadow: 0 3px 10px color-mix(in srgb, #000 16%, transparent); -} -.ge-canvas-area:hover .ge-ai-command-collapsed, -.ge-ai-command-collapsed:hover, -.ge-ai-command-collapsed:focus-within { - bottom: 10px; - opacity: 0.94; - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 24%, transparent); -} -.ge-ai-command-form { - display: flex; - align-items: center; - gap: 6px; -} -.ge-ai-command-input { - flex: 1 1 auto; - min-width: 0; - height: 32px; - box-sizing: border-box; - padding: 0 9px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--bg) 76%, #000 24%); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.ge-ai-command-input:focus { - outline: none; - border-color: var(--accent, var(--red)); -} -.ge-ai-command-clear { - position: relative; - top: -2px; - width: 26px; - height: 32px; - flex: 0 0 26px; - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: -38px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--fg-muted); - cursor: pointer; - padding: 0; - z-index: 1; -} -.ge-ai-command-clear:hover { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.ge-ai-command-run { - position: relative; - top: -2px; - background: var(--send-btn-bg, var(--red)); - color: #fff; - border: none; - border-radius: 8px; - min-width: 32px; - width: 32px; - height: 32px !important; - padding: 0; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - transition: background 0.25s, opacity 0.12s; - overflow: hidden; -} -.ge-ai-command-run:hover { - background: var(--send-btn-hover, color-mix(in srgb, var(--red) 80%, white)); -} -.ge-ai-command-busy .ge-ai-command-run { - opacity: 0.58; - cursor: wait; -} -.ge-ai-command-busy .ge-ai-command-run svg { - display: none; -} -.ge-ai-command-close { - width: 28px; - height: 32px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border: 0; - border-radius: 8px; - background: transparent; - color: var(--fg); - opacity: 0.52; - cursor: pointer; - padding: 0; - margin-left: auto; -} -.ge-ai-command-close:hover { - opacity: 0.9; - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.ge-ai-command-suggestions { - position: absolute; - left: 7px; - right: 7px; - bottom: calc(100% - 2px); - z-index: 2; - display: flex; - flex-direction: column; - gap: 2px; - max-height: 236px; - overflow: auto; - padding: 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 97%, #000 3%); - box-shadow: 0 10px 28px color-mix(in srgb, #000 28%, transparent); -} -.ge-ai-command-suggestion { - width: 100%; - display: grid; - grid-template-columns: 38px minmax(82px, auto) minmax(0, 1fr); - align-items: center; - gap: 8px; - min-height: 28px; - padding: 4px 7px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--fg); - text-align: left; - cursor: pointer; - font: inherit; -} -.ge-ai-command-suggestion:hover, -.ge-ai-command-suggestion.active { - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); -} -.ge-ai-command-suggestion-kind { - min-width: 0; - overflow: hidden; - color: var(--accent, var(--red)); - font-size: 8px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; -} -.ge-ai-command-suggestion-main { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 11px; - font-weight: 650; -} -.ge-ai-command-suggestion-hint { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--fg-muted); - font-size: 10px; -} diff --git a/static/css/11-tasks.css b/static/css/11-tasks.css new file mode 100644 index 000000000..1ff602fa3 --- /dev/null +++ b/static/css/11-tasks.css @@ -0,0 +1,1972 @@ +/* 11-tasks - 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. + */ +/* Snapped/narrow Settings window: move the tab rail to the top. Viewport media + alone misses desktop right-half snapping, where the modal is narrow but the + browser window is not. */ +@container settings-modal (max-width: 620px) { + .settings-layout { + flex-direction: column; + } + .settings-sidebar { + width: auto; + max-height: none; + flex-direction: row; + border-right: none; + border-bottom: 1px solid var(--border); + overflow-x: auto; + overflow-y: hidden; + padding: 6px; + scrollbar-width: none; + } + .settings-sidebar::-webkit-scrollbar { + display: none; + } + .settings-sidebar-divider, + .settings-sidebar-label { + display: none; + } + .settings-nav-item { + flex: 0 0 auto; + padding: 6px 10px; + font-size: 11px; + } + .settings-panels { + padding: 12px 14px; + } +} + +/* ── Entrance Animations ── */ + +/* Welcome name — left-to-right wipe with a touch of horizontal stretch. + Fires on initial render via the .welcome-name rule; restarts on Nobody⇄ + Odysseus toggle via JS reflow trick. */ +.welcome-name { + transform-origin: left center; + animation: welcome-name-reveal 0.55s cubic-bezier(0.34, 1.32, 0.55, 1) both; +} +/* Hold the welcome-screen entrance animations until the app has finished its + initial load (fonts loaded + layout settled). Running them mid-load made the + splash jump/flicker as the page reflowed ("haywire"). Until body.welcome-ready + is set by JS, the splash sits in its final, static state (no flash, since the + non-animated state IS the resting state); adding the class then plays the + entrance once, cleanly. */ +body:not(.welcome-ready) #welcome-screen, +body:not(.welcome-ready) .welcome-name { + animation: none !important; +} +/* Hold the splash invisible (the entrance's start state) until ready, so when + the animation is released it fades/wipes in smoothly instead of flashing + from fully-visible to the animation's hidden first frame. */ +body:not(.welcome-ready) #welcome-screen { + opacity: 0; +} +@keyframes welcome-name-reveal { + from { + clip-path: inset(0 100% 0 0); + transform: scaleX(0.96); + opacity: 0.5; + } + to { + clip-path: inset(0 0 0 0); + transform: scaleX(1); + opacity: 1; + } +} + +@keyframes welcome-enter { + from { + opacity: 0; + transform: translate(-50%, -45%); + } + to { + opacity: 1; + transform: translate(-50%, -50%); + } +} + +@keyframes msg-enter { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes picker-roll-up { + from { + opacity: 0; + transform: scaleY(0.4) translateY(8px); + } + to { + opacity: 1; + transform: scaleY(1) translateY(0); + } +} +@keyframes picker-roll-down { + from { + opacity: 1; + transform: scaleY(1) translateY(0); + } + to { + opacity: 0; + transform: scaleY(0.4) translateY(8px); + } +} +@keyframes model-picker-refresh-spin { + to { transform: rotate(360deg); } +} + +@keyframes modal-enter { + from { + opacity: 0; + transform: scale(0.95) translateY(8px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} +@keyframes modal-exit { + from { + opacity: 1; + transform: scale(1) translateY(0); + } + to { + opacity: 0; + transform: scale(0.97) translateY(6px); + } +} +.modal-content.modal-closing { + animation: modal-exit 0.18s ease-in both; +} + +#cookbook-modal .modal-content.cookbook-modal-entering { + animation: cookbook-modal-enter 0.28s cubic-bezier(0.22, 1.35, 0.36, 1) both; +} +/* Mobile: real bottom-sheet slide-up — the existing 12px translate is + too subtle on a phone, the modal effectively just appeared. */ +@media (max-width: 768px) { + #cookbook-modal .modal-content.cookbook-modal-entering { + animation: cookbook-modal-enter-mobile 0.32s cubic-bezier(0.22, 1, 0.36, 1) both; + } +} +#cookbook-modal .modal-content:not(.cookbook-modal-entering):not(.modal-closing) { + animation: none; +} +#cookbook-modal .modal-content.modal-closing { + animation: modal-exit 0.18s cubic-bezier(0.4, 0, 1, 1) both; +} + +/* Per-token streaming fade — new words materialize */ +.token-new { + animation: token-fade 0.4s ease-out both; +} + +@keyframes token-fade { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Disable entrance animations when loading chat history (bulk render) */ +.chat-history.no-animate .msg { + animation: none; +} + +/* Prefer reduced motion */ +@media (prefers-reduced-motion: reduce) { + .msg, + .modal-content, + #welcome-screen, + .toast, + .token-new { + animation: none !important; + transition: none !important; + } +} + +@media (min-width: 769px) { + #tool-tasks-btn #assistant-notif-dot.sidebar-notif-dot, + #tool-tasks-btn.task-failure-pending::after, + #tool-tasks-btn.task-completion-pending::after { + transform: translateX(-2px); + } +} +.tasks-modal-content { max-width: 600px; width: min(600px, 92vw); background: var(--bg); font-size: 12px; } +.task-completed-preview-row { + cursor: default; + margin-bottom: 6px; + padding: 8px 10px 10px; + max-height: none; + overflow: visible; +} +.task-completed-preview-head { + display: flex; + align-items: center; + gap: 7px; + min-height: 20px; + font-size: 11px; +} +.task-completed-chat-preview { + padding: 0 0 4px; +} +.task-completed-chat-preview .doclib-chat-preview-messages { + max-height: 220px; + margin-top: 7px; +} +.task-completed-chat-preview .doclib-chat-bubble { + max-width: 92%; +} +.task-completed-chat-preview .doclib-chat-preview-actions { + top: 2px; + margin-top: 4px; +} +.task-completed-preview-row .task-log-time { + margin-left: 8px; +} + +/* Tasks tabs reuse the .memory-tab look. The Brain window's tab bar is + full-bleed (its underline spans the whole modal width). The Tasks bar is a + direct child of .modal-content (padding:10px), so cancel that padding with + negative side margins to span edge-to-edge, then re-inset the tabs by 10px + so they line up with the rest of the modal content — matching the Brain bar. */ +.tasks-modal-content .tasks-tabs { + margin: -2px -10px 8px; + padding: 0 10px; +} + +/* Activity log — compact by default. Click the row to expand body+actions. */ +.task-log-row { + --cat-hue: 220; + border: 1px solid var(--border); + border-radius: 8px; + padding: 4px 10px; + margin-bottom: 3px; + background: color-mix(in srgb, var(--fg) 2%, transparent); + position: relative; + cursor: pointer; + transition: padding .12s ease; +} +.task-log-row:hover { + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.task-log-row.task-log-row-error { + border-color: color-mix(in srgb, var(--red, #f87171) 35%, var(--border)); + background: color-mix(in srgb, var(--red, #f87171) 4%, transparent); +} +.task-log-row.task-log-row-error:hover { + background: color-mix(in srgb, var(--red, #f87171) 7%, transparent); +} +.task-log-row.expanded { padding: 8px 10px 6px; } +.task-log-row-head { + display: flex; + align-items: center; + gap: 8px; + font-size: 11px; + margin-bottom: 0; +} +.task-log-row.expanded .task-log-row-head { margin-bottom: 4px; } +/* Collapsed: body + footer hidden. Expanded: visible. */ +.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-body, +.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-actions, +.task-log-row:not(.expanded):not(.is-skipped) .task-log-prompt { + display: none; +} +.task-log-name { + font-weight: 600; + /* Pull saturation from the per-row hue but mix with the foreground so the + title still reads in dark mode. Lightness stays adaptive. */ + color: hsl(var(--cat-hue) 60% 60%); +} +.task-log-failed-tag { + color: var(--red, #f87171); + font-size: 10px; + font-weight: 700; + line-height: 1; + white-space: nowrap; + margin-left: -2px; +} +.task-log-task-icon { + display: inline-flex; + align-items: center; + flex: 0 0 auto; + margin-right: -3px; +} +.task-log-row.task-log-row-error .task-log-name, +.task-log-row.task-log-row-error .task-log-task-icon { + color: var(--red, #f87171); +} +.task-log-task-icon svg { + top: 0 !important; + opacity: 0.46 !important; +} +.task-log-row-head .task-ai-mark { + top: 0; + margin-left: -4px; +} +.task-log-repeat { + font-size: 10px; + font-weight: 500; + line-height: 1; + color: color-mix(in srgb, var(--fg) 62%, transparent); + border-radius: 999px; + padding: 1px 0; + white-space: nowrap; +} +@media (prefers-color-scheme: light) { + .task-log-name { color: hsl(var(--cat-hue) 65% 38%); } +} +/* Per-account prefix in fan-out results — e.g. "[Default] No recent emails" + becomes a compact accent chip + plain message. Makes multi-account activity + rows readable instead of a bracket soup. */ +/* Running / queued status line in the Activity tab — whirlpool + label. */ +.task-log-running { + display: inline-flex; + align-items: center; + gap: 0; + font-size: 11px; + opacity: 0.75; +} +.task-log-running-label { font-style: normal; } + +/* New right-side placement: "Running " sits where the timestamp + normally would, on the head row's right edge. */ +.task-log-running-inline { + display: inline-flex; + align-items: center; + gap: 0; + font-size: 11px; + opacity: 0.75; +} +.task-log-force-run { + appearance: none; + box-sizing: border-box; + border: 0; + background: color-mix(in srgb, var(--fg) 7%, transparent); + box-shadow: none; + color: inherit; + opacity: .82; + margin-left: 7px; + padding: 1px 6px 1px 4px; + height: 18px; + min-height: 16px; + max-height: 18px; + border-radius: 999px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 3px; + font-family: inherit; + font-size: 10px; + line-height: 1; + cursor: pointer; + position: relative; + top: -3px; +} +.task-log-force-run svg, +.task-log-stop svg { + display: block; + flex-shrink: 0; + transform: translateY(1px); +} +.task-log-force-run:hover { + opacity: 1; + background: color-mix(in srgb, var(--green, #50fa7b) 16%, transparent); + color: var(--green, #50fa7b); +} +.task-log-stop { + border: 0; + background: transparent; + color: inherit; + opacity: .72; + padding: 0; + margin-left: 6px; + width: 12px; + height: 12px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + position: relative; + top: -3px; +} +.task-log-stop:hover { + opacity: 1; + color: var(--red, #f87171); +} + +/* Slim single-line row for skipped (noop) runs — body/actions stripped, font + shrunk, opacity dropped. Distinguishes "task ran but had nothing to do" + from a "real" entry without flooding the feed visually. */ +.task-log-row.is-skipped { + padding: 4px 8px; + opacity: 0.45; + font-size: 11px; + background: transparent; +} +.task-log-row.is-skipped .task-log-row-head { padding: 0; } +.task-log-row.is-skipped .task-log-name { font-weight: 500; } +.task-log-row.is-skipped .task-log-skipped-reason { + flex: 1 1 auto; + margin-left: 6px; + font-style: italic; + opacity: 0.85; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.task-log-row.is-skipped:hover { opacity: 0.7; } + +.task-log-account-tag { + display: inline-block; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.02em; + padding: 1px 7px; + margin-right: 4px; + border-radius: 10px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); + vertical-align: 1px; +} +.task-log-status { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; + background: color-mix(in srgb, var(--fg) 30%, transparent); +} +.task-log-status-ok { background: #4ade80; box-shadow: 0 0 6px #4ade80, 0 0 3px #4ade80; } +.task-log-status-error { background: var(--red, #f87171); box-shadow: 0 0 6px var(--red, #f87171), 0 0 3px var(--red, #f87171); } +.task-log-status-info { background: color-mix(in srgb, var(--fg) 25%, transparent); } +.task-log-status-queued { background: #fbbf24; box-shadow: 0 0 0 2px color-mix(in srgb, #fbbf24 30%, transparent); } +.task-log-status-running { background: #60a5fa; animation: task-log-pulse 1.4s ease-in-out infinite; } +.task-log-status-skipped { background: color-mix(in srgb, var(--fg) 20%, transparent); } +.task-log-status-aborted { background: color-mix(in srgb, var(--fg) 30%, transparent); border: 1px dashed color-mix(in srgb, var(--fg) 50%, transparent); } +.task-log-row-body { + font-size: 12px; + line-height: 1.5; + color: color-mix(in srgb, var(--fg) 85%, transparent); + overflow-wrap: anywhere; + word-break: break-word; +} +.task-log-row-body p { margin: 0 0 6px 0; } +.task-log-row-body p:last-child { margin-bottom: 0; } +.task-log-row-body pre { + margin: 4px 0; + padding: 6px 8px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + border-radius: 4px; + white-space: pre-wrap; + word-break: break-word; + font-size: 11px; + max-height: 180px; + overflow: auto; +} +.task-log-row-body code { + font-size: 11px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + padding: 1px 4px; + border-radius: 3px; +} +.task-log-row-body ul, .task-log-row-body ol { + margin: 4px 0; + padding-left: 20px; +} +/* Markdown headings inside a task result — without these, ## headings + render at browser-default huge sizes and blow out the cramped row. */ +.task-log-row-body h1, +.task-log-row-body h2, +.task-log-row-body h3, +.task-log-row-body h4 { + margin: 10px 0 4px; + line-height: 1.3; + font-weight: 650; + color: var(--fg); +} +.task-log-row-body h1 { font-size: 14px; } +.task-log-row-body h2 { font-size: 13px; } +.task-log-row-body h3, +.task-log-row-body h4 { font-size: 12px; opacity: 0.9; } +.task-log-row-body h1:first-child, +.task-log-row-body h2:first-child, +.task-log-row-body h3:first-child { margin-top: 0; } +.task-log-row-body blockquote { + margin: 4px 0; + padding: 2px 0 2px 10px; + border-left: 2px solid color-mix(in srgb, var(--fg) 20%, transparent); + opacity: 0.85; +} +.task-log-row-body a { color: var(--accent, #60a5fa); text-decoration: none; } +.task-log-row-body a:hover { text-decoration: underline; } +.task-log-row-body strong { font-weight: 650; color: var(--fg); } +/* Email-summary tables — render compactly instead of default browser ugliness. */ +.task-log-row-body table { + border-collapse: collapse; + width: 100%; + margin: 6px 0; + font-size: 11px; +} +.task-log-row-body th, +.task-log-row-body td { + border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); + padding: 3px 6px; + text-align: left; + vertical-align: top; +} +.task-log-row-body th { + background: color-mix(in srgb, var(--fg) 5%, transparent); + font-weight: 600; +} +.task-log-row-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-top: 4px; +} +.task-log-open-chat, +.task-log-open-report, +.task-log-copy, +.task-log-clear-cache, +.task-log-run-again, +.task-log-force-run-action { + display: inline-flex; + align-items: center; + gap: 3px; + background: none; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + border-radius: 5px; + color: color-mix(in srgb, var(--fg) 60%, transparent); + font-family: inherit; + font-size: 10px; + padding: 1px 6px; + cursor: pointer; + transition: background .15s, color .15s, border-color .15s; + line-height: 1.4; +} +.task-log-open-chat:hover, +.task-log-open-report:hover, +.task-log-copy:hover, +.task-log-clear-cache:hover, +.task-log-run-again:hover, +.task-log-force-run-action:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--fg) 30%, transparent); + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.task-log-row-actions > .task-log-open-chat, +.task-log-row-actions > .task-log-copy { + margin-left: auto; +} +.task-log-clear-cache svg { + position: relative; + top: 0; +} +/* Activity filter chips — toggle-out model: ON by default (solid), + click to toggle OFF (dimmed + strikethrough) to hide that group. */ +.tasks-af-chip { + font-size: 11px; + padding: 3px 10px; + border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); + border-radius: 12px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + color: var(--fg); + cursor: pointer; + font-family: inherit; + transition: opacity .12s, background .12s, border-color .12s; +} +.tasks-af-chip:hover { border-color: color-mix(in srgb, var(--fg) 32%, transparent); } +.tasks-af-chip.off { + opacity: 0.4; + text-decoration: line-through; + background: transparent; +} +.tasks-af-chip.active { + border-color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + opacity: 1; +} +.tasks-af-chip-error { border-color: color-mix(in srgb, var(--red, #f87171) 45%, transparent); } +.tasks-af-chip-error:not(.off) { color: var(--red, #f87171); } + +/* "Open in Deep Research" is now a regular clickable chat link (a + `#research-` markdown anchor in the assistant's message), rendered as + an a.chat-link. Nudge it 4px right so it sits slightly inset. */ +a.chat-link[href^="#research-"] { + margin-left: 4px; +} +.task-log-row.is-long .task-log-row-body { + max-height: 8.5em; + overflow: hidden; + position: relative; + mask-image: linear-gradient(to bottom, #000 70%, transparent); + -webkit-mask-image: linear-gradient(to bottom, #000 70%, transparent); +} +.task-log-row.is-long.expanded .task-log-row-body { + max-height: none; + overflow: visible; + mask-image: none; + -webkit-mask-image: none; +} +.task-log-row-toggle { + margin-top: 6px; + background: none; + border: none; + font-family: inherit; + font-size: 11px; + cursor: pointer; + padding: 2px 0; + color: transparent; /* hide raw "Show more" text */ +} +.task-log-row:not(.expanded) .task-log-row-toggle::before { content: 'Show more'; } +.task-log-row.expanded .task-log-row-toggle::before { content: 'Show less'; } +.task-log-prompt { + margin-top: 6px; + font-size: 11px; +} +.task-log-prompt summary { + cursor: pointer; + opacity: 0.5; + user-select: none; +} +.task-log-prompt summary:hover { opacity: 0.8; } +.task-log-prompt pre { + margin: 4px 0 0; + padding: 6px 8px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border-radius: 4px; + white-space: pre-wrap; + word-break: break-word; + font-size: 11px; + opacity: 0.75; + max-height: 200px; + overflow: auto; +} + +/* Tasks is the shared theme reference for procedural cards. Keep the tint + together so Skills cannot drift when theme colors change. Hover and + selection remain owned by the shared card state rules above. */ +.task-card, +.skill-card { + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +.task-card { + border: 1px solid var(--border); + border-radius: 8px; + padding: 7px 8px; + margin-bottom: 0; + min-height: 46px; + box-sizing: border-box; + align-items: center; +} +/* When expanded, height grows to fit the detail panel. */ +.task-card.expanded { align-items: flex-start; } +.task-card .memory-item-title { + position: relative; + top: -2px; +} +/* Always show the per-card ⋮ kebab on task cards (the default opacity:0 + + hover-reveal is unreliable on touch and easy to miss on desktop). + pointer-events / z-index belt-and-braces against any sibling overlay, + margin reset undoes the global `.modal-body button { margin-top:6px }` + that was punting the hit-target down. */ +.task-card .memory-item-actions { + opacity: 0.55; + pointer-events: auto; + position: relative; + z-index: 5; +} +.task-card:hover .memory-item-actions, +.task-card .memory-item-actions:hover { opacity: 1; } +.task-card .memory-item-actions .memory-item-btn { + margin: 0 !important; + pointer-events: auto; + position: relative; + z-index: 5; + width: 28px; + min-width: 28px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; +} +/* Make the SVG inside the kebab transparent to pointer events so the click + always lands on the BUTTON itself (some browsers / nested SVG events lose + the click when hitting the inner glyph). */ +.task-card .memory-item-actions .memory-item-btn svg { pointer-events: none; } +.task-ai-mark { + flex: 0 0 auto; + color: var(--accent, var(--red)); + opacity: 0.78; +} +/* Per-card select checkbox rides up to the title line. The "All" checkbox is + #tasks-select-all (not .memory-select-cb), so it stays put. */ +.task-card .memory-select-cb { position: relative; } +.task-card.task-card-deleting { + opacity: 0.68; + pointer-events: none; + position: relative; +} +.task-card-delete-busy { + position: absolute; + right: 11px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + gap: 5px; + height: 22px; + padding: 0 8px; + border-radius: 999px; + color: var(--red); + background: color-mix(in srgb, var(--red) 12%, var(--panel, #111)); + border: 1px solid color-mix(in srgb, var(--red) 24%, transparent); + font-size: 10px; + font-weight: 600; + line-height: 1; + z-index: 7; +} +.task-card-delete-busy-spin { + width: 10px; + height: 10px; + border-radius: 999px; + border: 1.5px solid color-mix(in srgb, currentColor 28%, transparent); + border-top-color: currentColor; + animation: spin 0.8s linear infinite; +} +#tasks-pause-all-btn { position: relative; top: -2px; } +#tasks-pause-all-btn:has(input:disabled) { opacity: 0.4; } +/* Bigger ⋮ dropdown on mobile (its buttons carry inline styles → !important). */ +@media (max-width: 768px) { + .task-card .task-status-badge { + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; + } + .task-card .task-state-badge svg, + .task-card .task-card-run-btn svg { + width: 10px; + height: 10px; + } +} + +.task-card-header { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.task-card-name { + font-size: 12px; + font-weight: 600; +} + +.task-card-schedule { + font-size: 10.5px; + opacity: 0.5; + margin-left: auto; +} + +.task-card-output { + font-size: 10px; + opacity: 0.45; +} + +.task-card-prompt { + font-size: 11px; + opacity: 0.55; + margin: 4px 0; + line-height: 1.4; +} + +.task-card-meta { + font-size: 10.5px; + opacity: 0.45; + margin-bottom: 6px; +} + +.task-card-actions { + display: flex; + gap: 4px; + flex-wrap: wrap; +} + +.task-btn { + font-family: inherit; + font-size: 11px; + padding: 4px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: none; + color: var(--fg); + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s, background 0.15s; +} +.task-btn:hover { opacity: 1; } +.task-btn:disabled { opacity: 0.3; cursor: not-allowed; } + +.task-btn-primary { + background: var(--red); + color: #fff; + border-color: transparent; + opacity: 1; +} +.task-btn-primary:hover { opacity: 0.85; } + +.task-btn-danger { color: var(--red); border-color: color-mix(in srgb, var(--red) 30%, transparent); } +.task-btn-danger:hover { opacity: 1; border-color: var(--red); } + +.tasks-clock { + font-size: 10px; + opacity: 0.35; + text-align: center; + padding: 6px 0 8px; +} + +.task-preset-grid { + display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; +} +.task-preset-card { + display: flex; flex-direction: column; align-items: flex-start; gap: 2px; + padding: 12px; border: 1px solid var(--border); border-radius: 8px; + background: var(--bg); cursor: pointer; transition: all 0.15s; text-align: left; + color: var(--fg); +} +.task-preset-card:hover { + border-color: var(--accent, #cc6a3a); background: color-mix(in srgb, var(--accent, #cc6a3a) 8%, var(--bg)); +} +.task-preset-icon { font-size: 20px; margin-bottom: 2px; } +.task-preset-label { font-size: 12px; font-weight: 600; } +.task-preset-desc { font-size: 10px; opacity: 0.5; line-height: 1.3; } + +.task-form { display: flex; flex-direction: column; gap: 4px; } +.task-form-label { + font-size: 11px; + opacity: 0.55; + font-weight: 500; + display: block; + /* Consistent rhythm: 8px above each label (section gap), 4px below to the + field — same spacing for Name/Prompt/Trigger/Output/Model/Chain. */ + margin: 8px 0 4px; +} +.task-form-input { + font-family: inherit; + font-size: 11px; + padding: 6px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + transition: border-color 0.15s; + box-sizing: border-box; + width: 100%; + max-width: 100%; + min-width: 0; +} +.task-form-input:focus { + outline: none; + border-color: var(--red); +} +.task-form-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } + +.task-form-notif-toggle { + display: flex; + align-items: center; + gap: 8px; + margin: 9px 0 3px; + cursor: pointer; + color: var(--fg); +} +.task-form-notif-toggle input { + position: absolute; + opacity: 0; + pointer-events: none; +} +.task-form-notif-switch { + width: 30px; + height: 16px; + border-radius: 999px; + border: 1px solid var(--border); + background: color-mix(in srgb, var(--fg) 8%, transparent); + position: relative; + flex: 0 0 auto; + transition: background 0.15s, border-color 0.15s; +} +.task-form-notif-toggle input:checked + .task-form-notif-switch { + border-color: color-mix(in srgb, var(--accent, var(--red)) 58%, transparent); + background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); +} +.task-form-notif-copy { + display: flex; + flex-direction: column; + gap: 1px; + font-size: 11px; + font-weight: 500; +} +.task-form-notif-copy span:last-child { + font-size: 10px; + font-weight: 400; + opacity: 0.52; +} + +.task-form-toggle { + display: flex; gap: 4px; +} +.task-toggle-btn { + flex: 1; padding: 4px 8px; font-size: 10px; font-family: inherit; + border: 1px solid var(--border); border-radius: 6px; background: transparent; + color: var(--fg); opacity: 0.4; cursor: pointer; transition: all 0.15s; +} +.task-toggle-btn:hover { opacity: 0.7; border-color: var(--fg); } +.task-toggle-btn.active { + opacity: 1; border-color: var(--red); color: var(--red); + background: color-mix(in srgb, var(--red) 10%, transparent); +} + +.task-time-picker, .task-date-picker { + display: flex; + align-items: center; + gap: 4px; +} +.task-time-select, .task-date-select { + width: auto; + min-width: 48px; + font-size: 11px; +} +.task-time-sep { opacity: 0.4; font-size: 11px; } + +.task-history-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.task-runs-list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; } +.task-run-item { + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 10px; +} +.task-run-item-header { + display: flex; + align-items: center; + gap: 4px; + font-size: 11px; +} +.task-run-time { margin-left: auto; font-size: 10px; opacity: 0.45; } +.task-run-result { + font-size: 11px; + opacity: 0.6; + margin-top: 4px; + line-height: 1.4; + white-space: pre-wrap; + word-break: break-word; +} + +/* ── Settings Form Utilities ── */ + +.settings-row { + display: flex; + align-items: center; + gap: 8px; +} +.settings-col { + display: flex; + flex-direction: column; + gap: 6px; +} +.settings-label { + font-size: 12px; + min-width: 70px; +} +.settings-select { + flex: 1; + /* Allow the select to shrink below its longest option's width inside a flex + row — without this a long model name (e.g. the Vision picker) overflowed + the card off-screen on mobile. */ + min-width: 0; + max-width: 100%; + padding: 5px 8px; + font-family: inherit; + font-size: 12px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + transition: border-color 0.15s; + appearance: none; + -webkit-appearance: none; + -moz-appearance: textfield; + outline: none; +} +input.settings-select::-webkit-outer-spin-button, +input.settings-select::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +input.settings-select::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } +.settings-select:focus { + outline: none; + border-color: var(--red); +} + +/* Deep Research uses long labels and previously fixed-width fields. Keep + every control constrained by the card at intermediate desktop widths too. */ +.admin-card:has(#set-researchSearch) .settings-row { min-width: 0; } +.admin-card:has(#set-researchSearch) .settings-row > .settings-label { flex: 0 0 112px; min-width: 0; } +#set-researchSearch, +#set-researchMaxTokens, +#set-researchRunTimeout, +.settings-row div:has(> #set-researchExtractTimeout), +.settings-row div:has(> #set-researchExtractConcurrency) { + width: auto !important; + max-width: 100% !important; + min-width: 0 !important; + flex: 1 1 0 !important; +} + +@media (max-width: 768px) { + /* Deep Research settings contain desktop-sized inline widths. On mobile, + let each control claim the card width instead of escaping the admin block. */ + .settings-row:has(#set-researchSearch), + .settings-row:has(#set-researchMaxTokens), + .settings-row:has(#set-researchExtractTimeout), + .settings-row:has(#set-researchExtractConcurrency), + .settings-row:has(#set-researchRunTimeout) { + flex-wrap: wrap; + align-items: stretch; + } + .settings-row:has(#set-researchSearch) .settings-label, + .settings-row:has(#set-researchMaxTokens) .settings-label, + .settings-row:has(#set-researchExtractTimeout) .settings-label, + .settings-row:has(#set-researchExtractConcurrency) .settings-label, + .settings-row:has(#set-researchRunTimeout) .settings-label { + flex: 1 1 auto; + } + #set-researchSearch, + #set-researchMaxTokens, + #set-researchRunTimeout, + .settings-row div:has(> #set-researchExtractTimeout), + .settings-row div:has(> #set-researchExtractConcurrency) { + width: 100% !important; + max-width: 100% !important; + flex: 1 1 100% !important; + margin-left: 0 !important; + } +} + +/* Default-chat fallback chain editor. Each row mirrors the primary + endpoint/model selectors, indented under them to read as a chain. */ +.settings-fallbacks { + display: flex; + flex-direction: column; + gap: 6px; +} +.settings-fallbacks:not(:empty) { margin-top: 2px; } +.settings-fallback-row { + display: flex; + align-items: center; + gap: 6px; + padding-left: 12px; + border-left: 2px solid color-mix(in srgb, var(--fg) 12%, transparent); + max-width: 100%; + box-sizing: border-box; +} +.settings-fallback-num { + font-size: 11px; + opacity: 0.4; + min-width: 14px; + text-align: right; +} +.settings-fallback-row .settings-select { + width: 0; + min-width: 0; + min-inline-size: 0; + box-sizing: border-box; + overflow: hidden; + text-overflow: ellipsis; +} +.settings-fallback-row .settings-select:first-of-type { + flex: 0 1 128px; + width: 128px; + max-width: 34%; +} +.settings-fallback-row .settings-select:nth-of-type(2) { + flex: 1 1 0; + width: 0; + max-width: 100%; +} +details.hwfit-serve-advanced > .hwfit-serve-row, +details.hwfit-serve-advanced > .hwfit-serve-checks, +details.hwfit-serve-advanced > .hwfit-serve-cmd-wrap, +details.hwfit-serve-advanced > .hwfit-serve-extra { + margin-top: 0; + margin-bottom: 0; +} +/* Inside Advanced, tighten the per-row grid gap and the label → + select gap so rows of single-line dropdowns don't visually sit + on top of half a row of empty space. Outside Advanced (Core, + etc.) the original ~6px breathing room stays. */ +details.hwfit-serve-advanced > .hwfit-serve-row { + gap: 4px; + row-gap: 4px; +} +details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp, +details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-llamacpp { + margin-top: -20px; + position: relative; + left: 4px; +} +details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp { + column-gap: 8px; + row-gap: 2px; +} +details.hwfit-serve-advanced > .hwfit-llama-placement-row { + margin-top: -6px !important; +} +details.hwfit-serve-advanced > .hwfit-llama-memory-row { + margin-top: -20px !important; +} +.cookbook-llama-cpu-mode details.hwfit-serve-advanced > .hwfit-llama-memory-row { + margin-top: -8px !important; +} +details.hwfit-serve-advanced > .hwfit-llama-batch-row, +details.hwfit-serve-advanced > .hwfit-llama-monitor-row, +details.hwfit-serve-advanced > .hwfit-llama-checks-row { + margin-top: -18px !important; +} +details.hwfit-serve-advanced > .hwfit-diff-settings-row { + row-gap: 2px; +} +details.hwfit-serve-advanced > .hwfit-diff-checks-row, +details.hwfit-serve-advanced > .hwfit-diff-harmonize-row { + margin-top: -10px !important; +} +details.hwfit-serve-advanced > .hwfit-diff-checks-row { + row-gap: 2px; +} +details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-vram-monitor { + gap: 4px !important; + grid-template-columns: max-content minmax(0, 1fr); + white-space: nowrap; +} +details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_block_size"] { + width: calc(100% - 6px); +} +details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_kv_cache_dtype"] { + width: 60px; + min-width: 60px; +} +details.hwfit-serve-advanced > .hwfit-serve-checks { + gap: 4px; + row-gap: 4px; +} +/* Pull the vLLM/SGLang checks row, Extra args, and the trailing + model-specific (Speculative) checks row up tight against the row + above — the previous 4px gap plus per-row baseline padding left a + ~8px gap that read as too airy in the Advanced fold. */ +details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-vllm, +details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-sglang, +details.hwfit-serve-advanced > .hwfit-serve-extra { + margin-top: -8px; +} +details.hwfit-serve-advanced > .hwfit-serve-extra { + margin-top: -18px; +} +details.hwfit-serve-advanced > .hwfit-serve-row:last-of-type, +details.hwfit-serve-advanced > .hwfit-serve-checks:last-of-type { + margin-bottom: 0; +} + +.settings-fallback-remove { + flex-shrink: 0; + width: 32px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 55%, transparent); + cursor: pointer; + transition: border-color 0.12s, color 0.12s, background 0.12s; +} +.settings-fallback-remove:hover { + border-color: var(--red); + color: var(--red); + background: color-mix(in srgb, var(--red) 10%, transparent); +} +.settings-fallback-add { + align-self: flex-start; + margin-top: 2px; + font-size: 11px; + padding: 3px 9px; + border: 1px dashed var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 65%, transparent); + cursor: pointer; + transition: border-color 0.12s, color 0.12s; +} +.settings-fallback-add:hover { + border-color: var(--red); + color: var(--red); +} +.settings-input { + flex: 1; + padding: 5px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font-family: inherit; + font-size: 12px; + transition: border-color 0.15s; +} +.settings-input:focus { + outline: none; + border-color: var(--red); +} +/* ── Contacts manager (Settings → Integrations → CardDAV) ── */ +.contacts-add-row { + display: flex; + gap: 6px; + margin-bottom: 8px; + align-items: center; +} +.contacts-list { + display: flex; + flex-direction: column; + gap: 2px; + max-height: 360px; + overflow-y: auto; +} +.contact-row { + padding: 6px 4px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 35%, transparent); +} +.contact-row:last-child { border-bottom: none; } +.contact-row-edit { margin-top: 4px; } + +/* ── Sort Dropdown ── */ + +.sort-dropdown { + position: absolute; + right: 0; + top: 100%; + z-index: 1000; + min-width: 120px !important; + width: max-content; + padding: 4px !important; + margin-top: 4px; + background: var(--panel) !important; + border: 1px solid var(--border) !important; + border-radius: 8px !important; + box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; + backdrop-filter: none !important; +} +.sort-dropdown-item { + cursor: pointer; + padding: 6px 8px 6px 6px !important; + font-size: 11px !important; + border-radius: 6px !important; + border-bottom: none !important; + white-space: nowrap; + transition: background 0.1s; +} +.sort-dropdown-item:hover { + background: color-mix(in srgb, var(--accent) 10%, transparent) !important; +} +.sort-dropdown-item.ai-sort-option { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); +} +.sort-dropdown-item.ai-sort-option .auto-sort-icon { + color: var(--accent, var(--red)); + display: inline-flex; + align-items: center; +} +#auto-sort-sessions-btn { + transform: translateX(-4px); +} +.sort-dropdown-item.ai-sort-option:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent) !important; +} +.sort-dropdown-sep { + border-top: 1px solid var(--border); + margin-top: 2px; + padding-top: 6px; +} + +/* Mobile: bigger taps for the chats sort/funnel menu and the select-mode + bulk-action bar. Also drop the "Rearrange" item — it's a touch-finicky + feature better left to desktop. */ +@media (max-width: 768px) { + /* Nudge the funnel/sort button right on mobile so it doesn't hug the + chevron next to it. */ + #session-sort-btn { transform: translateX(13px); } + #session-sort-dropdown.sort-dropdown { + min-width: 200px !important; + padding: 6px !important; + } + #session-sort-dropdown .sort-dropdown-item { + padding: 12px 14px !important; + font-size: 14px !important; + } + #session-rearrange-toggle { display: none !important; } + + /* Select-mode bar — make Archive / Delete / Cancel buttons + the + Select-all toggle finger-sized. */ + .session-bulk-bar { + padding: 8px 10px; + font-size: 14px; + gap: 10px; + } + .session-bulk-btn { + padding: 10px; + min-width: 44px; + min-height: 44px; + } + .session-bulk-btn svg { width: 20px; height: 20px; } + #session-select-all-dot { font-size: 22px !important; padding: 6px; } + #session-select-all-label { font-size: 14px !important; padding: 4px; } +} + + +/* ── Admin: Built-in tools ── */ +.admin-tool-category { + border: 1px solid var(--border); + border-radius: 6px; + margin-bottom: 6px; + overflow: hidden; + transition: border-color 0.15s; +} +.admin-tool-category:hover { + border-color: color-mix(in srgb, var(--fg) 20%, var(--border)); +} +.admin-tool-cat-header { + font-size: 12px; + font-weight: 500; + padding: 8px 10px; + transition: background 0.15s; +} +.admin-tool-cat-header:hover { + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.admin-tool-cat-body { + border-top: 1px solid var(--border); + padding: 4px 0; +} +.admin-tool-cat-body.hidden { + display: none; +} +.admin-tool-row { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 10px; + padding-right: 28px; /* align toggles with header toggle (chevron 12px + gap 6px + padding) */ + transition: background 0.08s; +} +.admin-tool-row:hover { + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.admin-tool-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 1px; +} +.admin-tool-name { + font-size: 11px; + font-weight: 500; +} +.admin-tool-desc { + font-size: 10px; + opacity: 0.4; +} +.admin-tool-ctx { + font-size: 9px; + opacity: 0.3; + flex-shrink: 0; + min-width: 35px; + text-align: right; + font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; +} + +/* Cookbook serve param hints */ +.hwfit-hint { + display: inline-flex; + align-items: center; + justify-content: center; + width: 12px; + height: 12px; + border-radius: 50%; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + font-size: 8px; + font-weight: 600; + opacity: 0.35; + cursor: help; + margin-left: 2px; + vertical-align: middle; + transition: opacity 0.1s; +} +.hwfit-hint:hover { + opacity: 0.8; + background: color-mix(in srgb, var(--accent) 15%, transparent); +} +.hwfit-dl-dot { + color: var(--green); + font-size: 8px; + margin-left: 4px; + opacity: 0.7; +} +.hwfit-parser-tag { + font-size: 9px; + opacity: 0.4; + background: color-mix(in srgb, var(--fg) 8%, transparent); + padding: 1px 5px; + border-radius: 3px; + margin-left: 4px; + font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; +} + +/* Cookbook output copy — show only on hover */ +.cookbook-output-wrap .cookbook-output-copy { + opacity: 0; + transition: opacity 0.1s; +} + +/* Cached model menu button */ +.hwfit-cached-menu-btn { + background: none; + border: none; + color: var(--fg); + font-size: 16px; + cursor: pointer; + opacity: 0; + padding: 2px 4px; + border-radius: 4px; + transition: opacity 0.1s; + flex-shrink: 0; + position: relative; + top: -2px; +} +.memory-item:hover .hwfit-cached-menu-btn, +.hwfit-cached-item:hover .hwfit-cached-menu-btn { opacity: 0.4; } +.hwfit-cached-menu-btn:hover { opacity: 1 !important; } +.hwfit-cached-menu-btn svg { pointer-events: none; } +@media (max-width: 768px) { + #cookbook-modal .hwfit-cached-menu-btn { + opacity: 0.72 !important; + width: 32px; + height: 32px; + min-width: 32px; + padding: 0; + justify-content: center; + top: -4px; + } + #cookbook-modal .hwfit-cached-menu-btn:active { + opacity: 1 !important; + background: color-mix(in srgb, var(--fg) 9%, transparent); + } +} + +/* Quick run button */ +.cookbook-run-btn { + background: var(--accent, var(--red)) !important; + color: var(--panel) !important; + border-color: var(--accent, var(--red)) !important; + font-weight: 600; +} +.cookbook-run-btn:hover { + opacity: 0.9; +} +#hwfit-cache-scan { + position: relative; + top: 1px; + width: 26px; + height: 24px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +#hwfit-cache-scan.spinning svg { + animation: modelPickerRefreshSpin 0.75s linear infinite; +} +#serve-search { + height: 28px; + min-height: 28px; + font-family: inherit; + font-size: 11px; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: 9px center; + padding-left: 28px; +} +.cookbook-sort-select-wrap { + position: relative; + display: inline-flex; + flex-shrink: 0; + align-items: center; +} +.cookbook-sort-select-wrap > svg { + position: absolute; + left: 9px; + z-index: 1; + pointer-events: none; + opacity: 0.65; + transform: translateY(4px); +} + +/* Cookbook model directory tags */ +.cookbook-modeldir-tag { + font-size: 11px; + padding: 3px 8px; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + display: inline-flex; + align-items: center; + gap: 4px; + font-family: 'Berkeley Mono', 'SF Mono', monospace; +} +.cookbook-modeldir-default { + opacity: 0.5; +} +.cookbook-server-bottom-actions { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 4px; + margin-top: 5px; +} +.cookbook-server-bottom-actions > button { + height: 22px; + box-sizing: border-box; + display: inline-flex; + align-items: center; +} +/* Default-server radio-check in a Settings server title (same look as the + model-dir download target). Active = accent-tinted. */ +.cookbook-srv-default { + cursor: pointer; + opacity: 0.35; + display: inline-flex; + align-items: center; + gap: 3px; + line-height: 1; + transition: opacity 0.12s, color 0.12s; +} +.cookbook-srv-default:hover { opacity: 0.8; } +.cookbook-srv-default.active { opacity: 1; color: var(--accent, var(--red)); } +.cookbook-srv-default-label { font-size: 10px; font-weight: 600; letter-spacing: 0.02em; } +.cookbook-srv-default-icon { + display: inline-flex; + align-items: center; + line-height: 0; +} +.cookbook-srv-default-icon svg { + width: 13px; + height: 13px; +} +.cookbook-srv-default-icon, +.cookbook-srv-default-label { + position: relative; + top: 2px; +} +.email-settings-default, +.email-settings-default .cookbook-srv-default-icon, +.email-settings-default .cookbook-srv-default-label { + position: relative; + top: -2px; +} +/* Download-target toggle inside a model-dir tag */ +.cookbook-modeldir-dl { + cursor: pointer; + opacity: 0.35; + display: inline-flex; + align-items: center; + line-height: 0; + transition: opacity 0.12s, color 0.12s; +} +.cookbook-modeldir-dl:hover { opacity: 0.8; } +.cookbook-modeldir-dl.active { opacity: 1; color: var(--accent, var(--red)); } +/* The tag currently flagged as the download target — clearly highlighted so + it's obvious where downloads land. */ +.cookbook-modeldir-target { + opacity: 1; + color: var(--accent, var(--red)); + font-weight: 600; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); +} +.cookbook-modeldir-rm { + cursor: pointer; + opacity: 0.4; + font-size: 10px; +} +.cookbook-modeldir-rm:hover { + opacity: 1; +} +.cookbook-modeldir-add { + background: none; + border: 1px solid var(--border); + border-radius: 4px; + color: var(--fg); + font-size: 10px; + font-weight: 500; + height: 19.5px; + box-sizing: border-box; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 2px; + position: relative; + top: -3px; + padding: 0 9px; + transition: border-color 0.12s, background 0.12s; +} +.cookbook-modeldir-add:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); } + +/* Cookbook serve optimizations */ +.hwfit-serve-opts { + display: flex; + align-items: center; + gap: 8px; + margin: 4px 0; +} +.hwfit-apply-opts { + font-size: 11px !important; + min-width: auto !important; + white-space: nowrap; +} +.hwfit-opts-desc { + font-size: 9px; + opacity: 0.4; + font-style: italic; +} + +/* Library modal tabs */ +.lib-tabs, +.admin-tabs { + display: flex; + gap: 0; + margin-bottom: 8px; + border-bottom: 1px solid var(--border); +} +.lib-tab, +.admin-tab { + background: none; + border: none; + border-bottom: 2px solid transparent; + color: var(--fg-muted); + font-size: 12px; + font-family: inherit; + padding: 6px 14px; + cursor: pointer; + transition: color 0.1s, border-color 0.1s; +} +.lib-tab:hover, +.admin-tab:hover { color: var(--fg); } +.lib-tab.active, +.admin-tab.active { + color: var(--accent, var(--red)); + border-bottom-color: var(--accent, var(--red)); +} + +/* Cookbook tab count badge */ +.cookbook-tab-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 16px; + height: 16px; + padding: 0 4px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); + color: var(--accent, var(--red)); + font-size: 9px; + font-weight: 700; + margin-left: 4px; + line-height: 1; +} + +/* ── Generate Visual Report button (deep research) ── */ +.view-report-btn { + display: inline-flex; + align-items: center; + gap: 6px; + margin-top: 12px; + padding: 8px 16px; + border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--red) 10%, transparent); + color: var(--fg); + font-family: inherit; + font-size: 0.88em; + font-weight: 500; + transition: background 0.15s ease; + cursor: pointer; +} +.view-report-btn:hover:not(:disabled) { + background: color-mix(in srgb, var(--red) 18%, transparent); +} +.view-report-btn svg { + flex-shrink: 0; +} +.report-btn-wrap { + margin-top: 12px; + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.view-report-btn.chat-about-btn { + border-color: color-mix(in srgb, var(--fg) 22%, transparent); + background: color-mix(in srgb, var(--fg) 6%, transparent); +} +.view-report-btn.chat-about-btn:hover:not(:disabled) { + background: color-mix(in srgb, var(--fg) 12%, transparent); +} +.view-report-btn:disabled { + opacity: 0.6; + cursor: progress; +} +.report-spinner { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.88em; + color: var(--fg); + opacity: 0.7; +} +.report-spinner-text { + font-weight: 500; +} +.report-spinner-dots::after { + content: ''; + animation: report-dots 1.4s steps(4, end) infinite; +} +@keyframes report-dots { + 0% { content: ''; } + 25% { content: '.'; } + 50% { content: '..'; } + 75% { content: '...'; } + 100% { content: ''; } +} + +/* ── Continue research hint ────────────────────────── */ +.continue-research-wrap { + margin-top: 8px; +} +.continue-research-hint { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 0.78em; + color: var(--fg-dim, var(--fg)); + opacity: 0.5; + padding: 4px 0; +} +.continue-research-hint svg { + flex-shrink: 0; + opacity: 0.6; +} + +/* ── Research reconnect & timer ────────────────────── */ +.msg.research-reconnect { + border-left: 3px solid var(--red); + background: color-mix(in srgb, var(--red) 5%, transparent); +} +.research-timer { + font-size: 0.8em; + opacity: 0.6; + margin-top: 4px; + font-family: monospace; + color: var(--fg-dim, var(--fg)); +} + +/* ── Research synapse visualization ─────────────────────────────── + Live SVG graph of an in-flight deep-research run. The query is the + central node; sub-questions branch off per round; sources are leaf + nodes that pop in as they're captured. Pulses indicate activity. */ +.research-synapse { + margin: 6px 0 4px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 24%, var(--border)); + border-radius: 7px; + background: + linear-gradient(color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), + linear-gradient(90deg, color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), + radial-gradient(ellipse at 50% 48%, color-mix(in srgb, var(--accent, var(--red)) 13%, transparent) 0%, transparent 62%), + color-mix(in srgb, var(--panel) 68%, var(--bg)); + background-size: 18px 18px, 18px 18px, auto, auto; + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--fg) 5%, transparent); + overflow: hidden; +} +.research-synapse .rs-stage { + height: 400px; + position: relative; +} +.research-synapse .rs-meta-header { + position: absolute; + z-index: 4; + top: 0; + left: 0; + right: 0; + border-top: 0; + border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + padding: 5px 8px 6px; + background: color-mix(in srgb, var(--panel) 76%, transparent); +} +.research-synapse-ascii-filter > .rs-stage > svg { + display: none; +} +.research-synapse-ascii-filter .rs-ascii-overlay { + position: absolute; + z-index: 3; + inset: 28px 10px 10px; + margin: 0; + overflow: hidden; + pointer-events: none; + color: color-mix(in srgb, var(--accent, var(--red)) 76%, var(--fg)); + font: 600 10px/1.35 ui-monospace, SFMono-Regular, Consolas, monospace; + white-space: pre; + text-align: center; + opacity: 1; + text-shadow: 0 0 7px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); + mix-blend-mode: screen; +} +.research-synapse-compact .rs-meta { padding: 5px 8px 6px; font-size: 10px; } +.research-synapse-compact .rs-label-sub { font-size: 8px; } +.research-synapse .rs-stage-head { + position: absolute; + z-index: 2; + top: 7px; + left: 9px; + right: 9px; + display: flex; + align-items: center; + gap: 5px; + color: var(--fg-dim, var(--fg)); + font: 600 8px/1 ui-monospace, "JetBrains Mono", monospace; + text-transform: uppercase; + pointer-events: none; +} +.research-synapse .rs-live-dot { + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--accent, var(--red)); + box-shadow: 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 75%, transparent); + animation: rs-live 1.8s ease-in-out infinite; +} +.research-synapse .rs-phase-chip { + margin-left: auto; + padding: 3px 5px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, var(--border)); + border-radius: 3px; + background: color-mix(in srgb, var(--accent, var(--red)) 8%, var(--bg)); + color: var(--accent, var(--red)); +} +.research-synapse svg { + display: block; + width: 100%; + height: 100%; +} +.research-synapse .rs-edge { + stroke: color-mix(in srgb, var(--accent, var(--red)) 32%, var(--border)); + stroke-width: 1.35; + fill: none; + opacity: 0.72; +} +.research-synapse .rs-node { + fill: var(--bg); + stroke: var(--accent, var(--red)); + stroke-width: 1.5; + transition: all 0.3s ease; + transform-box: fill-box; + transform-origin: center; +} +.research-synapse .rs-source-index { + fill: var(--fg); + font: 650 4.5px/1 ui-monospace, "JetBrains Mono", monospace; + pointer-events: none; + opacity: 0.88; +} +.research-synapse .rs-node-expanded { animation: rs-hub-expand 0.75s ease-out; } +.research-synapse .rs-node-new { + animation: rs-pop 0.6s ease-out; +} +.research-synapse .rs-pulse { + fill: var(--accent, var(--red)); + opacity: 0; + animation: rs-pulse 2.6s ease-out infinite; + transform-box: fill-box; + transform-origin: center; +} +.research-synapse .rs-label { + fill: var(--fg); + font-size: 10px; + font-family: ui-monospace, "JetBrains Mono", monospace; + pointer-events: none; + opacity: 0.85; +} +.research-synapse .rs-label-sub { + font-size: 9px; + opacity: 0.7; +} +.research-synapse .rs-meta { + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto auto; + align-items: center; + gap: 10px; + padding: 6px 10px 8px; + font-family: ui-monospace, "JetBrains Mono", monospace; + font-size: 11px; + color: var(--fg-dim, var(--fg)); + opacity: 0.92; + border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + background: color-mix(in srgb, var(--panel) 76%, transparent); +} +.research-synapse .rs-meta .rs-status { + display: none; + color: var(--accent, var(--red)); + font-weight: 600; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.research-synapse .rs-meta b { + color: var(--fg); + font-weight: 600; +} +.research-synapse.rs-complete .rs-meta .rs-status { color: var(--color-success, #4caf50); } +.research-synapse.rs-error .rs-meta .rs-status { color: var(--red); } diff --git a/static/css/12-gallery.css b/static/css/12-gallery.css new file mode 100644 index 000000000..16f47e863 --- /dev/null +++ b/static/css/12-gallery.css @@ -0,0 +1,839 @@ +/* 12-gallery - 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. + */ +.research-synapse.rs-phase-reading .rs-phase-chip, +.research-synapse.rs-phase-analyzing .rs-phase-chip { color: var(--gold, #d4a72c); } +.research-synapse.rs-phase-writing .rs-phase-chip { color: var(--color-success, #4caf50); } + +@media (prefers-reduced-motion: reduce) { + .research-synapse .rs-live-dot, + .research-synapse .rs-pulse, + .research-synapse .rs-node-new, + .research-synapse .rs-node-expanded, + .research-synapse .rs-edge-firing { animation: none !important; } +} + +/* ── Raw findings items (inside sources-style box) ── */ +.finding-item { + display: flex; + flex-direction: column; + gap: 4px; +} +.finding-item .source-link { + border-radius: 6px 6px 0 0; +} +.finding-summary { + padding: 6px 8px 8px; + font-size: 0.82em; + line-height: 1.5; + color: var(--fg-dim, var(--fg)); + opacity: 0.85; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); + margin-bottom: 4px; + white-space: pre-wrap; + word-break: break-word; +} +.finding-item:last-child .finding-summary { + border-bottom: none; + margin-bottom: 0; +} + +/* ═══════════════════════════════════════════════════════════ + Gallery Editor + ═══════════════════════════════════════════════════════════ */ + +/* Tab bar */ +.gallery-tabs { + display: flex; + gap: 0; + border-bottom: 1px solid var(--border); + padding: 0 16px; + background: var(--panel); +} +.gallery-tab { + padding: 8px 18px; + background: none; + border: none; + border-bottom: 2px solid transparent; + color: var(--fg); + opacity: 0.6; + cursor: pointer; + font-size: 13px; + font-weight: 500; + transition: opacity 0.15s, border-color 0.15s; +} +.gallery-tab:hover { opacity: 0.85; } +.gallery-tab.active { + opacity: 1; + border-bottom-color: var(--red); +} + +/* Narrow modal tab strips should stay on one row. Resized docked windows can + be much narrower than the viewport, so this cannot live only in mobile media + queries. */ +.cookbook-tabs, +.memory-tabs, +.admin-tabs, +.lib-tabs, +.gallery-tabs, +.preset-tabs { + flex-wrap: nowrap !important; + overflow-x: auto !important; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + overscroll-behavior-x: contain; + scrollbar-width: none; +} +.cookbook-tabs::-webkit-scrollbar, +.memory-tabs::-webkit-scrollbar, +.admin-tabs::-webkit-scrollbar, +.lib-tabs::-webkit-scrollbar, +.gallery-tabs::-webkit-scrollbar, +.preset-tabs::-webkit-scrollbar { + display: none; +} +.cookbook-tabs > *, +.memory-tabs > *, +.admin-tabs > *, +.lib-tabs > *, +.gallery-tabs > *, +.preset-tabs > * { + flex: 0 0 auto; +} +.cookbook-tab, +.memory-tab, +.admin-tab, +.lib-tab, +.gallery-tab, +.preset-tab { + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; + line-height: 1; +} +.gallery-tab { + gap: 6px; +} + +@container (max-width: 360px) { + .cookbook-tab:has(svg), + .memory-tab:has(svg), + .admin-tab:has(svg), + .lib-tab:has(svg), + .gallery-tab:has(svg), + .preset-tab:has(svg) { + width: 34px; + min-width: 34px; + padding-left: 0; + padding-right: 0; + font-size: 0; + } + + .cookbook-tab:has(svg) svg, + .memory-tab:has(svg) svg, + .admin-tab:has(svg) svg, + .lib-tab:has(svg) svg, + .gallery-tab:has(svg) svg, + .preset-tab:has(svg) svg { + width: 14px; + height: 14px; + margin-right: 0 !important; + vertical-align: middle !important; + } + + .memory-tab:has(svg) .memory-count, + .gallery-tab:has(svg) .gallery-tab-label, + .gallery-tab:has(svg) .gallery-tab-close, + .cookbook-tab:has(svg) .cookbook-tab-count, + .preset-tab:has(svg) .preset-count { + display: none !important; + } +} +/* Icon + label layout inside each tab. */ +.gallery-tab { + display: inline-flex; + align-items: center; + gap: 6px; +} +.gallery-tab-icon { + display: inline-flex; + align-items: center; + opacity: 0.85; +} +.gallery-tab.active .gallery-tab-icon { opacity: 1; } +/* Close × on the Edit tab — appears on hover. */ +.gallery-tab-close { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + margin-left: 2px; + border-radius: 4px; + opacity: 0; + transition: opacity 0.12s, background 0.12s; + /* Use a slightly larger glyph at a larger line-height so the × sits + visually centered — the U+00D7 character has uneven em-box metrics. */ + font-size: 16px; + line-height: 16px; + font-weight: 500; + cursor: pointer; + padding-bottom: 2px; + box-sizing: border-box; +} +.gallery-tab:hover .gallery-tab-close, +.gallery-tab.active .gallery-tab-close { opacity: 0.65; } +/* Hide the close × entirely on the Edit tab when there's no edit open. */ +.gallery-tab[data-tab="editor"]:not(.has-edit) .gallery-tab-close { display: none !important; } +.gallery-tab-close:hover { opacity: 1 !important; background: color-mix(in srgb, var(--red) 25%, transparent); color: var(--red); } +/* Inline rename input shown when the Edit tab is double-clicked. */ +.gallery-tab-rename-input { + background: var(--bg); + border: 1px solid var(--red); + border-radius: 3px; + color: var(--fg); + font: inherit; + font-size: 13px; + padding: 1px 4px; + width: 140px; + outline: none; +} + +/* Albums tab — grid of album cards with cover thumbnails. */ +.gallery-albums-container { + padding: 12px 4px; + max-height: calc(92vh - 120px); + overflow-y: auto; + border: 2px dashed transparent; + border-radius: 8px; + transition: border-color 0.15s, background 0.15s; +} +.gallery-settings-container { + padding: 8px 4px; + max-height: calc(92vh - 120px); + overflow-y: auto; +} +.gallery-albums-container.gallery-dragover { + border-color: var(--red); + background: color-mix(in srgb, var(--red) 5%, transparent); +} +.gallery-albums-empty { + display: flex; flex-direction: column; align-items: center; gap: 12px; + padding: 48px 16px; + opacity: 0.7; + font-size: 13px; +} +.gallery-albums-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 12px; +} +.gallery-album-card { + position: relative; + display: flex; + flex-direction: column; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + overflow: hidden; + cursor: pointer; + transition: transform 0.12s, border-color 0.12s, box-shadow 0.12s; +} +.gallery-album-card:hover { + transform: translateY(-2px); + border-color: var(--red); + box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 12%, transparent); +} +.gallery-album-menu-btn { + position: absolute; + top: 6px; right: 6px; + width: 24px; height: 24px; + display: flex; align-items: center; justify-content: center; + background: color-mix(in srgb, var(--bg) 70%, transparent); + color: var(--fg); + border: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + border-radius: 6px; + cursor: pointer; + opacity: 0; + transition: opacity 0.12s, background 0.12s; + z-index: 2; + backdrop-filter: blur(4px); + /* Plain "…" text — the previous SVG was rendering as a flat black + block on some themes when its currentColor didn't contrast. */ + font-size: 16px; + line-height: 1; + font-weight: 600; + padding: 0 0 4px; +} +.gallery-album-card:hover .gallery-album-menu-btn, +.gallery-album-menu-btn:focus-visible { opacity: 1; } +.gallery-album-menu-btn:hover { background: var(--bg); } +.gallery-album-menu-pop { + position: absolute; + top: 34px; right: 6px; + min-width: 140px; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); + display: flex; flex-direction: column; + padding: 4px; + z-index: 3; +} +.gallery-album-menu-pop[hidden] { display: none; } +.gallery-album-cover { + aspect-ratio: 4 / 3; + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + display: flex; align-items: center; justify-content: center; + overflow: hidden; +} +.gallery-album-placeholder { + opacity: 0.4; + font-size: 40px; + display: flex; align-items: center; justify-content: center; + width: 100%; height: 100%; +} +.gallery-album-info { + padding: 8px 10px; + display: flex; flex-direction: column; gap: 2px; +} +.gallery-album-name { + font-size: 13px; + font-weight: 500; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.gallery-album-count { + font-size: 11px; + opacity: 0.55; +} +.gallery-album-card-add { + border-style: dashed; + opacity: 0.7; +} +.gallery-album-card-add:hover { opacity: 1; } +.gallery-album-action-tile { + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); +} + +/* Edit-tab empty state — shown before any image is loaded. */ +.gallery-editor-landing { + display: flex; flex-direction: column; align-items: center; justify-content: center; + gap: 10px; + padding: 28px 16px; + flex: 1; + text-align: center; + color: var(--fg); +} +.gallery-editor-landing h3 { + margin: 0; + font-size: 16px; + font-weight: 600; +} +.gallery-editor-heading { + display: inline-flex; + align-items: center; + gap: 7px; +} +.gallery-editor-heading > svg { opacity: 0.6; flex: 0 0 auto; } +/* "vision model" link in the AI-tagging description → opens Settings. */ +.ge-vision-link { + color: var(--accent, var(--red)); + text-decoration: underline; + text-underline-offset: 2px; + cursor: pointer; +} +.ge-vision-link:hover { opacity: 0.8; } +.ge-alpha-tag { + font-size: 9px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + vertical-align: middle; + padding: 1px 5px; + border-radius: 4px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + position: relative; + top: -1px; +} +.gallery-editor-landing-actions { + display: flex; gap: 10px; + margin-top: 8px; +} +/* Bigger primary action buttons specifically inside the editor landing — + the gallery-wide .gallery-select-btn is a compact toolbar style which is + too small for the empty-state hero. */ +.gallery-editor-landing-actions .gallery-select-btn { + padding: 7px 22px 17px; + font-size: 13px; + opacity: 0.85; +} +/* Visible template picker: every canvas ratio is inspectable without opening + a native menu, and its SVG outline previews the resulting canvas shape. */ +.gallery-editor-template-picker { + width: min(760px, 96%); + margin-top: 10px; +} +.gallery-editor-template-picker h4 { + margin: 0 0 7px; + font-size: 11px; + opacity: 0.55; + text-transform: uppercase; + letter-spacing: 0.06em; +} +.gallery-editor-template-options { + display: grid; + grid-template-columns: repeat(4, minmax(105px, 1fr)); + gap: 6px; +} +.gallery-editor-template-option { + height: 72px; + min-width: 0; + padding: 6px 7px; + display: grid; + grid-template-rows: 28px auto auto; + justify-items: center; + align-items: center; + background: var(--panel); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 5px; + font: inherit; + font-size: 11px; + cursor: pointer; + overflow: hidden; +} +.gallery-editor-template-option svg { color: var(--accent, var(--red)); } +.gallery-editor-template-option:hover, +.gallery-editor-template-option:focus-visible { + outline: none; + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 7%, var(--panel)); +} +.gallery-editor-template-option span { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 600; +} +.gallery-editor-template-option small { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 9px; + opacity: 0.52; +} +@media (max-width: 620px) { + .gallery-editor-landing { + justify-content: flex-start; + gap: 7px; + padding: 18px 12px; + overflow-y: auto; + } + .gallery-editor-landing-actions { + width: 100%; + gap: 6px; + justify-content: center; + } + .gallery-editor-landing-actions .gallery-select-btn { + flex: 1 1 0; + min-width: 0; + max-width: 180px; + padding: 6px 8px 10px; + font-size: 11px; + } + .gallery-editor-template-picker { + width: 100%; + margin-top: 4px; + } + .gallery-editor-template-options { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + /* The common square, widescreen, portrait, and Instagram presets are + enough to start on a phone. Keep the print and 4K presets on desktop. */ + .gallery-editor-template-option:nth-child(n + 5) { + display: none; + } +} +/* Saved-drafts grid on the editor landing. Surfaces every persisted + in-progress project so the user can resume without re-opening the + original photo. Each card: thumbnail + name + last-saved hint + ×. */ +.gallery-editor-drafts { + width: min(720px, 92%); + margin: 28px auto 0; /* centered */ + padding-top: 18px; + border-top: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 10px; +} +.gallery-editor-drafts { + position: relative; +} +.gallery-editor-drafts-loading { + position: absolute; + inset: 30px 0 0 0; + z-index: 4; + display: flex; + align-items: center; + justify-content: center; + background: var(--panel); + border-radius: 6px; + min-height: 80px; +} +.gallery-editor-drafts-header { + display: flex; + align-items: center; + justify-content: center; /* center the title / search / select */ + gap: 8px; + margin-bottom: 8px; +} +.gallery-editor-drafts-title { + margin: 0; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.06em; + opacity: 0.55; + font-weight: 600; + text-align: left; + flex-shrink: 0; +} +.gallery-editor-drafts-search { + flex: 1 1 auto; + min-width: 0; + max-width: none; + height: 26px; /* same thickness as the Select button */ + box-sizing: border-box; + padding: 0 8px; + font-size: 12px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; +} +.gallery-editor-drafts-search:focus { outline: none; border-color: var(--red); } +/* The shared .gallery-select-btn has a -4px top margin + tall asymmetric padding + meant for the main gallery toolbar; in this header it makes the button a + different height / offset from the search box. Normalize so it lines up. */ +.gallery-editor-drafts-header .gallery-select-btn { + margin-top: 0 !important; + height: 26px; /* match the search bar */ + box-sizing: border-box; + padding: 0 10px; + font-size: 12px; + line-height: 1; + flex-shrink: 0; +} + +.cookbook-official-filter { + display: inline-flex; + align-items: center; + gap: 4px; + flex: 0 1 auto; + min-width: 0; + max-width: 100%; + color: var(--fg); + font-size: 10px; + opacity: 0.72; + white-space: nowrap; + position: relative; + top: 1px; +} +.cookbook-official-filter > span:first-child { + position: relative; + top: 1px; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: 9px; +} +.cookbook-official-filter .toggle { + width: 26px; + height: 14px; +} +.cookbook-official-filter .toggle .slider:before { + width: 10px; + height: 10px; + left: 2px; + bottom: 2px; +} +.cookbook-official-filter .toggle input:checked + .slider:before { + transform: translateX(12px); +} +#gallery-editor-drafts-select { margin-left: auto; flex-shrink: 0; } +#gallery-editor-drafts-select.active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); +} +/* Graceful exit when a project card is deleted — fade + shrink before re-render. */ +.gallery-editor-draft-card.gallery-draft-removing { + opacity: 0; + transform: scale(0.92); + transition: opacity 0.24s ease, transform 0.24s ease; + pointer-events: none; +} +/* Draft select dot uses the standard small .gallery-select-dot look (same as the + photo grid) — no oversized white-ring checkbox. */ +.gallery-editor-drafts-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 10px; +} +.gallery-drafts-just-opened > .gallery-editor-draft-card { + animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; +} +.gallery-drafts-just-opened > :nth-child(1) { animation-delay: 0.02s; } +.gallery-drafts-just-opened > :nth-child(2) { animation-delay: 0.04s; } +.gallery-drafts-just-opened > :nth-child(3) { animation-delay: 0.06s; } +.gallery-drafts-just-opened > :nth-child(4) { animation-delay: 0.08s; } +.gallery-drafts-just-opened > :nth-child(5) { animation-delay: 0.10s; } +.gallery-drafts-just-opened > :nth-child(6) { animation-delay: 0.12s; } +.gallery-drafts-just-opened > :nth-child(7) { animation-delay: 0.14s; } +.gallery-drafts-just-opened > :nth-child(8) { animation-delay: 0.16s; } +.gallery-drafts-just-opened > :nth-child(9) { animation-delay: 0.18s; } +.gallery-drafts-just-opened > :nth-child(10) { animation-delay: 0.20s; } +.gallery-drafts-just-opened > :nth-child(11) { animation-delay: 0.22s; } +.gallery-drafts-just-opened > :nth-child(12) { animation-delay: 0.24s; } +.gallery-drafts-just-opened > :nth-child(13) { animation-delay: 0.26s; } +.gallery-drafts-just-opened > :nth-child(14) { animation-delay: 0.28s; } +.gallery-drafts-just-opened > :nth-child(15) { animation-delay: 0.30s; } +.gallery-drafts-just-opened > :nth-child(16) { animation-delay: 0.32s; } +.gallery-drafts-just-opened > :nth-child(17) { animation-delay: 0.34s; } +.gallery-drafts-just-opened > :nth-child(18) { animation-delay: 0.36s; } +.gallery-drafts-just-opened > :nth-child(19) { animation-delay: 0.38s; } +.gallery-drafts-just-opened > :nth-child(20) { animation-delay: 0.40s; } +.gallery-drafts-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } +.gallery-drafts-view-toggle { + display: inline-flex; + position: relative; + top: 0; + height: 24px; + flex: 0 0 auto; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); +} +.gallery-drafts-view-toggle button { + width: 25px; + height: 22px; + margin: 0; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + border: 0; + border-right: 1px solid var(--border); + background: transparent; + color: color-mix(in srgb, var(--fg) 55%, transparent); + cursor: pointer; + line-height: 0; +} +.gallery-drafts-view-toggle button:last-child { border-right: 0; } +.gallery-drafts-view-toggle button:hover { color: var(--fg); } +.gallery-drafts-view-toggle button.active { + height: 22px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-thumb { + width: 44px; + height: 44px; + aspect-ratio: auto; +} +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete { + position: static; + opacity: 0.55; + background: transparent; + color: var(--fg); +} +.gallery-editor-draft-delete-trash { display: none; } +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete-trash { + display: block; + width: 18px; + height: 18px; + position: relative; + top: -2px; +} +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete:hover { + opacity: 1; + color: var(--color-error, #f44); + background: color-mix(in srgb, var(--color-error, #f44) 10%, transparent); +} +.gallery-editor-draft-card { + position: relative; + display: flex; + flex-direction: column; + gap: 4px; + padding: 6px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 70%, transparent); + cursor: pointer; + transition: border-color 0.15s, background 0.15s, transform 0.15s; + text-align: left; +} +.gallery-editor-draft-card:hover { + border-color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--panel) 90%, transparent); +} +.gallery-editor-draft-thumb { + width: 100%; + aspect-ratio: 1; + object-fit: cover; + border-radius: 4px; + background: var(--bg); + display: block; +} +.gallery-editor-draft-thumb-empty { + background: repeating-linear-gradient( + 45deg, + color-mix(in srgb, var(--fg) 4%, transparent), + color-mix(in srgb, var(--fg) 4%, transparent) 6px, + transparent 6px, + transparent 12px + ); +} +.gallery-editor-draft-info { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} +.gallery-editor-draft-name { + font-size: 12px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.gallery-editor-draft-meta { + font-size: 10px; + opacity: 0.55; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.gallery-editor-draft-delete { + position: absolute; + top: -2px; + right: 8px; + width: 22px; + height: 22px; + border-radius: 50%; + border: none; + background: rgba(0, 0, 0, 0.55); + color: #fff; + font-size: 14px; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: opacity 0.12s, background 0.12s; +} +.gallery-editor-draft-delete { + padding-bottom: 2px; +} +.gallery-editor-draft-delete:hover { background: var(--red); } +/* On very narrow phones, give project search its own row instead of + compressing the title and view controls into an overflowing header. */ +@media (max-width: 360px) { + .gallery-editor-drafts-header { + flex-wrap: wrap; + justify-content: flex-start; + gap: 6px; + } + .gallery-editor-drafts-title { + flex: 1 1 auto; + } + .gallery-editor-drafts-search { + order: 10; + flex: 1 1 100%; + width: 100%; + max-width: none; + } + #gallery-editor-drafts-select { + margin-left: 0; + } +} +/* Editor layout */ +.gallery-editor { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + height: 100%; + overflow: hidden; + /* Prevent the editor's own scroll/touch from chaining out to the + gallery modal or the page body — critical on mobile so touching + the canvas doesn't slide the whole page. */ + overscroll-behavior: contain; + touch-action: pan-y; +} +/* Pin the topbar to the top of the editor so its action buttons remain + visible if the gallery modal body itself ends up scrolling. */ +.ge-topbar { position: sticky; top: 0; z-index: 5; } + +/* Top bar */ +.ge-topbar { + display: flex; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: thin; + align-items: center; + justify-content: space-between; + padding: 6px 10px; + background: var(--panel); + border-bottom: 1px solid var(--border); + flex-shrink: 0; + gap: 8px; +} +.ge-topbar-left, .ge-topbar-right { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: 4px; +} +.ge-topbar-right > * { + flex-shrink: 0; +} +.ge-topbar .dropdown[popover] { + position: fixed; + margin: 0; + bottom: auto; + max-height: calc(100vh - 80px); + overflow-y: auto; +} +.ge-draft-status { + flex: 0 1 auto; + min-width: 0; + max-width: 92px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: color-mix(in srgb, var(--fg) 52%, transparent); + font-size: 10px; + line-height: 1; + transition: color 0.15s, opacity 0.15s; +} diff --git a/static/css/style-part-06.css b/static/css/13-image-editor.css similarity index 71% rename from static/css/style-part-06.css rename to static/css/13-image-editor.css index a781aeda8..d759ed7d3 100644 --- a/static/css/style-part-06.css +++ b/static/css/13-image-editor.css @@ -1,14 +1,551 @@ -/* Fragment 7/9 of the former static/style.css. +/* 13-image-editor - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ +.ge-draft-status[data-state="pending"], +.ge-draft-status[data-state="saving"] { color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); } +.ge-draft-status[data-state="saved"] { color: color-mix(in srgb, var(--color-success, #4ade80) 78%, var(--fg)); } +.ge-draft-status[data-state="error"] { color: var(--color-error, #ef4444); } +/* "ALPHA" badge — flags the editor as in-development. */ +.ge-alpha-badge { + flex-shrink: 0; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.1em; + line-height: 1; + padding: 3px 6px; + margin-right: 4px; + border-radius: 4px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + text-transform: uppercase; + user-select: none; + cursor: default; +} +@media (max-width: 768px) { + /* Keep the toolbar tight on mobile — badge stays but shrinks. */ + .ge-alpha-badge { font-size: 8px; padding: 2px 5px; margin-right: 2px; } +} +.ge-diffusion-status { + font-size: 11px; + cursor: pointer; + padding: 2px 8px; + border-radius: 4px; + transition: all 0.15s; + white-space: nowrap; +} +.ge-diffusion-status.online { + color: var(--color-success, #4ade80); +} +.ge-diffusion-status.offline { + color: var(--color-error, #ef4444); + opacity: 0.7; +} +.ge-diffusion-status.offline:hover { + opacity: 1; + background: color-mix(in srgb, var(--color-error) 10%, transparent); +} +.ge-topbar-fill { + display: inline-flex; + align-items: center; + gap: 4px; + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +.ge-topbar-fill:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); + border-color: var(--accent, var(--red)); +} +/* Mask-color swatch in the topbar — small label + circular swatch so + the user can pick a contrasting overlay colour from anywhere. */ +.ge-topbar-mask-color-wrap { + display: inline-flex; + align-items: center; + gap: 4px; + cursor: pointer; +} +.ge-topbar-mask-color-label { + font-size: 10px; + opacity: 0.6; + text-transform: uppercase; + letter-spacing: 0.06em; +} +.ge-topbar-mask-color { + width: 18px; + height: 18px; + flex: 0 0 18px; +} +.ge-topbar-mask-color.cp-swatch-input { + width: 18px; + height: 18px; + flex: 0 0 18px; + border-radius: 50%; + padding: 0; +} +.ge-topbar-sep { + width: 1px; + height: 16px; + background: var(--border); + margin: 0 4px; +} + +/* Editor body (toolbar + canvas + panel) */ +.ge-editor-body { + display: flex; + flex: 1; + min-height: 0; + overflow: hidden; + position: relative; +} +.gallery-editor-container { + flex: 1; + min-height: 0; + display: none; +} + +.ge-toolbar { + display: flex; + flex-direction: column; + gap: 2px; + /* Asymmetric horizontal padding shifts the whole column 4 px left — + buttons, hover backgrounds, active highlight pills, and section + separators all move together. */ + padding: 8px 8px 8px 0; + background: var(--panel); + border-right: 1px solid var(--border); + width: 64px; + flex-shrink: 0; + min-height: 0; + overflow-x: hidden; + overflow-y: auto; + scrollbar-width: none; +} +.ge-toolbar::-webkit-scrollbar { display: none; } +.ge-tool-sep { + font-size: 8px; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-muted); + text-align: center; + padding: 8px 0 4px; + border-top: 1px solid var(--border); + margin-top: 4px; + opacity: 0.6; + flex-shrink: 0; +} +/* All tool buttons share a fixed height so the column reads as a clean + grid. Long labels (e.g. "Remove BG") truncate with ellipsis instead of + wrapping to two lines and making one button taller than the others. */ +.ge-tool-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + padding: 0 2px; + height: 42px; + flex-shrink: 0; + border: none; + background: none; + color: var(--fg); + opacity: 0.6; + cursor: pointer; + border-radius: 6px; + transition: background 0.15s, opacity 0.15s; +} +.ge-tool-btn { position: relative; } +/* Hover background is now a pseudo so we can shift it 2 px left + independently from the button's own bounds (which contain the icon + and label at their already-shifted positions). */ +.ge-tool-btn:hover { opacity: 0.85; } +.ge-tool-btn.active { + opacity: 1; + color: var(--red); + background: none; + /* Padding stays the same as inactive buttons so the icon stays + horizontally aligned with its neighbors. The taller highlight is + drawn by ::before so it can extend up/down without pushing the + button or shifting the icon. */ +} +/* Tool button contents stay above the active highlight pseudo. Also + shifted an additional 2 px left so the icon + label sit further inside + the highlight pill (which still anchors to the button bounds). */ +.ge-tool-btn > * { position: relative; z-index: 1; left: -2px; } +.ge-tool-icon { font-size: 18px; line-height: 1; } +.ge-tool-label { font-size: 10px; line-height: 1.25; } +/* AI badge — same ✦ glyph the Enhance tool uses, pinned to the top- + left of any tool button marked `ai: true`. Same color as the icon + itself (inherits foreground) so it reads as part of the tool, not + a flag. */ +.ge-tool-ai { + position: absolute !important; + top: 1px; + z-index: 2; + color: inherit; + opacity: 0.7; + pointer-events: none; + font-size: 11px; + line-height: 1; + font-weight: 700; + left: 3px !important; +} +.ge-tool-btn.is-ai:hover .ge-tool-ai { opacity: 0.95; } +.ge-tool-btn.is-ai.active .ge-tool-ai { opacity: 1; } +/* Inline ✦ marker used on AI action buttons (Generate, Remove, etc.) + so they read as AI-backed at a glance — same glyph as .ge-tool-ai + in the toolbar, just sitting inline next to the label. */ +.ge-btn-ai-mark { + display: inline-block; + font-size: 11px; + line-height: 1; + font-weight: 700; + opacity: 0.75; + margin-right: 1px; +} + +/* Per-tool clear-selection badge — shows a tiny X in the top-right of + the Lasso / Wand button when each holds a selection. Click clears + the selection without switching tools (handler stops propagation). */ +.ge-tool-clear { + position: absolute !important; + top: 3px; + right: 3px; + width: 14px; + height: 14px; + display: none; + align-items: center; + justify-content: center; + border-radius: 50%; + color: #fff; + background: var(--red); + cursor: pointer; + opacity: 0.95; + z-index: 2; + box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 70%, transparent), + 0 1px 4px color-mix(in srgb, var(--red) 50%, transparent); + transition: opacity 0.12s, transform 0.12s, box-shadow 0.12s; + left: auto !important; +} +.ge-tool-clear:hover { + opacity: 1; + transform: scale(1.15); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 80%, transparent), + 0 2px 6px color-mix(in srgb, var(--red) 60%, transparent); +} + +/* Aspect-ratio placeholder shown while a draft is loading. */ +.ge-canvas-placeholder { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border: 1px dashed color-mix(in srgb, var(--fg) 20%, transparent); + border-radius: 4px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1; +} +/* Canvas area (center) */ +.ge-canvas-area { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + overflow: auto; + background: var(--bg); + position: relative; + min-width: 0; + /* Stop touch interactions inside the editor from dragging the whole + page (or the gallery modal). Touches on the canvas are owned by the + editor's drawing logic — no native pan/zoom. */ + overscroll-behavior: contain; +} +.ge-ruler { + position: absolute; + z-index: 16; + touch-action: none; + user-select: none; +} +.ge-ruler-horizontal { + top: 0; + left: 18px; + right: 0; + width: calc(100% - 18px); + height: 18px; + cursor: row-resize; +} +.ge-ruler-vertical { + top: 18px; + bottom: 0; + left: 0; + width: 18px; + height: calc(100% - 18px); + cursor: col-resize; +} +.ge-ruler-corner { + position: absolute; + top: 0; + left: 0; + z-index: 17; + width: 18px; + height: 18px; + background: var(--panel); + border-right: 1px solid var(--border); + border-bottom: 1px solid var(--border); + box-sizing: border-box; +} +.ge-wand-loading { + position: absolute; + inset: 0; + z-index: 25; + display: flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--bg) 30%, transparent); + pointer-events: none; +} +.ge-ai-command { + position: absolute; + left: 50%; + bottom: 14px; + z-index: 18; + width: min(430px, calc(100% - 28px)); + transform: translateX(-50%); + padding: 7px; + box-sizing: border-box; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--panel, var(--bg)) 94%, transparent); + box-shadow: 0 8px 22px color-mix(in srgb, #000 26%, transparent); + backdrop-filter: blur(8px); + transition: bottom 0.16s ease, opacity 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; +} +.ge-ai-command-toggle { + width: 100%; + height: 28px; + display: none; + align-items: center; + justify-content: center; + gap: 5px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--accent, var(--red)); + font: inherit; + font-size: 11px; + font-weight: 600; + cursor: pointer; +} +.ge-ai-command-toggle-caret { + opacity: 0.7; + flex-shrink: 0; +} +.ge-ai-command-head { + display: flex; + align-items: center; + min-height: 24px; + padding: 0 2px 5px; + color: var(--fg); +} +.ge-ai-command-title { + margin-left: 5px; + font-size: 11px; + font-weight: 650; +} +.ge-ai-command-scope { + margin-left: 7px; + padding-left: 7px; + border-left: 1px solid var(--border); + color: var(--fg-muted); + font-size: 9px; + line-height: 1; + text-transform: uppercase; +} +.ge-ai-command-collapsed { + bottom: -16px; + width: auto; + min-width: 102px; + padding: 2px 9px; + opacity: 0.32; + border-color: color-mix(in srgb, var(--accent, var(--red)) 18%, var(--border)); + box-shadow: 0 3px 10px color-mix(in srgb, #000 16%, transparent); +} +.ge-canvas-area:hover .ge-ai-command-collapsed, +.ge-ai-command-collapsed:hover, +.ge-ai-command-collapsed:focus-within { + bottom: 10px; + opacity: 0.94; + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 24%, transparent); +} +.ge-ai-command-form { + display: flex; + align-items: center; + gap: 6px; +} +.ge-ai-command-input { + flex: 1 1 auto; + min-width: 0; + height: 32px; + box-sizing: border-box; + padding: 0 9px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--bg) 76%, #000 24%); + color: var(--fg); + font: inherit; + font-size: 12px; +} +.ge-ai-command-input:focus { + outline: none; + border-color: var(--accent, var(--red)); +} +.ge-ai-command-clear { + position: relative; + top: -2px; + width: 26px; + height: 32px; + flex: 0 0 26px; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: -38px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--fg-muted); + cursor: pointer; + padding: 0; + z-index: 1; +} +.ge-ai-command-clear:hover { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.ge-ai-command-run { + position: relative; + top: -2px; + background: var(--send-btn-bg, var(--red)); + color: #fff; + border: none; + border-radius: 8px; + min-width: 32px; + width: 32px; + height: 32px !important; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + transition: background 0.25s, opacity 0.12s; + overflow: hidden; +} +.ge-ai-command-run:hover { + background: var(--send-btn-hover, color-mix(in srgb, var(--red) 80%, white)); +} +.ge-ai-command-busy .ge-ai-command-run { + opacity: 0.58; + cursor: wait; +} +.ge-ai-command-busy .ge-ai-command-run svg { + display: none; +} +.ge-ai-command-close { + width: 28px; + height: 32px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border: 0; + border-radius: 8px; + background: transparent; + color: var(--fg); + opacity: 0.52; + cursor: pointer; + padding: 0; + margin-left: auto; +} +.ge-ai-command-close:hover { + opacity: 0.9; + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.ge-ai-command-suggestions { + position: absolute; + left: 7px; + right: 7px; + bottom: calc(100% - 2px); + z-index: 2; + display: flex; + flex-direction: column; + gap: 2px; + max-height: 236px; + overflow: auto; + padding: 5px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--panel, var(--bg)) 97%, #000 3%); + box-shadow: 0 10px 28px color-mix(in srgb, #000 28%, transparent); +} +.ge-ai-command-suggestion { + width: 100%; + display: grid; + grid-template-columns: 38px minmax(82px, auto) minmax(0, 1fr); + align-items: center; + gap: 8px; + min-height: 28px; + padding: 4px 7px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--fg); + text-align: left; + cursor: pointer; + font: inherit; +} +.ge-ai-command-suggestion:hover, +.ge-ai-command-suggestion.active { + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); +} +.ge-ai-command-suggestion-kind { + min-width: 0; + overflow: hidden; + color: var(--accent, var(--red)); + font-size: 8px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} +.ge-ai-command-suggestion-main { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 11px; + font-weight: 650; +} +.ge-ai-command-suggestion-hint { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--fg-muted); + font-size: 10px; +} .ge-ai-command-status { margin-top: 4px; font-size: 10px; @@ -4284,1728 +4821,3 @@ button .spinner-whirlpool { #group-model-picker .memory-item:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); } -#group-model-picker input[type="checkbox"] { - accent-color: var(--accent, var(--red)); -} -#group-model-picker input[type="radio"] { - accent-color: var(--accent, var(--red)); -} -#group-model-picker .btn-primary { - background: var(--accent, var(--red)); - color: #fff; - border: none; - border-radius: 6px; - cursor: pointer; - font-weight: 500; -} -#group-model-picker .btn-primary:disabled { - opacity: 0.4; - cursor: not-allowed; -} -#group-model-picker .btn-primary:hover:not(:disabled) { - filter: brightness(1.1); -} - -/* ── Email document type ── */ -.doc-email-header { - display: flex; - flex-direction: column; - gap: 6px; - padding: 10px 12px; - border-bottom: 1px solid var(--border); - background: var(--bg); - flex-shrink: 0; -} -.email-field { - display: flex; - align-items: center; - gap: 8px; -} -.email-field label { - font-size: 11px; - font-weight: 600; - color: var(--fg); - opacity: 0.5; - min-width: 50px; - text-align: right; -} -.email-field input { - flex: 1; - background: transparent; - border: 1px solid var(--border); - border-radius: 4px; - padding: 5px 8px; - font-size: 13px; - font-family: inherit; - color: var(--fg); - outline: none; -} -.email-field input:focus { - border-color: var(--accent, #4a9eff); -} -.email-actions { - display: flex; - gap: 8px; - justify-content: flex-end; - margin-top: 2px; -} -.email-draft-btn { - background: transparent; - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - padding: 6px 14px; - font-size: 12px; - cursor: pointer; - font-family: inherit; -} -.email-draft-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } -.email-draft-btn:disabled { opacity: 0.5; cursor: not-allowed; } - -/* ── Email inbox list ── */ -.email-body { border-top: 1px solid var(--border); } -.email-folder-bar { padding: 4px 8px; border-bottom: 1px solid var(--border); } -.email-folder-select { - background: transparent; border: 1px solid var(--border); border-radius: 4px; - color: var(--fg); font-size: 11px; padding: 2px 6px; cursor: pointer; width: 100%; height: 26px; -} -.email-item { - display: flex; align-items: flex-start; gap: 8px; padding: 8px 12px; - cursor: pointer; border-bottom: 1px solid var(--border); position: relative; transition: background 0.1s; -} -.email-item:hover { background: var(--hover-bg, rgba(255,255,255,0.03)); } -.email-item-spam:hover { opacity: 0.75; } -.email-tag-spam { - display: inline-flex; align-items: center; gap: 4px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); - color: var(--accent, var(--red)); - border-radius: 10px; padding: 1px 6px; font-size: 9px; font-weight: 600; - text-transform: lowercase; letter-spacing: 0.3px; -} -.email-spam-unflag { - background: none; border: none; color: inherit; cursor: pointer; - padding: 0 2px; font-size: 10px; line-height: 1; opacity: 0.6; -} -.email-spam-unflag:hover { opacity: 1; color: var(--fg); } -/* Unread state is now a subtle icon, not full styling */ -.email-done-check { - display: inline-flex; align-items: center; justify-content: center; - width: 16px; height: 16px; border-radius: 50%; background: transparent; - border: 1.5px solid var(--border); color: transparent; - flex-shrink: 0; cursor: pointer; transition: all 0.15s; - margin-right: 4px; -} -.email-done-check:hover { border-color: var(--accent, #4a9eff); } -.email-done-check.active { background: var(--accent, #4a9eff); border-color: var(--accent, #4a9eff); color: #fff; } -.email-done-check.active svg { display: block; } -.email-done-check svg { display: none; } - - - - -.email-delete-busy > :not(.email-delete-overlay) { - opacity: 0.42; - transition: opacity 0.12s ease; -} - -.email-delete-overlay { - position: absolute; - inset: 0; - z-index: 8; - display: flex; - align-items: center; - justify-content: center; - border-radius: inherit; - background: color-mix(in srgb, var(--panel, var(--bg)) 82%, transparent); - pointer-events: none; -} - -.email-delete-overlay .spinner-whirlpool, -.email-delete-overlay .ai-spinner-whirlpool { - margin: 0 !important; -} - -.doclib-card.email-card-removing { - pointer-events: none !important; - max-height: 0 !important; - padding-top: 0 !important; - padding-bottom: 0 !important; - margin-top: 0 !important; - margin-bottom: 0 !important; - border-color: transparent !important; - opacity: 0; - transform: translateX(-10px) scale(0.985); - transition: - opacity 0.18s ease, - transform 0.22s ease, - max-height 0.23s ease, - padding 0.23s ease, - margin 0.23s ease, - border-color 0.18s ease; -} - -@media (prefers-reduced-motion: reduce) { - .doclib-card.email-card-removing { - transition: opacity 0.08s ease; - transform: none; - } -} - -/* Library card done check — always visible, subtle icon next to title */ -.email-card-done { - display: inline-flex; align-items: center; justify-content: center; - flex-shrink: 0; cursor: pointer; - transition: opacity 0.15s, color 0.15s; - opacity: 0.15; color: var(--fg); -} -.email-card-titlerow .memory-item-title { - flex: 1 1 auto; - min-width: 0; -} -.email-card-titlerow { position: relative; min-width: 0; } -.email-card-status { - display: inline-flex; - align-items: center; - justify-content: flex-end; - gap: 7px; - flex: 0 0 auto; - min-width: max-content; - margin-left: auto; - white-space: nowrap; -} -.email-card-status > * { - flex: 0 0 auto; -} -.email-card-status + .email-card-nav-arrows { - margin-left: 6px; -} -.email-card-status .email-card-nav-arrows { - margin-left: 2px; -} -.email-card-attachment { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 13px; - width: 13px; - height: 18px; -} -/* Hover preview: bright accent when unchecked so the user sees a check coming. - Once active, keep the exact same color on hover so the done state does not - visually flip while the pointer is still over it. */ -.email-card-done:not(.active):hover { - opacity: 0.75 !important; - color: var(--accent-primary, var(--red)); -} -.email-card-done.active { opacity: 0.95; color: var(--accent-primary, var(--red)); } -.email-card-done.active:hover { - opacity: 0.95 !important; - color: var(--accent-primary, var(--red)) !important; -} -.email-card-favorite { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 18px; - width: 18px; - height: 18px; - margin: 0; - padding: 0; - border: 0; - background: transparent; - color: var(--fg); - cursor: pointer; - position: relative; - top: -3px; - opacity: 0.18; - transition: opacity 0.15s, color 0.15s, transform 0.15s; -} -.email-card-favorite:hover, -.email-card-favorite:focus-visible { - opacity: 0.8; - color: var(--accent-primary, var(--red)); -} -.email-card-favorite.active { - opacity: 0.9; - color: var(--accent-primary, var(--red)); -} -@keyframes check-pop { - 0% { transform: scale(1); } - 40% { transform: scale(1.7) rotate(-10deg); } - 70% { transform: scale(1.1) rotate(4deg); } - 100% { transform: scale(1) rotate(0); } -} -@keyframes check-unpop { - 0% { transform: scale(1); opacity: 0.95; } - 35% { transform: scale(0.45) rotate(8deg); opacity: 0.05; } - 65% { transform: scale(0.9); opacity: 0.2; } - 100% { transform: scale(1); } -} - -/* Expanded email preview in library — override .memory-item row layout */ -.doclib-card.email-card-expanded.memory-item { - cursor: default !important; - background: var(--bg) !important; - border: 1px solid var(--border) !important; - display: flex !important; - flex-direction: column !important; - align-items: stretch !important; - /* Fill the available grid height instead of the old hardcoded 70vh — - the modal already caps height (90dvh on mobile, auto on desktop), so - the card just needs to flex into whatever remains. */ - flex: 1 1 auto !important; - height: 100% !important; - min-height: 0 !important; - max-height: none !important; - padding: 0 !important; - overflow: hidden !important; - gap: 0 !important; -} -.doclib-card.email-card-expanded.memory-item > div:first-child { - /* The summary content (subject, date) - acts as the title bar. Left padding - trimmed 14→6px to shift the title 8px left total. */ - padding: 2px 4px 10px !important; /* top trimmed to lift the header text up */ - margin-top: -14px !important; /* pull the whole bar up into the freed space */ - border-bottom: 1px solid var(--border); - flex: 0 0 auto !important; - width: 100%; -} -/* Keep the per-card '...' actions menu visible while the email card is - expanded — we used to hide it and surface a duplicate menu button in - the expanded header, but that was redundant. The bottom menu now - carries the actions in both states. */ -/* When expanded inline, show the subject a touch larger than the 12px card - title (close to the new-tab window header) without being oversized. */ -.doclib-card.email-card-expanded .memory-item-title { - font-size: 14px; - font-weight: 600; -} -/* The sender name is redundant once expanded (the From: row shows it just - below), so drop it and keep only the date — which then sits left-aligned - directly under the subject. Nudge the date 1px right to line up exactly with - the subject text, and up closer to it. */ -.doclib-card.email-card-expanded .email-meta-sender, -.doclib-card.email-card-expanded .email-meta-sep { - display: none; -} -.doclib-card.email-card-expanded .memory-item-meta { - margin-top: -9px !important; -} -.doclib-card.email-card-expanded .email-meta-date { - margin-left: 3px; -} -.email-card-reader { - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; - font-size: 12px; -} -.email-card-reader-loading { - background: color-mix(in srgb, var(--bg) 86%, #000 14%); - border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); - min-height: 240px; -} -.email-card-reader-error { - background: color-mix(in srgb, var(--bg) 86%, #000 14%); - border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); -} -.email-reader-load-error { - display: flex; - min-height: 180px; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 9px; - padding: 24px 16px; - text-align: center; - color: var(--fg); -} -.email-reader-load-error-title { - font-size: 13px; - font-weight: 700; -} -.email-reader-load-error-msg { - max-width: min(460px, 92%); - color: color-mix(in srgb, var(--fg) 62%, transparent); - font-size: 11px; - line-height: 1.35; - overflow-wrap: anywhere; -} -.email-skeleton-line, -.email-skeleton-dot { - display: block; - border-radius: 999px; - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--fg) 12%, transparent) 0%, - color-mix(in srgb, var(--fg) 22%, transparent) 45%, - color-mix(in srgb, var(--fg) 12%, transparent) 90% - ); - background-size: 220% 100%; - animation: email-skeleton-shimmer 1.35s ease-in-out infinite; -} -.email-skeleton-dot { - width: 9px; - height: 9px; - flex: 0 0 auto; -} -.email-list-skeleton { - position: absolute; - inset: 0; - padding: 10px 7px; - display: flex; - flex-direction: column; - gap: 8px; - pointer-events: none; -} -.email-skeleton-row { - display: flex; - align-items: center; - gap: 10px; - min-height: 48px; - padding: 8px 10px; - border: 1px solid color-mix(in srgb, var(--border) 76%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 70%, transparent); -} -.email-skeleton-lines { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 7px; -} -.email-reader-skeleton { - display: flex; - flex-direction: column; - min-height: 100%; - flex: 1; -} -.email-reader-skeleton-header { - min-height: 60px; - padding: 12px 14px; - display: flex; - align-items: flex-start; - gap: 8px; - border-bottom: 1px solid var(--border); - background: color-mix(in srgb, var(--panel) 72%, transparent); -} -.email-reader-skeleton-header .chip { width: 132px; height: 18px; } -.email-reader-skeleton-header .chip.short { width: 78px; } -.email-reader-skeleton-header .action { - width: 38px; - height: 28px; - margin-left: auto; - border-radius: 6px; -} -.email-reader-skeleton-header .action + .action { margin-left: 0; } -.email-reader-skeleton-atts { - display: flex; - gap: 7px; - padding: 8px 14px; - border-bottom: 1px solid var(--border); -} -.email-reader-skeleton-atts .attachment { width: 126px; height: 20px; border-radius: 6px; } -.email-reader-skeleton-atts .attachment.short { width: 88px; } -.email-reader-skeleton-body { - display: flex; - flex-direction: column; - gap: 11px; - padding: 16px 14px; - min-height: 340px; - flex: 1; -} -.email-reader-skeleton-body .body { width: 88%; height: 9px; border-radius: 6px; } -.email-reader-skeleton-body .body.wide { width: 100%; } -.email-reader-skeleton-body .body.medium { width: 94%; } -.email-reader-skeleton-body .body.short { width: 62%; } -.email-reader-skeleton-body .body.gap { - margin-top: 12px; - width: 78%; -} -.email-search-progress-row { - display: flex; - align-items: center; - justify-content: center; - gap: 7px; - min-height: 30px; - padding: 6px 10px; - font-size: 11px; - opacity: 0.65; -} -.email-search-progress-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--accent-primary, var(--red)); - box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); - animation: email-search-progress-pulse 1.2s ease-in-out infinite; -} -@media (prefers-reduced-motion: reduce) { - .email-skeleton-line, - .email-skeleton-dot, - .email-inline-image-skeleton, - .email-inline-image-placeholder.is-loading .email-inline-image-icon, - .email-inline-image-frame.is-loading, - .email-search-progress-dot { - animation: none; - } -} -/* Per-email unread dot in the expanded reader's title row. Background color - stays inline (per-sender hue from _senderColor), but the dot gets a soft - breathing glow + a 4px vertical nudge so it reads as centered against - the row instead of riding the baseline. */ -.email-card-unread-dot { - position: relative; - top: 0; - animation: email-card-unread-breathe 2.2s ease-in-out infinite; -} -.email-reader-header { - display: flex; - flex-direction: row; - /* Top-align so when the user expands To/Cc, the action cluster - stays on the From line above instead of drifting to the middle. */ - align-items: flex-start; - gap: 12px; - /* Taller header so the 44px-tall absolute-positioned action - cluster inside the From row has vertical breathing room. */ - padding: 8px 14px; - min-height: 60px; - border-bottom: 1px solid var(--border); - background: var(--bg); - flex-shrink: 0; - /* Containing block for the absolute action cluster. */ - position: relative; - /* Container so the action-cluster wrap breakpoints below react to - the reader's own width instead of the page viewport. */ - container-type: inline-size; - container-name: emailreader; -} -.email-reader-header > .email-reader-actions { - margin-left: auto; - flex-shrink: 0; -} -/* Meta block stacks the From / To / Cc rows; each row keeps label - tight to its chip(s) on the same line. The recipient-chips inside - scroll horizontally so long lists slide under the floating action - cluster on the right. */ -.email-reader-meta { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; - /* Positioning context for the absolute action cluster. */ - position: relative; -} -.email-reader-meta .recipient-chips { - display: inline-flex; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - gap: 4px; - max-width: 100%; - scrollbar-width: none; - -ms-overflow-style: none; -} -.email-reader-meta .recipient-chips::-webkit-scrollbar { display: none; } -.email-reader-meta-row { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.email-reader-meta-row.email-reader-meta-from { - position: relative; - /* Never wrap "From:" off the chip onto its own line — the chip - span has overflow-x scroll built in, so it shrinks/scrolls - instead of jumping to a new row. */ - flex-wrap: nowrap; - min-width: 0; - /* Reserve space on the right for the 1-row cluster in wide mode. */ - padding-right: 320px; -} -.email-reader-meta > .email-reader-meta-details { - min-width: 0; - padding-right: 320px; -} -.email-reader-meta > .email-reader-actions-inline { - position: absolute; - top: 1px; - right: 0; - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: flex-start; - justify-content: flex-end; - gap: 4px; - z-index: 2; - /* Sit visually above the From baseline like the lifted overlay. */ - margin-top: -9px; -} -/* Recipient chip span keeps at least ~4 characters visible (60px) - before the overflow-scroll kicks in, so the sender / first - recipient name is always partly readable. */ -.email-reader-meta .recipient-chips { - min-width: 60px; -} -/* Medium reader widths: cap the cluster at 3 icons per row so it - never wraps past 2 rows. min-width forces the cluster to reserve - enough room for 3 icons in a row — without it, grid col 2 could - shrink below the cap and the cluster fanned out into 4+ short - rows. */ -@container emailreader (max-width: 450px) { - .email-reader-meta > .email-reader-actions-inline { - /* 3 icons (48px each) + 2 gaps (4px each) = 152px exact. Adding - 6px slack so subpixel rounding can't tip a row from 3 to 2 - icons and fan the cluster into a 3rd row. */ - min-width: 158px; - max-width: 158px; - /* Solid background so the email body content behind the cluster - doesn't peek through the gaps between icons or the slack on - the right edge when the cluster's wrapped to 2 rows. */ - background: var(--bg); - } - .email-reader-meta > .email-reader-actions-inline .memory-toolbar-btn.reader-icon-btn { - width: 48px; - min-width: 48px; - max-width: 48px; - height: 40px; - padding-left: 0; - padding-right: 0; - gap: 2px; - } - .email-reader-meta > .email-reader-actions-inline .reader-btn-label { - display: block; - max-width: 44px; - overflow: hidden; - text-overflow: ellipsis; - font-size: 8px; - line-height: 1; - } - /* Tighten the right-side padding now that the cluster is 158px - wide (instead of the ~308 wide 1-row layout). */ - .email-reader-meta-row.email-reader-meta-from, - .email-reader-meta > .email-reader-meta-details { - padding-right: 170px; - } - /* Cluster wrapped to 2 rows (44px + 4 gap + 44px = 92px) sits - absolute above the From baseline. Set min-height on the meta - (a descendant of the container, more reliable than targeting - the container itself) so the header grows enough to contain - the cluster + 4px of breathing room between the cluster's - bottom and the header bottom. */ - .email-reader-meta { - min-height: 96px; - } -} -/* Very narrow reader: once the chip span has shrunk to its ~4 - character min, the cluster snaps to absolute overlay with a soft - shadow so it visually floats above the truncated chip. */ -@container emailreader (max-width: 380px) { - /* Pane too narrow even for 158px of right-side reservation — kill - the padding so chips use the full width, and let the cluster - overlay via z-index + shadow. */ - .email-reader-meta-row.email-reader-meta-from, - .email-reader-meta > .email-reader-meta-details { - padding-right: 0; - } - .email-reader-meta > .email-reader-actions-inline { - /* Keep the same -7px lift as the medium-width state so the - cluster doesn't slide ~4px DOWN when it transitions into - overlay mode. */ - /* margin-top stays inherited at -7px from the base rule. */ - /* 158px content + 22px padding-left so the inner row still fits - 3 icons cleanly. */ - min-width: 180px; - max-width: 180px; - background: linear-gradient(to right, transparent 0, var(--bg) 18px); - padding-left: 22px; - /* Soft shadow on the left edge so the overlap reads as the - cluster "floating over" the chip. */ - box-shadow: -6px 0 12px -6px rgba(0, 0, 0, 0.25); - } -} -/* Mobile: drop the box-shadow AND the left-edge gradient fade — - either can read as a shadow under the icons on phone screens. - Also lift the cluster 1px more. !important needed because the - @container query overlay rule has the same selector specificity. */ -@media (max-width: 768px) { - .memory-toolbar-btn[data-mode="ai-reply-fast"] { - height: 30px !important; - min-height: 30px !important; - } - .email-reader-meta > .email-reader-actions-inline { - box-shadow: none !important; - background: none !important; - padding-left: 0 !important; - margin-top: -10px; - } -} -/* Docked panes used to flow the cluster under the meta; now they - share the same absolute-top-right + wrap behaviour as undocked. - No docked-specific overrides — the container queries below pick - the right cluster size and overlay state from the reader width. */ -/* Gmail-style chevron — collapsed view shows only From; the chevron - reveals the To/Cc details inline below. */ -.email-reader-meta-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - margin-left: -3px; - padding: 0; - border: none; - background: transparent; - color: inherit; - opacity: 0.55; - cursor: pointer; - border-radius: 4px; - flex-shrink: 0; -} -.email-reader-meta-toggle:hover { opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent); } -.email-reader-meta-toggle svg { transition: transform 0.15s ease; } -.email-reader-meta-toggle.open svg { transform: rotate(180deg); } -.email-reader-meta-toggle.email-reader-cc-toggle { - width: auto; - min-width: 18px; - gap: 3px; - margin-left: 1px; - padding: 0 5px; - color: var(--accent, var(--red)); - font-family: inherit; - font-size: 9px; - font-weight: 600; - line-height: 1; -} -.email-reader-meta-details { - display: flex; - flex-direction: column; - gap: 6px; -} -/* When expanded, To/Cc details live in the header flow. A floating popover - can overlap the folded attachment strip directly beneath the header. */ -.email-reader-meta > .email-reader-meta-details:not([hidden]) { - position: relative; - top: auto; - left: auto; - right: auto; - margin-top: 4px; - background: transparent; - border: 0; - border-radius: 0; - padding: 0; - box-shadow: none; - z-index: 1; -} -/* Reserve a fixed label width so From: / To: / Cc: all align and - their chips start at the same x. */ -.email-reader-meta-row strong { opacity: 0.5; font-weight: 600; flex-shrink: 0; min-width: 36px; } -.email-reader-meta-row > span { - white-space: nowrap; - min-width: 0; -} -/* Recipient chips */ -.recipient-chips { - display: inline-flex !important; flex-wrap: wrap; gap: 4px; - white-space: normal !important; overflow: visible !important; - text-overflow: clip !important; -} -.recipient-chip { - display: inline-flex; align-items: center; - gap: 5px; - padding: 1px 8px; font-size: 10px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--border); - border-radius: 10px; - color: var(--fg); - white-space: nowrap; - cursor: pointer; - transition: background 0.15s, border-color 0.15s, max-width 0.2s; - max-width: 220px; - overflow: hidden; - text-overflow: ellipsis; -} -.recipient-chip-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} -.recipient-chip-copy { - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - top: -2px; - width: 14px; - height: 14px; - padding: 0; - border: none; - background: none; - color: inherit; - opacity: 0.55; - cursor: pointer; - flex: 0 0 auto; -} -.recipient-chip-copy:hover, -.recipient-chip-copy.copied { - opacity: 1; - color: var(--accent-primary, var(--red)); -} -.recipient-chip-copy[hidden] { - display: none !important; -} -.recipient-chip:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); -} -.recipient-chip.expanded { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); - border-color: var(--accent-primary, var(--red)); - max-width: 500px; -} -.recipient-chip.popover-open { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); - border-color: var(--accent-primary, var(--red)); -} -.recipient-chip-popover { - position: fixed; - z-index: 12050; - display: flex; - align-items: center; - gap: 8px; - max-width: min(420px, calc(100vw - 20px)); - padding: 8px 10px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--bg) 96%, var(--fg) 4%); - color: var(--fg); - box-shadow: 0 14px 34px rgba(0, 0, 0, 0.42); - font-size: 12px; - line-height: 1.35; - pointer-events: auto; -} -.recipient-chip-popover-main { - min-width: 0; -} -.recipient-chip-popover-name { - font-weight: 650; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.recipient-chip-popover-detail { - overflow-wrap: anywhere; - opacity: 0.86; -} -.recipient-chip-popover-copy { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 26px; - height: 26px; - padding: 0; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: inherit; - cursor: pointer; -} -.recipient-chip-popover-copy:hover, -.recipient-chip-popover-copy.copied { - color: var(--accent-primary, var(--red)); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, transparent); -} -@container docpane (max-width: 460px) { - .email-reader-header { - flex-direction: column; - gap: 6px; - } - .email-reader-header > .email-reader-actions { - /* On narrow widths fall back to in-flow so it doesn't overlap - the meta entirely. */ - position: static; - align-self: flex-end; - background: none; - padding-left: 0; - } - .email-reader-meta-row:not(.email-reader-meta-from) { - padding-right: 0; - } - .email-reader-meta-row strong { - min-width: 0; - } - .recipient-chip { - max-width: 100%; - } -} -/* Mobile: long recipient lists (To/Cc with many addresses) shouldn't wrap to - N rows and push the body down. Keep them on one row, horizontally scrollable, - no scrollbar chrome. */ -@media (max-width: 768px) { - .recipient-chips { - flex-wrap: nowrap !important; - overflow-x: auto !important; - overflow-y: hidden !important; - scrollbar-width: none; - -webkit-overflow-scrolling: touch; - } - .recipient-chips::-webkit-scrollbar { display: none; } - .recipient-chip { flex-shrink: 0; } -} -.email-reader-actions { - display: flex; flex-direction: column; gap: 4px; align-items: flex-end; - flex-shrink: 0; - margin-top: -4px; -} -/* Two stacked rows: Reply/Reply-all/Forward on the From line, with - Summary/AI-reply/More on a row below. */ -.email-reader-actions-row { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: center; - justify-content: flex-end; - gap: 4px; -} -.email-reader-atts { - display: flex; flex-wrap: wrap; gap: 6px; - padding: 8px 14px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} -.email-reader-body { - font-size: 12px; - line-height: 1.55; - white-space: pre-wrap; - word-wrap: break-word; - overflow-wrap: anywhere; - flex: 1; - overflow-y: auto; - padding: 14px; - min-height: 0; - background: var(--panel); - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--border) 65%, transparent); - /* Use the OS colored emoji font for incoming mail content */ - font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"; - font-variant-emoji: emoji; -} -.email-reader-body.html-body img { max-width: 100%; height: auto; } -.email-reader-body.html-body [style*="max-width:600px"], -.email-reader-body.html-body [style*="max-width: 600px"], -.email-reader-body.html-body [style*="width:600px"], -.email-reader-body.html-body [style*="width: 600px"], -.email-reader-body.html-body table[width="600"] { - max-width: none !important; - width: 100% !important; - margin-left: 0 !important; - margin-right: 0 !important; -} -.email-reader-body.html-body [align="center"] { - margin-left: 0 !important; - margin-right: 0 !important; -} -.email-reader-body .email-inline-image-placeholder { - display: inline-flex; - align-items: center; - gap: 9px; - max-width: min(100%, 360px); - min-height: 58px; - margin: 6px 0; - padding: 8px 10px; - border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 72%, transparent); - color: var(--fg); - vertical-align: middle; -} -.email-inline-image-skeleton { - width: 58px; - height: 42px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 6px; - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--fg) 7%, transparent) 0%, - color-mix(in srgb, var(--fg) 13%, transparent) 45%, - color-mix(in srgb, var(--fg) 7%, transparent) 90% - ); - background-size: 220% 100%; - animation: email-skeleton-shimmer 1.35s ease-in-out infinite; -} -.email-inline-image-icon { - opacity: 0.52; - display: inline-flex; -} -.email-inline-image-placeholder.is-loading .email-inline-image-icon svg { - display: none; -} -.email-inline-image-info { - display: inline-flex; - flex-direction: column; - min-width: 0; - line-height: 1.25; -} -.email-inline-image-title { - font-size: 11px; - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-inline-image-sub { - font-size: 10px; - opacity: 0.58; -} -.email-inline-image-actions { - display: inline-flex; - align-items: center; - gap: 5px; - margin-left: auto; - flex: 0 0 auto; -} -.email-inline-image-btn { - display: inline-flex; - align-items: center; - justify-content: center; - transform: translateY(-2px); - height: 24px; - line-height: 1; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: inherit; - font: inherit; - font-size: 10px; - text-decoration: none; - cursor: pointer; -} -.email-inline-image-btn:hover { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); - color: var(--accent-primary, var(--red)); -} -.email-inline-image-download-btn { - width: 24px; - min-width: 24px; - padding: 0; -} -.email-inline-image-loaded { - max-width: 100%; - height: auto; - border-radius: 6px; -} -.email-inline-image-frame { - display: inline-flex; - max-width: 100%; - min-width: min(360px, 100%); - min-height: 90px; - margin: 6px 0; - align-items: center; - justify-content: center; - border-radius: 8px; - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--fg) 7%, transparent) 0%, - color-mix(in srgb, var(--fg) 13%, transparent) 45%, - color-mix(in srgb, var(--fg) 7%, transparent) 90% - ); - background-size: 220% 100%; -} -.email-inline-image-frame .email-inline-image-loaded { - opacity: 0; - transition: opacity 0.15s ease; -} -.email-inline-image-load-all { - display: flex; - align-items: center; - gap: 8px; - width: 100%; - max-width: 100%; - margin: -4px 0 6px; - padding: 7px 9px; - border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 72%, transparent); - font-size: 11px; -} -.email-inline-image-load-all span { - opacity: 0.68; - margin-right: auto; -} -.email-inline-image-load-all button { - transform: translateY(-4px); - height: 24px; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: inherit; - font: inherit; - font-size: 10px; - cursor: pointer; -} -.email-inline-image-load-all button:hover { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); - color: var(--accent-primary, var(--red)); -} -@media (max-width: 520px) { - .email-reader-body .email-inline-image-placeholder { - display: flex; - width: 100%; - } - .email-inline-image-skeleton { - width: 48px; - } - .email-inline-image-actions { - flex-direction: column; - align-items: stretch; - } -} -.email-reader-body.html-body table { max-width: 100%; border-collapse: collapse; } -/* Gmail Drive chips arrive with inline width/height/border/padding from - Gmail's composer (border:1px solid #ddd, white-ish bg assumed). On a - dark theme that reads as a near-invisible white box. Override with a - theme-aware chip surface so the filename + icon stay visible. */ -.email-reader-body .gmail_chip, -.email-reader-body .gmail_drive_chip { - width: auto !important; - max-width: 100% !important; - height: auto !important; - max-height: none !important; - display: inline-flex !important; - align-items: center !important; - gap: 6px; - padding: 6px 10px !important; - margin: 6px 0 !important; - border: 1px solid var(--border) !important; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.email-reader-body .gmail_chip:hover, -.email-reader-body .gmail_drive_chip:hover { - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)) !important; - background: color-mix(in srgb, var(--accent, var(--red)) 6%, transparent); -} -.email-reader-body .gmail_chip a, -.email-reader-body .gmail_drive_chip a { - color: var(--accent, var(--red)) !important; - display: inline-flex !important; - align-items: center; - gap: 6px; - max-width: 100% !important; -} -.email-reader-body .gmail_chip a > span, -.email-reader-body .gmail_drive_chip a > span { - color: var(--fg) !important; - font-size: 12px; -} -.email-reader-body .gmail_chip img, -.email-reader-body .gmail_drive_chip img { - width: 16px !important; - height: 16px !important; - padding-right: 0 !important; - filter: none; -} - -/* Force any phone/sms/date/address auto-link to inherit the body color so - numbers don't render as bright-blue browser-default text inside email - content. format-detection in turns off detection on iOS, but some - mail clients ship pre-wrapped tel: links — those still need taming. */ -.email-reader-body a[href^="tel:"], -.email-reader-body a[href^="sms:"], -.email-reader-body a[x-apple-data-detectors], -.email-bubble-body a[href^="tel:"], -.email-bubble-body a[href^="sms:"], -.email-bubble-body a[x-apple-data-detectors], -.email-thread-turn-body a[href^="tel:"], -.email-thread-turn-body a[href^="sms:"], -.email-thread-turn-body a[x-apple-data-detectors] { - color: inherit !important; - text-decoration: none !important; - pointer-events: auto; -} -.email-reader-body a, -.email-bubble-body a, -.email-thread-turn-body a { - color: var(--accent-primary, var(--red)); - text-decoration: underline; - overflow-wrap: anywhere; - word-break: normal; - cursor: pointer; -} -.email-reader-body a:hover, -.email-bubble-body a:hover, -.email-thread-turn-body a:hover { opacity: 0.8; } -/* Summary block — same band chrome as Attachments / Signature / Earlier- - thread (no accent tint, no rounded corners, no leading star icon). The - only difference is the label, so all collapsible sections in the email - reader look uniform. */ -.email-summary-panel { - margin: 0 0 12px 0; - padding: 10px 12px; - background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); - border-radius: 6px; -} -.email-summary-header { - display: flex; align-items: center; gap: 5px; - font-size: 10px; font-weight: 600; text-transform: uppercase; - color: var(--accent-primary, var(--red)); opacity: 0.85; - margin-bottom: 6px; letter-spacing: 0.4px; -} -.email-summary-content { - font-size: 12px; line-height: 1.5; color: var(--fg); white-space: pre-wrap; -} -/* Click-to-fold: tap the summary header to collapse/expand. The chevron - flips when collapsed, the content hides + the panel's bottom margin - tightens so a folded summary doesn't take up vertical space. */ -.email-summary-toggle { cursor: pointer; user-select: none; } -.email-summary-panel.collapsed { padding-bottom: 6px; margin-bottom: 6px; } -.email-translation-loading { - display: flex; - align-items: center; - justify-content: center; - min-height: 26px; -} -.email-translation-busy { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 7px; -} -.email-translation-spinner { - display: inline-flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; -} -.email-translation-loading-text { - opacity: 0.68; - font-size: 12px; -} -.email-translation-panel .email-translation-icon { - color: var(--accent-primary, var(--red)); -} - -/* Foldable attachments — same fold-on-click UX as the summary panel. */ -.email-reader-atts-wrap { - display: flex; flex-direction: column; - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} -.email-reader-atts-header { - display: flex; align-items: center; gap: 5px; - padding: 6px 14px; - font-size: 10px; font-weight: 600; text-transform: uppercase; - color: var(--fg); opacity: 0.7; - cursor: pointer; user-select: none; - letter-spacing: 0.4px; -} -.email-attachments-download-all { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - height: 22px; - padding: 0 8px; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); - color: var(--accent-primary, var(--red)); - font: inherit; - font-size: 10px; - font-weight: 700; - text-transform: none; - letter-spacing: 0; - cursor: pointer; - flex: 0 0 auto; -} -.email-attachments-download-all:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 60%, transparent); -} -.email-attachments-download-all .spinner-whirlpool, -.email-attachments-download-all .ai-spinner-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 !important; -} -.email-reader-atts-wrap > .email-reader-atts { - border-bottom: none !important; -} -.email-reader-atts-hidden-note { - padding: 0 14px 6px; - font-size: 10px; - color: var(--fg-muted); - opacity: 0.65; -} - -/* Quote fold = neutral full-width band (matches attachments header). */ -.email-quote-fold { - margin: 0 -14px; - padding: 0; - border: 0; - border-top: 1px solid var(--border) !important; - border-radius: 0; - outline: 0 !important; - box-shadow: none !important; - /* Neutralise any rich-mail HTML that injects a yellow / colored bg - or border onto blockquote ancestors. */ - background: transparent !important; -} -/* Same neutralisation for any element nested inside the fold (the inner - blockquote, paragraphs, divs from the original message). Yellow outlines - in Outlook-style mails almost always come from inline border-color or - box-shadow on these. */ -.email-quote-fold *, -.email-quote-fold *::before, -.email-quote-fold *::after { - outline-color: transparent !important; - box-shadow: none !important; -} -.email-quote-fold blockquote, -.email-quote-fold blockquote[type="cite"], -.email-quote-fold table, -.email-quote-fold div { - border-color: var(--border) !important; - /* Override the .msg blockquote rule that sets a colored left border via - var(--hl-function) — that's where the "yellow" outline was coming from. */ - border-left-color: var(--border) !important; - border-radius: 0 !important; - background: transparent !important; -} -/* JS marks the last .email-quote-fold in each email body with this class, - so it's the only one that gets rounded bottom corners. Avoids :has() - browser-support quirks. */ -.email-quote-fold.last-fold { - border-radius: 0 0 8px 8px; - overflow: hidden; -} -/* ── Threaded reply turns (recursive parser) ── */ -.email-thread-turn-body { - padding: 8px 14px 4px; - font-size: 12px; - line-height: 1.45; -} -/* Nested turns indent slightly + get their own card outline so the - reply hierarchy is obvious at a glance. */ -.email-thread-turn-body .email-thread-turn { - margin: 8px 0 4px; - margin-left: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -/* The original blockquote's left stripe is redundant when each turn is - already a card — drop it inside thread bodies. */ -.email-thread-turn-body blockquote { - border-left: none !important; - margin: 0 !important; - padding: 0 !important; - background: transparent !important; -} -/* Flatten the inner blockquote: no rounded corners, no yellow tint - inherited from rich-mail HTML. Subtle left border + neutral bg. */ -.email-quote-fold blockquote { - margin: 0; - padding: 8px 12px; - border: 0; - border-left: 2px solid var(--border); - border-radius: 0 !important; - background: transparent !important; - color: inherit; -} -/* "From … · Date …" chip appended to the summary line */ -.email-fold-summary-meta { - margin-left: 6px; - font-size: 10px; - font-weight: 500; - text-transform: none; - letter-spacing: 0; - opacity: 0.55; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -/* Sender name is the primary affordance — slightly larger and not all-caps so - "From: Sam" feels like a click target on the person, not a section label. */ -.email-fold-summary-name { - font-size: 11px; - font-weight: 600; - text-transform: none; - letter-spacing: 0.1px; - color: var(--fg); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.email-quote-fold .email-fold-summary { - display: flex; align-items: center; gap: 5px; - padding: 6px 14px; - cursor: pointer; list-style: none; - font-size: 10px; font-weight: 600; text-transform: uppercase; - letter-spacing: 0.4px; - /* Force neutral page colors regardless of any rich-mail HTML wrapping us */ - color: var(--fg) !important; - background: transparent !important; - background-color: transparent !important; - text-shadow: none !important; - font-style: normal !important; - opacity: 0.7; - user-select: none; - border-radius: 0; -} -.email-quote-fold .email-fold-summary > * { - /* Prevent inline-color attrs on summary children (icon SVGs, meta span) - from picking up a forced color from ancestor email HTML. */ - color: inherit !important; - background: transparent !important; -} -.email-quote-fold .email-fold-summary { list-style: none; list-style-type: none; } -.email-quote-fold[open] > *:not(summary) { - padding: 8px 14px 12px 14px; - border-top: 1px solid var(--border); -} - -/* Signature fold = clean full-width band like the attachments header. No - accent overlay, no rounded corners — neutral chrome so it doesn't look - like the AI Summary panel. */ -.email-sig-fold { - margin: 8px 0 0; - padding: 0; - border: 0; - border-top: 1px dashed color-mix(in srgb, var(--fg) 18%, transparent); - background: transparent; - border-radius: 0; -} -.email-sig-fold .email-fold-summary { - display: flex; align-items: center; gap: 5px; - padding: 4px 0; - cursor: pointer; - list-style: none; list-style-type: none; - font-size: 9.5px; font-weight: 600; text-transform: uppercase; - letter-spacing: 0.4px; - color: var(--fg); opacity: 0.55; - user-select: none; -} -.email-sig-fold[open] > *:not(summary) { - padding: 6px 0 0; - border-top: 0; - font-size: 11px; line-height: 1.45; - color: color-mix(in srgb, var(--fg) 75%, transparent); - white-space: pre-wrap; -} - -/* ── Chat-bubble layout for email threads ── - Each parsed turn becomes a bubble. The active account's outgoing - replies align right (mine); everyone else aligns left (theirs). - Bubbles read top→bottom oldest→newest, like a chat. */ -.email-bubbles { - display: flex; - flex-direction: column; - gap: 10px; - padding: 4px 0 8px; - margin: 0 -2px; -} -.email-bubble-row { - display: flex; - align-items: flex-end; - gap: 8px; - width: 100%; - min-width: 0; -} -.email-bubble { - flex: 0 1 auto; - max-width: 90%; - min-width: 0; - padding: 8px 12px; - border: 1px solid var(--border); - background: var(--panel); - font-size: 12.5px; - line-height: 1.5; - word-wrap: break-word; - overflow-wrap: anywhere; - overflow: hidden; -} -.email-bubble-mine .email-bubble { - background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 10%, var(--bg)); - border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 35%, var(--border)); - border-radius: 14px 14px 4px 14px; -} -.email-bubble-theirs .email-bubble { - background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 5%, var(--panel)); - border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 25%, var(--border)); - border-radius: 14px 14px 14px 4px; -} -.email-bubble-head { - display: flex; - align-items: baseline; - gap: 8px; - font-size: 10.5px; - margin-bottom: 4px; - opacity: 0.7; - min-width: 0; -} -.email-bubble-author { - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 50ch; -} -.email-bubble-date { - opacity: 0.7; - font-weight: 400; - white-space: nowrap; -} -.email-bubble-body { - white-space: normal; - /* Reader-controlled typography — override sender's inline styles AND - attribute-based sizing (, , , h1..h6). Weight - and italics still pass through for emphasis. */ - font-family: inherit; - font-size: var(--email-body-size, 13.5px); - line-height: 1.5; -} -/* Heading tags carry browser-default font-size multipliers that bypass - `font-size: inherit` when an inline style was set; pin them too. */ -.email-bubble-body h1, -.email-bubble-body h2, -.email-bubble-body h3, -.email-bubble-body h4, -.email-bubble-body h5, -.email-bubble-body h6, -.email-bubble-body big, -.email-bubble-body small, -.email-bubble-body font { - font-size: inherit !important; - line-height: inherit !important; -} -/* Sender HTML often wraps content in a fixed-width - or . Override so content fills the reader's available - width — otherwise resizing the email window doesn't reflow anything. */ -.email-bubble-body, -.email-bubble-body * { - max-width: 100% !important; -} -.email-bubble-body div[style*="width"], -.email-bubble-body table[width], -.email-bubble-body table { - width: 100% !important; - box-sizing: border-box !important; -} -.email-bubble-body blockquote { - border-left: 2px solid var(--border) !important; - margin: 4px 0 !important; - padding: 4px 10px !important; - background: transparent !important; - border-radius: 0 !important; - color: inherit; -} -.email-bubble-avatar { - flex-shrink: 0; - width: 26px; - height: 26px; - border-radius: 50%; - /* Per-sender color set inline (`style="background:hsl(...)"`); this - fallback only kicks in when JS hasn't assigned one. */ - background: color-mix(in srgb, var(--fg) 14%, transparent); - color: #fff; - text-shadow: 0 1px 0 rgba(0, 0, 0, 0.22); - display: flex; - align-items: center; - justify-content: center; - font-size: 9.5px; - font-weight: 600; - letter-spacing: 0.3px; - user-select: none; -} -/* Sig folds inside a bubble: tone down — no full-bleed band, since the - bubble already provides the chrome. */ -.email-bubble-body .email-sig-fold { - margin: 8px 0 0; - border-top: 1px dashed var(--border); -} -.email-bubble-body .email-sig-fold[open] > *:not(summary), -.email-bubble-body .email-quote-fold[open] > *:not(summary) { - padding: 6px 0 0; - border-top: 0; -} -@media (max-width: 600px) { - .email-bubble { max-width: 96%; } - .email-bubble-avatar { display: none; } -} - -/* ── Schedule Send modal ── */ -.schedule-send-body { - display: flex; - flex-direction: column; - gap: 6px; - padding: 16px 18px 12px; -} -.schedule-send-label { - font-size: 11px; - font-weight: 500; - letter-spacing: 0.3px; - text-transform: uppercase; - color: var(--fg); - opacity: 0.55; - margin-top: 6px; -} -.schedule-send-label:first-child { margin-top: 0; } -.schedule-send-presets { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 6px; - margin-bottom: 4px; -} -.schedule-send-presets .memory-toolbar-btn { - padding: 5px 10px 11px; - font-size: 12px; - justify-content: center; - line-height: 1; -} -.schedule-send-confirm svg { - width: 12px; - height: 12px; - vertical-align: -1px; - margin-right: 6px; -} -.schedule-send-datetime { - font-size: 13px; - padding: 8px 10px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - outline: none; - font-family: inherit; - width: 100%; - box-sizing: border-box; -} -.schedule-send-datetime:focus { - border-color: var(--hl-function); -} -.schedule-send-footer { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 8px; - padding: 10px 18px 14px; - border-top: 1px solid var(--border); - margin-top: 4px; -} -.schedule-send-confirm { - background: var(--accent-primary, var(--red)) !important; - color: #fff !important; - border-color: var(--accent-primary, var(--red)) !important; -} -.schedule-send-confirm:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 85%, white) !important; -} - -/* Senders embed inline colors in their HTML that clash with the app's - theme (black text in dark mode, white backgrounds, yellow highlights, - etc.). Force theme colors on every descendant of the rendered body so - emails inherit the user's chosen palette regardless of what the - sender's mail client emits. */ -.email-reader-body *, -.email-bubble-body * { - color: var(--fg) !important; - -webkit-text-fill-color: currentColor !important; - background-color: transparent !important; - background-image: none !important; -} -.email-reader-body a, -.email-bubble-body a, -.email-thread-turn-body a { - color: var(--hl-function) !important; -} -.email-reader-body a[href^="tel:"], -.email-reader-body a[href^="sms:"], -.email-reader-body a[href^="x-apple-data-detectors:"], -.email-reader-body a[x-apple-data-detectors], -.email-bubble-body a[href^="tel:"], -.email-bubble-body a[href^="sms:"], -.email-bubble-body a[href^="x-apple-data-detectors:"], -.email-bubble-body a[x-apple-data-detectors], -.email-thread-turn-body a[href^="tel:"], -.email-thread-turn-body a[href^="sms:"], -.email-thread-turn-body a[href^="x-apple-data-detectors:"], -.email-thread-turn-body a[x-apple-data-detectors] { - color: inherit !important; - -webkit-text-fill-color: currentColor !important; - text-decoration: none !important; - background-color: transparent !important; - background-image: none !important; - -webkit-background-clip: border-box !important; - background-clip: border-box !important; -} - -/* Prefer local/system emoji fonts; avoid remote font providers. */ -.doc-editor-textarea, .doc-editor-highlight, #doc-editor-code, -.doc-md-preview, .doc-csv-preview { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', monospace; - font-variant-emoji: text; -} -/* Preview panes claim the editor's space so the action footer stays pinned - at the bottom of the pane even when the rendered content is short - (shorter table, single-paragraph markdown, etc). */ -.doc-csv-preview, -.doc-md-preview { - flex: 1 1 auto; - min-height: 0; - overflow: auto; -} - -/* Email documents override: use colored system emoji in compose. */ -.doc-editor-textarea.email-mode, -#doc-editor-code.email-mode, -#doc-editor-code.email-mode .doc-editor-highlight, -#doc-editor-code.email-mode .doc-editor-textarea { - font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla" !important; - font-variant-emoji: emoji; -} - -/* Single-layer rendering for ALL documents (not just email): the highlight - overlay ( with hljs spans) and the transparent textarea use two - different rendering paths internally — the textarea uses the browser's - native form line-breaker, the overlay uses CSS line-breaking — and they - can never be guaranteed to wrap byte-identical no matter how many CSS - properties are pinned. Pick one source of truth: make the textarea its - own visible text, hide the overlay. Trade-off: no syntax highlighting in - the live editor (rendered markdown preview / chat output still has it). - The caret is now glued to the typed text by definition since both are - rendered by the same element. */ -.doc-editor-highlight, -#doc-editor-code.email-mode .doc-editor-highlight { - display: none !important; -} -/* Find bar is open: float the highlight overlay ON TOP of the - textarea with transparent background + transparent text, so only - the solid spans are visible. pointer-events - off so clicks still go through to the textarea below. */ -body.doc-find-active .doc-editor-highlight, -body.doc-find-active #doc-editor-code.email-mode .doc-editor-highlight { - display: block !important; - z-index: 5 !important; - background: transparent !important; - pointer-events: none; -} -body.doc-find-active .doc-editor-highlight, -body.doc-find-active .doc-editor-highlight * { - color: transparent !important; - background: transparent !important; -} diff --git a/static/css/14-email.css b/static/css/14-email.css new file mode 100644 index 000000000..56d1df6df --- /dev/null +++ b/static/css/14-email.css @@ -0,0 +1,4729 @@ +/* 14-email - 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. + */ +#group-model-picker input[type="checkbox"] { + accent-color: var(--accent, var(--red)); +} +#group-model-picker input[type="radio"] { + accent-color: var(--accent, var(--red)); +} +#group-model-picker .btn-primary { + background: var(--accent, var(--red)); + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 500; +} +#group-model-picker .btn-primary:disabled { + opacity: 0.4; + cursor: not-allowed; +} +#group-model-picker .btn-primary:hover:not(:disabled) { + filter: brightness(1.1); +} + +/* ── Email document type ── */ +.doc-email-header { + display: flex; + flex-direction: column; + gap: 6px; + padding: 10px 12px; + border-bottom: 1px solid var(--border); + background: var(--bg); + flex-shrink: 0; +} +.email-field { + display: flex; + align-items: center; + gap: 8px; +} +.email-field label { + font-size: 11px; + font-weight: 600; + color: var(--fg); + opacity: 0.5; + min-width: 50px; + text-align: right; +} +.email-field input { + flex: 1; + background: transparent; + border: 1px solid var(--border); + border-radius: 4px; + padding: 5px 8px; + font-size: 13px; + font-family: inherit; + color: var(--fg); + outline: none; +} +.email-field input:focus { + border-color: var(--accent, #4a9eff); +} +.email-actions { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 2px; +} +.email-draft-btn { + background: transparent; + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 14px; + font-size: 12px; + cursor: pointer; + font-family: inherit; +} +.email-draft-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } +.email-draft-btn:disabled { opacity: 0.5; cursor: not-allowed; } + +/* ── Email inbox list ── */ +.email-body { border-top: 1px solid var(--border); } +.email-folder-bar { padding: 4px 8px; border-bottom: 1px solid var(--border); } +.email-folder-select { + background: transparent; border: 1px solid var(--border); border-radius: 4px; + color: var(--fg); font-size: 11px; padding: 2px 6px; cursor: pointer; width: 100%; height: 26px; +} +.email-item { + display: flex; align-items: flex-start; gap: 8px; padding: 8px 12px; + cursor: pointer; border-bottom: 1px solid var(--border); position: relative; transition: background 0.1s; +} +.email-item:hover { background: var(--hover-bg, rgba(255,255,255,0.03)); } +.email-item-spam:hover { opacity: 0.75; } +.email-tag-spam { + display: inline-flex; align-items: center; gap: 4px; + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); + color: var(--accent, var(--red)); + border-radius: 10px; padding: 1px 6px; font-size: 9px; font-weight: 600; + text-transform: lowercase; letter-spacing: 0.3px; +} +.email-spam-unflag { + background: none; border: none; color: inherit; cursor: pointer; + padding: 0 2px; font-size: 10px; line-height: 1; opacity: 0.6; +} +.email-spam-unflag:hover { opacity: 1; color: var(--fg); } +/* Unread state is now a subtle icon, not full styling */ +.email-done-check { + display: inline-flex; align-items: center; justify-content: center; + width: 16px; height: 16px; border-radius: 50%; background: transparent; + border: 1.5px solid var(--border); color: transparent; + flex-shrink: 0; cursor: pointer; transition: all 0.15s; + margin-right: 4px; +} +.email-done-check:hover { border-color: var(--accent, #4a9eff); } +.email-done-check.active { background: var(--accent, #4a9eff); border-color: var(--accent, #4a9eff); color: #fff; } +.email-done-check.active svg { display: block; } +.email-done-check svg { display: none; } + + + + +.email-delete-busy > :not(.email-delete-overlay) { + opacity: 0.42; + transition: opacity 0.12s ease; +} + +.email-delete-overlay { + position: absolute; + inset: 0; + z-index: 8; + display: flex; + align-items: center; + justify-content: center; + border-radius: inherit; + background: color-mix(in srgb, var(--panel, var(--bg)) 82%, transparent); + pointer-events: none; +} + +.email-delete-overlay .spinner-whirlpool, +.email-delete-overlay .ai-spinner-whirlpool { + margin: 0 !important; +} + +.doclib-card.email-card-removing { + pointer-events: none !important; + max-height: 0 !important; + padding-top: 0 !important; + padding-bottom: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + border-color: transparent !important; + opacity: 0; + transform: translateX(-10px) scale(0.985); + transition: + opacity 0.18s ease, + transform 0.22s ease, + max-height 0.23s ease, + padding 0.23s ease, + margin 0.23s ease, + border-color 0.18s ease; +} + +@media (prefers-reduced-motion: reduce) { + .doclib-card.email-card-removing { + transition: opacity 0.08s ease; + transform: none; + } +} + +/* Library card done check — always visible, subtle icon next to title */ +.email-card-done { + display: inline-flex; align-items: center; justify-content: center; + flex-shrink: 0; cursor: pointer; + transition: opacity 0.15s, color 0.15s; + opacity: 0.15; color: var(--fg); +} +.email-card-titlerow .memory-item-title { + flex: 1 1 auto; + min-width: 0; +} +.email-card-titlerow { position: relative; min-width: 0; } +.email-card-status { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 7px; + flex: 0 0 auto; + min-width: max-content; + margin-left: auto; + white-space: nowrap; +} +.email-card-status > * { + flex: 0 0 auto; +} +.email-card-status + .email-card-nav-arrows { + margin-left: 6px; +} +.email-card-status .email-card-nav-arrows { + margin-left: 2px; +} +.email-card-attachment { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 13px; + width: 13px; + height: 18px; +} +/* Hover preview: bright accent when unchecked so the user sees a check coming. + Once active, keep the exact same color on hover so the done state does not + visually flip while the pointer is still over it. */ +.email-card-done:not(.active):hover { + opacity: 0.75 !important; + color: var(--accent-primary, var(--red)); +} +.email-card-done.active { opacity: 0.95; color: var(--accent-primary, var(--red)); } +.email-card-done.active:hover { + opacity: 0.95 !important; + color: var(--accent-primary, var(--red)) !important; +} +.email-card-favorite { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 18px; + width: 18px; + height: 18px; + margin: 0; + padding: 0; + border: 0; + background: transparent; + color: var(--fg); + cursor: pointer; + position: relative; + top: -3px; + opacity: 0.18; + transition: opacity 0.15s, color 0.15s, transform 0.15s; +} +.email-card-favorite:hover, +.email-card-favorite:focus-visible { + opacity: 0.8; + color: var(--accent-primary, var(--red)); +} +.email-card-favorite.active { + opacity: 0.9; + color: var(--accent-primary, var(--red)); +} +@keyframes check-pop { + 0% { transform: scale(1); } + 40% { transform: scale(1.7) rotate(-10deg); } + 70% { transform: scale(1.1) rotate(4deg); } + 100% { transform: scale(1) rotate(0); } +} +@keyframes check-unpop { + 0% { transform: scale(1); opacity: 0.95; } + 35% { transform: scale(0.45) rotate(8deg); opacity: 0.05; } + 65% { transform: scale(0.9); opacity: 0.2; } + 100% { transform: scale(1); } +} + +/* Expanded email preview in library — override .memory-item row layout */ +.doclib-card.email-card-expanded.memory-item { + cursor: default !important; + background: var(--bg) !important; + border: 1px solid var(--border) !important; + display: flex !important; + flex-direction: column !important; + align-items: stretch !important; + /* Fill the available grid height instead of the old hardcoded 70vh — + the modal already caps height (90dvh on mobile, auto on desktop), so + the card just needs to flex into whatever remains. */ + flex: 1 1 auto !important; + height: 100% !important; + min-height: 0 !important; + max-height: none !important; + padding: 0 !important; + overflow: hidden !important; + gap: 0 !important; +} +.doclib-card.email-card-expanded.memory-item > div:first-child { + /* The summary content (subject, date) - acts as the title bar. Left padding + trimmed 14→6px to shift the title 8px left total. */ + padding: 2px 4px 10px !important; /* top trimmed to lift the header text up */ + margin-top: -14px !important; /* pull the whole bar up into the freed space */ + border-bottom: 1px solid var(--border); + flex: 0 0 auto !important; + width: 100%; +} +/* Keep the per-card '...' actions menu visible while the email card is + expanded — we used to hide it and surface a duplicate menu button in + the expanded header, but that was redundant. The bottom menu now + carries the actions in both states. */ +/* When expanded inline, show the subject a touch larger than the 12px card + title (close to the new-tab window header) without being oversized. */ +.doclib-card.email-card-expanded .memory-item-title { + font-size: 14px; + font-weight: 600; +} +/* The sender name is redundant once expanded (the From: row shows it just + below), so drop it and keep only the date — which then sits left-aligned + directly under the subject. Nudge the date 1px right to line up exactly with + the subject text, and up closer to it. */ +.doclib-card.email-card-expanded .email-meta-sender, +.doclib-card.email-card-expanded .email-meta-sep { + display: none; +} +.doclib-card.email-card-expanded .memory-item-meta { + margin-top: -9px !important; +} +.doclib-card.email-card-expanded .email-meta-date { + margin-left: 3px; +} +.email-card-reader { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + font-size: 12px; +} +.email-card-reader-loading { + background: color-mix(in srgb, var(--bg) 86%, #000 14%); + border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); + min-height: 240px; +} +.email-card-reader-error { + background: color-mix(in srgb, var(--bg) 86%, #000 14%); + border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); +} +.email-reader-load-error { + display: flex; + min-height: 180px; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 9px; + padding: 24px 16px; + text-align: center; + color: var(--fg); +} +.email-reader-load-error-title { + font-size: 13px; + font-weight: 700; +} +.email-reader-load-error-msg { + max-width: min(460px, 92%); + color: color-mix(in srgb, var(--fg) 62%, transparent); + font-size: 11px; + line-height: 1.35; + overflow-wrap: anywhere; +} +.email-skeleton-line, +.email-skeleton-dot { + display: block; + border-radius: 999px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 12%, transparent) 0%, + color-mix(in srgb, var(--fg) 22%, transparent) 45%, + color-mix(in srgb, var(--fg) 12%, transparent) 90% + ); + background-size: 220% 100%; + animation: email-skeleton-shimmer 1.35s ease-in-out infinite; +} +.email-skeleton-dot { + width: 9px; + height: 9px; + flex: 0 0 auto; +} +.email-list-skeleton { + position: absolute; + inset: 0; + padding: 10px 7px; + display: flex; + flex-direction: column; + gap: 8px; + pointer-events: none; +} +.email-skeleton-row { + display: flex; + align-items: center; + gap: 10px; + min-height: 48px; + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--border) 76%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 70%, transparent); +} +.email-skeleton-lines { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 7px; +} +.email-reader-skeleton { + display: flex; + flex-direction: column; + min-height: 100%; + flex: 1; +} +.email-reader-skeleton-header { + min-height: 60px; + padding: 12px 14px; + display: flex; + align-items: flex-start; + gap: 8px; + border-bottom: 1px solid var(--border); + background: color-mix(in srgb, var(--panel) 72%, transparent); +} +.email-reader-skeleton-header .chip { width: 132px; height: 18px; } +.email-reader-skeleton-header .chip.short { width: 78px; } +.email-reader-skeleton-header .action { + width: 38px; + height: 28px; + margin-left: auto; + border-radius: 6px; +} +.email-reader-skeleton-header .action + .action { margin-left: 0; } +.email-reader-skeleton-atts { + display: flex; + gap: 7px; + padding: 8px 14px; + border-bottom: 1px solid var(--border); +} +.email-reader-skeleton-atts .attachment { width: 126px; height: 20px; border-radius: 6px; } +.email-reader-skeleton-atts .attachment.short { width: 88px; } +.email-reader-skeleton-body { + display: flex; + flex-direction: column; + gap: 11px; + padding: 16px 14px; + min-height: 340px; + flex: 1; +} +.email-reader-skeleton-body .body { width: 88%; height: 9px; border-radius: 6px; } +.email-reader-skeleton-body .body.wide { width: 100%; } +.email-reader-skeleton-body .body.medium { width: 94%; } +.email-reader-skeleton-body .body.short { width: 62%; } +.email-reader-skeleton-body .body.gap { + margin-top: 12px; + width: 78%; +} +.email-search-progress-row { + display: flex; + align-items: center; + justify-content: center; + gap: 7px; + min-height: 30px; + padding: 6px 10px; + font-size: 11px; + opacity: 0.65; +} +.email-search-progress-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent-primary, var(--red)); + box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); + animation: email-search-progress-pulse 1.2s ease-in-out infinite; +} +@media (prefers-reduced-motion: reduce) { + .email-skeleton-line, + .email-skeleton-dot, + .email-inline-image-skeleton, + .email-inline-image-placeholder.is-loading .email-inline-image-icon, + .email-inline-image-frame.is-loading, + .email-search-progress-dot { + animation: none; + } +} +/* Per-email unread dot in the expanded reader's title row. Background color + stays inline (per-sender hue from _senderColor), but the dot gets a soft + breathing glow + a 4px vertical nudge so it reads as centered against + the row instead of riding the baseline. */ +.email-card-unread-dot { + position: relative; + top: 0; + animation: email-card-unread-breathe 2.2s ease-in-out infinite; +} +.email-reader-header { + display: flex; + flex-direction: row; + /* Top-align so when the user expands To/Cc, the action cluster + stays on the From line above instead of drifting to the middle. */ + align-items: flex-start; + gap: 12px; + /* Taller header so the 44px-tall absolute-positioned action + cluster inside the From row has vertical breathing room. */ + padding: 8px 14px; + min-height: 60px; + border-bottom: 1px solid var(--border); + background: var(--bg); + flex-shrink: 0; + /* Containing block for the absolute action cluster. */ + position: relative; + /* Container so the action-cluster wrap breakpoints below react to + the reader's own width instead of the page viewport. */ + container-type: inline-size; + container-name: emailreader; +} +.email-reader-header > .email-reader-actions { + margin-left: auto; + flex-shrink: 0; +} +/* Meta block stacks the From / To / Cc rows; each row keeps label + tight to its chip(s) on the same line. The recipient-chips inside + scroll horizontally so long lists slide under the floating action + cluster on the right. */ +.email-reader-meta { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + /* Positioning context for the absolute action cluster. */ + position: relative; +} +.email-reader-meta .recipient-chips { + display: inline-flex; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + gap: 4px; + max-width: 100%; + scrollbar-width: none; + -ms-overflow-style: none; +} +.email-reader-meta .recipient-chips::-webkit-scrollbar { display: none; } +.email-reader-meta-row { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.email-reader-meta-row.email-reader-meta-from { + position: relative; + /* Never wrap "From:" off the chip onto its own line — the chip + span has overflow-x scroll built in, so it shrinks/scrolls + instead of jumping to a new row. */ + flex-wrap: nowrap; + min-width: 0; + /* Reserve space on the right for the 1-row cluster in wide mode. */ + padding-right: 320px; +} +.email-reader-meta > .email-reader-meta-details { + min-width: 0; + padding-right: 320px; +} +.email-reader-meta > .email-reader-actions-inline { + position: absolute; + top: 1px; + right: 0; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + justify-content: flex-end; + gap: 4px; + z-index: 2; + /* Sit visually above the From baseline like the lifted overlay. */ + margin-top: -9px; +} +/* Recipient chip span keeps at least ~4 characters visible (60px) + before the overflow-scroll kicks in, so the sender / first + recipient name is always partly readable. */ +.email-reader-meta .recipient-chips { + min-width: 60px; +} +/* Medium reader widths: cap the cluster at 3 icons per row so it + never wraps past 2 rows. min-width forces the cluster to reserve + enough room for 3 icons in a row — without it, grid col 2 could + shrink below the cap and the cluster fanned out into 4+ short + rows. */ +@container emailreader (max-width: 450px) { + .email-reader-meta > .email-reader-actions-inline { + /* 3 icons (48px each) + 2 gaps (4px each) = 152px exact. Adding + 6px slack so subpixel rounding can't tip a row from 3 to 2 + icons and fan the cluster into a 3rd row. */ + min-width: 158px; + max-width: 158px; + /* Solid background so the email body content behind the cluster + doesn't peek through the gaps between icons or the slack on + the right edge when the cluster's wrapped to 2 rows. */ + background: var(--bg); + } + .email-reader-meta > .email-reader-actions-inline .memory-toolbar-btn.reader-icon-btn { + width: 48px; + min-width: 48px; + max-width: 48px; + height: 40px; + padding-left: 0; + padding-right: 0; + gap: 2px; + } + .email-reader-meta > .email-reader-actions-inline .reader-btn-label { + display: block; + max-width: 44px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 8px; + line-height: 1; + } + /* Tighten the right-side padding now that the cluster is 158px + wide (instead of the ~308 wide 1-row layout). */ + .email-reader-meta-row.email-reader-meta-from, + .email-reader-meta > .email-reader-meta-details { + padding-right: 170px; + } + /* Cluster wrapped to 2 rows (44px + 4 gap + 44px = 92px) sits + absolute above the From baseline. Set min-height on the meta + (a descendant of the container, more reliable than targeting + the container itself) so the header grows enough to contain + the cluster + 4px of breathing room between the cluster's + bottom and the header bottom. */ + .email-reader-meta { + min-height: 96px; + } +} +/* Very narrow reader: once the chip span has shrunk to its ~4 + character min, the cluster snaps to absolute overlay with a soft + shadow so it visually floats above the truncated chip. */ +@container emailreader (max-width: 380px) { + /* Pane too narrow even for 158px of right-side reservation — kill + the padding so chips use the full width, and let the cluster + overlay via z-index + shadow. */ + .email-reader-meta-row.email-reader-meta-from, + .email-reader-meta > .email-reader-meta-details { + padding-right: 0; + } + .email-reader-meta > .email-reader-actions-inline { + /* Keep the same -7px lift as the medium-width state so the + cluster doesn't slide ~4px DOWN when it transitions into + overlay mode. */ + /* margin-top stays inherited at -7px from the base rule. */ + /* 158px content + 22px padding-left so the inner row still fits + 3 icons cleanly. */ + min-width: 180px; + max-width: 180px; + background: linear-gradient(to right, transparent 0, var(--bg) 18px); + padding-left: 22px; + /* Soft shadow on the left edge so the overlap reads as the + cluster "floating over" the chip. */ + box-shadow: -6px 0 12px -6px rgba(0, 0, 0, 0.25); + } +} +/* Mobile: drop the box-shadow AND the left-edge gradient fade — + either can read as a shadow under the icons on phone screens. + Also lift the cluster 1px more. !important needed because the + @container query overlay rule has the same selector specificity. */ +@media (max-width: 768px) { + .memory-toolbar-btn[data-mode="ai-reply-fast"] { + height: 30px !important; + min-height: 30px !important; + } + .email-reader-meta > .email-reader-actions-inline { + box-shadow: none !important; + background: none !important; + padding-left: 0 !important; + margin-top: -10px; + } +} +/* Docked panes used to flow the cluster under the meta; now they + share the same absolute-top-right + wrap behaviour as undocked. + No docked-specific overrides — the container queries below pick + the right cluster size and overlay state from the reader width. */ +/* Gmail-style chevron — collapsed view shows only From; the chevron + reveals the To/Cc details inline below. */ +.email-reader-meta-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + margin-left: -3px; + padding: 0; + border: none; + background: transparent; + color: inherit; + opacity: 0.55; + cursor: pointer; + border-radius: 4px; + flex-shrink: 0; +} +.email-reader-meta-toggle:hover { opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent); } +.email-reader-meta-toggle svg { transition: transform 0.15s ease; } +.email-reader-meta-toggle.open svg { transform: rotate(180deg); } +.email-reader-meta-toggle.email-reader-cc-toggle { + width: auto; + min-width: 18px; + gap: 3px; + margin-left: 1px; + padding: 0 5px; + color: var(--accent, var(--red)); + font-family: inherit; + font-size: 9px; + font-weight: 600; + line-height: 1; +} +.email-reader-meta-details { + display: flex; + flex-direction: column; + gap: 6px; +} +/* When expanded, To/Cc details live in the header flow. A floating popover + can overlap the folded attachment strip directly beneath the header. */ +.email-reader-meta > .email-reader-meta-details:not([hidden]) { + position: relative; + top: auto; + left: auto; + right: auto; + margin-top: 4px; + background: transparent; + border: 0; + border-radius: 0; + padding: 0; + box-shadow: none; + z-index: 1; +} +/* Reserve a fixed label width so From: / To: / Cc: all align and + their chips start at the same x. */ +.email-reader-meta-row strong { opacity: 0.5; font-weight: 600; flex-shrink: 0; min-width: 36px; } +.email-reader-meta-row > span { + white-space: nowrap; + min-width: 0; +} +/* Recipient chips */ +.recipient-chips { + display: inline-flex !important; flex-wrap: wrap; gap: 4px; + white-space: normal !important; overflow: visible !important; + text-overflow: clip !important; +} +.recipient-chip { + display: inline-flex; align-items: center; + gap: 5px; + padding: 1px 8px; font-size: 10px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid var(--border); + border-radius: 10px; + color: var(--fg); + white-space: nowrap; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, max-width 0.2s; + max-width: 220px; + overflow: hidden; + text-overflow: ellipsis; +} +.recipient-chip-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} +.recipient-chip-copy { + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + top: -2px; + width: 14px; + height: 14px; + padding: 0; + border: none; + background: none; + color: inherit; + opacity: 0.55; + cursor: pointer; + flex: 0 0 auto; +} +.recipient-chip-copy:hover, +.recipient-chip-copy.copied { + opacity: 1; + color: var(--accent-primary, var(--red)); +} +.recipient-chip-copy[hidden] { + display: none !important; +} +.recipient-chip:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); +} +.recipient-chip.expanded { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); + border-color: var(--accent-primary, var(--red)); + max-width: 500px; +} +.recipient-chip.popover-open { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); + border-color: var(--accent-primary, var(--red)); +} +.recipient-chip-popover { + position: fixed; + z-index: 12050; + display: flex; + align-items: center; + gap: 8px; + max-width: min(420px, calc(100vw - 20px)); + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--bg) 96%, var(--fg) 4%); + color: var(--fg); + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.42); + font-size: 12px; + line-height: 1.35; + pointer-events: auto; +} +.recipient-chip-popover-main { + min-width: 0; +} +.recipient-chip-popover-name { + font-weight: 650; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.recipient-chip-popover-detail { + overflow-wrap: anywhere; + opacity: 0.86; +} +.recipient-chip-popover-copy { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 26px; + height: 26px; + padding: 0; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + color: inherit; + cursor: pointer; +} +.recipient-chip-popover-copy:hover, +.recipient-chip-popover-copy.copied { + color: var(--accent-primary, var(--red)); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, transparent); +} +@container docpane (max-width: 460px) { + .email-reader-header { + flex-direction: column; + gap: 6px; + } + .email-reader-header > .email-reader-actions { + /* On narrow widths fall back to in-flow so it doesn't overlap + the meta entirely. */ + position: static; + align-self: flex-end; + background: none; + padding-left: 0; + } + .email-reader-meta-row:not(.email-reader-meta-from) { + padding-right: 0; + } + .email-reader-meta-row strong { + min-width: 0; + } + .recipient-chip { + max-width: 100%; + } +} +/* Mobile: long recipient lists (To/Cc with many addresses) shouldn't wrap to + N rows and push the body down. Keep them on one row, horizontally scrollable, + no scrollbar chrome. */ +@media (max-width: 768px) { + .recipient-chips { + flex-wrap: nowrap !important; + overflow-x: auto !important; + overflow-y: hidden !important; + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + } + .recipient-chips::-webkit-scrollbar { display: none; } + .recipient-chip { flex-shrink: 0; } +} +.email-reader-actions { + display: flex; flex-direction: column; gap: 4px; align-items: flex-end; + flex-shrink: 0; + margin-top: -4px; +} +/* Two stacked rows: Reply/Reply-all/Forward on the From line, with + Summary/AI-reply/More on a row below. */ +.email-reader-actions-row { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: flex-end; + gap: 4px; +} +.email-reader-atts { + display: flex; flex-wrap: wrap; gap: 6px; + padding: 8px 14px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.email-reader-body { + font-size: 12px; + line-height: 1.55; + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: anywhere; + flex: 1; + overflow-y: auto; + padding: 14px; + min-height: 0; + background: var(--panel); + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--border) 65%, transparent); + /* Use the OS colored emoji font for incoming mail content */ + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"; + font-variant-emoji: emoji; +} +.email-reader-body.html-body img { max-width: 100%; height: auto; } +.email-reader-body.html-body [style*="max-width:600px"], +.email-reader-body.html-body [style*="max-width: 600px"], +.email-reader-body.html-body [style*="width:600px"], +.email-reader-body.html-body [style*="width: 600px"], +.email-reader-body.html-body table[width="600"] { + max-width: none !important; + width: 100% !important; + margin-left: 0 !important; + margin-right: 0 !important; +} +.email-reader-body.html-body [align="center"] { + margin-left: 0 !important; + margin-right: 0 !important; +} +.email-reader-body .email-inline-image-placeholder { + display: inline-flex; + align-items: center; + gap: 9px; + max-width: min(100%, 360px); + min-height: 58px; + margin: 6px 0; + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 72%, transparent); + color: var(--fg); + vertical-align: middle; +} +.email-inline-image-skeleton { + width: 58px; + height: 42px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 6px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 7%, transparent) 0%, + color-mix(in srgb, var(--fg) 13%, transparent) 45%, + color-mix(in srgb, var(--fg) 7%, transparent) 90% + ); + background-size: 220% 100%; + animation: email-skeleton-shimmer 1.35s ease-in-out infinite; +} +.email-inline-image-icon { + opacity: 0.52; + display: inline-flex; +} +.email-inline-image-placeholder.is-loading .email-inline-image-icon svg { + display: none; +} +.email-inline-image-info { + display: inline-flex; + flex-direction: column; + min-width: 0; + line-height: 1.25; +} +.email-inline-image-title { + font-size: 11px; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-inline-image-sub { + font-size: 10px; + opacity: 0.58; +} +.email-inline-image-actions { + display: inline-flex; + align-items: center; + gap: 5px; + margin-left: auto; + flex: 0 0 auto; +} +.email-inline-image-btn { + display: inline-flex; + align-items: center; + justify-content: center; + transform: translateY(-2px); + height: 24px; + line-height: 1; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: inherit; + font: inherit; + font-size: 10px; + text-decoration: none; + cursor: pointer; +} +.email-inline-image-btn:hover { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); + color: var(--accent-primary, var(--red)); +} +.email-inline-image-download-btn { + width: 24px; + min-width: 24px; + padding: 0; +} +.email-inline-image-loaded { + max-width: 100%; + height: auto; + border-radius: 6px; +} +.email-inline-image-frame { + display: inline-flex; + max-width: 100%; + min-width: min(360px, 100%); + min-height: 90px; + margin: 6px 0; + align-items: center; + justify-content: center; + border-radius: 8px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 7%, transparent) 0%, + color-mix(in srgb, var(--fg) 13%, transparent) 45%, + color-mix(in srgb, var(--fg) 7%, transparent) 90% + ); + background-size: 220% 100%; +} +.email-inline-image-frame .email-inline-image-loaded { + opacity: 0; + transition: opacity 0.15s ease; +} +.email-inline-image-load-all { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + max-width: 100%; + margin: -4px 0 6px; + padding: 7px 9px; + border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 72%, transparent); + font-size: 11px; +} +.email-inline-image-load-all span { + opacity: 0.68; + margin-right: auto; +} +.email-inline-image-load-all button { + transform: translateY(-4px); + height: 24px; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: inherit; + font: inherit; + font-size: 10px; + cursor: pointer; +} +.email-inline-image-load-all button:hover { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); + color: var(--accent-primary, var(--red)); +} +@media (max-width: 520px) { + .email-reader-body .email-inline-image-placeholder { + display: flex; + width: 100%; + } + .email-inline-image-skeleton { + width: 48px; + } + .email-inline-image-actions { + flex-direction: column; + align-items: stretch; + } +} +.email-reader-body.html-body table { max-width: 100%; border-collapse: collapse; } +/* Gmail Drive chips arrive with inline width/height/border/padding from + Gmail's composer (border:1px solid #ddd, white-ish bg assumed). On a + dark theme that reads as a near-invisible white box. Override with a + theme-aware chip surface so the filename + icon stay visible. */ +.email-reader-body .gmail_chip, +.email-reader-body .gmail_drive_chip { + width: auto !important; + max-width: 100% !important; + height: auto !important; + max-height: none !important; + display: inline-flex !important; + align-items: center !important; + gap: 6px; + padding: 6px 10px !important; + margin: 6px 0 !important; + border: 1px solid var(--border) !important; + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.email-reader-body .gmail_chip:hover, +.email-reader-body .gmail_drive_chip:hover { + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)) !important; + background: color-mix(in srgb, var(--accent, var(--red)) 6%, transparent); +} +.email-reader-body .gmail_chip a, +.email-reader-body .gmail_drive_chip a { + color: var(--accent, var(--red)) !important; + display: inline-flex !important; + align-items: center; + gap: 6px; + max-width: 100% !important; +} +.email-reader-body .gmail_chip a > span, +.email-reader-body .gmail_drive_chip a > span { + color: var(--fg) !important; + font-size: 12px; +} +.email-reader-body .gmail_chip img, +.email-reader-body .gmail_drive_chip img { + width: 16px !important; + height: 16px !important; + padding-right: 0 !important; + filter: none; +} + +/* Force any phone/sms/date/address auto-link to inherit the body color so + numbers don't render as bright-blue browser-default text inside email + content. format-detection in turns off detection on iOS, but some + mail clients ship pre-wrapped tel: links — those still need taming. */ +.email-reader-body a[href^="tel:"], +.email-reader-body a[href^="sms:"], +.email-reader-body a[x-apple-data-detectors], +.email-bubble-body a[href^="tel:"], +.email-bubble-body a[href^="sms:"], +.email-bubble-body a[x-apple-data-detectors], +.email-thread-turn-body a[href^="tel:"], +.email-thread-turn-body a[href^="sms:"], +.email-thread-turn-body a[x-apple-data-detectors] { + color: inherit !important; + text-decoration: none !important; + pointer-events: auto; +} +.email-reader-body a, +.email-bubble-body a, +.email-thread-turn-body a { + color: var(--accent-primary, var(--red)); + text-decoration: underline; + overflow-wrap: anywhere; + word-break: normal; + cursor: pointer; +} +.email-reader-body a:hover, +.email-bubble-body a:hover, +.email-thread-turn-body a:hover { opacity: 0.8; } +/* Summary block — same band chrome as Attachments / Signature / Earlier- + thread (no accent tint, no rounded corners, no leading star icon). The + only difference is the label, so all collapsible sections in the email + reader look uniform. */ +.email-summary-panel { + margin: 0 0 12px 0; + padding: 10px 12px; + background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); + border-radius: 6px; +} +.email-summary-header { + display: flex; align-items: center; gap: 5px; + font-size: 10px; font-weight: 600; text-transform: uppercase; + color: var(--accent-primary, var(--red)); opacity: 0.85; + margin-bottom: 6px; letter-spacing: 0.4px; +} +.email-summary-content { + font-size: 12px; line-height: 1.5; color: var(--fg); white-space: pre-wrap; +} +/* Click-to-fold: tap the summary header to collapse/expand. The chevron + flips when collapsed, the content hides + the panel's bottom margin + tightens so a folded summary doesn't take up vertical space. */ +.email-summary-toggle { cursor: pointer; user-select: none; } +.email-summary-panel.collapsed { padding-bottom: 6px; margin-bottom: 6px; } +.email-translation-loading { + display: flex; + align-items: center; + justify-content: center; + min-height: 26px; +} +.email-translation-busy { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; +} +.email-translation-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; +} +.email-translation-loading-text { + opacity: 0.68; + font-size: 12px; +} +.email-translation-panel .email-translation-icon { + color: var(--accent-primary, var(--red)); +} + +/* Foldable attachments — same fold-on-click UX as the summary panel. */ +.email-reader-atts-wrap { + display: flex; flex-direction: column; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.email-reader-atts-header { + display: flex; align-items: center; gap: 5px; + padding: 6px 14px; + font-size: 10px; font-weight: 600; text-transform: uppercase; + color: var(--fg); opacity: 0.7; + cursor: pointer; user-select: none; + letter-spacing: 0.4px; +} +.email-attachments-download-all { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + height: 22px; + padding: 0 8px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); + color: var(--accent-primary, var(--red)); + font: inherit; + font-size: 10px; + font-weight: 700; + text-transform: none; + letter-spacing: 0; + cursor: pointer; + flex: 0 0 auto; +} +.email-attachments-download-all:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 60%, transparent); +} +.email-attachments-download-all .spinner-whirlpool, +.email-attachments-download-all .ai-spinner-whirlpool { + width: 12px !important; + height: 12px !important; + margin: 0 !important; +} +.email-reader-atts-wrap > .email-reader-atts { + border-bottom: none !important; +} +.email-reader-atts-hidden-note { + padding: 0 14px 6px; + font-size: 10px; + color: var(--fg-muted); + opacity: 0.65; +} + +/* Quote fold = neutral full-width band (matches attachments header). */ +.email-quote-fold { + margin: 0 -14px; + padding: 0; + border: 0; + border-top: 1px solid var(--border) !important; + border-radius: 0; + outline: 0 !important; + box-shadow: none !important; + /* Neutralise any rich-mail HTML that injects a yellow / colored bg + or border onto blockquote ancestors. */ + background: transparent !important; +} +/* Same neutralisation for any element nested inside the fold (the inner + blockquote, paragraphs, divs from the original message). Yellow outlines + in Outlook-style mails almost always come from inline border-color or + box-shadow on these. */ +.email-quote-fold *, +.email-quote-fold *::before, +.email-quote-fold *::after { + outline-color: transparent !important; + box-shadow: none !important; +} +.email-quote-fold blockquote, +.email-quote-fold blockquote[type="cite"], +.email-quote-fold table, +.email-quote-fold div { + border-color: var(--border) !important; + /* Override the .msg blockquote rule that sets a colored left border via + var(--hl-function) — that's where the "yellow" outline was coming from. */ + border-left-color: var(--border) !important; + border-radius: 0 !important; + background: transparent !important; +} +/* JS marks the last .email-quote-fold in each email body with this class, + so it's the only one that gets rounded bottom corners. Avoids :has() + browser-support quirks. */ +.email-quote-fold.last-fold { + border-radius: 0 0 8px 8px; + overflow: hidden; +} +/* ── Threaded reply turns (recursive parser) ── */ +.email-thread-turn-body { + padding: 8px 14px 4px; + font-size: 12px; + line-height: 1.45; +} +/* Nested turns indent slightly + get their own card outline so the + reply hierarchy is obvious at a glance. */ +.email-thread-turn-body .email-thread-turn { + margin: 8px 0 4px; + margin-left: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +/* The original blockquote's left stripe is redundant when each turn is + already a card — drop it inside thread bodies. */ +.email-thread-turn-body blockquote { + border-left: none !important; + margin: 0 !important; + padding: 0 !important; + background: transparent !important; +} +/* Flatten the inner blockquote: no rounded corners, no yellow tint + inherited from rich-mail HTML. Subtle left border + neutral bg. */ +.email-quote-fold blockquote { + margin: 0; + padding: 8px 12px; + border: 0; + border-left: 2px solid var(--border); + border-radius: 0 !important; + background: transparent !important; + color: inherit; +} +/* "From … · Date …" chip appended to the summary line */ +.email-fold-summary-meta { + margin-left: 6px; + font-size: 10px; + font-weight: 500; + text-transform: none; + letter-spacing: 0; + opacity: 0.55; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +/* Sender name is the primary affordance — slightly larger and not all-caps so + "From: Sam" feels like a click target on the person, not a section label. */ +.email-fold-summary-name { + font-size: 11px; + font-weight: 600; + text-transform: none; + letter-spacing: 0.1px; + color: var(--fg); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.email-quote-fold .email-fold-summary { + display: flex; align-items: center; gap: 5px; + padding: 6px 14px; + cursor: pointer; list-style: none; + font-size: 10px; font-weight: 600; text-transform: uppercase; + letter-spacing: 0.4px; + /* Force neutral page colors regardless of any rich-mail HTML wrapping us */ + color: var(--fg) !important; + background: transparent !important; + background-color: transparent !important; + text-shadow: none !important; + font-style: normal !important; + opacity: 0.7; + user-select: none; + border-radius: 0; +} +.email-quote-fold .email-fold-summary > * { + /* Prevent inline-color attrs on summary children (icon SVGs, meta span) + from picking up a forced color from ancestor email HTML. */ + color: inherit !important; + background: transparent !important; +} +.email-quote-fold .email-fold-summary { list-style: none; list-style-type: none; } +.email-quote-fold[open] > *:not(summary) { + padding: 8px 14px 12px 14px; + border-top: 1px solid var(--border); +} + +/* Signature fold = clean full-width band like the attachments header. No + accent overlay, no rounded corners — neutral chrome so it doesn't look + like the AI Summary panel. */ +.email-sig-fold { + margin: 8px 0 0; + padding: 0; + border: 0; + border-top: 1px dashed color-mix(in srgb, var(--fg) 18%, transparent); + background: transparent; + border-radius: 0; +} +.email-sig-fold .email-fold-summary { + display: flex; align-items: center; gap: 5px; + padding: 4px 0; + cursor: pointer; + list-style: none; list-style-type: none; + font-size: 9.5px; font-weight: 600; text-transform: uppercase; + letter-spacing: 0.4px; + color: var(--fg); opacity: 0.55; + user-select: none; +} +.email-sig-fold[open] > *:not(summary) { + padding: 6px 0 0; + border-top: 0; + font-size: 11px; line-height: 1.45; + color: color-mix(in srgb, var(--fg) 75%, transparent); + white-space: pre-wrap; +} + +/* ── Chat-bubble layout for email threads ── + Each parsed turn becomes a bubble. The active account's outgoing + replies align right (mine); everyone else aligns left (theirs). + Bubbles read top→bottom oldest→newest, like a chat. */ +.email-bubbles { + display: flex; + flex-direction: column; + gap: 10px; + padding: 4px 0 8px; + margin: 0 -2px; +} +.email-bubble-row { + display: flex; + align-items: flex-end; + gap: 8px; + width: 100%; + min-width: 0; +} +.email-bubble { + flex: 0 1 auto; + max-width: 90%; + min-width: 0; + padding: 8px 12px; + border: 1px solid var(--border); + background: var(--panel); + font-size: 12.5px; + line-height: 1.5; + word-wrap: break-word; + overflow-wrap: anywhere; + overflow: hidden; +} +.email-bubble-mine .email-bubble { + background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 10%, var(--bg)); + border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 35%, var(--border)); + border-radius: 14px 14px 4px 14px; +} +.email-bubble-theirs .email-bubble { + background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 5%, var(--panel)); + border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 25%, var(--border)); + border-radius: 14px 14px 14px 4px; +} +.email-bubble-head { + display: flex; + align-items: baseline; + gap: 8px; + font-size: 10.5px; + margin-bottom: 4px; + opacity: 0.7; + min-width: 0; +} +.email-bubble-author { + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 50ch; +} +.email-bubble-date { + opacity: 0.7; + font-weight: 400; + white-space: nowrap; +} +.email-bubble-body { + white-space: normal; + /* Reader-controlled typography — override sender's inline styles AND + attribute-based sizing (, , , h1..h6). Weight + and italics still pass through for emphasis. */ + font-family: inherit; + font-size: var(--email-body-size, 13.5px); + line-height: 1.5; +} +/* Heading tags carry browser-default font-size multipliers that bypass + `font-size: inherit` when an inline style was set; pin them too. */ +.email-bubble-body h1, +.email-bubble-body h2, +.email-bubble-body h3, +.email-bubble-body h4, +.email-bubble-body h5, +.email-bubble-body h6, +.email-bubble-body big, +.email-bubble-body small, +.email-bubble-body font { + font-size: inherit !important; + line-height: inherit !important; +} +/* Sender HTML often wraps content in a fixed-width + or . Override so content fills the reader's available + width — otherwise resizing the email window doesn't reflow anything. */ +.email-bubble-body, +.email-bubble-body * { + max-width: 100% !important; +} +.email-bubble-body div[style*="width"], +.email-bubble-body table[width], +.email-bubble-body table { + width: 100% !important; + box-sizing: border-box !important; +} +.email-bubble-body blockquote { + border-left: 2px solid var(--border) !important; + margin: 4px 0 !important; + padding: 4px 10px !important; + background: transparent !important; + border-radius: 0 !important; + color: inherit; +} +.email-bubble-avatar { + flex-shrink: 0; + width: 26px; + height: 26px; + border-radius: 50%; + /* Per-sender color set inline (`style="background:hsl(...)"`); this + fallback only kicks in when JS hasn't assigned one. */ + background: color-mix(in srgb, var(--fg) 14%, transparent); + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.22); + display: flex; + align-items: center; + justify-content: center; + font-size: 9.5px; + font-weight: 600; + letter-spacing: 0.3px; + user-select: none; +} +/* Sig folds inside a bubble: tone down — no full-bleed band, since the + bubble already provides the chrome. */ +.email-bubble-body .email-sig-fold { + margin: 8px 0 0; + border-top: 1px dashed var(--border); +} +.email-bubble-body .email-sig-fold[open] > *:not(summary), +.email-bubble-body .email-quote-fold[open] > *:not(summary) { + padding: 6px 0 0; + border-top: 0; +} +@media (max-width: 600px) { + .email-bubble { max-width: 96%; } + .email-bubble-avatar { display: none; } +} + +/* ── Schedule Send modal ── */ +.schedule-send-body { + display: flex; + flex-direction: column; + gap: 6px; + padding: 16px 18px 12px; +} +.schedule-send-label { + font-size: 11px; + font-weight: 500; + letter-spacing: 0.3px; + text-transform: uppercase; + color: var(--fg); + opacity: 0.55; + margin-top: 6px; +} +.schedule-send-label:first-child { margin-top: 0; } +.schedule-send-presets { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; + margin-bottom: 4px; +} +.schedule-send-presets .memory-toolbar-btn { + padding: 5px 10px 11px; + font-size: 12px; + justify-content: center; + line-height: 1; +} +.schedule-send-confirm svg { + width: 12px; + height: 12px; + vertical-align: -1px; + margin-right: 6px; +} +.schedule-send-datetime { + font-size: 13px; + padding: 8px 10px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + outline: none; + font-family: inherit; + width: 100%; + box-sizing: border-box; +} +.schedule-send-datetime:focus { + border-color: var(--hl-function); +} +.schedule-send-footer { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 8px; + padding: 10px 18px 14px; + border-top: 1px solid var(--border); + margin-top: 4px; +} +.schedule-send-confirm { + background: var(--accent-primary, var(--red)) !important; + color: #fff !important; + border-color: var(--accent-primary, var(--red)) !important; +} +.schedule-send-confirm:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 85%, white) !important; +} + +/* Senders embed inline colors in their HTML that clash with the app's + theme (black text in dark mode, white backgrounds, yellow highlights, + etc.). Force theme colors on every descendant of the rendered body so + emails inherit the user's chosen palette regardless of what the + sender's mail client emits. */ +.email-reader-body *, +.email-bubble-body * { + color: var(--fg) !important; + -webkit-text-fill-color: currentColor !important; + background-color: transparent !important; + background-image: none !important; +} +.email-reader-body a, +.email-bubble-body a, +.email-thread-turn-body a { + color: var(--hl-function) !important; +} +.email-reader-body a[href^="tel:"], +.email-reader-body a[href^="sms:"], +.email-reader-body a[href^="x-apple-data-detectors:"], +.email-reader-body a[x-apple-data-detectors], +.email-bubble-body a[href^="tel:"], +.email-bubble-body a[href^="sms:"], +.email-bubble-body a[href^="x-apple-data-detectors:"], +.email-bubble-body a[x-apple-data-detectors], +.email-thread-turn-body a[href^="tel:"], +.email-thread-turn-body a[href^="sms:"], +.email-thread-turn-body a[href^="x-apple-data-detectors:"], +.email-thread-turn-body a[x-apple-data-detectors] { + color: inherit !important; + -webkit-text-fill-color: currentColor !important; + text-decoration: none !important; + background-color: transparent !important; + background-image: none !important; + -webkit-background-clip: border-box !important; + background-clip: border-box !important; +} + +/* Prefer local/system emoji fonts; avoid remote font providers. */ +.doc-editor-textarea, .doc-editor-highlight, #doc-editor-code, +.doc-md-preview, .doc-csv-preview { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', monospace; + font-variant-emoji: text; +} +/* Preview panes claim the editor's space so the action footer stays pinned + at the bottom of the pane even when the rendered content is short + (shorter table, single-paragraph markdown, etc). */ +.doc-csv-preview, +.doc-md-preview { + flex: 1 1 auto; + min-height: 0; + overflow: auto; +} + +/* Email documents override: use colored system emoji in compose. */ +.doc-editor-textarea.email-mode, +#doc-editor-code.email-mode, +#doc-editor-code.email-mode .doc-editor-highlight, +#doc-editor-code.email-mode .doc-editor-textarea { + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla" !important; + font-variant-emoji: emoji; +} + +/* Single-layer rendering for ALL documents (not just email): the highlight + overlay ( with hljs spans) and the transparent textarea use two + different rendering paths internally — the textarea uses the browser's + native form line-breaker, the overlay uses CSS line-breaking — and they + can never be guaranteed to wrap byte-identical no matter how many CSS + properties are pinned. Pick one source of truth: make the textarea its + own visible text, hide the overlay. Trade-off: no syntax highlighting in + the live editor (rendered markdown preview / chat output still has it). + The caret is now glued to the typed text by definition since both are + rendered by the same element. */ +.doc-editor-highlight, +#doc-editor-code.email-mode .doc-editor-highlight { + display: none !important; +} +/* Find bar is open: float the highlight overlay ON TOP of the + textarea with transparent background + transparent text, so only + the solid spans are visible. pointer-events + off so clicks still go through to the textarea below. */ +body.doc-find-active .doc-editor-highlight, +body.doc-find-active #doc-editor-code.email-mode .doc-editor-highlight { + display: block !important; + z-index: 5 !important; + background: transparent !important; + pointer-events: none; +} +body.doc-find-active .doc-editor-highlight, +body.doc-find-active .doc-editor-highlight * { + color: transparent !important; + background: transparent !important; +} +.doc-editor-textarea, +#doc-editor-code.email-mode .doc-editor-textarea { + color: var(--fg) !important; + caret-color: var(--fg) !important; + z-index: 1; +} + +/* Emoji picker (monochrome icon picker) */ +.emoji-picker-btn { + background: none; border: none; color: var(--fg); opacity: 0.35; + padding: 4px 7px; cursor: pointer; min-height: 28px; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.1s; +} +.emoji-picker-btn:hover { opacity: 1; } +.emoji-picker { + width: min(300px, 92vw); max-height: min(300px, 55vh); + background: var(--panel); border: 1px solid var(--border); border-radius: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.3); + display: flex; flex-direction: column; overflow: hidden; +} +.emoji-picker-search { + padding: 8px 12px; background: var(--panel); border: none; + border-bottom: 1px solid var(--border); color: var(--fg); + font-size: 12px; font-family: inherit; outline: none; +} +.emoji-picker-groups { overflow-y: auto; padding: 4px 0; flex: 1; } +.emoji-picker-group { padding: 0 6px 4px 6px; } +.emoji-picker-group-name { + font-size: 10px; font-weight: 600; opacity: 0.5; + text-transform: uppercase; letter-spacing: 0.5px; + padding: 4px 4px 2px 4px; +} +.emoji-picker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; } +.emoji-picker-item { + background: none; border: none; cursor: pointer; + padding: 4px; border-radius: 4px; + display: flex; align-items: center; justify-content: center; + color: var(--fg); transition: background 0.1s; +} +.emoji-picker-item:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } +.emoji-picker-item svg { width: 16px; height: 16px; } + +.email-avatar { + width: 28px; height: 28px; border-radius: 50%; background: var(--accent, #4a9eff); + color: #fff; display: flex; align-items: center; justify-content: center; + font-size: 12px; font-weight: 600; flex-shrink: 0; margin-top: 1px; +} +.email-item-content { flex: 1; min-width: 0; overflow: hidden; } +.email-item-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } +.email-sender { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.email-filter-chip { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; + margin: 4px 8px 6px; + padding: 4px 8px; + border-radius: 12px; + font-size: 11px; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + color: var(--fg); +} +.email-filter-chip-clear { + background: none; + border: none; + color: inherit; + cursor: pointer; + font-size: 14px; + line-height: 1; + padding: 0 2px; + opacity: 0.6; +} +.email-date { font-size: 10px; opacity: 0.5; white-space: nowrap; flex-shrink: 0; } +.email-subject { font-size: 11px; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } +.email-sent-chip, +.email-folder-chip { + display: inline-flex; + align-items: center; + height: 14px; + padding: 0 5px; + margin-right: 5px; + border-radius: 999px; + font-size: 8px; + line-height: 1; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + vertical-align: 1px; +} +.email-folder-chip { + position: relative; + top: 4px; + color: color-mix(in srgb, var(--fg) 75%, transparent); + background: rgba(127, 127, 127, 0.13); + border-color: rgba(127, 127, 127, 0.24); +} +.email-meta-date-group { + display: inline-flex; + align-items: center; + gap: 5px; + white-space: nowrap; +} +.email-meta-date-group .email-folder-chip { + margin-right: 0; +} +.email-card-expanded .email-sent-chip, +.email-card-reader .email-sent-chip { + position: relative; + top: 4px; +} +.email-tags { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 3px; margin-left: 6px; vertical-align: middle; position: relative; top: -2px; } +.email-card-tags { + flex: 0 0 auto; + min-width: 0; + max-width: 48%; + position: relative; + top: -4px; +} +.email-card-expanded .email-tags, +.email-card-reader .email-tags { top: -4px; } +.doclib-card-expanded .email-card-tags { top: -4px; } +.email-card-expanded .email-card-titlerow .email-tag, +.email-card-expanded .email-card-titlerow .email-tags-more { + transform: translateY(-2px); +} +@media (max-width: 768px) { + .email-card-expanded .email-card-titlerow .email-tag, + .email-card-expanded .email-card-titlerow .email-tags-more { + transform: translateY(2px); + } +} +.email-card-tags.email-tags-expanded { + position: absolute; + z-index: 4; + right: calc(var(--email-status-width, 44px) + 6px); + top: -5px; + max-width: min(58%, 260px); + padding: 4px 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent); +} +.email-tags-more { + appearance: none; + border: 0; + box-sizing: border-box; + border-radius: 999px; + background: rgba(127, 127, 127, 0.16); + color: var(--fg); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 1px; + height: 13px !important; + min-height: 0 !important; + min-width: 23px; + padding: 1px 4px; + font-family: inherit; + font-size: 9px; + line-height: 1; + margin: 0; + opacity: 0.68; + position: relative; + top: 0; + vertical-align: middle; +} +.email-tags-more svg { display: block; flex: 0 0 auto; } +.email-tags-expanded .email-tags-more svg { transform: rotate(180deg); } +.email-tags-toggle-inline { + position: absolute; + right: 94px; + top: 50%; + transform: translateY(calc(-50% - 5px)); + height: 20px !important; + min-width: 34px; + padding: 2px 6px !important; + font-size: 10px !important; + line-height: 1 !important; + opacity: 0.62; + border-radius: 999px !important; +} +.email-tags-toggle-inline.active { + opacity: 1; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 36%, transparent); +} +.email-tags-toggle-inline.email-tags-empty { + color: var(--warning, #d6b35a); + background: color-mix(in srgb, var(--warning, #d6b35a) 12%, transparent); + border-color: color-mix(in srgb, var(--warning, #d6b35a) 34%, transparent); +} +.email-tag { + display: inline-block; + font-size: 9px; + line-height: 1; + padding: 2px 5px; + border-radius: 8px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.3px; + background: rgba(127, 127, 127, 0.18); + color: var(--fg); +} +.email-tag-clickable { + appearance: none; + border: 0; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + font-family: inherit; + font-size: 9px; + font-weight: 500; + height: 13px !important; + min-height: 0 !important; + line-height: 1; + margin: 0; + padding: 2px 5px !important; + text-transform: uppercase; + vertical-align: middle; + flex: 0 0 auto; +} +.email-tag-work { background: rgba(96, 165, 250, 0.22); color: #60a5fa; } +.email-tag-personal { background: rgba(74, 222, 128, 0.22); color: #4ade80; } +.email-tag-finance { background: rgba(250, 204, 21, 0.22); color: #facc15; } +.email-tag-bills { background: rgba(251, 146, 60, 0.22); color: #fb923c; } +.email-tag-receipt { background: rgba(251, 146, 60, 0.22); color: #fb923c; } +.email-tag-travel { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } +.email-tag-newsletter { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } +.email-tag-promo, +.email-tag-marketing { background: rgba(244, 114, 182, 0.22); color: #f472b6; } +.email-tag-notification { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } +.email-tag-security { background: rgba(248, 113, 113, 0.22); color: #f87171; } +.email-tag-urgent { background: color-mix(in srgb, var(--color-error, #e06c75) 25%, transparent); color: var(--color-error, #e06c75); font-weight: 600; } +.email-tag-reply-soon { background: rgba(240, 173, 78, 0.22); color: #f0ad4e; } +.email-tag-social { background: rgba(56, 189, 248, 0.22); color: #38bdf8; } +.email-tag-shopping { background: rgba(236, 72, 153, 0.22); color: #ec4899; } +.email-tag-calendar { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } +.email-menu-wrap { position: relative; flex-shrink: 0; } +.email-menu-btn { + background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; + padding: 4px; transition: opacity 0.15s; + position: relative; top: 0px; +} +.email-dropdown { + position: absolute; right: 0; top: 100%; z-index: 9999; + min-width: 120px; background: var(--bg); border: 1px solid var(--border); + border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); padding: 4px 0; +} +.email-loading { padding: 16px; text-align: center; font-size: 12px; opacity: 0.5; } +.email-load-more { padding: 8px; text-align: center; } +.email-load-more-btn { + background: transparent; border: 1px solid var(--border); border-radius: 4px; + color: var(--fg); padding: 4px 12px; font-size: 11px; cursor: pointer; +} +.email-load-more-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } +.email-spinner { + display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); + border-top-color: #fff; border-radius: 50%; animation: email-spin 0.6s linear infinite; + vertical-align: middle; margin-right: 4px; +} + +/* Email section doesn't collapse — hide the auto-injected chevron */ +#email-section .section-collapse-btn { display: none !important; } + +#email-section:hover #email-compose-btn { + opacity: 0.7 !important; +} +#email-compose-btn:hover { + opacity: 1 !important; + background: none !important; +} +#email-compose-btn svg { + width: 11px; + height: 11px; + transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); +} +#email-compose-btn .list-item-plus-label { + top: calc(50% - 0.5px); + color: var(--fg); + font-size: 10px; +} +#email-compose-btn:hover svg { + transform: rotate(180deg) scale(1.15); +} + +/* Library row "+" — identical to the email compose "+": hidden until the row + is hovered, no accent box, and the same rotate-in animation. */ +#library-new-doc-btn, +#notes-new-note-btn { + opacity: 0.4 !important; + transition: opacity 0.15s ease; +} +#tool-library-btn:hover #library-new-doc-btn, +#tool-notes-btn:hover #notes-new-note-btn { + opacity: 0.7 !important; +} +#library-new-doc-btn:hover, +#notes-new-note-btn:hover { + opacity: 1 !important; + background: none !important; +} +#library-new-doc-btn svg, +#notes-new-note-btn svg { + transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); +} +#library-new-doc-btn .list-item-plus-label, +#notes-new-note-btn .list-item-plus-label { + top: calc(50% + 1px); +} +#library-new-doc-btn:hover svg, +#notes-new-note-btn:hover svg { + transform: rotate(180deg) scale(1.15); +} + + +/* MD toolbar attach button — matches other toolbar buttons */ +.md-toolbar-attach-btn { + background: none; border: none; color: var(--fg); opacity: 0.35; + padding: 4px 7px; cursor: pointer; min-height: 28px; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.1s; +} +.md-toolbar-attach-btn:hover { opacity: 1; } + +/* Email reader icon buttons — vertical icon + label stack. */ +.memory-toolbar-btn.reader-icon-btn { + width: auto; + height: 44px; + /* padding: 4px 2px; */ + position: relative; + top: 1px; + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + flex: 0 0 auto; +} +.memory-toolbar-btn.reader-icon-btn svg { width: 18px; height: 18px; } +.memory-toolbar-btn.reader-icon-btn[data-act="reply"] svg { + color: inherit; + stroke: currentColor; +} +.memory-toolbar-btn.reader-icon-btn.active { + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + color: var(--accent, var(--red)); +} +.memory-toolbar-btn.reader-icon-btn[data-act="more"] { + color: inherit; +} +.memory-toolbar-btn.reader-icon-btn[data-act="more"] svg { fill: currentColor; } +.memory-toolbar-btn.reader-icon-btn.reader-more-active { + background: color-mix(in srgb, var(--fg) 12%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); + color: inherit; +} +/* Cookbook Serve dropdown triggers (the cached-model kebab + the saved-config + arrow) — accent-colored to signal they open a menu, with a filled tint when + their dropdown is open. Matches the email reader More button pattern. */ +.hwfit-cached-menu-btn, +.cookbook-saved-arrow { + color: var(--accent, var(--red)); + /* Base .memory-item-btn is flex with align-items but no justify-content, + so the kebab SVG sat left-aligned inside the button instead of being + horizontally centered. Pin it center. */ + justify-content: center; +} +.hwfit-cached-menu-btn.cookbook-menu-active, +.cookbook-saved-arrow.cookbook-menu-active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); + color: var(--accent, var(--red)); +} + +/* Card action menus share one hit target and baseline. Keep this late in the + cascade because modal buttons and Cookbook Serve add their own offsets. */ +.memory-item-actions > .memory-item-btn, +.memory-item-actions .hwfit-cached-menu-btn { + box-sizing: border-box; + width: 28px; + min-width: 28px; + height: 22px; + min-height: 22px; + margin: 0 !important; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-muted); + font-size: 11px; + display: inline-flex; + align-items: center; + align-self: center; + justify-content: center; + position: relative; + top: 0 !important; + transform: none !important; + line-height: 1; + transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s; +} +.memory-item-actions .hwfit-cached-menu-btn:hover { + opacity: 1 !important; + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); + color: var(--fg); +} +.memory-item-actions .hwfit-cached-menu-btn { + transform: translateY(2px) !important; +} + +@media (min-width: 769px) { + .task-card .memory-item-actions { + top: 6px; + } + .task-card .memory-item-actions > .memory-item-btn { + transform: none !important; + } +} + +/* All kebab menus use the same muted tone as the expand/collapse chevron. */ +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +) { + color: var(--color-muted, var(--fg)) !important; +} +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +) svg { + color: inherit; + fill: currentColor; +} +/* Card kebabs are lightweight menu affordances, not bordered buttons. The + old base hover rule gave only some library cards a boxed outline while + chats, tasks, and cookbook stayed borderless. */ +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +):hover { + background: none !important; + border-color: transparent !important; + color: var(--fg) !important; + opacity: 1; +} +/* Skills used a separate, larger kebab with a different hover treatment. + Match every other card action trigger. */ +.skill-kebab-btn { + box-sizing: border-box; + width: 28px; + min-width: 28px; + height: 22px; + min-height: 22px; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-muted) !important; + opacity: 0.55; + top: -2px; +} +@media (max-width: 768px) { + .memory-item-actions > .memory-item-btn, + .memory-item-actions .hwfit-cached-menu-btn { + width: 40px; + min-width: 40px; + height: 40px; + min-height: 40px; + } + /* Card menus all need the same comfortable tap target. These components + previously escaped the shared action-button sizing through more-specific + desktop selectors. */ + .memory-menu-btn, + .skill-kebab-btn, + .task-card .memory-item-actions .memory-item-btn { + width: 40px !important; + min-width: 40px !important; + height: 40px !important; + min-height: 40px !important; + } + .memory-menu-btn { opacity: 0.7; } + /* Document cards seat their actions on a slightly higher title baseline + than chats/research cards. Bring only that kebab down 4px. */ + #doclib-modal .doclib-card[data-doc-id] > .doclib-document-header .memory-item-actions .memory-item-btn { + transform: translateY(-2px) !important; + } +} +/* Mobile: center the kebab/menu dropdowns in the viewport so they don't + slide left/right as the window width changes (they were positioned via + inline `right: ...px` relative to the kebab button). Stable position is + easier to land on by touch. The inline `top:` set by the menu code is + preserved; we only override the horizontal axis. */ +@media (max-width: 768px) { + .email-card-dropdown, + .cookbook-saved-menu, + .cookbook-dep-menu { + left: 50% !important; + right: auto !important; + transform: translateX(-50%); + } +} +/* Mobile-only Cancel item appended to dropdowns (kebab/More menus). On + desktop outside-click closes them cleanly; on touch that's fiddly, so + give an explicit Cancel row. Visually separated from the action items + above by a top divider + slight muted color. */ +.dropdown-cancel-mobile { display: none; } +@media (max-width: 768px) { + .dropdown-cancel-mobile { + display: flex; + border-top: 1px solid var(--border); + margin-top: 4px; + padding-top: 8px; + opacity: 0.85; + } +} +/* Launch-command Copy button morphs into Cancel once Launch has been clicked. */ +.hwfit-serve-copy.hwfit-serve-cancel { + border-color: var(--color-danger, #e06c75) !important; + color: var(--color-danger, #e06c75) !important; +} +.hwfit-serve-copy.hwfit-serve-cancel:hover { + background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); +} + +/* One visual contract for card kebab menus. These menus are built by several + modules, and native button defaults previously made identical rows render + with different fonts, heights, and hover movement. */ +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) { + box-sizing: border-box; + padding: 4px !important; + color: var(--fg); + background: var(--panel, var(--bg)) !important; + border: 1px solid var(--border) !important; + border-radius: 8px !important; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important; + font-family: inherit !important; + font-size: 11px !important; + line-height: 1.2; +} +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) > .dropdown-item-compact { + box-sizing: border-box; + width: 100%; + min-height: 28px; + padding: 6px 8px; + gap: 10px; + border: 0; + border-radius: 6px; + color: var(--fg); + background: transparent; + font: inherit !important; + line-height: 1.2; + text-align: left; + white-space: nowrap; +} +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) > .dropdown-item-compact:hover:not(:disabled) { + color: var(--fg); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + transform: none; +} +@media (max-width: 768px) { + :is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu + ) > .dropdown-item-compact { + min-height: 44px; + padding: 12px !important; + gap: 10px !important; + font-size: 14px !important; + } +} +/* Email modal title unread badge — small accent pill next to "Email". */ +.email-lib-unread-badge { + display: inline-block; + font-size: 0.55em; + font-weight: 700; + background: var(--accent-primary, var(--red)); + color: var(--bg, #1a1a1a); + border-radius: 9px; + padding: 1px 7px; + margin-left: 8px; + vertical-align: 2px; + line-height: 1.3; + letter-spacing: 0.02em; + cursor: pointer; +} +.email-lib-unread-badge.email-lib-away-badge { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); + padding: 0 7px; +} +.email-lib-auto-reply-badge { + display: inline-block; + margin-left: 7px; + padding: 1px 7px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border)); + border-radius: 9px; + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + color: var(--accent, var(--red)); + font-size: 0.55em; + font-weight: 700; + line-height: 1.3; + letter-spacing: 0.01em; + vertical-align: 2px; + cursor: pointer; +} +.email-lib-auto-reply-status-dot { + width: 6px; + height: 6px; + margin-left: 6px; + border-radius: 50%; + background: var(--accent, var(--red)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), + 0 0 8px color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); + vertical-align: 2px; +} +.modal-header h4 > .email-lib-auto-reply-badge { + font-size: 0.65em; + white-space: nowrap; + vertical-align: 1px; +} +#email-lib-modal.email-lib-unread-active .doclib-modal-content { + box-shadow: + 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent), + 0 0 18px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), + var(--shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.35)); +} +#email-lib-modal .email-lib-header-actions .minimize-btn { + position: relative; + left: 6px; +} +/* Per-card prev/next nav — only visible on the currently expanded card, + sits at the right of the title row next to the done check. */ +.email-card-nav-arrows { + display: none; + margin-left: auto; + gap: 2px; + flex-shrink: 0; + align-items: center; + position: relative; + top: -3px; + left: 19px; +} +.doclib-card.email-card-expanded .email-card-nav-arrows { + display: inline-flex; +} +.email-card-header-menu { + display: none; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 28px; + height: 28px; + padding: 0; + border: none; + border-radius: 6px; + background: none; + color: var(--fg); + opacity: 0.65; + cursor: pointer; + position: relative; + top: -3px; + transition: opacity 0.12s, background 0.12s; +} +.doclib-card.email-card-expanded .email-card-header-menu { + display: inline-flex; +} +.email-card-header-menu:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.email-card-nav-btn { + background: none; + border: none; + color: var(--fg); + opacity: 0.65; + cursor: pointer; + padding: 6px 8px; + border-radius: 6px; + display: inline-flex; + align-items: center; + justify-content: center; + transition: opacity 0.12s, background 0.12s; +} +.email-card-nav-btn:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} + +/* ── Email document editor ── */ +.doc-email-header { + display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; + border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; +} +.doc-email-fields { + display: flex; + flex-direction: column; + gap: 6px; + min-height: 0; +} +.doc-email-collapse-btn { + width: 100%; + min-height: 24px; + display: none; + align-items: center; + gap: 7px; + padding: 2px 4px 3px; + border: none; + background: transparent; + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; + opacity: 0.72; + text-align: left; +} +.doc-email-collapse-btn:hover { opacity: 1; color: var(--accent, var(--red)); } +.doc-email-collapse-btn svg { + flex-shrink: 0; + opacity: 0.65; + transition: transform 0.14s ease; +} +.doc-email-collapse-summary { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + opacity: 0.74; +} +.doc-email-header.doc-email-header-collapsed .doc-email-collapse-btn svg { + transform: rotate(180deg); +} +.email-field { display: flex; align-items: center; gap: 8px; position: relative; } +.email-field label { font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.5; min-width: 50px; text-align: right; flex-shrink: 0; } +.email-field input { + flex: 1; width: 100%; background: transparent; border: 1px solid var(--border); border-radius: 4px; + padding: 5px 8px; font-size: 13px; font-family: inherit; color: var(--fg); outline: none; + min-width: 0; +} +.email-field input:focus { border-color: var(--accent, #4a9eff); } +@container docpane (max-width: 460px) { + .doc-email-header .email-field { + display: grid; + grid-template-columns: 1fr; + gap: 3px; + align-items: stretch; + } + .doc-email-header .email-field label { + min-width: 0; + text-align: left; + } + .doc-email-header .email-field input { + width: 100%; + } +} +/* Cc toggle and attach button are absolute so they don't steal width + from the To input. Anchored to the input's vertical center + 4px + nudge so the label sits where the user expects it. */ +.email-field .email-cc-toggle { + position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); + z-index: 2; +} +/* Accent-tinted prefix label (To / Cc / Bcc / Subject) sitting at + the left edge of each input. Replaces both the old tag + and the all-placeholder accent. */ +.email-field .email-field-prefix { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + font-size: 11px; + font-weight: 600; + color: var(--accent-primary, var(--red)); + pointer-events: none; + z-index: 1; + line-height: 1; +} +.email-field input { padding-left: 44px; } +.email-field #doc-email-subject { padding-left: 64px; } +/* X close button inside Cc / Bcc fields. */ +.email-field .email-cc-close { + position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); + background: none; border: none; color: var(--fg); + opacity: 0.4; cursor: pointer; + width: 22px; height: 22px; + display: inline-flex; align-items: center; justify-content: center; + border-radius: 4px; + padding: 0; + z-index: 2; +} +.email-field .email-cc-close:hover { + opacity: 1; + color: var(--accent, var(--red)); + background: color-mix(in srgb, currentColor 10%, transparent); +} +.email-field input { padding-right: 60px; } +.email-field #doc-email-cc, .email-field #doc-email-bcc { padding-right: 32px; } +.email-field #doc-email-subject { padding-right: 8px; } + +.doc-email-actions { + display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; + border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; + align-items: center; + position: relative; + z-index: 30; + pointer-events: auto; +} +#doc-actions-footer .email-send-split { + margin-left: auto; +} +.doc-save-state-icon { display: none; flex: 0 0 auto; } +.doc-save-button[data-save-state="dirty"] { color: var(--accent, var(--red)); } +.doc-save-button[data-save-state="saving"] { color: var(--fg-muted, var(--fg)); } +.doc-save-button[data-save-state="saved"] { color: var(--fg); } +.doc-save-button[data-save-state="error"] { color: var(--color-danger, #e06c75); } +.doc-stats-wrap { + position: relative; + margin-left: 0; + flex: 0 0 auto; +} +.doc-stats-btn { + height: 28px; + border: 0; + border-radius: 4px; + padding: 0 7px; + background: transparent; + color: var(--fg-muted, var(--fg)); + font: inherit; + font-size: 10px; + cursor: pointer; + white-space: nowrap; +} +.doc-stats-btn:hover, +.doc-stats-btn[aria-expanded="true"] { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 7%, transparent); +} +.doc-stats-btn.doc-stats-selected { + color: var(--accent, var(--red)); + font-weight: 600; +} +.doc-stats-popover { + position: fixed; + right: auto; + bottom: auto; + width: 190px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); + color: var(--fg); + font-size: 11px; +} +.doc-stats-scope { + padding-bottom: 6px; + margin-bottom: 5px; + border-bottom: 1px solid var(--border); + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + text-transform: uppercase; +} +.doc-stats-row { + min-height: 22px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; +} +/* WYSIWYG email body — what the recipient sees, edited in place. */ +.doc-email-richbody { + flex: 1; + min-height: 0; + overflow-y: auto; + padding: 12px 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.6; + color: var(--fg); + background: var(--bg); + outline: none; + white-space: pre-wrap; + text-align: left; + word-wrap: break-word; +} +.doc-email-richbody.richtext-mode { + background: var(--bg); +} +.doc-rich-empty-import { + position: absolute; + top: 90px; + right: 18px; + bottom: auto; + left: auto; + z-index: 2; + display: flex; + pointer-events: none; +} +.doc-rich-empty-import .doc-rich-empty-import-btn { + position: static; + float: none; + margin: 0; + opacity: 1; + pointer-events: auto; + transform: none; +} +.doc-email-richbody.richtext-mode ul.rich-checklist { + list-style: none; + padding-left: 1.8em; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li { + position: relative; + min-height: 1.6em; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li::before { + content: ""; + position: absolute; + left: -1.55em; + top: 0.3em; + width: 0.95em; + height: 0.95em; + box-sizing: border-box; + border: 1.5px solid color-mix(in srgb, var(--fg) 48%, transparent); + border-radius: 3px; + background: var(--sidebar-bg, var(--panel)); + cursor: pointer; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::before { + border-color: var(--accent, var(--red)); + background: var(--accent, var(--red)); +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::after { + content: ""; + position: absolute; + left: -1.27em; + top: 0.44em; + width: 0.28em; + height: 0.5em; + border: solid #fff; + border-width: 0 1.5px 1.5px 0; + transform: rotate(45deg); + pointer-events: none; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"] { + color: color-mix(in srgb, var(--fg) 62%, transparent); + text-decoration: line-through; +} +.doc-email-richbody.richtext-mode hr { + border: 0; + border-top: 1px solid var(--border); + margin: 1.25em 0; +} +.doc-email-richbody.richtext-mode hr.richtext-page-break { + height: 8px; + border-top: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); + border-bottom: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); + margin: 1.6em 0; + opacity: 0.8; +} +.doc-email-richbody.richtext-mode table { + width: 100%; + margin: 12px 0; + border-collapse: collapse; + table-layout: fixed; +} +.doc-email-richbody.richtext-mode th, +.doc-email-richbody.richtext-mode td { + min-width: 48px; + padding: 7px 8px; + border: 1px solid var(--border); + vertical-align: top; + overflow-wrap: anywhere; +} +.doc-email-richbody.richtext-mode th { + background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 85%, var(--fg) 15%); + font-weight: 600; +} +.doc-email-richbody.richtext-mode code, +.doc-email-richbody.richtext-mode span[style*="OdysseusInlineCode"] { + padding: 0.12em 0.35em; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + border-radius: 3px; + background: color-mix(in srgb, var(--fg) 8%, transparent); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + white-space: break-spaces; +} +.doc-email-richbody.richtext-mode pre { + margin: 1em 0; + padding: 12px 14px; + overflow-x: auto; + border: 1px solid var(--border); + border-radius: 4px; + background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 82%, #000 18%) !important; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + line-height: 1.5; + white-space: pre-wrap; + tab-size: 2; +} + +::highlight(doc-find-results) { + color: inherit; + background-color: color-mix(in srgb, #facc15 48%, transparent); +} + +::highlight(doc-find-current) { + color: inherit; + background-color: color-mix(in srgb, var(--accent, #ef4444) 68%, #facc15); +} + +::highlight(doc-ai-selections) { + color: inherit; + background-color: color-mix(in srgb, var(--accent, #ef4444) 30%, transparent); +} +.doc-email-richbody img.richtext-image { + display: block; + margin: 12px 0; + max-width: 100%; + height: auto; + border-radius: 4px; + cursor: pointer; +} +.doc-email-richbody figure.richtext-image { + margin: 12px 0; + max-width: 100%; +} +.doc-email-richbody figure.richtext-image:not(:has(img)) { display: none; } +.doc-email-richbody figure.richtext-image img.richtext-image { + display: block; + margin-top: 0; + margin-bottom: 0; + max-width: 100%; + height: auto; + border-radius: 4px; +} +.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-60) .richtext-image-caption { width: 60%; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-35) .richtext-image-caption { width: 35%; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-left) .richtext-image-caption { margin-left: 0; margin-right: auto; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-center) .richtext-image-caption { margin-left: auto; margin-right: auto; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-right) .richtext-image-caption { margin-left: auto; margin-right: 0; } +.doc-email-richbody img.richtext-image-size-100 { width: 100%; } +.doc-email-richbody img.richtext-image-size-60 { width: 60%; } +.doc-email-richbody img.richtext-image-size-35 { width: 35%; } +.doc-email-richbody img.richtext-image-align-left { margin-left: 0; margin-right: auto; } +.doc-email-richbody img.richtext-image-align-center { margin-left: auto; margin-right: auto; } +.doc-email-richbody img.richtext-image-align-right { margin-left: auto; margin-right: 0; } +.doc-email-richbody img.richtext-image[data-editor-image-selected] { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 3px; +} +.doc-rich-image-toolbar { + position: fixed; + display: inline-flex; + align-items: center; + gap: 2px; + padding: 2px; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); + z-index: 10032; +} +.doc-rich-image-toolbar button { + width: 24px; + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + border: 0; + border-radius: 3px; + background: transparent; + color: inherit; + cursor: pointer; +} +.doc-rich-image-toolbar button:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--accent, var(--red)); +} +.doc-rich-image-toolbar .doc-rich-image-remove:hover { + color: var(--accent, var(--red)); +} +.doc-rich-image-source-menu { + position: fixed; + width: 260px; + min-width: 260px; + padding: 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 4px 16px color-mix(in srgb, var(--fg) 20%, transparent); + z-index: 10033; + font-size: 11px; +} +.doc-rich-image-source-title { + padding: 5px 7px 4px; + color: var(--fg); + font-weight: 700; + opacity: .75; +} +.doc-rich-image-source-menu > button, +.doc-rich-gallery-item { + width: 100%; + display: flex; + align-items: center; + gap: 7px; + padding: 7px 8px; + border: 0; + border-radius: 4px; + background: transparent; + color: var(--fg); + font: inherit; + text-align: left; + cursor: pointer; +} +.doc-rich-image-source-menu > button[data-image-source="computer"] { + padding-left: 9px; +} +.doc-rich-image-source-menu > button:hover, +.doc-rich-gallery-item:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--accent, var(--red)); +} +.doc-rich-gallery-list { + display: grid; + gap: 2px; + max-height: 300px; + overflow-y: auto; +} +.doc-rich-gallery-item { + min-height: 44px; + min-width: 0; +} +.doc-rich-gallery-item > span:last-child { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.doc-rich-gallery-thumb { + width: 40px; + height: 40px; + flex: 0 0 40px; + border: 1px solid var(--border); + border-radius: 4px; + background-position: center; + background-size: cover; + background-repeat: no-repeat; +} +.rich-color-letter { + border-bottom: 2px solid var(--accent, var(--red)); + font-weight: 700; + line-height: 14px; +} +.rich-color-swatch { + width: 13px !important; + height: 13px !important; + border: 1px solid color-mix(in srgb, var(--fg) 35%, transparent); + border-radius: 50%; +} +.rich-highlight-swatch { border-radius: 50%; } +.rich-color-swatch.is-transparent { + background: + linear-gradient(135deg, transparent 43%, var(--red) 44%, var(--red) 56%, transparent 57%), + var(--bg); +} +.rich-color-palette-menu { + min-width: min(292px, calc(100vw - 16px)); +} +.rich-color-palette-menu .rich-color-reset { + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + margin-bottom: 3px; + padding-bottom: 7px; +} +.rich-color-palette-grid { + display: grid; + grid-template-columns: repeat(2, minmax(126px, 1fr)); + gap: 0 4px; + padding: 0 4px; +} +.rich-color-palette-group { + min-width: 0; +} +.rich-color-palette-label { + padding: 4px 8px 3px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 9px; + font-weight: 600; + text-transform: uppercase; +} +.rich-color-palette-group .doc-overflow-item { + min-width: 0; + padding-inline: 8px; +} +.rich-toolbar-color-custom { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-top: 3px; + padding: 7px 9px 4px 10px; + border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + color: var(--fg); + font-size: 11px; +} +.rich-toolbar-color-input.cp-swatch-input { + width: 22px; + height: 22px; + flex: 0 0 22px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 28%, var(--border)); + border-radius: 50%; + cursor: pointer; +} +.rich-toolbar-color-input.cp-swatch-input:focus-visible { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 2px; +} + +.rich-toolbar-slider-row { + display: grid; + grid-template-columns: auto minmax(104px, 1fr) 42px; + align-items: center; + gap: 9px; + min-width: 220px; + padding: 10px; + color: var(--fg); + font-size: 11px; +} +.rich-toolbar-range { + width: 100%; + min-width: 0; + height: 8px; + margin: 0; + border: 0; + border-radius: 999px; + background: color-mix(in srgb, var(--fg) 25%, transparent); + accent-color: var(--red); + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} +.rich-toolbar-range::-webkit-slider-thumb { + width: 9px; + height: 9px; + border: 0; + border-radius: 50%; + background: var(--red); + cursor: pointer; + -webkit-appearance: none; + appearance: none; + transition: width 0.12s ease, height 0.12s ease; +} +.rich-toolbar-range:hover::-webkit-slider-thumb, +.rich-toolbar-range:focus::-webkit-slider-thumb, +.rich-toolbar-range:active::-webkit-slider-thumb { + width: 16px; + height: 16px; +} +.rich-toolbar-range::-moz-range-thumb { + width: 9px; + height: 9px; + border: 0; + border-radius: 50%; + background: var(--red); + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.rich-toolbar-range:hover::-moz-range-thumb, +.rich-toolbar-range:focus::-moz-range-thumb, +.rich-toolbar-range:active::-moz-range-thumb { + width: 16px; + height: 16px; +} +.rich-toolbar-range:focus-visible { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 4px; +} +.rich-toolbar-slider-value { + width: 42px; + box-sizing: border-box; + padding: 2px 3px; + border: 1px solid transparent; + border-radius: 3px; + background: transparent; + text-align: right; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-variant-numeric: tabular-nums; + -webkit-appearance: textfield; + appearance: textfield; +} +.rich-toolbar-slider-value::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +.rich-toolbar-slider-value:focus { + border-color: var(--accent, var(--red)); + outline: none; + color: var(--fg); +} + +.rich-script-icon, +.rich-font-icon, +.rich-font-size-icon { + display: inline-block; + line-height: 1; + font-size: 11px; +} +.rich-highlight-icon { + display: block; + flex-shrink: 0; + color: var(--accent-primary, var(--red)); +} + +.rich-script-icon sup, +.rich-script-icon sub { + font-size: 7px; + line-height: 0; +} + +.rich-font-icon { + font-family: Georgia, serif; + font-size: 12px; +} + +.rich-font-size-icon { + min-width: 16px; + font-size: 10px; + font-weight: 600; + font-variant-numeric: tabular-nums; + text-align: center; +} +.rich-align-center { text-align: center; } +.rich-align-right { text-align: right; } +.doc-email-richbody .email-quoted-history { + margin-top: 14px; + padding: 10px 0 10px 12px; + border-left: 3px solid var(--border); + color: color-mix(in srgb, var(--fg) 70%, transparent); + font-size: 0.92em; + line-height: 1.45; + white-space: pre-wrap; + user-select: text; + pointer-events: none; +} +.doc-email-richbody h1, .doc-email-richbody h2, .doc-email-richbody h3, +.doc-email-richbody h4, .doc-email-richbody h5, .doc-email-richbody h6 { margin: 0.4em 0; } +.email-more-menu { + position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; + min-width: 160px; background: var(--bg); border: 1px solid var(--border); + border-radius: 6px; box-shadow: 0 -4px 12px rgba(0,0,0,0.2); padding: 4px; + z-index: 100; +} +.email-send-btn { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 20%, transparent); + color: var(--accent-primary, var(--red)); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); + border-radius: 6px; + padding: 0 14px; height: 28px; font-size: 11px; font-weight: 600; cursor: pointer; + font-family: inherit; transition: all 0.15s; white-space: nowrap; + display: inline-flex; align-items: center; gap: 6px; +} +/* Split send button: [ Send │ ▾ ] — the caret drops the send-options menu UP. */ +.email-send-split { position: relative; display: inline-flex; } +.email-send-split .email-send-main { + border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; +} +.email-send-split .email-send-caret { + border-top-left-radius: 0; border-bottom-left-radius: 0; + padding: 0 8px; gap: 0; + /* its left border is the single divider between the two halves */ + border-left: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); +} +.email-send-split .email-send-caret svg { transition: transform 0.15s ease; } +.email-send-split .email-send-caret[aria-expanded="true"] svg { transform: none; } +.email-send-btn svg { flex-shrink: 0; } +.email-send-btn:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); + border-color: var(--accent-primary, var(--red)); +} +.email-draft-btn { + background: none; border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border-radius: 6px; + padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; + font-family: inherit; transition: all 0.15s; white-space: nowrap; +} +.email-draft-btn:hover { border-color: var(--fg); color: var(--fg); } +.email-discard-btn { + background: transparent; + /* Dim the label text via color-mix instead of `opacity:0.6` on the whole + button — opacity multiplies through to the SVG and washes out the accent + X glyph. Per-element color dimming keeps the X at full accent strength. */ + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 14px; font-size: 12px; cursor: pointer; font-family: inherit; +} +/* The ✕ glyph itself reads in accent. */ +.email-discard-btn svg { color: var(--accent-primary, var(--red)); opacity: 1; } +.email-discard-btn:hover { opacity: 1; color: var(--red, #e55); border-color: var(--red, #e55); } + +/* Compose email "more" menu button — icon only, matches draft btn height */ +#doc-email-more-btn { + background: none; border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border-radius: 6px; + width: 28px; height: 28px; padding: 0; + cursor: pointer; font-family: inherit; transition: all 0.15s; + display: inline-flex; align-items: center; justify-content: center; +} +#doc-email-more-btn:hover { + border-color: var(--fg); color: var(--fg); +} +.email-more-wrap { display: inline-flex; } +.email-attachments { + display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 0 58px; +} +.email-attachment-chip { + display: inline-flex; align-items: center; gap: 4px; + padding: 4px 8px; font-size: 11px; background: var(--hover-bg, rgba(255,255,255,0.05)); + border: 1px solid var(--border); border-radius: 12px; color: var(--fg); + text-decoration: none; cursor: pointer; transition: background 0.15s, border-color 0.15s; + max-width: 200px; min-width: 0; +} +.email-attachment-chip:hover { + background: color-mix(in srgb, var(--accent) 15%, transparent); + border-color: var(--accent); +} +.email-attachment-chip.is-expanded { + max-width: 90vw; +} +.email-attachment-chip > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + flex: 1 1 auto; min-width: 0; +} +.email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-attachment-chip-related { + border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); +} +/* "Open in editor" launch icon — same prominent style on desktop AND mobile + (was 24px / dim / no border on desktop, easy to miss). Accent-tinted + background + border makes it read as a real action. */ +.email-attachment-open, +.email-attachment-download, +.email-attachment-calendar-open, +.cal-event-email-source { + display: inline-flex; align-items: center; gap: 4px; + height: 22px; padding: 0 9px; border-radius: 999px; + margin-left: 6px; flex: 0 0 auto; + min-width: 52px; justify-content: center; + font-size: 10px; font-weight: 500; letter-spacing: 0.02em; + color: var(--accent-primary, var(--red)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); + cursor: pointer; transition: background 0.1s, border-color 0.1s, color 0.1s; +} +.email-attachment-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-open svg, +.email-attachment-open > svg, +.email-attachment-download svg, +.email-attachment-download > svg, +.email-attachment-calendar-open svg, +.email-attachment-calendar-open > svg, +.cal-event-email-source svg, +.cal-event-email-source > svg { + width: 12px; height: 12px; + opacity: 0.9; +} +.email-attachment-open:hover, +.email-attachment-download:hover, +.email-attachment-calendar-open:hover, +.cal-event-email-source:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 70%, transparent); +} +.email-attachment-download { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-calendar-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.cal-event-email-source { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-open-label { line-height: 1; } +.email-attachment-chip.is-expanded .email-attachment-open, +.email-attachment-chip.is-expanded .email-attachment-download, +.email-attachment-chip.is-expanded .email-attachment-calendar-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-chip.is-expanded .email-attachment-open-label { + display: none; +} +@media (max-width: 768px) { + .doc-email-collapse-btn { + background: inherit; + } + /* Mobile: keep the pill but ensure a comfortable touch target. */ + .email-attachment-open { + height: 26px; padding: 0 10px; + min-height: 26px !important; + min-width: 58px; + } + .email-attachments, + .email-compose-atts { + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-left: 0; + padding-bottom: 2px; + } + .email-attachments::-webkit-scrollbar, + .email-compose-atts::-webkit-scrollbar { + display: none; + } + /* Attachment chip body — modest minimum height so the open icon sits + neatly without dominating. */ + .email-attachment-chip, + .email-compose-chip { + flex: 0 0 auto; + padding: 6px 8px !important; + min-height: 36px !important; + } + .email-attachment-chip.is-expanded { + flex-wrap: nowrap; + align-items: center; + max-width: min(92vw, 360px); + } + .email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + flex: 1 1 auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.25; + min-width: 0; + } + .email-attachment-chip.is-expanded .att-size { + margin-left: 0; + } + .email-attachment-chip.is-expanded .email-attachment-open, + .email-attachment-chip.is-expanded .email-attachment-download, + .email-attachment-chip.is-expanded .email-attachment-calendar-open { + width: 28px; + min-width: 28px; + padding: 0; + margin-left: 4px; + } + .email-compose-chip .compose-chip-name { + max-width: 190px; + } +} + +/* Compose attachment chips (when sending new email) */ +.email-compose-atts { + display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; + padding: 6px 0 0 0; +} +.email-compose-chip { + display: inline-flex; align-items: center; gap: 4px; + flex: 0 1 auto; + min-width: 0; + max-width: min(240px, 100%); + height: 24px; min-height: 24px; box-sizing: border-box; + padding: 2px 3px 2px 7px; font-size: 11px; + background: var(--hover-bg, rgba(255,255,255,0.05)); + border: 1px solid var(--border); border-radius: 12px; color: var(--fg); +} +.email-compose-chip .compose-chip-name { max-width: 180px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.email-compose-chip .compose-chip-remove { + background: none; border: none; color: var(--fg); opacity: 0.5; + height: 18px; font-size: 14px; line-height: 18px; padding: 0 3px; cursor: pointer; + flex-shrink: 0; +} +.email-compose-chip .compose-chip-remove:hover { opacity: 1; color: var(--red, #e55); } + +.pdf-annotation-line-value { + width: 48px; + height: 22px; + box-sizing: border-box; + padding: 0 5px; + border: 1px solid var(--border); + border-radius: 4px; + outline: none; + background: var(--bg-elev, var(--panel)); + color: var(--fg); + font-family: inherit; + font-size: 10px; + font-weight: 500; + line-height: 1; + text-align: right; +} +.pdf-annotation-line-value:hover { border-color: color-mix(in srgb, var(--fg) 38%, var(--border)); } +.pdf-annotation-line-value:focus { border-color: var(--accent, var(--red)); } + +.email-odysseus-attach-menu { + position: fixed; + z-index: 10050; + width: 300px; + max-width: calc(100vw - 16px); + max-height: min(420px, calc(100vh - 16px)); + padding: 7px; + border: 1px solid var(--border); + border-radius: 7px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 12px 30px rgba(0,0,0,0.28); + overflow: hidden; +} +.email-odysseus-attach-local { + width: 100%; + height: 30px; + display: flex; + align-items: center; + gap: 7px; + padding: 0 9px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, var(--border)); + border-radius: 5px; + background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-local:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); +} +.email-odysseus-attach-tabs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 5px; + margin: 7px 0; +} +.email-odysseus-attach-tabs button { + height: 26px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border: 1px solid var(--border); + border-radius: 5px; + background: transparent; + color: var(--fg); + opacity: 0.72; + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-tabs button.active { + opacity: 1; + color: var(--accent-primary, var(--red)); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); +} +.email-odysseus-attach-tabs button svg { + flex-shrink: 0; +} +.email-odysseus-attach-search-wrap { + height: 28px; + display: flex; + align-items: center; + gap: 6px; + margin: -1px 0 7px; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 5px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + color: color-mix(in srgb, var(--fg) 48%, transparent); +} +.email-odysseus-attach-search-wrap:focus-within { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + color: var(--accent-primary, var(--red)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, transparent); +} +.email-odysseus-attach-search { + min-width: 0; + flex: 1; + border: 0; + outline: 0; + background: transparent; + color: var(--fg); + font: inherit; + font-size: 11px; +} +.email-odysseus-attach-search::placeholder { + color: color-mix(in srgb, var(--fg) 38%, transparent); +} +.email-odysseus-attach-list { + max-height: 320px; + overflow: auto; + display: flex; + flex-direction: column; + gap: 4px; +} +.email-odysseus-attach-row { + width: 100%; + min-height: 38px; + display: flex; + align-items: center; + gap: 8px; + padding: 6px 7px; + border: 1px solid transparent; + border-radius: 5px; + background: transparent; + color: var(--fg); + text-align: left; + font: inherit; + cursor: pointer; +} +.email-odysseus-attach-row:hover { + background: var(--hover-bg, rgba(255,255,255,0.06)); + border-color: var(--border); +} +.email-odysseus-attach-row.is-selected { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); +} +.email-odysseus-attach-dot { + width: 8px; + height: 8px; + flex: 0 0 auto; + display: inline-block; + border: 1px solid var(--border); + border-radius: 50%; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.email-odysseus-attach-row.is-selected .email-odysseus-attach-dot { + border-color: var(--accent-primary, var(--red)); + background: var(--accent-primary, var(--red)); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); +} +.email-odysseus-attach-icon, +.email-odysseus-attach-thumb { + width: 28px; + height: 28px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 7%, transparent); + overflow: hidden; +} +.email-odysseus-attach-main { + min-width: 0; + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} +.email-odysseus-attach-title, +.email-odysseus-attach-meta { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-odysseus-attach-title { + font-size: 11px; + line-height: 1.2; +} +.email-odysseus-attach-meta { + font-size: 10px; + line-height: 1.15; + opacity: 0.52; +} +.email-odysseus-attach-empty { + padding: 14px 8px; + color: var(--muted, #888); + font-size: 11px; + text-align: center; +} +.email-odysseus-attach-actions { + display: flex; + align-items: center; + gap: 6px; + margin-top: 2px; + padding-top: 5px; + border-top: 1px solid color-mix(in srgb, var(--border) 75%, transparent); + background: var(--bg); + position: sticky; + bottom: 0; +} +.email-odysseus-attach-count { + flex: 1; + min-width: 0; + color: var(--muted, #888); + font-size: 11px; +} +.email-odysseus-attach-selected { + height: 28px; + min-width: 72px; + padding: 0 12px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + background: var(--accent-primary, var(--red)); + color: #fff; + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-selected svg { + flex-shrink: 0; +} + +.email-cc-toggle { + background: none; border: none; color: var(--fg); + opacity: 0.4; font-size: 11px; cursor: pointer; + padding: 4px 8px; font-family: inherit; +} +.email-cc-toggle:hover { + opacity: 1; + color: var(--accent, #4a9eff); + background: none !important; +} + +@media (max-width: 768px) { + .doc-email-collapse-btn { + display: flex; + } +} + +.email-autocomplete { + position: absolute; top: 100%; left: 58px; right: 0; z-index: 1000; + background: var(--bg); border: 1px solid var(--border); border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 240px; overflow-y: auto; + margin-top: 2px; +} +.contact-suggestion { + display: flex; justify-content: space-between; gap: 8px; + padding: 6px 10px; font-size: 12px; cursor: pointer; + border-bottom: 1px solid var(--border); +} +.contact-suggestion:last-child { border-bottom: none; } +.contact-suggestion:hover, +.contact-suggestion.active, +.contact-suggestion[aria-selected="true"] { + background: color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 15%, transparent); + outline: 1px solid color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 40%, transparent); + outline-offset: -1px; +} +.contact-suggestion .contact-name { font-weight: 600; color: var(--fg); } +.contact-suggestion .contact-email { opacity: 0.6; font-size: 11px; } +.contact-suggestion-status { + padding: 7px 10px; + color: color-mix(in srgb, var(--fg) 65%, transparent); + font-size: 11px; + cursor: default; +} +.contact-suggestion-status.is-loading { opacity: 0.72; } +.contact-suggestion-status.is-unavailable { + color: color-mix(in srgb, var(--red) 82%, var(--fg)); +} +.email-ai-reply-btn { + background: none; + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; + font-family: inherit; transition: all 0.15s; + display: inline-flex; align-items: center; white-space: nowrap; +} +.email-ai-reply-btn:hover { + border-color: var(--accent, #4a9eff); + color: var(--accent, #4a9eff); +} +/* Align select/new buttons in popup library toolbar */ +#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn, #email-lib-compose-btn { position: relative; top: -4px; } +/* Select + Refresh sit slightly lower than Compose on desktop. */ +#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -2px; } + +#email-lib-modal.email-settings-mode .email-accounts-row, +#email-lib-modal.email-settings-mode .admin-card > .memory-toolbar, +#email-lib-modal.email-settings-mode #email-lib-bulk, +#email-lib-modal.email-settings-mode #email-lib-grid, +#email-lib-modal.email-settings-mode #email-lib-sync-status, +#email-lib-modal.email-settings-mode #email-lib-fab { + display: none !important; +} +#email-lib-settings-page[hidden] { + display: none !important; +} +#email-lib-modal.email-settings-mode #email-lib-settings-page { + display: flex; +} +.email-settings-page { + display: flex; + flex: 1; + min-height: 0; + min-width: 0; + flex-direction: column; + gap: 6px; + overflow: hidden; + padding: 2px 0 8px; + width: 100%; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; +} +.settings-email-preferences-card { + display: contents; +} +.settings-email-preferences-card .email-settings-section { + margin: 0 0 10px; + padding: 14px 12px 12px; + background: var(--panel); +} +.settings-email-preferences-card .email-settings-section:last-child { + margin-bottom: 0; +} +.settings-email-preferences-card .email-settings-style-section { + margin-bottom: 4px; +} +.settings-email-default-card #settings-email-default-host { + display: flex; + justify-content: flex-end; + min-width: 0; + margin-left: auto; +} +.settings-email-default-card .settings-email-default-title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} +.settings-email-default-card .settings-email-default-title > span { + min-width: 0; +} +.settings-email-default-card .settings-email-default-title > span > svg { + color: var(--accent, var(--red)); +} +.settings-email-default-card .email-settings-account-picker { + margin-left: 0 !important; +} +.settings-email-default-card .email-settings-support-copy { + position: relative; + top: 6px; +} +.settings-email-default-spacer { + height: 12px; +} +.settings-email-default-card .cookbook-srv-default-label { + position: relative; + top: 2px; +} +.settings-email-default-card .cookbook-srv-default-icon > svg { + position: relative; + top: 7px; +} +.settings-email-settings-legacy { + display: none; +} + +/* Keep the standalone Email Settings cards aligned with the expandable + preference cards below them. */ +[data-settings-panel="email"] > .admin-card { + padding-top: 14px; +} +.email-settings-page-host { + display: flex; + flex: 0 0 auto; + min-height: 0; + padding: 0; + overflow: visible; +} +.settings-email-preferences-card > .email-settings-page-host { width: 100%; } +.email-settings-page-head { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + min-height: 32px; + padding-bottom: 2px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); +} +.email-settings-account-picker { + min-width: 0; + max-width: 100%; +} +.email-settings-account-picker select { + min-width: 0; + max-width: 100%; +} +.email-settings-back { + position: relative; + top: -2px; +} +.email-settings-header-back { + background: transparent; + color: color-mix(in srgb, var(--fg) 58%, transparent); + border: 1px solid transparent; + width: auto; + height: 24px; + padding: 0 8px 0 6px; + align-items: center; + justify-content: center; + gap: 4px; + cursor: pointer; + border-radius: 999px; + flex-shrink: 0; + position: relative; + font-size: 11px; + font-weight: 600; + line-height: 1; +} +.email-settings-header-back:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); + border-color: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--fg); +} +.email-settings-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; +} +.email-settings-title { + display: inline-flex; + align-items: center; + gap: 6px; + flex: 1 1 auto; + width: 100%; + min-width: 0; + font-size: 12px; + font-weight: 700; +} +.email-settings-title svg { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-settings-close { + width: 24px; + height: 24px; + border: 1px solid var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 70%, transparent); + cursor: pointer; + font-size: 14px; + line-height: 1; +} +.email-settings-close:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)); +} +.email-settings-body { + display: flex; + flex-direction: column; + flex: 1 1 auto; + gap: 6px; + padding-top: 4px; + box-sizing: border-box; + min-width: 0; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + overscroll-behavior: contain; +} +.email-settings-loading, +.email-settings-error { + font-size: 12px; + color: color-mix(in srgb, var(--fg) 65%, transparent); + padding: 8px 2px; +} +.email-settings-loading { + display: inline-flex; + align-items: center; + gap: 7px; +} +.email-settings-loading-css-whirlpool { + display: inline-block; + width: 14px; + height: 14px; + box-sizing: border-box; + border: 1.5px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + border-top-color: var(--accent, var(--red)); + border-right-color: color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); + border-radius: 50%; + animation: whirlpool-spin 0.72s linear infinite; + flex: 0 0 auto; +} +.email-settings-loading-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: var(--accent, var(--red)); +} +.email-settings-section { + display: flex; + flex-direction: column; + flex: 0 0 auto; + gap: 7px; + margin: 0; + padding: 14px 8px 8px; + min-width: 0; + box-sizing: border-box; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + transition: opacity 0.15s ease, background 0.15s ease; +} +.email-settings-section.is-disabled { + opacity: 0.58; + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +.email-settings-section-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.email-settings-section-head > div:first-child { + min-width: 0; + flex: 1 1 auto; +} +.email-settings-section-head > .email-settings-enabled-control, +.email-settings-section-head > button { + margin-left: auto; +} +.email-settings-section-toggle { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + width: 100%; + min-width: 0; + padding: 0; + border: 0; + background: transparent; + color: inherit; + text-align: left; + cursor: pointer; +} +.email-settings-section-toggle:hover, +.email-settings-section-toggle:active { + background: transparent; +} +.email-settings-section-toggle-copy { + display: flex; + flex-direction: column; + align-items: flex-start; + min-width: 0; + flex: 1 1 auto; +} +.email-settings-section-toggle > .email-settings-enabled-control { + margin-left: auto; + flex: 0 0 auto; +} +.email-settings-section-chevron { + flex: 0 0 auto; + color: color-mix(in srgb, var(--fg) 62%, transparent); + transition: transform .14s ease, color .14s ease; +} +.email-settings-section-toggle:hover .email-settings-section-chevron, +.email-settings-section-toggle:focus-visible .email-settings-section-chevron { + color: var(--accent, var(--red)); +} +.email-settings-section-title { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 14px; + line-height: 1; + font-weight: 600; + letter-spacing: 0; + margin: 0; + padding: 0; +} +.email-settings-section-title svg { + color: var(--accent, var(--red)); + opacity: 0.6; + flex-shrink: 0; +} +.email-settings-account-scope { + margin-left: 4px; + font-size: 11px; + font-weight: 500; + color: color-mix(in srgb, var(--fg) 60%, transparent); +} +.email-settings-section-body { + display: flex; + flex-direction: column; + gap: 7px; + min-width: 0; + width: 100%; +} +.email-settings-section[open] > .email-settings-section-body { + min-height: 0; + flex: 0 0 auto; + height: auto; + max-height: none; + overflow: visible; + overscroll-behavior: contain; + padding: 0 4px 4px 0; + scrollbar-width: thin; +} +.email-settings-section[open] { + min-height: 0; + flex: 0 0 auto; +} +.email-settings-auto-reply-section[open], +.email-settings-cleanup-section[open]:has(.email-unsub-card) { + flex: 1 1 auto; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + align-content: stretch; +} +.email-settings-auto-reply-section[open] > .email-settings-section-body, +.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body { + flex: 0 0 auto; + height: 100%; + max-height: 100%; + overflow-y: auto; +} +.email-settings-section[open] > .email-settings-section-body > * { + flex: 0 0 auto; +} +.email-settings-section-body-head { + display: flex; + justify-content: flex-end; +} +.email-settings-display-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.email-settings-display-title { + display: inline-flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.email-settings-display-title > svg { + flex: 0 0 auto; + color: var(--accent, var(--red)); +} +.email-settings-display-title-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + position: relative; + top: 10px; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-display-title-row .email-settings-section-title { + flex: 1 1 auto; + width: auto; + padding-bottom: 0; + border-bottom: 0; +} +.email-settings-style-section .email-settings-section-title { + display: flex; + width: 100%; + position: relative; + top: 0; + padding-bottom: 8px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-style-section .email-settings-section-head, +.email-settings-style-section .email-settings-section-body { + position: static; +} +.email-settings-style-section .email-settings-section-head { margin-top: 8px; } +.email-settings-style-section .email-settings-section-body { margin-top: 12px; } +.email-settings-style-section .email-settings-section-desc { + margin-top: 16px; + top: 0; +} +.email-settings-auto-reply-section .email-settings-section-title, +.email-settings-cleanup-section .email-settings-section-title { + display: flex; + width: 100%; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-cleanup-section .email-settings-section-title { + position: relative; + top: 10px; +} +.email-settings-auto-reply-section .email-settings-section-desc, +.email-settings-cleanup-section .email-settings-section-desc { + margin-top: 10px; +} +.email-settings-display-section .email-settings-section-desc, +.email-settings-cleanup-section .email-settings-section-desc, +.email-settings-inline-images-section .email-settings-section-desc { + margin-top: 12px; + top: 12px; +} +.email-settings-auto-reply-title-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + position: relative; + top: 10px; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-auto-reply-section .email-settings-section-head { + margin-top: 8px; +} +.email-settings-auto-reply-section .email-settings-section-body { + margin-top: 8px; +} +.email-settings-auto-reply-section .email-settings-section-desc { + margin-top: 12px; + top: 0; +} +.email-settings-display-section .email-settings-section-desc { + margin-top: 10px; + top: 10px; +} +.email-settings-auto-reply-section .email-settings-section-body { + margin-top: 14px; +} +.email-settings-auto-reply-title-row .email-settings-section-title { + flex: 1 1 auto; + width: auto; + padding-bottom: 0; + border-bottom: 0; +} +.email-settings-support-copy { + color: color-mix(in srgb, var(--fg) 65%, transparent); + font-size: 11px; + line-height: 1.3; +} +.email-settings-clean-btn > svg { + color: var(--accent, var(--red)); +} +.email-settings-clean-btn { + height: 32px; +} +.email-unsub-rescan-btn { + height: 32px; +} +.email-settings-clean-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 7px; + min-width: 0; + margin-left: auto; +} +.email-settings-clean-status { + display: none; + font-size: 10.5px !important; + flex: 0 1 auto; + width: auto !important; + max-width: min(52vw, 360px); + min-width: 0; + overflow: hidden; + text-align: right; + white-space: nowrap; + text-overflow: ellipsis; + position: relative; + top: 2px; +} +.email-settings-clean-status.is-busy { + order: 0; + align-self: center; + width: auto !important; + max-width: 100%; + margin-right: auto; + text-align: left; +} +.email-settings-clean-btn { + flex: 0 0 auto; +} +.email-settings-default .cookbook-srv-default-icon > svg { + position: relative; + top: 3px; +} +.email-unsub-auto-safe-btn > svg { + position: relative; + top: 2px; +} +.email-unsub-auto-safe-btn { + height: 29px; +} +.email-unsub-status, +.email-unsub-panel-status { + position: relative; + top: 2px; +} +.email-unsub-panel-status { + flex: 1 1 220px; + min-width: 0; + margin-right: auto; + overflow: hidden; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-size: 11px; + line-height: 1.3; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-unsub-panel-status.is-error { + color: var(--red, #e57373); +} +.email-unsub-card .email-tag { + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); +} +.email-unsub-duplicate-badge { + position: relative; + top: 2px; +} +.email-unsub-accent-icon { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-unsub-card { + position: relative; + transition: opacity .16s ease; +} +.email-unsub-card-details { + display: flex; + flex-direction: column; + gap: 7px; +} +.email-unsub-card-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 24px; + height: 24px; + padding: 0; + border: 0; + background: transparent; + color: color-mix(in srgb, var(--fg) 62%, transparent); + cursor: pointer; +} +.email-unsub-card-toggle:hover, +.email-unsub-card-toggle:focus-visible { + color: var(--accent, var(--red)); +} +.email-unsub-card-toggle > svg { + transition: transform .14s ease; +} +.email-unsub-done-badge { + display: inline-flex; + align-items: center; + gap: 3px; +} +.email-unsub-done-btn { + display: inline-flex; + align-items: center; + gap: 3px; + flex: 0 0 auto; + padding: 3px 6px; + position: relative; + top: -6px; +} +.email-unsub-card-details .email-unsub-done-btn { + top: 0; +} +.email-unsub-done-btn > svg { + color: var(--accent, var(--red)); +} +.email-unsub-ignore-btn { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 22px; + height: 22px; + padding: 0; + position: absolute; + top: 5px; + right: 6px; + color: color-mix(in srgb, var(--fg) 58%, transparent); +} +.email-unsub-card-details .email-unsub-ignore-btn { + position: static; +} +.email-unsub-ignore-btn:hover { + color: var(--accent, var(--red)); +} +.email-unsub-remaining-actions { + display: inline-flex; + align-items: center; + gap: 7px; + flex-wrap: wrap; +} +.email-unsub-remaining-actions button { + display: inline-flex; + align-items: center; + gap: 4px; + position: relative; + top: -2px; +} +.email-unsub-remaining-actions svg { + flex: 0 0 auto; +} +.email-unsub-link-btn { + position: relative; + top: 2px; +} +.email-unsub-status.is-error { + color: var(--accent, var(--red)) !important; +} +.email-settings-section-desc { + position: relative; + top: 8px; + margin-top: 4px; + font-size: 10.5px; + line-height: 1.25; + color: color-mix(in srgb, var(--fg) 55%, transparent); +} +.email-settings-inline-images-section .email-settings-section-desc { + margin-bottom: 4px; +} +.email-settings-inline-images-spacer { + height: 12px; +} +.email-settings-display-spacer { + height: 10px; +} +.email-settings-display-section .email-settings-section-desc { + margin-top: 10px; + top: 10px; +} +.email-settings-display-section, +.email-settings-inline-images-section { + padding-bottom: 18px; +} +.email-settings-cleanup-section .email-settings-section-desc { + margin-top: 14px; + top: 14px; +} +.email-settings-auto-reply-section .email-settings-section-desc { + margin-top: 12px; + top: 10px; +} +.email-settings-toggle, +.email-settings-check { + display: flex; + align-items: center; + gap: 7px; + font-size: 12px; + line-height: 1.25; +} +.email-settings-toggle { + flex-shrink: 0; +} +.email-settings-enabled-control { + display: inline-flex; + align-items: center; + gap: 7px; + flex-shrink: 0; +} +.email-settings-enabled-state { + font-size: 10px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.email-settings-section.is-enabled .email-settings-enabled-state { + color: var(--accent, var(--red)); +} +.email-settings-section.is-disabled .email-settings-enabled-state { + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.email-settings-display-section.is-enabled .email-settings-enabled-state { + color: var(--accent, var(--red)); +} +.email-settings-check { + min-height: 24px; + padding: 3px 0; +} +.email-settings-check > span:first-child { + min-width: 0; + margin-right: auto; +} +.email-settings-check-spacer { + flex: 1; +} +.email-settings-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 7px; + min-width: 0; +} +.email-settings-grid label, +.email-settings-field { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; +} +.email-settings-grid span, +.email-settings-field > span { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0; + color: color-mix(in srgb, var(--fg) 56%, transparent); +} +.email-settings-page input[type="date"], +.email-settings-page input[type="datetime-local"], +.email-settings-page select, +.email-settings-page textarea { + width: 100%; + min-width: 0; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + color: var(--fg); + font: inherit; + font-size: 12px; +} +.email-settings-page input[type="date"], +.email-settings-page input[type="datetime-local"], +.email-settings-page select { + height: 28px; + padding: 0 7px; +} +.email-settings-page textarea { + resize: vertical; + min-height: 72px; + padding: 7px; + line-height: 1.35; +} +.email-settings-static-value { + min-height: 28px; + display: flex; + align-items: center; + padding: 0 7px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--panel, var(--bg)) 74%, transparent); + color: color-mix(in srgb, var(--fg) 72%, transparent); + font-size: 12px; +} +.email-settings-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 8px; + margin-top: 2px; +} + +/* Email Settings actions use the same quiet outlined treatment everywhere: + the Settings links, the modal settings page, and unsubscribe actions. */ +[data-settings-panel="email"] #set-email-open-integrations, +[data-settings-panel="email"] #set-email-open-tasks, +[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn, +#email-lib-settings-page .email-settings-section button.memory-toolbar-btn { + background: transparent; + border: 1px solid var(--border); + color: var(--fg); + font-family: inherit; + font-weight: 400; +} +[data-settings-panel="email"] #set-email-open-integrations:hover, +[data-settings-panel="email"] #set-email-open-tasks:hover, +[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn:hover, +#email-lib-settings-page .email-settings-section button.memory-toolbar-btn:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); + color: var(--fg); +} +[data-settings-panel="email"] #set-email-open-integrations > svg, +[data-settings-panel="email"] #set-email-open-tasks > svg { + color: var(--accent, var(--red)); + opacity: 1 !important; +} +[data-settings-panel="email"] .email-style-settings-extract, +[data-settings-panel="email"] .email-style-settings-save, +#email-lib-settings-page .email-style-settings-extract, +#email-lib-settings-page .email-style-settings-save { + background: transparent; + border: 1px solid var(--border); + color: var(--fg); + font-weight: 400; +} +[data-settings-panel="email"] .email-style-settings-extract:hover, +[data-settings-panel="email"] .email-style-settings-save:hover, +#email-lib-settings-page .email-style-settings-extract:hover, +#email-lib-settings-page .email-style-settings-save:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); + color: var(--fg); +} +.email-settings-status, +.email-style-settings-status { + margin-right: auto; + font-size: 11px; + color: color-mix(in srgb, var(--fg) 62%, transparent); +} +.email-settings-status.is-success, +.email-style-settings-status.is-success { + color: var(--color-success, #4caf50); + position: relative; + top: 2px; +} +.email-settings-status.is-error, +.email-style-settings-status.is-error { + color: var(--accent, var(--red)); + position: relative; + top: 2px; +} +.email-settings-save, +.email-style-settings-save { + height: 28px; + min-width: 64px; + justify-content: center; + display: inline-flex; + align-items: center; + gap: 5px; +} +.email-settings-save { + height: 32px; +} +.email-style-settings-extract { + height: 32px; +} +.email-style-settings-save { + height: 32px; +} +.email-settings-save svg, +.email-style-settings-save svg { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-style-settings-extract svg { + color: var(--accent, var(--red)); + position: relative; + top: 2px; +} +.email-settings-task-setting { + color: color-mix(in srgb, var(--fg) 72%, transparent); +} +.email-settings-inline-link { + appearance: none; + border: 0; + padding: 0; + background: transparent; + color: var(--accent, var(--red)); + font: inherit; + text-decoration: underline; + cursor: pointer; +} +.email-style-extract-help { + width: 18px; + height: 18px; + min-width: 18px; + padding: 0; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: color-mix(in srgb, var(--fg) 68%, transparent); + display: inline-flex; + align-items: center; + justify-content: center; + font: inherit; + font-size: 11px; + font-weight: 700; + line-height: 1; + cursor: help; +} +.email-style-extract-help:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); +} +@media (max-width: 420px) { + .email-settings-grid { + grid-template-columns: 1fr; + } +} + + +/* ── Notes ── */ +/* Notes panel — body-level flex sibling of chat-container */ +body.notes-view .chat-container { flex: 1; min-width: 0; } + +/* Mobile: notes panel takes over full screen */ +@media (max-width: 768px) { + body.notes-view .notes-pane { + position: fixed; + inset: 0; + max-width: 100%; + width: 100% !important; + /* Desktop sets height: min(80vh, 820px) which wins over inset:0's bottom:0 + and leaves a gap at the bottom of the viewport. Force full height here. */ + height: 100dvh !important; + max-height: 100dvh !important; + z-index: 170; + /* Bottom-sheet treatment like the document editor: stroked, rounded top, + slides up from the bottom. */ + border: 1px solid var(--border); + border-bottom: none; + border-radius: 14px 14px 0 0; + /* Pane itself never scrolls — its inner .notes-pane-body is the scroller. + Two nested scrollers caused the body's flex:1 to lose its height bound + on Firefox mobile and the last row got clipped off-screen. */ + overflow: hidden; + animation: sheet-enter 0.25s cubic-bezier(0.2, 0.8, 0.2, 1) both; + transform-origin: bottom center; + } + /* Required for the flex:1 body to actually constrain to remaining pane + height instead of expanding to its content (default min-height:auto on + flex items). Without this both grid and list views overflow the viewport + bottom on phones. */ + body.notes-view .notes-pane-body { + min-height: 0; + -webkit-overflow-scrolling: touch; + } + body.notes-view #notes-divider { + display: none; + } + body.notes-view .notes-mobile-grabber { + display: block; + flex-shrink: 0; + height: 18px; + position: relative; + background: var(--bg); + touch-action: none; + cursor: grab; + } + body.notes-view .notes-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; + } + body.notes-view .chat-container { + display: none; + } + body.notes-view .mobile-new-chat-btn, + body.notes-view .hamburger-btn { + display: none !important; + } +} +/* ── Mobile notes UX ──────────────────────────────────── + Tiles become read-only previews on touch ≤768px wide. + Tap opens a fullscreen overlay where editing happens; long-press + toggles drag-to-reorder. Wired up in static/js/notes.js. */ +body.notes-mobile-mode .note-card-corner, +body.notes-mobile-mode .note-card-corner-edit, +body.notes-mobile-mode .note-card-corner-archive, +body.notes-mobile-mode .note-card-corner-copy, +body.notes-mobile-mode .note-card-corner-trash, +body.notes-mobile-mode .note-card-corner-unarchive, +body.notes-mobile-mode .note-card-corner-done, +body.notes-mobile-mode .note-card-edit-corner, +body.notes-mobile-mode .note-card-done, +body.notes-mobile-mode .note-card-actions, +body.notes-mobile-mode .note-cl-quickadd, +body.notes-mobile-mode .note-card .note-checkbox-rm, +/* Panel-fullscreen toggle is redundant on mobile — the panel always + fills the viewport already. */ +body.notes-mobile-mode #notes-fullscreen-toggle { + display: none !important; +} + +/* Header toggle icons (archive view, list/grid switch) — defaults are + tuned for the dense desktop header; on mobile bump them slightly so + they read as tap targets without dominating the header. */ +body.notes-mobile-mode #notes-archive-toggle, +body.notes-mobile-mode #notes-view-toggle { + width: 30px; + height: 30px; + padding: 6px; +} +body.notes-mobile-mode #notes-archive-toggle svg, +body.notes-mobile-mode #notes-view-toggle svg { + width: 17px; + height: 17px; +} +/* The whole card is the tap target on mobile — bigger feedback */ +body.notes-mobile-mode .note-card { + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform 0.12s ease, box-shadow 0.12s ease; +} + +/* Drag-to-reorder mode — desaturate the grid and sweep a subtle + shimmer across every tile so it's obvious you're in rearrange mode + (the Google Keep "active edit" cue). Also disable scroll inside the + panel so the drag works smoothly. */ +body.notes-drag-mode .notes-pane-body { + overflow: hidden; + touch-action: none; +} diff --git a/static/css/style-part-07.css b/static/css/15-notes.css similarity index 55% rename from static/css/style-part-07.css rename to static/css/15-notes.css index 0c6fc8fee..3c4103273 100644 --- a/static/css/style-part-07.css +++ b/static/css/15-notes.css @@ -1,3002 +1,19 @@ -/* Fragment 8/9 of the former static/style.css. +/* 15-notes - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -.doc-editor-textarea, -#doc-editor-code.email-mode .doc-editor-textarea { - color: var(--fg) !important; - caret-color: var(--fg) !important; - z-index: 1; -} - -/* Emoji picker (monochrome icon picker) */ -.emoji-picker-btn { - background: none; border: none; color: var(--fg); opacity: 0.35; - padding: 4px 7px; cursor: pointer; min-height: 28px; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.1s; -} -.emoji-picker-btn:hover { opacity: 1; } -.emoji-picker { - width: min(300px, 92vw); max-height: min(300px, 55vh); - background: var(--panel); border: 1px solid var(--border); border-radius: 8px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3); - display: flex; flex-direction: column; overflow: hidden; -} -.emoji-picker-search { - padding: 8px 12px; background: var(--panel); border: none; - border-bottom: 1px solid var(--border); color: var(--fg); - font-size: 12px; font-family: inherit; outline: none; -} -.emoji-picker-groups { overflow-y: auto; padding: 4px 0; flex: 1; } -.emoji-picker-group { padding: 0 6px 4px 6px; } -.emoji-picker-group-name { - font-size: 10px; font-weight: 600; opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.5px; - padding: 4px 4px 2px 4px; -} -.emoji-picker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; } -.emoji-picker-item { - background: none; border: none; cursor: pointer; - padding: 4px; border-radius: 4px; - display: flex; align-items: center; justify-content: center; - color: var(--fg); transition: background 0.1s; -} -.emoji-picker-item:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } -.emoji-picker-item svg { width: 16px; height: 16px; } - -.email-avatar { - width: 28px; height: 28px; border-radius: 50%; background: var(--accent, #4a9eff); - color: #fff; display: flex; align-items: center; justify-content: center; - font-size: 12px; font-weight: 600; flex-shrink: 0; margin-top: 1px; -} -.email-item-content { flex: 1; min-width: 0; overflow: hidden; } -.email-item-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } -.email-sender { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.email-filter-chip { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; - margin: 4px 8px 6px; - padding: 4px 8px; - border-radius: 12px; - font-size: 11px; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - color: var(--fg); -} -.email-filter-chip-clear { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 14px; - line-height: 1; - padding: 0 2px; - opacity: 0.6; -} -.email-date { font-size: 10px; opacity: 0.5; white-space: nowrap; flex-shrink: 0; } -.email-subject { font-size: 11px; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } -.email-sent-chip, -.email-folder-chip { - display: inline-flex; - align-items: center; - height: 14px; - padding: 0 5px; - margin-right: 5px; - border-radius: 999px; - font-size: 8px; - line-height: 1; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - vertical-align: 1px; -} -.email-folder-chip { - position: relative; - top: 4px; - color: color-mix(in srgb, var(--fg) 75%, transparent); - background: rgba(127, 127, 127, 0.13); - border-color: rgba(127, 127, 127, 0.24); -} -.email-meta-date-group { - display: inline-flex; - align-items: center; - gap: 5px; - white-space: nowrap; -} -.email-meta-date-group .email-folder-chip { - margin-right: 0; -} -.email-card-expanded .email-sent-chip, -.email-card-reader .email-sent-chip { - position: relative; - top: 4px; -} -.email-tags { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 3px; margin-left: 6px; vertical-align: middle; position: relative; top: -2px; } -.email-card-tags { - flex: 0 0 auto; - min-width: 0; - max-width: 48%; - position: relative; - top: -4px; -} -.email-card-expanded .email-tags, -.email-card-reader .email-tags { top: -4px; } -.doclib-card-expanded .email-card-tags { top: -4px; } -.email-card-expanded .email-card-titlerow .email-tag, -.email-card-expanded .email-card-titlerow .email-tags-more { - transform: translateY(-2px); -} -@media (max-width: 768px) { - .email-card-expanded .email-card-titlerow .email-tag, - .email-card-expanded .email-card-titlerow .email-tags-more { - transform: translateY(2px); - } -} -.email-card-tags.email-tags-expanded { - position: absolute; - z-index: 4; - right: calc(var(--email-status-width, 44px) + 6px); - top: -5px; - max-width: min(58%, 260px); - padding: 4px 5px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent); -} -.email-tags-more { - appearance: none; - border: 0; - box-sizing: border-box; - border-radius: 999px; - background: rgba(127, 127, 127, 0.16); - color: var(--fg); - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 1px; - height: 13px !important; - min-height: 0 !important; - min-width: 23px; - padding: 1px 4px; - font-family: inherit; - font-size: 9px; - line-height: 1; - margin: 0; - opacity: 0.68; - position: relative; - top: 0; - vertical-align: middle; -} -.email-tags-more svg { display: block; flex: 0 0 auto; } -.email-tags-expanded .email-tags-more svg { transform: rotate(180deg); } -.email-tags-toggle-inline { - position: absolute; - right: 94px; - top: 50%; - transform: translateY(calc(-50% - 5px)); - height: 20px !important; - min-width: 34px; - padding: 2px 6px !important; - font-size: 10px !important; - line-height: 1 !important; - opacity: 0.62; - border-radius: 999px !important; -} -.email-tags-toggle-inline.active { - opacity: 1; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 36%, transparent); -} -.email-tags-toggle-inline.email-tags-empty { - color: var(--warning, #d6b35a); - background: color-mix(in srgb, var(--warning, #d6b35a) 12%, transparent); - border-color: color-mix(in srgb, var(--warning, #d6b35a) 34%, transparent); -} -.email-tag { - display: inline-block; - font-size: 9px; - line-height: 1; - padding: 2px 5px; - border-radius: 8px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.3px; - background: rgba(127, 127, 127, 0.18); - color: var(--fg); -} -.email-tag-clickable { - appearance: none; - border: 0; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - font-family: inherit; - font-size: 9px; - font-weight: 500; - height: 13px !important; - min-height: 0 !important; - line-height: 1; - margin: 0; - padding: 2px 5px !important; - text-transform: uppercase; - vertical-align: middle; - flex: 0 0 auto; -} -.email-tag-work { background: rgba(96, 165, 250, 0.22); color: #60a5fa; } -.email-tag-personal { background: rgba(74, 222, 128, 0.22); color: #4ade80; } -.email-tag-finance { background: rgba(250, 204, 21, 0.22); color: #facc15; } -.email-tag-bills { background: rgba(251, 146, 60, 0.22); color: #fb923c; } -.email-tag-receipt { background: rgba(251, 146, 60, 0.22); color: #fb923c; } -.email-tag-travel { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } -.email-tag-newsletter { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } -.email-tag-promo, -.email-tag-marketing { background: rgba(244, 114, 182, 0.22); color: #f472b6; } -.email-tag-notification { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } -.email-tag-security { background: rgba(248, 113, 113, 0.22); color: #f87171; } -.email-tag-urgent { background: color-mix(in srgb, var(--color-error, #e06c75) 25%, transparent); color: var(--color-error, #e06c75); font-weight: 600; } -.email-tag-reply-soon { background: rgba(240, 173, 78, 0.22); color: #f0ad4e; } -.email-tag-social { background: rgba(56, 189, 248, 0.22); color: #38bdf8; } -.email-tag-shopping { background: rgba(236, 72, 153, 0.22); color: #ec4899; } -.email-tag-calendar { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } -.email-menu-wrap { position: relative; flex-shrink: 0; } -.email-menu-btn { - background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; - padding: 4px; transition: opacity 0.15s; - position: relative; top: 0px; -} -.email-dropdown { - position: absolute; right: 0; top: 100%; z-index: 9999; - min-width: 120px; background: var(--bg); border: 1px solid var(--border); - border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); padding: 4px 0; -} -.email-loading { padding: 16px; text-align: center; font-size: 12px; opacity: 0.5; } -.email-load-more { padding: 8px; text-align: center; } -.email-load-more-btn { - background: transparent; border: 1px solid var(--border); border-radius: 4px; - color: var(--fg); padding: 4px 12px; font-size: 11px; cursor: pointer; -} -.email-load-more-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } -.email-spinner { - display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); - border-top-color: #fff; border-radius: 50%; animation: email-spin 0.6s linear infinite; - vertical-align: middle; margin-right: 4px; -} - -/* Email section doesn't collapse — hide the auto-injected chevron */ -#email-section .section-collapse-btn { display: none !important; } - -#email-section:hover #email-compose-btn { - opacity: 0.7 !important; -} -#email-compose-btn:hover { - opacity: 1 !important; - background: none !important; -} -#email-compose-btn svg { - width: 11px; - height: 11px; - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); -} -#email-compose-btn .list-item-plus-label { - top: calc(50% - 0.5px); - color: var(--fg); - font-size: 10px; -} -#email-compose-btn:hover svg { - transform: rotate(180deg) scale(1.15); -} - -/* Library row "+" — identical to the email compose "+": hidden until the row - is hovered, no accent box, and the same rotate-in animation. */ -#library-new-doc-btn, -#notes-new-note-btn { - opacity: 0.4 !important; - transition: opacity 0.15s ease; -} -#tool-library-btn:hover #library-new-doc-btn, -#tool-notes-btn:hover #notes-new-note-btn { - opacity: 0.7 !important; -} -#library-new-doc-btn:hover, -#notes-new-note-btn:hover { - opacity: 1 !important; - background: none !important; -} -#library-new-doc-btn svg, -#notes-new-note-btn svg { - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); -} -#library-new-doc-btn .list-item-plus-label, -#notes-new-note-btn .list-item-plus-label { - top: calc(50% + 1px); -} -#library-new-doc-btn:hover svg, -#notes-new-note-btn:hover svg { - transform: rotate(180deg) scale(1.15); -} - - -/* MD toolbar attach button — matches other toolbar buttons */ -.md-toolbar-attach-btn { - background: none; border: none; color: var(--fg); opacity: 0.35; - padding: 4px 7px; cursor: pointer; min-height: 28px; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.1s; -} -.md-toolbar-attach-btn:hover { opacity: 1; } - -/* Email reader icon buttons — vertical icon + label stack. */ -.memory-toolbar-btn.reader-icon-btn { - width: auto; - height: 44px; - /* padding: 4px 2px; */ - position: relative; - top: 1px; - display: inline-flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 3px; - flex: 0 0 auto; -} -.memory-toolbar-btn.reader-icon-btn svg { width: 18px; height: 18px; } -.memory-toolbar-btn.reader-icon-btn[data-act="reply"] svg { - color: inherit; - stroke: currentColor; -} -.memory-toolbar-btn.reader-icon-btn.active { - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - color: var(--accent, var(--red)); -} -.memory-toolbar-btn.reader-icon-btn[data-act="more"] { - color: inherit; -} -.memory-toolbar-btn.reader-icon-btn[data-act="more"] svg { fill: currentColor; } -.memory-toolbar-btn.reader-icon-btn.reader-more-active { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); - color: inherit; -} -/* Cookbook Serve dropdown triggers (the cached-model kebab + the saved-config - arrow) — accent-colored to signal they open a menu, with a filled tint when - their dropdown is open. Matches the email reader More button pattern. */ -.hwfit-cached-menu-btn, -.cookbook-saved-arrow { - color: var(--accent, var(--red)); - /* Base .memory-item-btn is flex with align-items but no justify-content, - so the kebab SVG sat left-aligned inside the button instead of being - horizontally centered. Pin it center. */ - justify-content: center; -} -.hwfit-cached-menu-btn.cookbook-menu-active, -.cookbook-saved-arrow.cookbook-menu-active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - color: var(--accent, var(--red)); -} - -/* Card action menus share one hit target and baseline. Keep this late in the - cascade because modal buttons and Cookbook Serve add their own offsets. */ -.memory-item-actions > .memory-item-btn, -.memory-item-actions .hwfit-cached-menu-btn { - box-sizing: border-box; - width: 28px; - min-width: 28px; - height: 22px; - min-height: 22px; - margin: 0 !important; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - color: var(--color-muted); - font-size: 11px; - display: inline-flex; - align-items: center; - align-self: center; - justify-content: center; - position: relative; - top: 0 !important; - transform: none !important; - line-height: 1; - transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s; -} -.memory-item-actions .hwfit-cached-menu-btn:hover { - opacity: 1 !important; - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); - color: var(--fg); -} -.memory-item-actions .hwfit-cached-menu-btn { - transform: translateY(2px) !important; -} - -@media (min-width: 769px) { - .task-card .memory-item-actions { - top: 6px; - } - .task-card .memory-item-actions > .memory-item-btn { - transform: none !important; - } -} - -/* All kebab menus use the same muted tone as the expand/collapse chevron. */ -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -) { - color: var(--color-muted, var(--fg)) !important; -} -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -) svg { - color: inherit; - fill: currentColor; -} -/* Card kebabs are lightweight menu affordances, not bordered buttons. The - old base hover rule gave only some library cards a boxed outline while - chats, tasks, and cookbook stayed borderless. */ -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -):hover { - background: none !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1; -} -/* Skills used a separate, larger kebab with a different hover treatment. - Match every other card action trigger. */ -.skill-kebab-btn { - box-sizing: border-box; - width: 28px; - min-width: 28px; - height: 22px; - min-height: 22px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - color: var(--color-muted) !important; - opacity: 0.55; - top: -2px; -} -@media (max-width: 768px) { - .memory-item-actions > .memory-item-btn, - .memory-item-actions .hwfit-cached-menu-btn { - width: 40px; - min-width: 40px; - height: 40px; - min-height: 40px; - } - /* Card menus all need the same comfortable tap target. These components - previously escaped the shared action-button sizing through more-specific - desktop selectors. */ - .memory-menu-btn, - .skill-kebab-btn, - .task-card .memory-item-actions .memory-item-btn { - width: 40px !important; - min-width: 40px !important; - height: 40px !important; - min-height: 40px !important; - } - .memory-menu-btn { opacity: 0.7; } - /* Document cards seat their actions on a slightly higher title baseline - than chats/research cards. Bring only that kebab down 4px. */ - #doclib-modal .doclib-card[data-doc-id] > .doclib-document-header .memory-item-actions .memory-item-btn { - transform: translateY(-2px) !important; - } -} -/* Mobile: center the kebab/menu dropdowns in the viewport so they don't - slide left/right as the window width changes (they were positioned via - inline `right: ...px` relative to the kebab button). Stable position is - easier to land on by touch. The inline `top:` set by the menu code is - preserved; we only override the horizontal axis. */ -@media (max-width: 768px) { - .email-card-dropdown, - .cookbook-saved-menu, - .cookbook-dep-menu { - left: 50% !important; - right: auto !important; - transform: translateX(-50%); - } -} -/* Mobile-only Cancel item appended to dropdowns (kebab/More menus). On - desktop outside-click closes them cleanly; on touch that's fiddly, so - give an explicit Cancel row. Visually separated from the action items - above by a top divider + slight muted color. */ -.dropdown-cancel-mobile { display: none; } -@media (max-width: 768px) { - .dropdown-cancel-mobile { - display: flex; - border-top: 1px solid var(--border); - margin-top: 4px; - padding-top: 8px; - opacity: 0.85; - } -} -/* Launch-command Copy button morphs into Cancel once Launch has been clicked. */ -.hwfit-serve-copy.hwfit-serve-cancel { - border-color: var(--color-danger, #e06c75) !important; - color: var(--color-danger, #e06c75) !important; -} -.hwfit-serve-copy.hwfit-serve-cancel:hover { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); -} - -/* One visual contract for card kebab menus. These menus are built by several - modules, and native button defaults previously made identical rows render - with different fonts, heights, and hover movement. */ -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) { - box-sizing: border-box; - padding: 4px !important; - color: var(--fg); - background: var(--panel, var(--bg)) !important; - border: 1px solid var(--border) !important; - border-radius: 8px !important; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important; - font-family: inherit !important; - font-size: 11px !important; - line-height: 1.2; -} -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) > .dropdown-item-compact { - box-sizing: border-box; - width: 100%; - min-height: 28px; - padding: 6px 8px; - gap: 10px; - border: 0; - border-radius: 6px; - color: var(--fg); - background: transparent; - font: inherit !important; - line-height: 1.2; - text-align: left; - white-space: nowrap; -} -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) > .dropdown-item-compact:hover:not(:disabled) { - color: var(--fg); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - transform: none; -} -@media (max-width: 768px) { - :is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu - ) > .dropdown-item-compact { - min-height: 44px; - padding: 12px !important; - gap: 10px !important; - font-size: 14px !important; - } -} -/* Email modal title unread badge — small accent pill next to "Email". */ -.email-lib-unread-badge { - display: inline-block; - font-size: 0.55em; - font-weight: 700; - background: var(--accent-primary, var(--red)); - color: var(--bg, #1a1a1a); - border-radius: 9px; - padding: 1px 7px; - margin-left: 8px; - vertical-align: 2px; - line-height: 1.3; - letter-spacing: 0.02em; - cursor: pointer; -} -.email-lib-unread-badge.email-lib-away-badge { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); - padding: 0 7px; -} -.email-lib-auto-reply-badge { - display: inline-block; - margin-left: 7px; - padding: 1px 7px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border)); - border-radius: 9px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - color: var(--accent, var(--red)); - font-size: 0.55em; - font-weight: 700; - line-height: 1.3; - letter-spacing: 0.01em; - vertical-align: 2px; - cursor: pointer; -} -.email-lib-auto-reply-status-dot { - width: 6px; - height: 6px; - margin-left: 6px; - border-radius: 50%; - background: var(--accent, var(--red)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); - vertical-align: 2px; -} -.modal-header h4 > .email-lib-auto-reply-badge { - font-size: 0.65em; - white-space: nowrap; - vertical-align: 1px; -} -#email-lib-modal.email-lib-unread-active .doclib-modal-content { - box-shadow: - 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent), - 0 0 18px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), - var(--shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.35)); -} -#email-lib-modal .email-lib-header-actions .minimize-btn { - position: relative; - left: 6px; -} -/* Per-card prev/next nav — only visible on the currently expanded card, - sits at the right of the title row next to the done check. */ -.email-card-nav-arrows { - display: none; - margin-left: auto; - gap: 2px; - flex-shrink: 0; - align-items: center; - position: relative; - top: -3px; - left: 19px; -} -.doclib-card.email-card-expanded .email-card-nav-arrows { - display: inline-flex; -} -.email-card-header-menu { - display: none; - align-items: center; - justify-content: center; - flex-shrink: 0; - width: 28px; - height: 28px; - padding: 0; - border: none; - border-radius: 6px; - background: none; - color: var(--fg); - opacity: 0.65; - cursor: pointer; - position: relative; - top: -3px; - transition: opacity 0.12s, background 0.12s; -} -.doclib-card.email-card-expanded .email-card-header-menu { - display: inline-flex; -} -.email-card-header-menu:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.email-card-nav-btn { - background: none; - border: none; - color: var(--fg); - opacity: 0.65; - cursor: pointer; - padding: 6px 8px; - border-radius: 6px; - display: inline-flex; - align-items: center; - justify-content: center; - transition: opacity 0.12s, background 0.12s; -} -.email-card-nav-btn:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} - -/* ── Email document editor ── */ -.doc-email-header { - display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; - border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; -} -.doc-email-fields { - display: flex; - flex-direction: column; - gap: 6px; - min-height: 0; -} -.doc-email-collapse-btn { - width: 100%; - min-height: 24px; - display: none; - align-items: center; - gap: 7px; - padding: 2px 4px 3px; - border: none; - background: transparent; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - opacity: 0.72; - text-align: left; -} -.doc-email-collapse-btn:hover { opacity: 1; color: var(--accent, var(--red)); } -.doc-email-collapse-btn svg { - flex-shrink: 0; - opacity: 0.65; - transition: transform 0.14s ease; -} -.doc-email-collapse-summary { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - opacity: 0.74; -} -.doc-email-header.doc-email-header-collapsed .doc-email-collapse-btn svg { - transform: rotate(180deg); -} -.email-field { display: flex; align-items: center; gap: 8px; position: relative; } -.email-field label { font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.5; min-width: 50px; text-align: right; flex-shrink: 0; } -.email-field input { - flex: 1; width: 100%; background: transparent; border: 1px solid var(--border); border-radius: 4px; - padding: 5px 8px; font-size: 13px; font-family: inherit; color: var(--fg); outline: none; - min-width: 0; -} -.email-field input:focus { border-color: var(--accent, #4a9eff); } -@container docpane (max-width: 460px) { - .doc-email-header .email-field { - display: grid; - grid-template-columns: 1fr; - gap: 3px; - align-items: stretch; - } - .doc-email-header .email-field label { - min-width: 0; - text-align: left; - } - .doc-email-header .email-field input { - width: 100%; - } -} -/* Cc toggle and attach button are absolute so they don't steal width - from the To input. Anchored to the input's vertical center + 4px - nudge so the label sits where the user expects it. */ -.email-field .email-cc-toggle { - position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); - z-index: 2; -} -/* Accent-tinted prefix label (To / Cc / Bcc / Subject) sitting at - the left edge of each input. Replaces both the old tag - and the all-placeholder accent. */ -.email-field .email-field-prefix { - position: absolute; - left: 10px; - top: 50%; - transform: translateY(-50%); - font-size: 11px; - font-weight: 600; - color: var(--accent-primary, var(--red)); - pointer-events: none; - z-index: 1; - line-height: 1; -} -.email-field input { padding-left: 44px; } -.email-field #doc-email-subject { padding-left: 64px; } -/* X close button inside Cc / Bcc fields. */ -.email-field .email-cc-close { - position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); - background: none; border: none; color: var(--fg); - opacity: 0.4; cursor: pointer; - width: 22px; height: 22px; - display: inline-flex; align-items: center; justify-content: center; - border-radius: 4px; - padding: 0; - z-index: 2; -} -.email-field .email-cc-close:hover { - opacity: 1; - color: var(--accent, var(--red)); - background: color-mix(in srgb, currentColor 10%, transparent); -} -.email-field input { padding-right: 60px; } -.email-field #doc-email-cc, .email-field #doc-email-bcc { padding-right: 32px; } -.email-field #doc-email-subject { padding-right: 8px; } - -.doc-email-actions { - display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; - border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; - align-items: center; - position: relative; - z-index: 30; - pointer-events: auto; -} -#doc-actions-footer .email-send-split { - margin-left: auto; -} -.doc-save-state-icon { display: none; flex: 0 0 auto; } -.doc-save-button[data-save-state="dirty"] { color: var(--accent, var(--red)); } -.doc-save-button[data-save-state="saving"] { color: var(--fg-muted, var(--fg)); } -.doc-save-button[data-save-state="saved"] { color: var(--fg); } -.doc-save-button[data-save-state="error"] { color: var(--color-danger, #e06c75); } -.doc-stats-wrap { - position: relative; - margin-left: 0; - flex: 0 0 auto; -} -.doc-stats-btn { - height: 28px; - border: 0; - border-radius: 4px; - padding: 0 7px; - background: transparent; - color: var(--fg-muted, var(--fg)); - font: inherit; - font-size: 10px; - cursor: pointer; - white-space: nowrap; -} -.doc-stats-btn:hover, -.doc-stats-btn[aria-expanded="true"] { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 7%, transparent); -} -.doc-stats-btn.doc-stats-selected { - color: var(--accent, var(--red)); - font-weight: 600; -} -.doc-stats-popover { - position: fixed; - right: auto; - bottom: auto; - width: 190px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); - color: var(--fg); - font-size: 11px; -} -.doc-stats-scope { - padding-bottom: 6px; - margin-bottom: 5px; - border-bottom: 1px solid var(--border); - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - text-transform: uppercase; -} -.doc-stats-row { - min-height: 22px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 14px; -} -/* WYSIWYG email body — what the recipient sees, edited in place. */ -.doc-email-richbody { - flex: 1; - min-height: 0; - overflow-y: auto; - padding: 12px 16px; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.6; - color: var(--fg); - background: var(--bg); - outline: none; - white-space: pre-wrap; - text-align: left; - word-wrap: break-word; -} -.doc-email-richbody.richtext-mode { - background: var(--bg); -} -.doc-rich-empty-import { - position: absolute; - top: 90px; - right: 18px; - bottom: auto; - left: auto; - z-index: 2; - display: flex; - pointer-events: none; -} -.doc-rich-empty-import .doc-rich-empty-import-btn { - position: static; - float: none; - margin: 0; - opacity: 1; - pointer-events: auto; - transform: none; -} -.doc-email-richbody.richtext-mode ul.rich-checklist { - list-style: none; - padding-left: 1.8em; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li { - position: relative; - min-height: 1.6em; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li::before { - content: ""; - position: absolute; - left: -1.55em; - top: 0.3em; - width: 0.95em; - height: 0.95em; - box-sizing: border-box; - border: 1.5px solid color-mix(in srgb, var(--fg) 48%, transparent); - border-radius: 3px; - background: var(--sidebar-bg, var(--panel)); - cursor: pointer; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::before { - border-color: var(--accent, var(--red)); - background: var(--accent, var(--red)); -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::after { - content: ""; - position: absolute; - left: -1.27em; - top: 0.44em; - width: 0.28em; - height: 0.5em; - border: solid #fff; - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); - pointer-events: none; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"] { - color: color-mix(in srgb, var(--fg) 62%, transparent); - text-decoration: line-through; -} -.doc-email-richbody.richtext-mode hr { - border: 0; - border-top: 1px solid var(--border); - margin: 1.25em 0; -} -.doc-email-richbody.richtext-mode hr.richtext-page-break { - height: 8px; - border-top: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); - border-bottom: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); - margin: 1.6em 0; - opacity: 0.8; -} -.doc-email-richbody.richtext-mode table { - width: 100%; - margin: 12px 0; - border-collapse: collapse; - table-layout: fixed; -} -.doc-email-richbody.richtext-mode th, -.doc-email-richbody.richtext-mode td { - min-width: 48px; - padding: 7px 8px; - border: 1px solid var(--border); - vertical-align: top; - overflow-wrap: anywhere; -} -.doc-email-richbody.richtext-mode th { - background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 85%, var(--fg) 15%); - font-weight: 600; -} -.doc-email-richbody.richtext-mode code, -.doc-email-richbody.richtext-mode span[style*="OdysseusInlineCode"] { - padding: 0.12em 0.35em; - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 0.9em; - white-space: break-spaces; -} -.doc-email-richbody.richtext-mode pre { - margin: 1em 0; - padding: 12px 14px; - overflow-x: auto; - border: 1px solid var(--border); - border-radius: 4px; - background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 82%, #000 18%) !important; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 0.9em; - line-height: 1.5; - white-space: pre-wrap; - tab-size: 2; -} - -::highlight(doc-find-results) { - color: inherit; - background-color: color-mix(in srgb, #facc15 48%, transparent); -} - -::highlight(doc-find-current) { - color: inherit; - background-color: color-mix(in srgb, var(--accent, #ef4444) 68%, #facc15); -} - -::highlight(doc-ai-selections) { - color: inherit; - background-color: color-mix(in srgb, var(--accent, #ef4444) 30%, transparent); -} -.doc-email-richbody img.richtext-image { - display: block; - margin: 12px 0; - max-width: 100%; - height: auto; - border-radius: 4px; - cursor: pointer; -} -.doc-email-richbody figure.richtext-image { - margin: 12px 0; - max-width: 100%; -} -.doc-email-richbody figure.richtext-image:not(:has(img)) { display: none; } -.doc-email-richbody figure.richtext-image img.richtext-image { - display: block; - margin-top: 0; - margin-bottom: 0; - max-width: 100%; - height: auto; - border-radius: 4px; -} -.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-60) .richtext-image-caption { width: 60%; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-35) .richtext-image-caption { width: 35%; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-left) .richtext-image-caption { margin-left: 0; margin-right: auto; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-center) .richtext-image-caption { margin-left: auto; margin-right: auto; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-right) .richtext-image-caption { margin-left: auto; margin-right: 0; } -.doc-email-richbody img.richtext-image-size-100 { width: 100%; } -.doc-email-richbody img.richtext-image-size-60 { width: 60%; } -.doc-email-richbody img.richtext-image-size-35 { width: 35%; } -.doc-email-richbody img.richtext-image-align-left { margin-left: 0; margin-right: auto; } -.doc-email-richbody img.richtext-image-align-center { margin-left: auto; margin-right: auto; } -.doc-email-richbody img.richtext-image-align-right { margin-left: auto; margin-right: 0; } -.doc-email-richbody img.richtext-image[data-editor-image-selected] { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 3px; -} -.doc-rich-image-toolbar { - position: fixed; - display: inline-flex; - align-items: center; - gap: 2px; - padding: 2px; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 10032; -} -.doc-rich-image-toolbar button { - width: 24px; - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - border: 0; - border-radius: 3px; - background: transparent; - color: inherit; - cursor: pointer; -} -.doc-rich-image-toolbar button:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--accent, var(--red)); -} -.doc-rich-image-toolbar .doc-rich-image-remove:hover { - color: var(--accent, var(--red)); -} -.doc-rich-image-source-menu { - position: fixed; - width: 260px; - min-width: 260px; - padding: 5px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 4px 16px color-mix(in srgb, var(--fg) 20%, transparent); - z-index: 10033; - font-size: 11px; -} -.doc-rich-image-source-title { - padding: 5px 7px 4px; - color: var(--fg); - font-weight: 700; - opacity: .75; -} -.doc-rich-image-source-menu > button, -.doc-rich-gallery-item { - width: 100%; - display: flex; - align-items: center; - gap: 7px; - padding: 7px 8px; - border: 0; - border-radius: 4px; - background: transparent; - color: var(--fg); - font: inherit; - text-align: left; - cursor: pointer; -} -.doc-rich-image-source-menu > button[data-image-source="computer"] { - padding-left: 9px; -} -.doc-rich-image-source-menu > button:hover, -.doc-rich-gallery-item:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--accent, var(--red)); -} -.doc-rich-gallery-list { - display: grid; - gap: 2px; - max-height: 300px; - overflow-y: auto; -} -.doc-rich-gallery-item { - min-height: 44px; - min-width: 0; -} -.doc-rich-gallery-item > span:last-child { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.doc-rich-gallery-thumb { - width: 40px; - height: 40px; - flex: 0 0 40px; - border: 1px solid var(--border); - border-radius: 4px; - background-position: center; - background-size: cover; - background-repeat: no-repeat; -} -.rich-color-letter { - border-bottom: 2px solid var(--accent, var(--red)); - font-weight: 700; - line-height: 14px; -} -.rich-color-swatch { - width: 13px !important; - height: 13px !important; - border: 1px solid color-mix(in srgb, var(--fg) 35%, transparent); - border-radius: 50%; -} -.rich-highlight-swatch { border-radius: 50%; } -.rich-color-swatch.is-transparent { - background: - linear-gradient(135deg, transparent 43%, var(--red) 44%, var(--red) 56%, transparent 57%), - var(--bg); -} -.rich-color-palette-menu { - min-width: min(292px, calc(100vw - 16px)); -} -.rich-color-palette-menu .rich-color-reset { - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - margin-bottom: 3px; - padding-bottom: 7px; -} -.rich-color-palette-grid { - display: grid; - grid-template-columns: repeat(2, minmax(126px, 1fr)); - gap: 0 4px; - padding: 0 4px; -} -.rich-color-palette-group { - min-width: 0; -} -.rich-color-palette-label { - padding: 4px 8px 3px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 9px; - font-weight: 600; - text-transform: uppercase; -} -.rich-color-palette-group .doc-overflow-item { - min-width: 0; - padding-inline: 8px; -} -.rich-toolbar-color-custom { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-top: 3px; - padding: 7px 9px 4px 10px; - border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - color: var(--fg); - font-size: 11px; -} -.rich-toolbar-color-input.cp-swatch-input { - width: 22px; - height: 22px; - flex: 0 0 22px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 28%, var(--border)); - border-radius: 50%; - cursor: pointer; -} -.rich-toolbar-color-input.cp-swatch-input:focus-visible { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 2px; -} - -.rich-toolbar-slider-row { - display: grid; - grid-template-columns: auto minmax(104px, 1fr) 42px; - align-items: center; - gap: 9px; - min-width: 220px; - padding: 10px; - color: var(--fg); - font-size: 11px; -} -.rich-toolbar-range { - width: 100%; - min-width: 0; - height: 8px; - margin: 0; - border: 0; - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 25%, transparent); - accent-color: var(--red); - cursor: pointer; - -webkit-appearance: none; - appearance: none; -} -.rich-toolbar-range::-webkit-slider-thumb { - width: 9px; - height: 9px; - border: 0; - border-radius: 50%; - background: var(--red); - cursor: pointer; - -webkit-appearance: none; - appearance: none; - transition: width 0.12s ease, height 0.12s ease; -} -.rich-toolbar-range:hover::-webkit-slider-thumb, -.rich-toolbar-range:focus::-webkit-slider-thumb, -.rich-toolbar-range:active::-webkit-slider-thumb { - width: 16px; - height: 16px; -} -.rich-toolbar-range::-moz-range-thumb { - width: 9px; - height: 9px; - border: 0; - border-radius: 50%; - background: var(--red); - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.rich-toolbar-range:hover::-moz-range-thumb, -.rich-toolbar-range:focus::-moz-range-thumb, -.rich-toolbar-range:active::-moz-range-thumb { - width: 16px; - height: 16px; -} -.rich-toolbar-range:focus-visible { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 4px; -} -.rich-toolbar-slider-value { - width: 42px; - box-sizing: border-box; - padding: 2px 3px; - border: 1px solid transparent; - border-radius: 3px; - background: transparent; - text-align: right; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-variant-numeric: tabular-nums; - -webkit-appearance: textfield; - appearance: textfield; -} -.rich-toolbar-slider-value::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -.rich-toolbar-slider-value:focus { - border-color: var(--accent, var(--red)); - outline: none; - color: var(--fg); -} - -.rich-script-icon, -.rich-font-icon, -.rich-font-size-icon { - display: inline-block; - line-height: 1; - font-size: 11px; -} -.rich-highlight-icon { - display: block; - flex-shrink: 0; - color: var(--accent-primary, var(--red)); -} - -.rich-script-icon sup, -.rich-script-icon sub { - font-size: 7px; - line-height: 0; -} - -.rich-font-icon { - font-family: Georgia, serif; - font-size: 12px; -} - -.rich-font-size-icon { - min-width: 16px; - font-size: 10px; - font-weight: 600; - font-variant-numeric: tabular-nums; - text-align: center; -} -.rich-align-center { text-align: center; } -.rich-align-right { text-align: right; } -.doc-email-richbody .email-quoted-history { - margin-top: 14px; - padding: 10px 0 10px 12px; - border-left: 3px solid var(--border); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.92em; - line-height: 1.45; - white-space: pre-wrap; - user-select: text; - pointer-events: none; -} -.doc-email-richbody h1, .doc-email-richbody h2, .doc-email-richbody h3, -.doc-email-richbody h4, .doc-email-richbody h5, .doc-email-richbody h6 { margin: 0.4em 0; } -.email-more-menu { - position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; - min-width: 160px; background: var(--bg); border: 1px solid var(--border); - border-radius: 6px; box-shadow: 0 -4px 12px rgba(0,0,0,0.2); padding: 4px; - z-index: 100; -} -.email-send-btn { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 20%, transparent); - color: var(--accent-primary, var(--red)); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); - border-radius: 6px; - padding: 0 14px; height: 28px; font-size: 11px; font-weight: 600; cursor: pointer; - font-family: inherit; transition: all 0.15s; white-space: nowrap; - display: inline-flex; align-items: center; gap: 6px; -} -/* Split send button: [ Send │ ▾ ] — the caret drops the send-options menu UP. */ -.email-send-split { position: relative; display: inline-flex; } -.email-send-split .email-send-main { - border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; -} -.email-send-split .email-send-caret { - border-top-left-radius: 0; border-bottom-left-radius: 0; - padding: 0 8px; gap: 0; - /* its left border is the single divider between the two halves */ - border-left: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); -} -.email-send-split .email-send-caret svg { transition: transform 0.15s ease; } -.email-send-split .email-send-caret[aria-expanded="true"] svg { transform: none; } -.email-send-btn svg { flex-shrink: 0; } -.email-send-btn:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); - border-color: var(--accent-primary, var(--red)); -} -.email-draft-btn { - background: none; border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border-radius: 6px; - padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; - font-family: inherit; transition: all 0.15s; white-space: nowrap; -} -.email-draft-btn:hover { border-color: var(--fg); color: var(--fg); } -.email-discard-btn { - background: transparent; - /* Dim the label text via color-mix instead of `opacity:0.6` on the whole - button — opacity multiplies through to the SVG and washes out the accent - X glyph. Per-element color dimming keeps the X at full accent strength. */ - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; font-family: inherit; -} -/* The ✕ glyph itself reads in accent. */ -.email-discard-btn svg { color: var(--accent-primary, var(--red)); opacity: 1; } -.email-discard-btn:hover { opacity: 1; color: var(--red, #e55); border-color: var(--red, #e55); } - -/* Compose email "more" menu button — icon only, matches draft btn height */ -#doc-email-more-btn { - background: none; border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border-radius: 6px; - width: 28px; height: 28px; padding: 0; - cursor: pointer; font-family: inherit; transition: all 0.15s; - display: inline-flex; align-items: center; justify-content: center; -} -#doc-email-more-btn:hover { - border-color: var(--fg); color: var(--fg); -} -.email-more-wrap { display: inline-flex; } -.email-attachments { - display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 0 58px; -} -.email-attachment-chip { - display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px; font-size: 11px; background: var(--hover-bg, rgba(255,255,255,0.05)); - border: 1px solid var(--border); border-radius: 12px; color: var(--fg); - text-decoration: none; cursor: pointer; transition: background 0.15s, border-color 0.15s; - max-width: 200px; min-width: 0; -} -.email-attachment-chip:hover { - background: color-mix(in srgb, var(--accent) 15%, transparent); - border-color: var(--accent); -} -.email-attachment-chip.is-expanded { - max-width: 90vw; -} -.email-attachment-chip > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - flex: 1 1 auto; min-width: 0; -} -.email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-attachment-chip-related { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); -} -/* "Open in editor" launch icon — same prominent style on desktop AND mobile - (was 24px / dim / no border on desktop, easy to miss). Accent-tinted - background + border makes it read as a real action. */ -.email-attachment-open, -.email-attachment-download, -.email-attachment-calendar-open, -.cal-event-email-source { - display: inline-flex; align-items: center; gap: 4px; - height: 22px; padding: 0 9px; border-radius: 999px; - margin-left: 6px; flex: 0 0 auto; - min-width: 52px; justify-content: center; - font-size: 10px; font-weight: 500; letter-spacing: 0.02em; - color: var(--accent-primary, var(--red)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); - cursor: pointer; transition: background 0.1s, border-color 0.1s, color 0.1s; -} -.email-attachment-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-open svg, -.email-attachment-open > svg, -.email-attachment-download svg, -.email-attachment-download > svg, -.email-attachment-calendar-open svg, -.email-attachment-calendar-open > svg, -.cal-event-email-source svg, -.cal-event-email-source > svg { - width: 12px; height: 12px; - opacity: 0.9; -} -.email-attachment-open:hover, -.email-attachment-download:hover, -.email-attachment-calendar-open:hover, -.cal-event-email-source:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 70%, transparent); -} -.email-attachment-download { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-calendar-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.cal-event-email-source { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-open-label { line-height: 1; } -.email-attachment-chip.is-expanded .email-attachment-open, -.email-attachment-chip.is-expanded .email-attachment-download, -.email-attachment-chip.is-expanded .email-attachment-calendar-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-chip.is-expanded .email-attachment-open-label { - display: none; -} -@media (max-width: 768px) { - .doc-email-collapse-btn { - background: inherit; - } - /* Mobile: keep the pill but ensure a comfortable touch target. */ - .email-attachment-open { - height: 26px; padding: 0 10px; - min-height: 26px !important; - min-width: 58px; - } - .email-attachments, - .email-compose-atts { - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-left: 0; - padding-bottom: 2px; - } - .email-attachments::-webkit-scrollbar, - .email-compose-atts::-webkit-scrollbar { - display: none; - } - /* Attachment chip body — modest minimum height so the open icon sits - neatly without dominating. */ - .email-attachment-chip, - .email-compose-chip { - flex: 0 0 auto; - padding: 6px 8px !important; - min-height: 36px !important; - } - .email-attachment-chip.is-expanded { - flex-wrap: nowrap; - align-items: center; - max-width: min(92vw, 360px); - } - .email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - flex: 1 1 auto; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.25; - min-width: 0; - } - .email-attachment-chip.is-expanded .att-size { - margin-left: 0; - } - .email-attachment-chip.is-expanded .email-attachment-open, - .email-attachment-chip.is-expanded .email-attachment-download, - .email-attachment-chip.is-expanded .email-attachment-calendar-open { - width: 28px; - min-width: 28px; - padding: 0; - margin-left: 4px; - } - .email-compose-chip .compose-chip-name { - max-width: 190px; - } -} - -/* Compose attachment chips (when sending new email) */ -.email-compose-atts { - display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; - padding: 6px 0 0 0; -} -.email-compose-chip { - display: inline-flex; align-items: center; gap: 4px; - flex: 0 1 auto; - min-width: 0; - max-width: min(240px, 100%); - height: 24px; min-height: 24px; box-sizing: border-box; - padding: 2px 3px 2px 7px; font-size: 11px; - background: var(--hover-bg, rgba(255,255,255,0.05)); - border: 1px solid var(--border); border-radius: 12px; color: var(--fg); -} -.email-compose-chip .compose-chip-name { max-width: 180px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.email-compose-chip .compose-chip-remove { - background: none; border: none; color: var(--fg); opacity: 0.5; - height: 18px; font-size: 14px; line-height: 18px; padding: 0 3px; cursor: pointer; - flex-shrink: 0; -} -.email-compose-chip .compose-chip-remove:hover { opacity: 1; color: var(--red, #e55); } - -.pdf-annotation-line-value { - width: 48px; - height: 22px; - box-sizing: border-box; - padding: 0 5px; - border: 1px solid var(--border); - border-radius: 4px; - outline: none; - background: var(--bg-elev, var(--panel)); - color: var(--fg); - font-family: inherit; - font-size: 10px; - font-weight: 500; - line-height: 1; - text-align: right; -} -.pdf-annotation-line-value:hover { border-color: color-mix(in srgb, var(--fg) 38%, var(--border)); } -.pdf-annotation-line-value:focus { border-color: var(--accent, var(--red)); } - -.email-odysseus-attach-menu { - position: fixed; - z-index: 10050; - width: 300px; - max-width: calc(100vw - 16px); - max-height: min(420px, calc(100vh - 16px)); - padding: 7px; - border: 1px solid var(--border); - border-radius: 7px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 12px 30px rgba(0,0,0,0.28); - overflow: hidden; -} -.email-odysseus-attach-local { - width: 100%; - height: 30px; - display: flex; - align-items: center; - gap: 7px; - padding: 0 9px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, var(--border)); - border-radius: 5px; - background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-local:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); -} -.email-odysseus-attach-tabs { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 5px; - margin: 7px 0; -} -.email-odysseus-attach-tabs button { - height: 26px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border: 1px solid var(--border); - border-radius: 5px; - background: transparent; - color: var(--fg); - opacity: 0.72; - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-tabs button.active { - opacity: 1; - color: var(--accent-primary, var(--red)); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); -} -.email-odysseus-attach-tabs button svg { - flex-shrink: 0; -} -.email-odysseus-attach-search-wrap { - height: 28px; - display: flex; - align-items: center; - gap: 6px; - margin: -1px 0 7px; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 5px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.email-odysseus-attach-search-wrap:focus-within { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - color: var(--accent-primary, var(--red)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, transparent); -} -.email-odysseus-attach-search { - min-width: 0; - flex: 1; - border: 0; - outline: 0; - background: transparent; - color: var(--fg); - font: inherit; - font-size: 11px; -} -.email-odysseus-attach-search::placeholder { - color: color-mix(in srgb, var(--fg) 38%, transparent); -} -.email-odysseus-attach-list { - max-height: 320px; - overflow: auto; - display: flex; - flex-direction: column; - gap: 4px; -} -.email-odysseus-attach-row { - width: 100%; - min-height: 38px; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 7px; - border: 1px solid transparent; - border-radius: 5px; - background: transparent; - color: var(--fg); - text-align: left; - font: inherit; - cursor: pointer; -} -.email-odysseus-attach-row:hover { - background: var(--hover-bg, rgba(255,255,255,0.06)); - border-color: var(--border); -} -.email-odysseus-attach-row.is-selected { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); -} -.email-odysseus-attach-dot { - width: 8px; - height: 8px; - flex: 0 0 auto; - display: inline-block; - border: 1px solid var(--border); - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.email-odysseus-attach-row.is-selected .email-odysseus-attach-dot { - border-color: var(--accent-primary, var(--red)); - background: var(--accent-primary, var(--red)); - box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); -} -.email-odysseus-attach-icon, -.email-odysseus-attach-thumb { - width: 28px; - height: 28px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 7%, transparent); - overflow: hidden; -} -.email-odysseus-attach-main { - min-width: 0; - flex: 1; - display: flex; - flex-direction: column; - gap: 2px; -} -.email-odysseus-attach-title, -.email-odysseus-attach-meta { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-odysseus-attach-title { - font-size: 11px; - line-height: 1.2; -} -.email-odysseus-attach-meta { - font-size: 10px; - line-height: 1.15; - opacity: 0.52; -} -.email-odysseus-attach-empty { - padding: 14px 8px; - color: var(--muted, #888); - font-size: 11px; - text-align: center; -} -.email-odysseus-attach-actions { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - padding-top: 5px; - border-top: 1px solid color-mix(in srgb, var(--border) 75%, transparent); - background: var(--bg); - position: sticky; - bottom: 0; -} -.email-odysseus-attach-count { - flex: 1; - min-width: 0; - color: var(--muted, #888); - font-size: 11px; -} -.email-odysseus-attach-selected { - height: 28px; - min-width: 72px; - padding: 0 12px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - background: var(--accent-primary, var(--red)); - color: #fff; - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-selected svg { - flex-shrink: 0; -} - -.email-cc-toggle { - background: none; border: none; color: var(--fg); - opacity: 0.4; font-size: 11px; cursor: pointer; - padding: 4px 8px; font-family: inherit; -} -.email-cc-toggle:hover { - opacity: 1; - color: var(--accent, #4a9eff); - background: none !important; -} - -@media (max-width: 768px) { - .doc-email-collapse-btn { - display: flex; - } -} - -.email-autocomplete { - position: absolute; top: 100%; left: 58px; right: 0; z-index: 1000; - background: var(--bg); border: 1px solid var(--border); border-radius: 6px; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 240px; overflow-y: auto; - margin-top: 2px; -} -.contact-suggestion { - display: flex; justify-content: space-between; gap: 8px; - padding: 6px 10px; font-size: 12px; cursor: pointer; - border-bottom: 1px solid var(--border); -} -.contact-suggestion:last-child { border-bottom: none; } -.contact-suggestion:hover, -.contact-suggestion.active, -.contact-suggestion[aria-selected="true"] { - background: color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 15%, transparent); - outline: 1px solid color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 40%, transparent); - outline-offset: -1px; -} -.contact-suggestion .contact-name { font-weight: 600; color: var(--fg); } -.contact-suggestion .contact-email { opacity: 0.6; font-size: 11px; } -.contact-suggestion-status { - padding: 7px 10px; - color: color-mix(in srgb, var(--fg) 65%, transparent); - font-size: 11px; - cursor: default; -} -.contact-suggestion-status.is-loading { opacity: 0.72; } -.contact-suggestion-status.is-unavailable { - color: color-mix(in srgb, var(--red) 82%, var(--fg)); -} -.email-ai-reply-btn { - background: none; - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; - font-family: inherit; transition: all 0.15s; - display: inline-flex; align-items: center; white-space: nowrap; -} -.email-ai-reply-btn:hover { - border-color: var(--accent, #4a9eff); - color: var(--accent, #4a9eff); -} -/* Align select/new buttons in popup library toolbar */ -#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn, #email-lib-compose-btn { position: relative; top: -4px; } -/* Select + Refresh sit slightly lower than Compose on desktop. */ -#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -2px; } - -#email-lib-modal.email-settings-mode .email-accounts-row, -#email-lib-modal.email-settings-mode .admin-card > .memory-toolbar, -#email-lib-modal.email-settings-mode #email-lib-bulk, -#email-lib-modal.email-settings-mode #email-lib-grid, -#email-lib-modal.email-settings-mode #email-lib-sync-status, -#email-lib-modal.email-settings-mode #email-lib-fab { - display: none !important; -} -#email-lib-settings-page[hidden] { - display: none !important; -} -#email-lib-modal.email-settings-mode #email-lib-settings-page { - display: flex; -} -.email-settings-page { - display: flex; - flex: 1; - min-height: 0; - min-width: 0; - flex-direction: column; - gap: 6px; - overflow: hidden; - padding: 2px 0 8px; - width: 100%; - box-sizing: border-box; - -webkit-overflow-scrolling: touch; -} -.settings-email-preferences-card { - display: contents; -} -.settings-email-preferences-card .email-settings-section { - margin: 0 0 10px; - padding: 14px 12px 12px; - background: var(--panel); -} -.settings-email-preferences-card .email-settings-section:last-child { - margin-bottom: 0; -} -.settings-email-preferences-card .email-settings-style-section { - margin-bottom: 4px; -} -.settings-email-default-card #settings-email-default-host { - display: flex; - justify-content: flex-end; - min-width: 0; - margin-left: auto; -} -.settings-email-default-card .settings-email-default-title { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} -.settings-email-default-card .settings-email-default-title > span { - min-width: 0; -} -.settings-email-default-card .settings-email-default-title > span > svg { - color: var(--accent, var(--red)); -} -.settings-email-default-card .email-settings-account-picker { - margin-left: 0 !important; -} -.settings-email-default-card .email-settings-support-copy { - position: relative; - top: 6px; -} -.settings-email-default-spacer { - height: 12px; -} -.settings-email-default-card .cookbook-srv-default-label { - position: relative; - top: 2px; -} -.settings-email-default-card .cookbook-srv-default-icon > svg { - position: relative; - top: 7px; -} -.settings-email-settings-legacy { - display: none; -} - -/* Keep the standalone Email Settings cards aligned with the expandable - preference cards below them. */ -[data-settings-panel="email"] > .admin-card { - padding-top: 14px; -} -.email-settings-page-host { - display: flex; - flex: 0 0 auto; - min-height: 0; - padding: 0; - overflow: visible; -} -.settings-email-preferences-card > .email-settings-page-host { width: 100%; } -.email-settings-page-head { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; - min-height: 32px; - padding-bottom: 2px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.email-settings-account-picker { - min-width: 0; - max-width: 100%; -} -.email-settings-account-picker select { - min-width: 0; - max-width: 100%; -} -.email-settings-back { - position: relative; - top: -2px; -} -.email-settings-header-back { - background: transparent; - color: color-mix(in srgb, var(--fg) 58%, transparent); - border: 1px solid transparent; - width: auto; - height: 24px; - padding: 0 8px 0 6px; - align-items: center; - justify-content: center; - gap: 4px; - cursor: pointer; - border-radius: 999px; - flex-shrink: 0; - position: relative; - font-size: 11px; - font-weight: 600; - line-height: 1; -} -.email-settings-header-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); -} -.email-settings-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 8px; -} -.email-settings-title { - display: inline-flex; - align-items: center; - gap: 6px; - flex: 1 1 auto; - width: 100%; - min-width: 0; - font-size: 12px; - font-weight: 700; -} -.email-settings-title svg { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-settings-close { - width: 24px; - height: 24px; - border: 1px solid var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 70%, transparent); - cursor: pointer; - font-size: 14px; - line-height: 1; -} -.email-settings-close:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)); -} -.email-settings-body { - display: flex; - flex-direction: column; - flex: 1 1 auto; - gap: 6px; - padding-top: 4px; - box-sizing: border-box; - min-width: 0; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; - overscroll-behavior: contain; -} -.email-settings-loading, -.email-settings-error { - font-size: 12px; - color: color-mix(in srgb, var(--fg) 65%, transparent); - padding: 8px 2px; -} -.email-settings-loading { - display: inline-flex; - align-items: center; - gap: 7px; -} -.email-settings-loading-css-whirlpool { - display: inline-block; - width: 14px; - height: 14px; - box-sizing: border-box; - border: 1.5px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - border-top-color: var(--accent, var(--red)); - border-right-color: color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); - border-radius: 50%; - animation: whirlpool-spin 0.72s linear infinite; - flex: 0 0 auto; -} -.email-settings-loading-spinner { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - color: var(--accent, var(--red)); -} -.email-settings-section { - display: flex; - flex-direction: column; - flex: 0 0 auto; - gap: 7px; - margin: 0; - padding: 14px 8px 8px; - min-width: 0; - box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - transition: opacity 0.15s ease, background 0.15s ease; -} -.email-settings-section.is-disabled { - opacity: 0.58; - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -.email-settings-section-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.email-settings-section-head > div:first-child { - min-width: 0; - flex: 1 1 auto; -} -.email-settings-section-head > .email-settings-enabled-control, -.email-settings-section-head > button { - margin-left: auto; -} -.email-settings-section-toggle { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - width: 100%; - min-width: 0; - padding: 0; - border: 0; - background: transparent; - color: inherit; - text-align: left; - cursor: pointer; -} -.email-settings-section-toggle:hover, -.email-settings-section-toggle:active { - background: transparent; -} -.email-settings-section-toggle-copy { - display: flex; - flex-direction: column; - align-items: flex-start; - min-width: 0; - flex: 1 1 auto; -} -.email-settings-section-toggle > .email-settings-enabled-control { - margin-left: auto; - flex: 0 0 auto; -} -.email-settings-section-chevron { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); - transition: transform .14s ease, color .14s ease; -} -.email-settings-section-toggle:hover .email-settings-section-chevron, -.email-settings-section-toggle:focus-visible .email-settings-section-chevron { - color: var(--accent, var(--red)); -} -.email-settings-section-title { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 14px; - line-height: 1; - font-weight: 600; - letter-spacing: 0; - margin: 0; - padding: 0; -} -.email-settings-section-title svg { - color: var(--accent, var(--red)); - opacity: 0.6; - flex-shrink: 0; -} -.email-settings-account-scope { - margin-left: 4px; - font-size: 11px; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -.email-settings-section-body { - display: flex; - flex-direction: column; - gap: 7px; - min-width: 0; - width: 100%; -} -.email-settings-section[open] > .email-settings-section-body { - min-height: 0; - flex: 0 0 auto; - height: auto; - max-height: none; - overflow: visible; - overscroll-behavior: contain; - padding: 0 4px 4px 0; - scrollbar-width: thin; -} -.email-settings-section[open] { - min-height: 0; - flex: 0 0 auto; -} -.email-settings-auto-reply-section[open], -.email-settings-cleanup-section[open]:has(.email-unsub-card) { - flex: 1 1 auto; - display: grid; - grid-template-rows: auto minmax(0, 1fr); - align-content: stretch; -} -.email-settings-auto-reply-section[open] > .email-settings-section-body, -.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body { - flex: 0 0 auto; - height: 100%; - max-height: 100%; - overflow-y: auto; -} -.email-settings-section[open] > .email-settings-section-body > * { - flex: 0 0 auto; -} -.email-settings-section-body-head { - display: flex; - justify-content: flex-end; -} -.email-settings-display-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.email-settings-display-title { - display: inline-flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.email-settings-display-title > svg { - flex: 0 0 auto; - color: var(--accent, var(--red)); -} -.email-settings-display-title-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - position: relative; - top: 10px; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-display-title-row .email-settings-section-title { - flex: 1 1 auto; - width: auto; - padding-bottom: 0; - border-bottom: 0; -} -.email-settings-style-section .email-settings-section-title { - display: flex; - width: 100%; - position: relative; - top: 0; - padding-bottom: 8px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-style-section .email-settings-section-head, -.email-settings-style-section .email-settings-section-body { - position: static; -} -.email-settings-style-section .email-settings-section-head { margin-top: 8px; } -.email-settings-style-section .email-settings-section-body { margin-top: 12px; } -.email-settings-style-section .email-settings-section-desc { - margin-top: 16px; - top: 0; -} -.email-settings-auto-reply-section .email-settings-section-title, -.email-settings-cleanup-section .email-settings-section-title { - display: flex; - width: 100%; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-cleanup-section .email-settings-section-title { - position: relative; - top: 10px; -} -.email-settings-auto-reply-section .email-settings-section-desc, -.email-settings-cleanup-section .email-settings-section-desc { - margin-top: 10px; -} -.email-settings-display-section .email-settings-section-desc, -.email-settings-cleanup-section .email-settings-section-desc, -.email-settings-inline-images-section .email-settings-section-desc { - margin-top: 12px; - top: 12px; -} -.email-settings-auto-reply-title-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - position: relative; - top: 10px; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-auto-reply-section .email-settings-section-head { - margin-top: 8px; -} -.email-settings-auto-reply-section .email-settings-section-body { - margin-top: 8px; -} -.email-settings-auto-reply-section .email-settings-section-desc { - margin-top: 12px; - top: 0; -} -.email-settings-display-section .email-settings-section-desc { - margin-top: 10px; - top: 10px; -} -.email-settings-auto-reply-section .email-settings-section-body { - margin-top: 14px; -} -.email-settings-auto-reply-title-row .email-settings-section-title { - flex: 1 1 auto; - width: auto; - padding-bottom: 0; - border-bottom: 0; -} -.email-settings-support-copy { - color: color-mix(in srgb, var(--fg) 65%, transparent); - font-size: 11px; - line-height: 1.3; -} -.email-settings-clean-btn > svg { - color: var(--accent, var(--red)); -} -.email-settings-clean-btn { - height: 32px; -} -.email-unsub-rescan-btn { - height: 32px; -} -.email-settings-clean-actions { - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; - gap: 7px; - min-width: 0; - margin-left: auto; -} -.email-settings-clean-status { - display: none; - font-size: 10.5px !important; - flex: 0 1 auto; - width: auto !important; - max-width: min(52vw, 360px); - min-width: 0; - overflow: hidden; - text-align: right; - white-space: nowrap; - text-overflow: ellipsis; - position: relative; - top: 2px; -} -.email-settings-clean-status.is-busy { - order: 0; - align-self: center; - width: auto !important; - max-width: 100%; - margin-right: auto; - text-align: left; -} -.email-settings-clean-btn { - flex: 0 0 auto; -} -.email-settings-default .cookbook-srv-default-icon > svg { - position: relative; - top: 3px; -} -.email-unsub-auto-safe-btn > svg { - position: relative; - top: 2px; -} -.email-unsub-auto-safe-btn { - height: 29px; -} -.email-unsub-status, -.email-unsub-panel-status { - position: relative; - top: 2px; -} -.email-unsub-panel-status { - flex: 1 1 220px; - min-width: 0; - margin-right: auto; - overflow: hidden; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-size: 11px; - line-height: 1.3; - text-align: left; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-unsub-panel-status.is-error { - color: var(--red, #e57373); -} -.email-unsub-card .email-tag { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); -} -.email-unsub-duplicate-badge { - position: relative; - top: 2px; -} -.email-unsub-accent-icon { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-unsub-card { - position: relative; - transition: opacity .16s ease; -} -.email-unsub-card-details { - display: flex; - flex-direction: column; - gap: 7px; -} -.email-unsub-card-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 24px; - height: 24px; - padding: 0; - border: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 62%, transparent); - cursor: pointer; -} -.email-unsub-card-toggle:hover, -.email-unsub-card-toggle:focus-visible { - color: var(--accent, var(--red)); -} -.email-unsub-card-toggle > svg { - transition: transform .14s ease; -} -.email-unsub-done-badge { - display: inline-flex; - align-items: center; - gap: 3px; -} -.email-unsub-done-btn { - display: inline-flex; - align-items: center; - gap: 3px; - flex: 0 0 auto; - padding: 3px 6px; - position: relative; - top: -6px; -} -.email-unsub-card-details .email-unsub-done-btn { - top: 0; -} -.email-unsub-done-btn > svg { - color: var(--accent, var(--red)); -} -.email-unsub-ignore-btn { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 22px; - height: 22px; - padding: 0; - position: absolute; - top: 5px; - right: 6px; - color: color-mix(in srgb, var(--fg) 58%, transparent); -} -.email-unsub-card-details .email-unsub-ignore-btn { - position: static; -} -.email-unsub-ignore-btn:hover { - color: var(--accent, var(--red)); -} -.email-unsub-remaining-actions { - display: inline-flex; - align-items: center; - gap: 7px; - flex-wrap: wrap; -} -.email-unsub-remaining-actions button { - display: inline-flex; - align-items: center; - gap: 4px; - position: relative; - top: -2px; -} -.email-unsub-remaining-actions svg { - flex: 0 0 auto; -} -.email-unsub-link-btn { - position: relative; - top: 2px; -} -.email-unsub-status.is-error { - color: var(--accent, var(--red)) !important; -} -.email-settings-section-desc { - position: relative; - top: 8px; - margin-top: 4px; - font-size: 10.5px; - line-height: 1.25; - color: color-mix(in srgb, var(--fg) 55%, transparent); -} -.email-settings-inline-images-section .email-settings-section-desc { - margin-bottom: 4px; -} -.email-settings-inline-images-spacer { - height: 12px; -} -.email-settings-display-spacer { - height: 10px; -} -.email-settings-display-section .email-settings-section-desc { - margin-top: 10px; - top: 10px; -} -.email-settings-display-section, -.email-settings-inline-images-section { - padding-bottom: 18px; -} -.email-settings-cleanup-section .email-settings-section-desc { - margin-top: 14px; - top: 14px; -} -.email-settings-auto-reply-section .email-settings-section-desc { - margin-top: 12px; - top: 10px; -} -.email-settings-toggle, -.email-settings-check { - display: flex; - align-items: center; - gap: 7px; - font-size: 12px; - line-height: 1.25; -} -.email-settings-toggle { - flex-shrink: 0; -} -.email-settings-enabled-control { - display: inline-flex; - align-items: center; - gap: 7px; - flex-shrink: 0; -} -.email-settings-enabled-state { - font-size: 10px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.email-settings-section.is-enabled .email-settings-enabled-state { - color: var(--accent, var(--red)); -} -.email-settings-section.is-disabled .email-settings-enabled-state { - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.email-settings-display-section.is-enabled .email-settings-enabled-state { - color: var(--accent, var(--red)); -} -.email-settings-check { - min-height: 24px; - padding: 3px 0; -} -.email-settings-check > span:first-child { - min-width: 0; - margin-right: auto; -} -.email-settings-check-spacer { - flex: 1; -} -.email-settings-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 7px; - min-width: 0; -} -.email-settings-grid label, -.email-settings-field { - display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; -} -.email-settings-grid span, -.email-settings-field > span { - font-size: 10px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0; - color: color-mix(in srgb, var(--fg) 56%, transparent); -} -.email-settings-page input[type="date"], -.email-settings-page input[type="datetime-local"], -.email-settings-page select, -.email-settings-page textarea { - width: 100%; - min-width: 0; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.email-settings-page input[type="date"], -.email-settings-page input[type="datetime-local"], -.email-settings-page select { - height: 28px; - padding: 0 7px; -} -.email-settings-page textarea { - resize: vertical; - min-height: 72px; - padding: 7px; - line-height: 1.35; -} -.email-settings-static-value { - min-height: 28px; - display: flex; - align-items: center; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--panel, var(--bg)) 74%, transparent); - color: color-mix(in srgb, var(--fg) 72%, transparent); - font-size: 12px; -} -.email-settings-actions { - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; - gap: 8px; - margin-top: 2px; -} - -/* Email Settings actions use the same quiet outlined treatment everywhere: - the Settings links, the modal settings page, and unsubscribe actions. */ -[data-settings-panel="email"] #set-email-open-integrations, -[data-settings-panel="email"] #set-email-open-tasks, -[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn, -#email-lib-settings-page .email-settings-section button.memory-toolbar-btn { - background: transparent; - border: 1px solid var(--border); - color: var(--fg); - font-family: inherit; - font-weight: 400; -} -[data-settings-panel="email"] #set-email-open-integrations:hover, -[data-settings-panel="email"] #set-email-open-tasks:hover, -[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn:hover, -#email-lib-settings-page .email-settings-section button.memory-toolbar-btn:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); - color: var(--fg); -} -[data-settings-panel="email"] #set-email-open-integrations > svg, -[data-settings-panel="email"] #set-email-open-tasks > svg { - color: var(--accent, var(--red)); - opacity: 1 !important; -} -[data-settings-panel="email"] .email-style-settings-extract, -[data-settings-panel="email"] .email-style-settings-save, -#email-lib-settings-page .email-style-settings-extract, -#email-lib-settings-page .email-style-settings-save { - background: transparent; - border: 1px solid var(--border); - color: var(--fg); - font-weight: 400; -} -[data-settings-panel="email"] .email-style-settings-extract:hover, -[data-settings-panel="email"] .email-style-settings-save:hover, -#email-lib-settings-page .email-style-settings-extract:hover, -#email-lib-settings-page .email-style-settings-save:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); - color: var(--fg); -} -.email-settings-status, -.email-style-settings-status { - margin-right: auto; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.email-settings-status.is-success, -.email-style-settings-status.is-success { - color: var(--color-success, #4caf50); - position: relative; - top: 2px; -} -.email-settings-status.is-error, -.email-style-settings-status.is-error { - color: var(--accent, var(--red)); - position: relative; - top: 2px; -} -.email-settings-save, -.email-style-settings-save { - height: 28px; - min-width: 64px; - justify-content: center; - display: inline-flex; - align-items: center; - gap: 5px; -} -.email-settings-save { - height: 32px; -} -.email-style-settings-extract { - height: 32px; -} -.email-style-settings-save { - height: 32px; -} -.email-settings-save svg, -.email-style-settings-save svg { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-style-settings-extract svg { - color: var(--accent, var(--red)); - position: relative; - top: 2px; -} -.email-settings-task-setting { - color: color-mix(in srgb, var(--fg) 72%, transparent); -} -.email-settings-inline-link { - appearance: none; - border: 0; - padding: 0; - background: transparent; - color: var(--accent, var(--red)); - font: inherit; - text-decoration: underline; - cursor: pointer; -} -.email-style-extract-help { - width: 18px; - height: 18px; - min-width: 18px; - padding: 0; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: color-mix(in srgb, var(--fg) 68%, transparent); - display: inline-flex; - align-items: center; - justify-content: center; - font: inherit; - font-size: 11px; - font-weight: 700; - line-height: 1; - cursor: help; -} -.email-style-extract-help:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); -} -@media (max-width: 420px) { - .email-settings-grid { - grid-template-columns: 1fr; - } -} - - -/* ── Notes ── */ -/* Notes panel — body-level flex sibling of chat-container */ -body.notes-view .chat-container { flex: 1; min-width: 0; } - -/* Mobile: notes panel takes over full screen */ -@media (max-width: 768px) { - body.notes-view .notes-pane { - position: fixed; - inset: 0; - max-width: 100%; - width: 100% !important; - /* Desktop sets height: min(80vh, 820px) which wins over inset:0's bottom:0 - and leaves a gap at the bottom of the viewport. Force full height here. */ - height: 100dvh !important; - max-height: 100dvh !important; - z-index: 170; - /* Bottom-sheet treatment like the document editor: stroked, rounded top, - slides up from the bottom. */ - border: 1px solid var(--border); - border-bottom: none; - border-radius: 14px 14px 0 0; - /* Pane itself never scrolls — its inner .notes-pane-body is the scroller. - Two nested scrollers caused the body's flex:1 to lose its height bound - on Firefox mobile and the last row got clipped off-screen. */ - overflow: hidden; - animation: sheet-enter 0.25s cubic-bezier(0.2, 0.8, 0.2, 1) both; - transform-origin: bottom center; - } - /* Required for the flex:1 body to actually constrain to remaining pane - height instead of expanding to its content (default min-height:auto on - flex items). Without this both grid and list views overflow the viewport - bottom on phones. */ - body.notes-view .notes-pane-body { - min-height: 0; - -webkit-overflow-scrolling: touch; - } - body.notes-view #notes-divider { - display: none; - } - body.notes-view .notes-mobile-grabber { - display: block; - flex-shrink: 0; - height: 18px; - position: relative; - background: var(--bg); - touch-action: none; - cursor: grab; - } - body.notes-view .notes-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; - } - body.notes-view .chat-container { - display: none; - } - body.notes-view .mobile-new-chat-btn, - body.notes-view .hamburger-btn { - display: none !important; - } -} -/* ── Mobile notes UX ──────────────────────────────────── - Tiles become read-only previews on touch ≤768px wide. - Tap opens a fullscreen overlay where editing happens; long-press - toggles drag-to-reorder. Wired up in static/js/notes.js. */ -body.notes-mobile-mode .note-card-corner, -body.notes-mobile-mode .note-card-corner-edit, -body.notes-mobile-mode .note-card-corner-archive, -body.notes-mobile-mode .note-card-corner-copy, -body.notes-mobile-mode .note-card-corner-trash, -body.notes-mobile-mode .note-card-corner-unarchive, -body.notes-mobile-mode .note-card-corner-done, -body.notes-mobile-mode .note-card-edit-corner, -body.notes-mobile-mode .note-card-done, -body.notes-mobile-mode .note-card-actions, -body.notes-mobile-mode .note-cl-quickadd, -body.notes-mobile-mode .note-card .note-checkbox-rm, -/* Panel-fullscreen toggle is redundant on mobile — the panel always - fills the viewport already. */ -body.notes-mobile-mode #notes-fullscreen-toggle { - display: none !important; -} - -/* Header toggle icons (archive view, list/grid switch) — defaults are - tuned for the dense desktop header; on mobile bump them slightly so - they read as tap targets without dominating the header. */ -body.notes-mobile-mode #notes-archive-toggle, -body.notes-mobile-mode #notes-view-toggle { - width: 30px; - height: 30px; - padding: 6px; -} -body.notes-mobile-mode #notes-archive-toggle svg, -body.notes-mobile-mode #notes-view-toggle svg { - width: 17px; - height: 17px; -} -/* The whole card is the tap target on mobile — bigger feedback */ -body.notes-mobile-mode .note-card { - cursor: pointer; - -webkit-tap-highlight-color: transparent; - transition: transform 0.12s ease, box-shadow 0.12s ease; -} - -/* Drag-to-reorder mode — desaturate the grid and sweep a subtle - shimmer across every tile so it's obvious you're in rearrange mode - (the Google Keep "active edit" cue). Also disable scroll inside the - panel so the drag works smoothly. */ -body.notes-drag-mode .notes-pane-body { - overflow: hidden; - touch-action: none; -} body.notes-drag-mode .note-card { cursor: grab; transform: scale(0.985); @@ -6010,3 +3027,1036 @@ body.notes-drag-mode .note-card { align-items: center; gap: 6px; } +.note-cl-dot { + width: 8px; + height: 8px; + border-radius: 50%; + border: 1.5px solid color-mix(in srgb, var(--fg) 30%, transparent); + flex-shrink: 0; + cursor: pointer; + transition: all 0.15s; +} +.note-cl-dot:hover { border-color: var(--fg); transform: scale(1.2); } +.note-cl-row.done .note-cl-dot { + background: var(--accent); + border-color: var(--accent); +} +.note-cl-row.done .note-cl-text { + opacity: 0.4; + background: linear-gradient(currentColor, currentColor) no-repeat; + background-size: 0 1px; + background-position: 0 calc(50% - 1px); + animation: cl-strike 0.32s ease-out forwards; + transition: opacity 0.2s ease; +} +/* Draws the strikethrough line left-to-right when the row is marked + done, instead of snapping it in full-width on the same frame. */ +@keyframes cl-strike { + to { background-size: 100% 1px; } +} +.note-cl-text { + background: transparent; + border: none; + border-bottom: 1px solid var(--border); + color: var(--fg); + font-size: 12px; + padding: 3px 0 3px 3px; + flex: 1 1 auto; + width: auto; + min-width: 0; + outline: none; +} +.note-cl-text:focus { border-color: var(--fg); } +.note-cl-rm { + background: none; + border: none; + color: var(--fg); + opacity: 0.3; + cursor: pointer; + font-size: 14px; + padding: 0; + width: 24px; + height: 14px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: flex-end; + line-height: 1; +} +.note-cl-rm:hover { opacity: 0.8; color: var(--red); } +.note-cl-add { + background: none; + border: none; + color: var(--fg); + opacity: 0.4; + cursor: pointer; + font-size: 11px; + padding: 4px 0; + text-align: left; +} + +/* ── Calendar ── */ +.cal-modal-content { width:min(680px, 94vw); max-height:88vh; overflow-x:hidden; position:relative; } +#calendar-modal .cal-modal-content { animation: none; } +/* Was overflow-y:auto with grid + day-detail flowing naturally. Now uses a + true splitter layout: the body is a flex column that doesn't scroll + itself; the grid takes the remaining space and scrolls if needed; the + day-detail has an explicit height (driven by the splitter drag via + --cal-detail-h) and shrinks the grid visually as it grows. */ +#cal-body { display:flex; flex-direction:column; gap:8px; overflow:hidden; padding:0; min-height:0; } +#cal-body > .cal-grid { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overflow-x: hidden; } +@media (max-width: 768px) { + /* Let the calendar pane shrink to nothing on mobile so the day-detail + splitter can be dragged all the way to the top, hiding the calendar + entirely. Without min-height:0 the grid (or the week-wrap below) + refuses to shrink past its built-in floor. */ + #cal-body > .cal-grid, + #cal-body > .cal-wk-wrap { + min-height: 0; + max-height: none; + } + /* Let the week grid fill the calendar pane and scroll its hours internally + instead of overflowing #cal-body (which clips it and feels cramped). */ + #cal-body > .cal-wk-wrap { + flex: 1 1 auto; + overflow: auto; + } +} +.cal-toolbar { display:flex; align-items:center; gap:6px; margin-bottom:7px; flex-wrap:wrap; line-height:1; max-width:100%; row-gap:6px; } + +/* Quick-add bar: natural-language event creation. Sits directly under + the toolbar; focused with `Q`; opens the bespoke form pre-filled. */ +.cal-quickadd-row { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 8px; + padding: 1px 10px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + transition: border-color 0.15s, background 0.15s; + position: relative; +} +/* Two-tone placeholder hint: "Quick add" in accent, the example in grey, both + at one low opacity. Overlays the (empty) input; hidden once the user types. */ +.cal-quickadd-hint { + position: absolute; + left: 10px; + right: 10px; + top: 50%; + transform: translateY(-50%); + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--fg); + opacity: 0.5; + pointer-events: none; + white-space: nowrap; + overflow: hidden; +} +.cal-quickadd-hint .qa-hint-accent { color: var(--accent, var(--red)); } +/* ↵ enter glyph in the hint — makes it obvious the field submits on Enter. */ +.cal-quickadd-hint .qa-hint-enter { margin-left: auto; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.8; } +/* Cycling example text fades out before swap, then fades back in. */ +.cal-quickadd-hint .qa-hint-example { + display: block; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-style: italic; + transition: opacity 0.18s ease; +} +.cal-quickadd-hint .qa-hint-example.qa-hint-fade { opacity: 0; } +.cal-title-hint { + position: absolute; + left: 0; + top: 50%; + transform: translateY(calc(-50% - 2px)); + display: none; + align-items: center; + gap: 7px; + font-size: 18px; + line-height: 1.2; + white-space: nowrap; + pointer-events: none; + color: color-mix(in srgb, var(--fg) 42%, transparent); +} +.cal-hero-title:placeholder-shown ~ .cal-title-hint { display: inline-flex; } +.cal-title-hint .cal-title-enter-ico { color: var(--accent, var(--red)); flex-shrink: 0; } +/* Pure-CSS toggle: hide the hint as soon as the field has real text (no + per-keystroke JS, so typing never triggers a re-render / focus loss). */ +.cal-quickadd-input:not(:placeholder-shown) ~ .cal-quickadd-hint { display: none; } +.cal-quickadd-row:focus-within { + border-color: color-mix(in srgb, var(--accent, var(--fg)) 55%, var(--border)); + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); +} +.cal-quickadd-icon { + color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent); + display: inline-flex; + align-items: center; + flex-shrink: 0; + opacity: 0.7; +} +.cal-quickadd-input { + flex: 1; + min-width: 0; + background: transparent; + border: none; + outline: none; + color: var(--fg); + font-family: inherit; + font-size: 12px; + height: 22px; + padding: 0; +} +.cal-quickadd-status { + font-size: 10px; + opacity: 0.55; + font-variant-numeric: tabular-nums; +} +.cal-toolbar > *, .cal-toolbar-nav > *, .cal-toolbar-right > * { vertical-align:middle; } +.cal-toolbar-nav { display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; } +.cal-toolbar-right { + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 6px; + flex: 1 1 100%; + width: 100%; + margin-left: 0; + margin-top: 6px; + flex-wrap: wrap; +} +.cal-title { font-size:13px; font-weight:600; white-space:nowrap; height:24px; line-height:30px; padding:0 6px; display:inline-block; vertical-align:middle; box-sizing:border-box; } +.cal-toolbar-clock { + margin-left: 8px; + color: var(--accent, var(--red)); + font-size: 11px; + font-weight: 600; + font-variant-numeric: tabular-nums; +} +button.cal-nav { background:color-mix(in srgb, var(--fg) 6%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 8px; height:24px; line-height:22px; cursor:pointer; font-size:11px; font-family:inherit; box-sizing:border-box; vertical-align:middle; } +button.cal-nav:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); } +button.cal-nav.cal-toolbar-arrow { + display: none; + align-items: center; + justify-content: center; + width: 24px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + border-radius: 50%; + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + font-size: 18px; + line-height: 1; + opacity: 0.84; +} +.cal-toolbar-arrow-glyph { + position: relative; + top: -1px; + line-height: 1; +} +@media (max-width: 768px) { + button.cal-nav.cal-toolbar-arrow { display: inline-flex !important; } + .cal-toolbar-nav { + flex: 1 1 100%; + width: 100%; + min-width: 0; + } + #cal-next { margin-left: auto; } +} +.cal-week-start-toggle { + display: inline-flex; + align-items: center; + gap: 0; + width: max-content; + padding: 0; + height: 24px; + box-sizing: border-box; + border: 1px solid var(--border); + border-radius: 5px; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.cal-week-start-btn { + box-sizing: border-box; + height: 22px !important; + min-height: 22px !important; + margin-top: 0 !important; + padding: 0 7px; + border: 1px solid transparent; + border-radius: 4px; + background: transparent; + color: color-mix(in srgb, var(--fg) 68%, transparent); + cursor: pointer; + font-family: inherit; + font-size: 10px; + line-height: 20px; + transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; +} +.cal-settings-actions { + display: flex; + justify-content: flex-end; + width: 100%; +} +#cal-settings-panel .cal-week-start-toggle { + display: flex; + margin-left: auto; +} +.cal-settings-sync-status { + position: relative; + top: 2px; + font-size: 11px; + opacity: 0.6; +} +.cal-settings-sync-status.is-error { + color: var(--red); + opacity: 1; +} +.cal-week-start-btn:hover { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.cal-week-start-btn.active, +.cal-week-start-btn.active:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); +} +.cal-side-nav { + position: absolute; + top: var(--cal-side-nav-y, 50%); + transform: translateY(-50%); + width: 32px; + height: 32px; + border-radius: 50%; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + cursor: pointer; + z-index: 6; + opacity: 0; + pointer-events: none; + font-family: inherit; + font-size: 24px; + line-height: 28px; + padding: 0 0 3px; + transition: opacity 0.12s ease, background 0.12s ease, transform 0.12s ease; +} +.cal-modal-content:hover .cal-side-nav, +.cal-side-nav:focus-visible { + opacity: 0.84; + pointer-events: auto; +} +.cal-side-nav:hover { + opacity: 1; + background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); + transform: translateY(-50%) scale(1.04); +} +button.cal-today-btn { font-size:10px; opacity:0.5; } +button.cal-add-btn { background:var(--accent); color:#fff; border:none; border-radius:50%; width:24px; height:24px; line-height:22px; font-size:18px; cursor:pointer; flex-shrink:0; padding:0; box-sizing:border-box; font-family:inherit; vertical-align:middle; text-align:center; } +button.cal-add-btn.cal-add-btn-text { + width:auto; min-width:0; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 12px; + padding: 0 10px 0 6px; + display: inline-flex; + align-items: center; + gap: 4px; + height: 24px; + line-height: 1; + margin-top: 0; + transition: background 0.15s, border-color 0.15s; +} +button.cal-add-btn.cal-add-btn-text:hover { + background: color-mix(in srgb, var(--fg) 14%, transparent); + border-color: var(--accent); + opacity: 1; +} +.cal-add-plus { + font-size: 16px; + line-height: 1; + color: var(--accent, var(--red)); + font-weight: 500; + display: inline-block; + transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + padding-bottom: 2px; +} +button.cal-add-btn.cal-add-btn-text:hover .cal-add-plus { + transform: rotate(180deg); +} +.cal-add-label { font-size:11px; font-weight:600; line-height:1; opacity:0.85; } +/* Small variant of the toolbar's +New button. Inherits the .cal-add-btn-text + styles (pill with "+" + "New" + spring-rotate on the +), so the + animation is identical. Just shrunk a bit. */ +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { + height: 20px; + border-radius: 10px; + /* At rest: tight, equal padding around the "+" so it sits centred + with no trailing gap. On hover the right pad and the flex gap grow + to make room for the revealed "New". */ + padding: 0 5px; + gap: 0; + transition: padding 0.25s ease, gap 0.25s ease, background 0.15s, border-color 0.15s; +} +/* Don't touch .cal-add-plus on the small variant — the toolbar +New is + the reference and we want the same rotation centre / motion. The label + is shrunk and hidden until hover so the pill is compact at rest. */ +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { + font-size: 10px; + max-width: 0; + margin-left: 0; + opacity: 0; + overflow: hidden; + white-space: nowrap; + transition: max-width 0.25s ease, opacity 0.18s ease, margin-left 0.25s ease; +} +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover .cal-add-label { + max-width: 40px; + margin-left: 3px; + opacity: 0.85; +} +.cal-add-btn:hover { opacity:0.85; } + +/* Mobile: bump the +New pill and the +/- zoom buttons up to a tap-friendly + size so they're easy to hit with a thumb. */ +@media (max-width: 768px) { + /* Toolbar +New (relocated by JS into the quickadd row on mobile) */ + button.cal-add-btn.cal-add-btn-text { + height: 36px; + border-radius: 18px; + padding: 0 14px 0 12px; + gap: 6px; + flex-shrink: 0; + } + /* Day-detail +New: just a "+" on mobile — drop the label entirely. */ + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { + width: 28px; + height: 28px; + border-radius: 50%; + padding: 0; + gap: 0; + justify-content: center; + } + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { + display: none; + } + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { + font-size: 18px; + padding-bottom: 0; + } + .cal-add-plus { font-size: 22px; padding-bottom: 1px; } + .cal-add-label { font-size: 13px; } + .cal-wk-zoom { + width: 36px; + height: 36px; + font-size: 20px; + border-radius: 8px; + opacity: 0.85; + } + /* The +New button sits as a sibling of the quickadd row inside a flex + wrapper, so it's clearly outside the input's bordered box. The row + itself keeps its own styling. */ + .cal-quickadd-wrap { + display: flex; + align-items: stretch; + gap: 8px; + margin: 0 0 8px; + } + .cal-quickadd-wrap > #cal-quickadd-row { + flex: 1; + min-width: 0; + margin: 0; + } + .cal-quickadd-wrap > .cal-add-btn-text { + align-self: stretch; + flex-shrink: 0; + } + /* Nudge the "+" glyph up one pixel on the round day-detail button so it + sits visually centred inside the circle. */ + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { + transform: translateY(-1px); + } + /* Event form: bigger Create / Cancel buttons so they're easy to tap. */ + .cal-form-actions button.cal-btn { + height: 44px; + padding: 0 20px; + font-size: 14px; + border-radius: 8px; + min-width: 96px; + } + /* Left-align the day/month + tags row in the day-detail panel and + the calendar/category chip row so they sit flush against the + left edge on a narrow screen. */ + .cal-detail-header { + justify-content: flex-start; + gap: 12px; + } + .cal-filters { + justify-content: flex-start; + } +} + +/* Refresh button spin — driven by JS toggling .cal-syncing on the button. + transform-box + transform-origin keep the rotation pivoted at the SVG's + own centre so it spins in place instead of orbiting / drifting up. */ +#cal-sync svg, +#email-lib-refresh-btn svg { display: block; transform-box: fill-box; transform-origin: 50% 50%; } +/* Brief checkmark flash after a refresh completes. Lasts ~900ms in JS; + the small bounce + accent color give a clear "done" cue. */ +#cal-sync.cal-sync-done svg, +#email-lib-refresh-btn.email-lib-refresh-done svg { + color: var(--accent, #2ea043); + animation: refresh-check-pop 0.32s ease-out; +} +@keyframes refresh-check-pop { + 0% { transform: scale(0.55); opacity: 0.4; } + 60% { transform: scale(1.18); opacity: 1; } + 100% { transform: scale(1); opacity: 1; } +} +#cal-sync.cal-syncing svg, +#email-lib-refresh-btn.email-lib-refreshing svg { animation: spin 0.6s linear infinite; } +.email-undone-toggle.active, +.email-attach-toggle.active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); + color: var(--accent, var(--red)); + font-weight: 600; +} +/* Hide the gallery-style reader-btn labels on desktop — only the mobile + @media block re-displays them under each icon. Desktop keeps compact + icon-only reader buttons. */ +.reader-btn-label { + display: inline-block; + font-size: 9px; + font-weight: 500; + line-height: 1; + letter-spacing: 0.02em; + white-space: nowrap; + opacity: 0.85; +} + +/* Inline attachment-filter toggle nested inside the search input. The + input has padding-right:34px set inline so its text doesn't slide + under the button. */ +.email-search-wrap { display: flex; align-items: center; } +/* Keep the email search input and the adjacent "Select" button identical in + height regardless of base-vs-mobile overrides — pin both explicitly. */ +.email-search-row .memory-search-input { + height: 32px; + min-height: 32px; + box-sizing: border-box; +} +.email-search-row .email-search-select-btn { + height: 32px; + min-height: 32px; + box-sizing: border-box; + margin-top: 6px; + flex-shrink: 0; +} +/* Select moved into the dropdown row — dock it to the right and match the + selects' vertical nudge (.memory-sort-select has top:3px). */ +.memory-category-filters .email-filter-select-btn, +.memory-category-filters .email-filter-refresh-btn { + position: relative; + top: 3px; + flex-shrink: 0; +} +.memory-category-filters .email-filter-refresh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 8px; +} +.memory-category-filters .email-reminders-clear-btn { + position: relative; + top: -2px; + flex-shrink: 0; +} +.email-attach-toggle-inline { + position: absolute !important; + right: 4px !important; + top: 50%; + transform: translateY(calc(-50% - 5px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-attach-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-attach-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +/* Inline undone-toggle nested inside the search input, sits LEFT of the + attach toggle. Same visual treatment. */ +.email-undone-toggle-inline { + position: absolute !important; + right: 34px !important; + top: 50%; + transform: translateY(calc(-50% - 3px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-reminder-toggle-inline { + position: absolute !important; + right: 64px !important; + top: 50%; + transform: translateY(calc(-50% - 3px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-reminder-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-reminder-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +.email-undone-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-undone-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +/* Round (circular) toggles in the search bar, matching the app's default icon + buttons. Always fully visible (not dimmed until hover). */ +.email-attach-toggle-inline, +.email-undone-toggle-inline, +.email-reminder-toggle-inline { border-radius: 50% !important; opacity: 1 !important; } +.email-search-options-btn { + position: absolute !important; + right: 5px !important; + top: 50% !important; + transform: translateY(calc(-50% - 5px)); + width: 26px !important; + height: 26px !important; + min-width: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 999px !important; + background: transparent !important; + border: 0 !important; + color: inherit !important; + opacity: 0.7; + z-index: 2; +} +.email-search-options-btn:hover, +.email-search-options-btn.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +.email-search-options-menu { + position: absolute; + top: calc(100% + 2px); + right: 0; + z-index: 75; + min-width: 140px; + padding: 4px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--panel, var(--bg)); + box-shadow: 0 8px 24px rgba(0,0,0,0.3); +} +.email-search-options-title { + padding: 5px 9px 4px; + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + opacity: 0.65; + white-space: nowrap; +} +.email-search-options-menu button { + width: 100%; + border: 0; + background: transparent; + color: var(--fg); + display: flex; + align-items: center; + gap: 10px; + height: auto; + min-height: 0; + padding: 6px 8px; + border-radius: 8px; + font-family: inherit; + font-size: 11px; + font-weight: 400; + line-height: normal; + letter-spacing: normal; + text-align: left; + cursor: pointer; +} +.email-search-options-menu button:hover, +.email-search-options-menu button.active { + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + color: var(--accent, var(--red)); +} +.email-search-option-icon { + width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + opacity: 0.5; +} +.email-search-option-icon svg { width: 14px; height: 14px; } +.email-search-date-panel { + width: 220px; + padding: 6px 7px 5px; + border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); +} +.email-search-date-panel input { + width: 100%; + height: 26px; + box-sizing: border-box; + padding: 2px 6px; + border: 1px solid var(--border); + border-radius: 5px; + outline: none; + background: var(--bg); + color: var(--fg); + font: inherit; +} +.email-search-date-panel input:focus { border-color: var(--accent, var(--red)); } +.email-search-date-actions { display: flex; justify-content: flex-end; gap: 5px; } +.email-search-options-menu .email-search-date-actions [data-email-date-apply] { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +@media (max-width: 768px) { + .email-search-options-menu button { + min-height: 44px; + padding: 12px 12px !important; + font-size: 14px !important; + gap: 10px !important; + } + .email-search-options-menu .email-search-option-icon { + width: 18px; + height: 18px; + } + .email-search-options-menu .email-search-option-icon svg { + width: 16px; + height: 16px; + } +} +/* Mobile: enlarge the icons inside the inline search-bar toggles + (done / attachment / reminders) — buttons themselves stay the same, + only the SVG glyph scales up so it's tappable + visible. */ +@media (max-width: 768px) { + .email-filter-btn { + height: 30px; + min-height: 30px; + font-size: 12px; + top: -6px; + padding: 0 9px; + } + .email-folder-btn { + top: -4px; + } + + #email-lib-folder, + .memory-category-filters .email-filter-select-btn, + .memory-category-filters .email-filter-refresh-btn, + .memory-category-filters .email-filter-settings-btn { + height: 30px !important; + min-height: 30px !important; + box-sizing: border-box; + } + + .memory-category-filters .email-filter-refresh-btn, + .memory-category-filters .email-filter-settings-btn { + width: 30px; + padding: 0 !important; + } + .memory-category-filters .email-filter-settings-btn > svg { + width: 15px; + height: 15px; + } + + .email-search-row .memory-search-input { + height: 38px; + min-height: 38px; + } + + #email-lib-chip-bar { + padding-right: 42px !important; + padding-left: 30px !important; + gap: 5px !important; + } + + .email-lib-chip-bar-icon { + width: 14px !important; + height: 14px !important; + left: 9px !important; + } + + #email-lib-search { + font-size: 14px !important; + min-width: 88px !important; + top: 0 !important; + } + + #email-lib-search::placeholder { + font-size: 14px !important; + } + + #email-lib-chip-bar.has-email-lib-pills #email-lib-search { + min-width: 24px !important; + flex: 1 1 24px !important; + } + + .email-lib-pill { + min-height: 24px !important; + height: 24px !important; + line-height: 24px !important; + font-size: 12px !important; + padding: 0 6px 0 8px !important; + max-width: 180px !important; + } + + .email-lib-pill svg { + width: 14px !important; + height: 14px !important; + } + + .email-lib-pill-x { + font-size: 13px !important; + top: -4.5px !important; + } + + .email-lib-filter-pill { + min-height: 24px !important; + height: 24px !important; + line-height: 24px !important; + gap: 4px !important; + padding: 0 6px 0 8px !important; + } + + .email-lib-filter-pill .email-lib-pill-icon { + width: 17px !important; + height: 17px !important; + } + + .email-lib-filter-pill svg { + width: 18px !important; + height: 18px !important; + } + + .email-lib-filter-pill .email-lib-pill-x { + font-size: 13px !important; + top: -4.5px !important; + } + + .email-tags-toggle-inline { + right: 6px; + height: 28px !important; + min-width: 42px; + padding: 0 8px !important; + font-size: 11px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-attach-toggle-inline { + right: 56px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-undone-toggle-inline { + right: 92px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-reminder-toggle-inline { + right: 128px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-attach-toggle-inline svg, + .email-undone-toggle-inline svg, + .email-reminder-toggle-inline svg, + .email-filter-refresh-btn svg { + width: 16px !important; + height: 16px !important; + } +} +.email-accounts-row { + display: flex; + align-items: center; + gap: 10px; + padding: 0 0 6px; +} +.email-accounts-row .doclib-desc { flex-shrink: 0; } +/* Only the direct-child compose button gets pushed right; nested chips + inside #email-lib-accounts pack to the left as normal flex items. */ +.email-accounts-row > .memory-toolbar-btn { flex-shrink: 0; margin-left: auto; } +.email-account-chip { + display: inline-flex; + align-items: center; + gap: 5px; +} +.email-account-chip.email-account-has-unread { + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + color: color-mix(in srgb, var(--accent, var(--red)) 86%, var(--fg)); +} +.email-account-unread-dot { + width: 6px; + height: 6px; + border-radius: 999px; + background: var(--accent, var(--red)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + flex: 0 0 auto; +} +.email-account-chip-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} +.email-account-unread-count { + font-size: 9px; + line-height: 1; + font-weight: 700; + color: var(--accent, var(--red)); + opacity: 0.9; + position: relative; + top: 0; +} +.email-account-away-label { + color: var(--accent, var(--red)); + font-size: 9px; + font-weight: 700; + line-height: 1; + flex: 0 0 auto; +} +.email-accounts-loading-whirlpool { + width: 14px; + height: 14px; + margin: 3px 4px 0 1px; + display: inline-flex; + flex: 0 0 auto; +} +.email-accounts-loading-label { + font-size: 10px; + opacity: 0.55; + position: relative; + top: 2px; + white-space: nowrap; +} +.email-loading-with-label { + /* Fill the email list grid so the spinner lands in the middle of the + email window rather than the top of the empty list section. */ + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + text-align: center; + pointer-events: none; +} +.date-section-header { + display: flex; + align-items: center; + gap: 7px; + padding: 8px 9px 4px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 10px; + font-weight: 700; + letter-spacing: 0; + line-height: 1.2; + text-transform: uppercase; + user-select: none; +} +.date-section-header::after { + content: ''; + flex: 1 1 auto; + height: 1px; + background: color-mix(in srgb, var(--border) 72%, transparent); +} +#session-list .date-section-header { + padding-left: 10px; + padding-right: 8px; + cursor: pointer; +} +#session-list .date-section-header.collapsed { + color: color-mix(in srgb, var(--fg) 36%, transparent); +} +#session-list .date-section-header.collapsed::after { + opacity: 0.45; +} +#session-list .date-section-header:focus-visible { + outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); + outline-offset: 2px; + border-radius: 5px; +} +/* Bootstrap row shown while the session list hydrates, and on load failure. + Reads as a normal list row but is not selectable. */ +.session-list-bootstrap { + cursor: default; + pointer-events: none; +} +#email-lib-grid .date-section-header { + padding: 10px 5px 3px; +} +#email-lib-modal .email-lib-sync-status { + flex: 0 0 auto; + min-height: 14px; + padding: 1px 4px 0; + font-size: 10.5px; + line-height: 1.2; + text-align: center; + opacity: 0.58; + color: var(--muted-text, currentColor); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + top: 4px; +} diff --git a/static/css/16-calendar.css b/static/css/16-calendar.css new file mode 100644 index 000000000..d4112f705 --- /dev/null +++ b/static/css/16-calendar.css @@ -0,0 +1,1175 @@ +/* 16-calendar - 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. + */ +@media (max-width: 768px) { + #email-lib-modal .email-lib-sync-status { + min-height: 13px; + padding-right: 5px; + font-size: 10px; + } +} +/* Nudge the subject + sender/date meta line 4px right inside each + email card so they don't sit flush against the read/unread/done + markers on the left. Includes #email-lib-modal selector to + override the expanded card's `padding: 4px 0 6px` shorthand + which was zeroing out the title's padding-left. */ +#email-lib-grid .email-card-titlerow, +#email-lib-grid .memory-item-meta, +#email-lib-modal .doclib-card-expanded .email-card-titlerow { + padding-left: 4px; +} +.email-loading-label { + font-size: 11px; + opacity: 0.6; +} + +/* Refresh button now lives top-right in the modal header next to the close X. + Borderless (matches the close X), and a fixed square box so the spin and the + refresh→checkmark icon swap never change its size or nudge neighbours. */ +.email-lib-header-actions #email-lib-refresh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + border: none; + background: none; + padding: 0; + width: 24px; + height: 24px; +} +/* Bump the icon up on mobile for a bigger visual — but keep the button BOX at + 24px so it never exceeds the header's natural height (the title/close set + that). A taller box would grow the sticky header and push the list down. */ +@media (max-width: 768px) { + .email-lib-header-actions #email-lib-refresh-btn svg { + width: 17px; + height: 17px; + } +} +.cal-view-toggle { + display: inline-flex; + align-items: stretch; + border: 1px solid var(--border); + border-radius: 5px; + overflow: hidden; + vertical-align: middle; + box-sizing: border-box; + padding: 0; + margin: 0; + line-height: 1; +} +.cal-view-btn + .cal-view-btn { border-left: 1px solid var(--border); } +.cal-view-btn:hover { opacity: 0.75; } +.cal-view-btn.active { + background: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--accent, var(--red)); + opacity: 1; + font-weight: 600; +} +/* Toolbar holds only the toggle button; the chip row renders below the + toolbar on its own line and wraps freely. */ +.cal-filters { + display:flex; + gap:6px; + margin:6px 0 8px; + flex-wrap:wrap; + padding:6px 8px; + border:1px solid color-mix(in srgb, var(--fg) 8%, transparent); + border-radius:8px; + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 4%, transparent), transparent), + color-mix(in srgb, var(--fg) 2.5%, var(--bg)); + box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 4%, transparent); +} +/* Round the native color-input swatch in calendar settings. The outer + wraps a colored fill drawn by the browser; we need to round both + the wrapper and the fill so it actually appears circular. */ +.cal-s-color { border-radius: 50%; overflow: hidden; } +.cal-filter-item { display:inline-flex; align-items:center; gap:4px; cursor:pointer; font-size:10px; padding:2px 8px; line-height:1.4; border-radius:10px; background:color-mix(in srgb, var(--bg) 72%, transparent); border:1px solid color-mix(in srgb, var(--fg) 12%, transparent); transition:opacity 0.15s, background 0.15s, border-color 0.15s, transform 0.15s; } +.cal-filter-item:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); } +.cal-filter-item.cal-filter-important { font-weight:700; color:#e5a33a; border-color:color-mix(in srgb, #e5a33a 40%, transparent); } +/* Match the toolbar's other buttons (cal-nav, cal-view-btn, cal-add-btn-text): + 24px tall, 11px font, 5px corners. */ +.cal-filter-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font-size: 11px; + font-family: inherit; + font-weight: 500; + padding: 0 10px; + height: 24px; + line-height: 1; + border-radius: 5px; + box-sizing: border-box; + vertical-align: middle; + opacity: 0.65; + position: relative; + top: -3px; +} +.cal-filter-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } +.cal-filter-toggle-glyph { + display: inline-block; + position: relative; + top: 0; + margin-right: 3px; + color: var(--accent, var(--red)); +} +.cal-filter-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; } +/* The grid is now a flex column: one row of weekday headers + six + .cal-week-row rows. Each row holds the 7 day cells in its own + internal grid plus the absolute-positioned multi-day overlays. + --bars on a week-row is the number of multi-day events overlapping + it; cells in that row use it to reserve top padding so the bar + overlays don't cover the day numbers or single-event rows. */ +.cal-grid { display:flex; flex-direction:column; gap:1px; background:color-mix(in srgb, var(--fg) 8%, transparent); border-radius:6px; overflow:hidden; } +.cal-week-headers { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } +.cal-week-row { position:relative; display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } +.cal-weekday { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); text-align:center; font-size:10px; font-weight:600; opacity:0.4; padding:5px 0; } +.cal-day { + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 34px), + var(--bg); + min-height:78px; + padding:3px; + cursor:pointer; + position:relative; + transition:background 0.12s, box-shadow 0.12s; + overflow:hidden; +} +.cal-day:hover { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); } +.cal-day.cal-today { + box-shadow: inset 0 0 0 2px var(--accent, var(--red)); + background: var(--bg); + border-radius: 8px; +} +.cal-day.cal-selected:not(.cal-today) .cal-day-num { + color: var(--accent, var(--fg)); + opacity: 1; + font-weight: 700; +} +.cal-day.cal-drag-over { background:color-mix(in srgb, var(--accent) 18%, var(--bg)); } +.cal-day.cal-range-select { background:color-mix(in srgb, var(--accent, var(--red)) 25%, var(--bg)); box-shadow:inset 0 0 0 1px var(--accent, var(--red)); } +.cal-day-num { font-size:10px; font-weight:600; display:block; margin-bottom:1px; opacity:0.7; line-height:1.2; } +.cal-dots { display:flex; flex-direction:row; align-items:center; gap:2px; flex-wrap:nowrap; margin-bottom:1px; height:6px; } +.cal-dot { width:5px; height:5px; border-radius:50%; display:inline-block; cursor:grab; flex-shrink:0; } +.cal-dot-more { font-size:7px; opacity:0.4; } +.cal-event-row { + display:flex; + align-items:center; + gap:4px; + padding:2px 4px; + border-radius:4px; + cursor:grab; + line-height:1.2; + margin-bottom:2px; + min-height:17px; + background:color-mix(in srgb, var(--fg) 5%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); + animation:cal-event-enter 0.16s ease-out both; + transition:background 0.15s, box-shadow 0.15s, transform 0.15s; +} +.cal-event-row:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); transform:translateY(-1px); } +.cal-event-row-dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } +.cal-event-row-time { font-size:10px; opacity:0.72; font-variant-numeric:tabular-nums; flex-shrink:0; font-weight:600; } +.cal-event-row-name { font-size:10px; opacity:0.96; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; font-weight:600; } +.cal-event-reminder { + display: inline-flex; + width: 12px; + height: 12px; + margin-left: 5px; + vertical-align: -2px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + opacity: 0.9; + flex: 0 0 auto; +} +.cal-event-reminder svg { + width: 100%; + height: 100%; + display: block; +} +.cal-event-row .cal-event-reminder { + width: 10px; + height: 10px; + margin-left: 4px; +} +.cal-event-source { + display:inline-flex; + align-items:center; + justify-content:center; + width:12px; + height:12px; + margin-left:5px; + border-radius:50%; + font-size:7px; + font-weight:800; + line-height:1; + color:var(--cal-event-fg, var(--fg)); + background:color-mix(in srgb, currentColor 16%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); + vertical-align:1px; +} +.cal-event-source svg { + width: 10px; + height: 10px; + display: block; +} +.cal-event-row .cal-event-source { + width:11px; + height:11px; + font-size:7px; + margin-left:4px; + color:color-mix(in srgb, var(--fg) 72%, transparent); +} +.cal-multiday .cal-event-reminder, +.cal-wk-allday-event .cal-event-reminder { + color: var(--cal-event-fg, currentColor); + opacity: 0.88; +} +.cal-multiday .cal-event-source, +.cal-wk-allday-event .cal-event-source { + color: var(--cal-event-fg, currentColor); +} +.cal-event-more { font-size:9px; opacity:0.55; padding-left:6px; } +/* Multi-day bar — positioned as an absolute overlay inside its + .cal-week-row so it spans uninterrupted across every column it + covers. --col is the starting column (0–6), --span is the number + of days it covers within the row, --slot stacks parallel bars. */ +.cal-multiday { + position: absolute; + /* Fractional offsets let timed events that cross midnight render at + true proportional width. start-frac shifts the left edge into the + first day; end-frac trims the right edge inside the last day. + All-day events default to (0, 1) and still fill whole columns. */ + left: calc((var(--col, 0) + var(--start-frac, 0)) * (100% / 7)); + width: calc( + (var(--span, 1) - var(--start-frac, 0) - (1 - var(--end-frac, 1))) + * (100% / 7) + ); + top: calc(2px + var(--slot, 0) * 12px); + height: 12px; + font-size: 9px; + line-height: 12px; + padding: 0 5px; + border-radius: 4px; + color: var(--cal-event-fg, #fff); + font-weight: 700; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: grab; + opacity: 0.98; + z-index: 2; + pointer-events: auto; + box-shadow: 0 1px 2px color-mix(in srgb, #000 18%, transparent); +} +/* Splitter between the month/week grid and the day-tasks panel — drag + vertically to give the day panel more room. Height clamped by the + `--cal-detail-h` variable set on #cal-body by the splitter drag JS. */ +.cal-splitter { + height: 8px; + margin: 6px -10px 0; + cursor: row-resize; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + user-select: none; + touch-action: none; +} +/* Mobile: fat hitbox so the splitter is easy to grab with a thumb, + without changing how the grip itself looks. */ +@media (max-width: 768px) { + .cal-splitter { + height: 24px; + margin-top: 0; + margin-bottom: 0; + } + .cal-splitter-grip { + width: 56px; + height: 4px; + } +} +.cal-splitter-grip { + width: 42px; + height: 3px; + border-radius: 2px; + background: color-mix(in srgb, var(--fg) 25%, transparent); + transition: background 0.12s; +} +.cal-splitter:hover .cal-splitter-grip, +.cal-splitter-dragging .cal-splitter-grip { + background: var(--accent, var(--red)); +} +.cal-day-detail { + margin-top: 4px; + border-top: 1px solid var(--border); + padding-top: 8px; + height: var(--cal-detail-h, 240px); + overflow-y: auto; + overscroll-behavior: contain; + flex-shrink: 0; +} +.cal-detail-header { display:flex; justify-content:space-between; align-items:center; font-size:12px; font-weight:600; margin-bottom:6px; padding-right:8px; } +.cal-empty { font-size:11px; opacity:0.3; padding:4px 0; } +.cal-event-item { display:flex; gap:9px; padding:8px 9px; border-radius:6px; cursor:pointer; align-items:flex-start; background:color-mix(in srgb, var(--fg) 3.5%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); margin-bottom:4px; } +.cal-event-item:hover { background:color-mix(in srgb, var(--fg) 8%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); } +.cal-event-dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:4px; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } +.cal-event-info { flex:1; min-width:0; } +.cal-event-name { font-size:13px; font-weight:700; line-height:1.25; color:color-mix(in srgb, var(--fg) 96%, transparent); overflow-wrap:anywhere; } +.cal-event-tag { + display: inline-block; + font-size: 10px; + font-weight: 500; + padding: 1px 6px; + border-radius: 8px; + border: 1px solid; + margin-left: 6px; + vertical-align: 1px; + opacity: 0.85; + white-space: nowrap; +} +.cal-event-time { font-size:11px; opacity:0.68; line-height:1.35; margin-top:2px; font-weight:500; } +.cal-event-loc { font-size:11px; opacity:0.58; line-height:1.35; margin-top:1px; } +/* ── Week view: hour grid ────────────────────────────────────────── + Vertical hour rail on the left, 7 day columns on the right with their + own all-day strip and an absolute-positioned hour grid below it. */ +.cal-wk-wrap { + display: flex; + flex-direction: row; + gap: 0; + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 3%, transparent), transparent 120px), + var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + /* The wrap itself scrolls so the hour rail and day columns move + together; column headers and the rail spacer are sticky-pinned. */ + overflow: auto; + max-height: calc(100vh - 220px); + min-height: 360px; + position: relative; +} +.cal-wk-now-below-hint { + position: absolute; + height: 2px; + border-radius: 2px 2px 0 0; + background: var(--accent, var(--red)); + pointer-events: none; + z-index: 38; +} +@media (max-width: 768px) { + .cal-wk-wrap { + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + } + .cal-wk-wrap > .cal-wk-rail, + .cal-wk-wrap > .cal-wk-cols { + transform: translateY(var(--wk-edge-pull, 0)); + } + .cal-wk-wrap.cal-wk-edge-release > .cal-wk-rail, + .cal-wk-wrap.cal-wk-edge-release > .cal-wk-cols { + transition: transform 0.14s cubic-bezier(0.2, 0.75, 0.25, 1); + } + .cal-wk-wrap.cal-wk-pull-top { + border-top-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); + } + .cal-wk-wrap.cal-wk-pull-bottom { + border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); + } +} +.cal-wk-rail { + flex: 0 0 56px; + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + background: color-mix(in srgb, var(--fg) 2%, var(--bg)); + position: sticky; + left: 0; + z-index: 5; +} +.cal-wk-rail-spacer { + /* Match the 32px day header. The 24px all-day row gets its own spacer + below, so the zoom controls sit high without shifting the hour grid. */ + height: 32px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 0; + background: color-mix(in srgb, var(--fg) 2%, var(--bg)); + z-index: 9; + display: flex; + align-items: center; + justify-content: center; + gap: 2px; + padding: 0 4px; +} +.cal-wk-rail-allday-spacer { + height: 24px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 32px; + z-index: 8; + background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); +} +.cal-wk-zoom { + position: relative; + top: -3px; + width: 22px; + height: 22px; + border: 1px solid var(--border); + background: var(--bg); + color: var(--fg); + border-radius: 50%; + font-family: inherit; + font-size: 14px; + font-weight: 600; + line-height: 1; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + opacity: 0.55; + transition: opacity 0.15s, background 0.15s, border-color 0.15s; +} +.cal-wk-zoom:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--fg)) 50%, var(--border)); +} +.cal-wk-rail-cell { + font-size: 11px; + color: color-mix(in srgb, var(--fg) 55%, transparent); + padding: 4px 8px 0; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); + flex-shrink: 0; + display: flex; + align-items: flex-start; + justify-content: flex-end; + font-variant-numeric: tabular-nums; +} +.cal-wk-cols { + flex: 1; + display: grid; + grid-template-columns: repeat(7, 1fr); + position: relative; +} +.cal-wk-col { + border-left: 1px solid var(--border); + display: flex; + flex-direction: column; + min-width: 0; +} +.cal-wk-col:first-child { border-left: none; } +.cal-wk-col-head { + height: 32px; + padding: 0 6px; + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + flex-shrink: 0; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); + position: sticky; + top: 0; + z-index: 40; +} +/* Today on a Sunday still wins the accent treatment. */ +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); } +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dn, +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dt { color: var(--accent, var(--fg)); } +.cal-wk-dn { font-weight: 600; opacity: 0.6; } +.cal-wk-allday { + height: 24px; + padding: 2px 4px; + display: flex; + flex-direction: row; + gap: 2px; + align-items: center; + background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); + border-bottom: 1px solid var(--border); + flex-shrink: 0; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; + position: sticky; + top: 32px; + z-index: 39; +} +.cal-wk-allday-event { + font-size: 10.5px; + font-weight: 700; + padding: 3px 6px; + border-radius: 4px; + color: var(--cal-event-fg, var(--fg)); + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* Hour grid body */ +.cal-wk-grid { + position: relative; + flex: 0 0 auto; + cursor: cell; + user-select: none; + background: + repeating-linear-gradient( + to bottom, + transparent 0, + transparent calc(var(--wk-hour-px, 52px) / 2 - 1px), + color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--wk-hour-px, 52px) / 2), + transparent calc(var(--wk-hour-px, 52px) / 2 + 1px) + ); +} +.cal-wk-cell { + border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); + box-sizing: border-box; +} +.cal-wk-cell:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); } +/* "Now" line */ +.cal-wk-now { + position: absolute; + left: 0; right: 0; + height: 0; + border-top: 2px solid color-mix(in srgb, var(--accent, var(--red, #f87171)) 90%, transparent); + pointer-events: none; + z-index: 7; + filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent)); +} +/* Event blocks */ +.cal-wk-block { + position: absolute; + left: calc((var(--lane, 0) * (100% / var(--lane-count, 1))) + 2px); + width: calc((100% / var(--lane-count, 1)) - 4px); + right:auto; + border-left: 4px solid var(--fg); + border-radius: 5px; + padding: 5px 7px 7px; + font-size: 12px; + line-height: 1.3; + cursor: pointer; + /* `clip` preserves the rounded-card boundary without creating an overflow + container, allowing the label inside to stick to the week scroller. */ + overflow: clip; + z-index: 2; + box-shadow: 0 2px 6px color-mix(in srgb, #000 14%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); + animation:cal-event-enter 0.18s ease-out both; + transition: width 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), height 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), filter 0.12s, transform 0.12s, box-shadow 0.16s; + box-sizing: border-box; +} +.cal-wk-block:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); } +.cal-wk-block-label { + position: sticky; + top: 37px; + z-index: 1; + min-width: 0; + pointer-events: none; +} +.cal-wk-has-allday .cal-wk-block-label { + top: 61px; +} +.cal-wk-block-name { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color:color-mix(in srgb, var(--fg) 96%, transparent); } +.cal-wk-block-time { font-size: 10.5px; opacity: 0.82; font-variant-numeric: tabular-nums; margin-top: 2px; font-weight:600; } +@media (hover: hover) and (pointer: fine) { + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left { + left: auto; + right: calc(100% - var(--lane-right) + 2px); + } + .cal-wk-block.cal-wk-block-crowded:hover { + width: var(--hover-width, 170px); + height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; + overflow: clip; + z-index: 30; + filter: brightness(1.08); + transform: translateY(var(--hover-shift-y, -2px)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-hover-width-only:hover { + height: var(--event-height) !important; + transform: translateY(-1px); + } + .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-label { + position: absolute; + top: 5px; + width: calc(var(--hover-width, 170px) - 18px); + animation: cal-wk-hover-label-reveal 0.2s ease-out both; + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left:hover .cal-wk-block-label { + left: auto; + right: 7px; + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-right:hover .cal-wk-block-label { + left: 7px; + right: auto; + } + .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-name { + display: block; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + } +} +@media (max-width: 768px) { + /* Mobile week view: the hour rail already shows the time, so drop it from the + card and let the title wrap to fill the freed space (more readable text). */ + .cal-wk-block-time { display: none; } + .cal-wk-block-name { + white-space: normal; + line-height: 1.15; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left { + left: auto; + right: calc(100% - var(--lane-right) + 2px); + } + .cal-wk-block.cal-wk-mobile-expanded { + width: var(--hover-width, 170px); + height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; + overflow: clip; + z-index: 30; + filter: brightness(1.08); + transform: translateY(var(--hover-shift-y, 0)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); + } + .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-label { + position: absolute; + top: 5px; + width: calc(var(--hover-width, 170px) - 18px); + animation: cal-wk-hover-label-reveal 0.2s ease-out both; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left .cal-wk-block-label { + left: auto; + right: 7px; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-right .cal-wk-block-label { + left: 7px; + right: auto; + } + .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-name { + display: block; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + -webkit-line-clamp: unset; + } +} +/* Resize handle: a 6 px hit zone along the block's bottom edge. The + visible 2-px stripe only shows on hover so blocks at rest stay clean. */ +.cal-wk-block-resize { + position: absolute; + left: 4px; + right: 4px; + bottom: 0; + height: 6px; + cursor: ns-resize; + z-index: 3; +} +/* Drag-to-create ghost */ +.cal-wk-ghost { + position: absolute; + left: 2px; + right: 2px; + background: color-mix(in srgb, var(--accent, var(--fg)) 28%, transparent); + border: 1px dashed color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent); + border-radius: 4px; + pointer-events: none; + z-index: 3; + font-size: 10px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + color: var(--accent, var(--fg)); + font-variant-numeric: tabular-nums; +} +.cal-wk-slot-selected { + background: color-mix(in srgb, var(--accent, var(--fg)) 20%, var(--bg)); + border-style: solid; + border-color: var(--accent, var(--fg)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--fg)) 20%, transparent); + overflow: visible; + pointer-events: auto; + cursor: pointer; +} +.cal-wk-slot-resize { + position: absolute; + left: 14%; + right: 14%; + bottom: -7px; + height: 14px; + z-index: 4; + cursor: ns-resize; + touch-action: none; + pointer-events: auto; +} +.cal-year { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; } +.cal-year-month { background:color-mix(in srgb, var(--fg) 3%, var(--bg)); border:1px solid var(--border); border-radius:6px; padding:6px; cursor:pointer; transition:background 0.15s, border-color 0.15s, transform 0.15s; } +.cal-year-month:hover { background:color-mix(in srgb, var(--fg) 8%, var(--bg)); border-color:var(--accent); transform:translateY(-1px); } +.cal-year-month-title { font-size:11px; font-weight:600; text-align:center; margin-bottom:4px; opacity:0.7; } +.cal-year-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; } +.cal-year-wd { font-size:7px; text-align:center; opacity:0.3; padding:1px 0; } +.cal-year-cell { font-size:8px; text-align:center; padding:2px 0; min-height:14px; border-radius:2px; transition:transform 0.12s, background 0.12s; } +.cal-year-day:hover { background:var(--accent, var(--red)); color:#fff; opacity:1; transform:scale(1.15); font-weight:700; z-index:2; position:relative; } +.cal-year-today { color:var(--accent, var(--red)); font-weight:700; opacity:1; } +.cal-year-has { opacity:1; background:color-mix(in srgb, var(--accent) 15%, transparent); } +.cal-year-has.cal-year-today { background:color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } +.cal-loading { display:flex; justify-content:center; padding:40px 0; } +.cal-badge { background:var(--accent); border-radius:50%; width:6px; height:6px; margin-left:auto; display:inline-block; flex-shrink:0; } +/* Search input — in-panel variant lives inside the day-detail panel and + spans its width; no width animation since growing/shrinking on every + keystroke would jitter beside the events list. */ +.cal-search-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:0 8px; height:24px; color:var(--fg); font-size:11px; font-family:inherit; width:120px; box-sizing:border-box; transition:width 0.15s; } +.cal-search-input:focus { outline:none; border-color:var(--accent); width:180px; } +.cal-search-input.cal-day-search { width:100%; margin-bottom:0; transition:none; padding-left: 28px; } +/* Wrap for the search input so the magnifying-glass icon can be absolute- + positioned inside the field. */ +.cal-search-wrap { + position: relative; + display: block; + margin-bottom: 6px; +} +.cal-search-wrap .cal-search-icon { + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + color: var(--accent, var(--red)); +} +.cal-day-search-meta { font-size:10px; opacity:0.5; margin:0 2px 6px; } +.cal-search-results { display:flex; flex-direction:column; gap:4px; } +.cal-search-count { font-size:10px; opacity:0.5; padding:4px 2px 8px; } +/* Agenda view */ +.cal-agenda { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-left:4px; } +.cal-agenda-day { display:flex; flex-direction:column; gap:5px; position:relative; padding-left:14px; } +.cal-agenda-date { font-size:12px; font-weight:800; opacity:0.72; padding:5px 2px 3px; border-bottom:1px solid var(--border); } +.cal-agenda-today-badge { + display: inline-block; + margin-left: 6px; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + padding: 1px 6px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + vertical-align: 1px; +} +.cal-agenda-empty { font-size:11px; opacity:0.4; padding:8px; font-style:italic; } +.cal-agenda-event { + display:flex; + gap:9px; + padding:9px 10px; + border-radius:6px; + cursor:pointer; + align-items:center; + position:relative; + background:color-mix(in srgb, var(--fg) 3.5%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); + animation:cal-event-enter 0.18s ease-out both; + transition:background 0.15s, box-shadow 0.15s, transform 0.15s; +} +.cal-agenda-event:hover { + background:color-mix(in srgb, var(--fg) 8%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); + transform:translateX(2px); +} +.cal-agenda-event .cal-event-more, +.cal-event-item .cal-event-more { opacity:0; transition:opacity 0.15s; } +.cal-event-item { position:relative; } + +/* ── Personal Assistant ── */ +.sidebar-assistant-entry { gap: 6px; min-height: 29px; box-sizing: border-box; align-items: center; } +.sidebar-assistant-entry .sidebar-action-icon { position: relative; left: -2px; } +#sidebar-assistant-gear:hover { opacity: 0.8 !important; } +.assistant-settings-form { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; } +.assistant-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: color-mix(in srgb, var(--fg) 70%, transparent); } +.assistant-field > span { font-size: 11px; opacity: 0.6; } +.assistant-field input[type="text"], +.assistant-field input[type="time"], +.assistant-field select, +.assistant-field textarea { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 5px; + padding: 7px 10px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + box-sizing: border-box; +} +.assistant-field textarea { resize: vertical; min-height: 60px; } +.assistant-field input:focus, +.assistant-field select:focus, +.assistant-field textarea:focus { outline: none; border-color: var(--accent, var(--red)); } +.assistant-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } +.assistant-field-row > .assistant-field { flex: 1 1 180px; } +.assistant-field-check { flex-direction: row !important; align-items: center; gap: 6px; padding-bottom: 7px; } +.assistant-checkins { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); } +.assistant-checkins h5 { margin: 0 0 2px; font-size: 12px; font-weight: 600; opacity: 0.8; } +.assistant-checkin-row { + display: flex; + flex-direction: column; + gap: 4px; + padding: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); +} +.assistant-checkin-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } +.assistant-checkin-head input[type="text"], +.assistant-checkin-head input[type="time"] { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 4px; + padding: 4px 8px; + color: var(--fg); + font-size: 12px; + font-family: inherit; +} +.assistant-checkin-head input[type="text"] { flex: 1; min-width: 120px; } +.assistant-checkin-head input[type="time"] { width: 90px; } +.assistant-checkin-run { + background: none; + border: 1px solid var(--border); + color: var(--fg); + font-family: inherit; + font-size: 11px; + padding: 4px 8px; + border-radius: 4px; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s; +} +.assistant-checkin-run:hover { opacity: 1; } +.assistant-checkin-prompt { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 8px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + resize: vertical; + min-height: 48px; +} +.assistant-checkin-prompt:focus { outline: none; border-color: var(--accent, var(--red)); } +.assistant-checkin-meta { font-size: 10px; opacity: 0.45; } +.assistant-settings-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--border); +} +.assistant-tools-grid { + display: flex; + flex-direction: column; + gap: 6px; + max-height: 240px; + overflow-y: auto; + padding: 6px; + border: 1px solid var(--border); + border-radius: 6px; + margin-top: 4px; +} +.assistant-tool-group { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} +.assistant-tool-group-label { + font-size: 10px; + font-weight: 600; + opacity: 0.5; + text-transform: uppercase; + letter-spacing: 0.03em; + width: 100%; + margin-bottom: 1px; +} +.assistant-tool-item { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 11px; + padding: 2px 6px; + border: 1px solid var(--border); + border-radius: 4px; + cursor: pointer; + transition: background 0.1s, border-color 0.1s; + user-select: none; +} +.assistant-tool-item:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.assistant-tool-item:has(input:checked) { + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 10%, transparent); +} +.assistant-tool-item input[type="checkbox"] { + width: 12px; + height: 12px; + margin: 0; +} + +/* Empty state */ +.cal-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px 20px; text-align:center; gap:10px; } +.cal-empty-title { font-size:14px; font-weight:600; opacity:0.8; } +.cal-empty-msg { font-size:12px; opacity:0.5; max-width:320px; line-height:1.5; margin-bottom:8px; } +.cal-allday-switch { margin:0; flex-shrink:0; } +/* Stack the "All day" label above its toggle so it doesn't get squeezed / + truncated next to the date inputs in the row. */ +.cal-allday-ctrl { display:flex; flex-direction:column; align-items:center; gap:3px; flex-shrink:0; } +.cal-allday-label { font-size:10px; opacity:0.5; white-space:nowrap; line-height:1; } +.cal-form { display:flex; flex-direction:column; gap:8px; } +.cal-form-title { font-size:13px; font-weight:600; margin-bottom:2px; } +.cal-title-wrap { position: relative; } +/* Calendar-picker select sits 2px higher; its border-left/background tint is + set in JS to the chosen calendar's colour. */ +.cal-f-cal-select { position: relative; top: -4px; } + +/* ── Bespoke event form ──────────────────────────────────────────── + Big clock-face date/time hero with the title under it. Details panel + stays collapsed until the title gets focus or "Add details" is clicked. +*/ +.cal-form-bespoke { + position: relative; + width: min(720px, calc(100% - 24px)); + max-width: none; + margin: 12px auto; + padding: 18px 28px 14px; + box-sizing: border-box; + background: var(--bg); + /* Optional per-event tint set by JS via --ev-color. When unset, falls + back to the theme's neutral border so the card looks like normal. */ + border: 1px solid var(--border); + border-radius: 14px; + box-shadow: 0 8px 24px color-mix(in srgb, var(--fg) 6%, transparent); + transition: border-color 0.18s, box-shadow 0.18s; +} +/* When a colour is picked, lift the card with a soft tinted border + glow + so the choice is unmistakable. */ +.cal-form-bespoke[style*="--ev-color"] { + border-color: color-mix(in srgb, var(--ev-color) 55%, var(--border)); + box-shadow: 0 8px 28px color-mix(in srgb, var(--ev-color) 22%, transparent); +} +.cal-form-bespoke[style*="--ev-color"] .cal-btn-primary { + background: var(--ev-color); + border-color: var(--ev-color); +} +.cal-form-bespoke.cal-form-bg-image .cal-btn-primary { + background: var(--accent, var(--red)); + border-color: var(--accent, var(--red)); + color: var(--bg); +} +.cal-form-close { + position: absolute; top: 10px; right: 12px; + background: none; border: none; color: var(--fg); + opacity: 0.35; cursor: pointer; font-size: 20px; line-height: 1; + padding: 4px 8px; border-radius: 6px; +} +.cal-form-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 8%, transparent); } +.cal-form-mobile-cancel { display: none; } + +/* "Today is …" header pinned at the top of the form. Keeps you oriented + even when the event date you're picking is way off in the future. */ +.cal-form-today { + text-align: center; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + opacity: 0.45; + margin-bottom: 0; +} + +/* Hero — clock face + date label. Spacing between the two lives entirely + on the clock's bottom margin so it's predictable. */ +.cal-hero { + display: flex; flex-direction: column; align-items: center; + gap: 0; margin-bottom: 12px; +} +.cal-hero-time { + display: inline-flex; align-items: baseline; gap: 8px; + font-family: ui-monospace, SFMono-Regular, "Menlo", "Consolas", monospace; + font-variant-numeric: tabular-nums; + font-size: 56px; + letter-spacing: 0.02em; + font-weight: 200; + color: var(--fg); + line-height: 1; + /* Behaves like a button — strip native chrome but keep it tappable. */ + background: none; + border: none; + /* Pill that reads as a real hit target for a 56-px clock face. + Top padding kept tight so the clock sits close to the "Today is" line. */ + padding: 6px 38px 18px; + margin: 0 0 24px; + border-radius: 18px; + cursor: pointer; + line-height: 1; + transition: background 0.15s, color 0.15s; +} +/* The clock is a `` and a global `button:hover` rule paints + every button's background on hover. Suppress it explicitly. */ +.cal-hero-time:hover { background: transparent; border-color: transparent; } +.cal-hero-clock { font-feature-settings: "tnum"; display: inline-flex; align-items: baseline; } +/* Per-segment hover so it's clear hh vs mm are individually clickable. */ +.cal-hero-clock-hh, .cal-hero-clock-mm { + display: inline-block; + border-radius: 6px; + padding: 0 2px; + transition: background 0.12s; +} +.cal-hero-clock-hh:hover, .cal-hero-clock-mm:hover { + background: color-mix(in srgb, var(--fg) 9%, transparent); +} +.cal-hero-sep { padding: 0 2px; opacity: 0.55; pointer-events: none; } +.cal-hero-ampm { + font-size: 14px; + letter-spacing: 0.12em; + opacity: 0.55; + font-weight: 500; + text-transform: uppercase; +} +.cal-hero-date { + font-size: 16px; + opacity: 0.6; + letter-spacing: 0.04em; + background: none; + border: none; + color: inherit; + font-family: inherit; + padding: 4px 10px; + border-radius: 6px; + cursor: pointer; + margin: 7px 0 0; /* extra nudge below the clock */ + transition: background 0.15s, opacity 0.15s; +} +.cal-hero-date:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); opacity: 0.85; } + +/* Title input — flat, large, no chrome until focused */ +.cal-input.cal-hero-title { + font-size: 18px; + text-align: left; + padding: 10px 12px 10px 0; + background: transparent; + border: 1px solid transparent; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); + border-radius: 0; + margin-bottom: 4px; +} + +/* Details panel — animated reveal via max-height + opacity. */ +.cal-form-bespoke .cal-form-details { + display: block; + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 240ms ease, opacity 180ms ease, margin 220ms ease; + margin-top: 0; +} +/* The detail children themselves keep the existing flex/gap rules from + the regular .cal-form, so we don't restyle inputs/rows here. */ +.cal-form-bespoke .cal-form-details > * + * { margin-top: 8px; } + + +/* Location row: input + Apple Maps pin link. Pin only lights up when the + field has content; clicking opens maps.apple.com (native Maps app on + Apple devices, web fallback elsewhere). All theme vars. */ +.cal-loc-row { + display: flex; + align-items: stretch; + gap: 6px; +} +.cal-loc-input-wrap { + position: relative; + flex: 1; + min-width: 0; +} +.cal-loc-hint { + position: absolute; + inset: 0 auto 0 10px; + display: none; + align-items: center; + gap: 5px; + pointer-events: none; + color: color-mix(in srgb, var(--fg) 42%, transparent); + font-size: 12px; +} +.cal-loc-map { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + flex-shrink: 0; + border: 1px solid var(--border); + border-radius: 5px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + text-decoration: none; + transition: background 0.15s, border-color 0.15s, opacity 0.15s; +} +.cal-loc-map:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); border-color: var(--accent, var(--red)); } + +@media (max-width: 520px) { + .cal-hero-time { font-size: 44px; } + .cal-form-bespoke { margin: 16px 12px; padding: 32px 16px 14px; } + .cal-form-mobile-cancel { + display: inline-flex; + position: absolute; + top: 8px; + right: 8px; + width: 30px; + height: 30px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + border-radius: 999px; + background: color-mix(in srgb, var(--bg) 82%, transparent); + color: var(--fg); + opacity: 0.72; + cursor: pointer; + z-index: 3; + -webkit-tap-highlight-color: transparent; + } + .cal-form-mobile-cancel:active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); + } +} +.cal-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:7px 10px; color:var(--fg); font-size:12px; width:100%; box-sizing:border-box; } +.cal-input:focus { outline:none; border-color:var(--accent); } +.cal-input-time { width:auto; flex:1; } +.cal-form-row { display:flex; gap:8px; align-items:center; } +.cal-form-actions { display:flex; gap:8px; justify-content:flex-end; margin-top:4px; } diff --git a/static/css/style-part-08.css b/static/css/17-research.css similarity index 57% rename from static/css/style-part-08.css rename to static/css/17-research.css index df8418a28..d2fa2b1d1 100644 --- a/static/css/style-part-08.css +++ b/static/css/17-research.css @@ -1,2206 +1,19 @@ -/* Fragment 9/9 of the former static/style.css. +/* 17-research - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -.note-cl-dot { - width: 8px; - height: 8px; - border-radius: 50%; - border: 1.5px solid color-mix(in srgb, var(--fg) 30%, transparent); - flex-shrink: 0; - cursor: pointer; - transition: all 0.15s; -} -.note-cl-dot:hover { border-color: var(--fg); transform: scale(1.2); } -.note-cl-row.done .note-cl-dot { - background: var(--accent); - border-color: var(--accent); -} -.note-cl-row.done .note-cl-text { - opacity: 0.4; - background: linear-gradient(currentColor, currentColor) no-repeat; - background-size: 0 1px; - background-position: 0 calc(50% - 1px); - animation: cl-strike 0.32s ease-out forwards; - transition: opacity 0.2s ease; -} -/* Draws the strikethrough line left-to-right when the row is marked - done, instead of snapping it in full-width on the same frame. */ -@keyframes cl-strike { - to { background-size: 100% 1px; } -} -.note-cl-text { - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - color: var(--fg); - font-size: 12px; - padding: 3px 0 3px 3px; - flex: 1 1 auto; - width: auto; - min-width: 0; - outline: none; -} -.note-cl-text:focus { border-color: var(--fg); } -.note-cl-rm { - background: none; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - font-size: 14px; - padding: 0; - width: 24px; - height: 14px; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: flex-end; - line-height: 1; -} -.note-cl-rm:hover { opacity: 0.8; color: var(--red); } -.note-cl-add { - background: none; - border: none; - color: var(--fg); - opacity: 0.4; - cursor: pointer; - font-size: 11px; - padding: 4px 0; - text-align: left; -} - -/* ── Calendar ── */ -.cal-modal-content { width:min(680px, 94vw); max-height:88vh; overflow-x:hidden; position:relative; } -#calendar-modal .cal-modal-content { animation: none; } -/* Was overflow-y:auto with grid + day-detail flowing naturally. Now uses a - true splitter layout: the body is a flex column that doesn't scroll - itself; the grid takes the remaining space and scrolls if needed; the - day-detail has an explicit height (driven by the splitter drag via - --cal-detail-h) and shrinks the grid visually as it grows. */ -#cal-body { display:flex; flex-direction:column; gap:8px; overflow:hidden; padding:0; min-height:0; } -#cal-body > .cal-grid { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overflow-x: hidden; } -@media (max-width: 768px) { - /* Let the calendar pane shrink to nothing on mobile so the day-detail - splitter can be dragged all the way to the top, hiding the calendar - entirely. Without min-height:0 the grid (or the week-wrap below) - refuses to shrink past its built-in floor. */ - #cal-body > .cal-grid, - #cal-body > .cal-wk-wrap { - min-height: 0; - max-height: none; - } - /* Let the week grid fill the calendar pane and scroll its hours internally - instead of overflowing #cal-body (which clips it and feels cramped). */ - #cal-body > .cal-wk-wrap { - flex: 1 1 auto; - overflow: auto; - } -} -.cal-toolbar { display:flex; align-items:center; gap:6px; margin-bottom:7px; flex-wrap:wrap; line-height:1; max-width:100%; row-gap:6px; } - -/* Quick-add bar: natural-language event creation. Sits directly under - the toolbar; focused with `Q`; opens the bespoke form pre-filled. */ -.cal-quickadd-row { - display: flex; - align-items: center; - gap: 8px; - margin: 0 0 8px; - padding: 1px 10px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - transition: border-color 0.15s, background 0.15s; - position: relative; -} -/* Two-tone placeholder hint: "Quick add" in accent, the example in grey, both - at one low opacity. Overlays the (empty) input; hidden once the user types. */ -.cal-quickadd-hint { - position: absolute; - left: 10px; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: flex; - align-items: center; - gap: 4px; - font-size: 12px; - color: var(--fg); - opacity: 0.5; - pointer-events: none; - white-space: nowrap; - overflow: hidden; -} -.cal-quickadd-hint .qa-hint-accent { color: var(--accent, var(--red)); } -/* ↵ enter glyph in the hint — makes it obvious the field submits on Enter. */ -.cal-quickadd-hint .qa-hint-enter { margin-left: auto; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.8; } -/* Cycling example text fades out before swap, then fades back in. */ -.cal-quickadd-hint .qa-hint-example { - display: block; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-style: italic; - transition: opacity 0.18s ease; -} -.cal-quickadd-hint .qa-hint-example.qa-hint-fade { opacity: 0; } -.cal-title-hint { - position: absolute; - left: 0; - top: 50%; - transform: translateY(calc(-50% - 2px)); - display: none; - align-items: center; - gap: 7px; - font-size: 18px; - line-height: 1.2; - white-space: nowrap; - pointer-events: none; - color: color-mix(in srgb, var(--fg) 42%, transparent); -} -.cal-hero-title:placeholder-shown ~ .cal-title-hint { display: inline-flex; } -.cal-title-hint .cal-title-enter-ico { color: var(--accent, var(--red)); flex-shrink: 0; } -/* Pure-CSS toggle: hide the hint as soon as the field has real text (no - per-keystroke JS, so typing never triggers a re-render / focus loss). */ -.cal-quickadd-input:not(:placeholder-shown) ~ .cal-quickadd-hint { display: none; } -.cal-quickadd-row:focus-within { - border-color: color-mix(in srgb, var(--accent, var(--fg)) 55%, var(--border)); - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); -} -.cal-quickadd-icon { - color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent); - display: inline-flex; - align-items: center; - flex-shrink: 0; - opacity: 0.7; -} -.cal-quickadd-input { - flex: 1; - min-width: 0; - background: transparent; - border: none; - outline: none; - color: var(--fg); - font-family: inherit; - font-size: 12px; - height: 22px; - padding: 0; -} -.cal-quickadd-status { - font-size: 10px; - opacity: 0.55; - font-variant-numeric: tabular-nums; -} -.cal-toolbar > *, .cal-toolbar-nav > *, .cal-toolbar-right > * { vertical-align:middle; } -.cal-toolbar-nav { display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; } -.cal-toolbar-right { - display: inline-flex; - align-items: center; - justify-content: flex-start; - gap: 6px; - flex: 1 1 100%; - width: 100%; - margin-left: 0; - margin-top: 6px; - flex-wrap: wrap; -} -.cal-title { font-size:13px; font-weight:600; white-space:nowrap; height:24px; line-height:30px; padding:0 6px; display:inline-block; vertical-align:middle; box-sizing:border-box; } -.cal-toolbar-clock { - margin-left: 8px; - color: var(--accent, var(--red)); - font-size: 11px; - font-weight: 600; - font-variant-numeric: tabular-nums; -} -button.cal-nav { background:color-mix(in srgb, var(--fg) 6%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 8px; height:24px; line-height:22px; cursor:pointer; font-size:11px; font-family:inherit; box-sizing:border-box; vertical-align:middle; } -button.cal-nav:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); } -button.cal-nav.cal-toolbar-arrow { - display: none; - align-items: center; - justify-content: center; - width: 24px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 1; - opacity: 0.84; -} -.cal-toolbar-arrow-glyph { - position: relative; - top: -1px; - line-height: 1; -} -@media (max-width: 768px) { - button.cal-nav.cal-toolbar-arrow { display: inline-flex !important; } - .cal-toolbar-nav { - flex: 1 1 100%; - width: 100%; - min-width: 0; - } - #cal-next { margin-left: auto; } -} -.cal-week-start-toggle { - display: inline-flex; - align-items: center; - gap: 0; - width: max-content; - padding: 0; - height: 24px; - box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 5px; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.cal-week-start-btn { - box-sizing: border-box; - height: 22px !important; - min-height: 22px !important; - margin-top: 0 !important; - padding: 0 7px; - border: 1px solid transparent; - border-radius: 4px; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - cursor: pointer; - font-family: inherit; - font-size: 10px; - line-height: 20px; - transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; -} -.cal-settings-actions { - display: flex; - justify-content: flex-end; - width: 100%; -} -#cal-settings-panel .cal-week-start-toggle { - display: flex; - margin-left: auto; -} -.cal-settings-sync-status { - position: relative; - top: 2px; - font-size: 11px; - opacity: 0.6; -} -.cal-settings-sync-status.is-error { - color: var(--red); - opacity: 1; -} -.cal-week-start-btn:hover { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.cal-week-start-btn.active, -.cal-week-start-btn.active:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); -} -.cal-side-nav { - position: absolute; - top: var(--cal-side-nav-y, 50%); - transform: translateY(-50%); - width: 32px; - height: 32px; - border-radius: 50%; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - cursor: pointer; - z-index: 6; - opacity: 0; - pointer-events: none; - font-family: inherit; - font-size: 24px; - line-height: 28px; - padding: 0 0 3px; - transition: opacity 0.12s ease, background 0.12s ease, transform 0.12s ease; -} -.cal-modal-content:hover .cal-side-nav, -.cal-side-nav:focus-visible { - opacity: 0.84; - pointer-events: auto; -} -.cal-side-nav:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); - transform: translateY(-50%) scale(1.04); -} -button.cal-today-btn { font-size:10px; opacity:0.5; } -button.cal-add-btn { background:var(--accent); color:#fff; border:none; border-radius:50%; width:24px; height:24px; line-height:22px; font-size:18px; cursor:pointer; flex-shrink:0; padding:0; box-sizing:border-box; font-family:inherit; vertical-align:middle; text-align:center; } -button.cal-add-btn.cal-add-btn-text { - width:auto; min-width:0; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 12px; - padding: 0 10px 0 6px; - display: inline-flex; - align-items: center; - gap: 4px; - height: 24px; - line-height: 1; - margin-top: 0; - transition: background 0.15s, border-color 0.15s; -} -button.cal-add-btn.cal-add-btn-text:hover { - background: color-mix(in srgb, var(--fg) 14%, transparent); - border-color: var(--accent); - opacity: 1; -} -.cal-add-plus { - font-size: 16px; - line-height: 1; - color: var(--accent, var(--red)); - font-weight: 500; - display: inline-block; - transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); - padding-bottom: 2px; -} -button.cal-add-btn.cal-add-btn-text:hover .cal-add-plus { - transform: rotate(180deg); -} -.cal-add-label { font-size:11px; font-weight:600; line-height:1; opacity:0.85; } -/* Small variant of the toolbar's +New button. Inherits the .cal-add-btn-text - styles (pill with "+" + "New" + spring-rotate on the +), so the - animation is identical. Just shrunk a bit. */ -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { - height: 20px; - border-radius: 10px; - /* At rest: tight, equal padding around the "+" so it sits centred - with no trailing gap. On hover the right pad and the flex gap grow - to make room for the revealed "New". */ - padding: 0 5px; - gap: 0; - transition: padding 0.25s ease, gap 0.25s ease, background 0.15s, border-color 0.15s; -} -/* Don't touch .cal-add-plus on the small variant — the toolbar +New is - the reference and we want the same rotation centre / motion. The label - is shrunk and hidden until hover so the pill is compact at rest. */ -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { - font-size: 10px; - max-width: 0; - margin-left: 0; - opacity: 0; - overflow: hidden; - white-space: nowrap; - transition: max-width 0.25s ease, opacity 0.18s ease, margin-left 0.25s ease; -} -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover .cal-add-label { - max-width: 40px; - margin-left: 3px; - opacity: 0.85; -} -.cal-add-btn:hover { opacity:0.85; } - -/* Mobile: bump the +New pill and the +/- zoom buttons up to a tap-friendly - size so they're easy to hit with a thumb. */ -@media (max-width: 768px) { - /* Toolbar +New (relocated by JS into the quickadd row on mobile) */ - button.cal-add-btn.cal-add-btn-text { - height: 36px; - border-radius: 18px; - padding: 0 14px 0 12px; - gap: 6px; - flex-shrink: 0; - } - /* Day-detail +New: just a "+" on mobile — drop the label entirely. */ - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { - width: 28px; - height: 28px; - border-radius: 50%; - padding: 0; - gap: 0; - justify-content: center; - } - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { - display: none; - } - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { - font-size: 18px; - padding-bottom: 0; - } - .cal-add-plus { font-size: 22px; padding-bottom: 1px; } - .cal-add-label { font-size: 13px; } - .cal-wk-zoom { - width: 36px; - height: 36px; - font-size: 20px; - border-radius: 8px; - opacity: 0.85; - } - /* The +New button sits as a sibling of the quickadd row inside a flex - wrapper, so it's clearly outside the input's bordered box. The row - itself keeps its own styling. */ - .cal-quickadd-wrap { - display: flex; - align-items: stretch; - gap: 8px; - margin: 0 0 8px; - } - .cal-quickadd-wrap > #cal-quickadd-row { - flex: 1; - min-width: 0; - margin: 0; - } - .cal-quickadd-wrap > .cal-add-btn-text { - align-self: stretch; - flex-shrink: 0; - } - /* Nudge the "+" glyph up one pixel on the round day-detail button so it - sits visually centred inside the circle. */ - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { - transform: translateY(-1px); - } - /* Event form: bigger Create / Cancel buttons so they're easy to tap. */ - .cal-form-actions button.cal-btn { - height: 44px; - padding: 0 20px; - font-size: 14px; - border-radius: 8px; - min-width: 96px; - } - /* Left-align the day/month + tags row in the day-detail panel and - the calendar/category chip row so they sit flush against the - left edge on a narrow screen. */ - .cal-detail-header { - justify-content: flex-start; - gap: 12px; - } - .cal-filters { - justify-content: flex-start; - } -} - -/* Refresh button spin — driven by JS toggling .cal-syncing on the button. - transform-box + transform-origin keep the rotation pivoted at the SVG's - own centre so it spins in place instead of orbiting / drifting up. */ -#cal-sync svg, -#email-lib-refresh-btn svg { display: block; transform-box: fill-box; transform-origin: 50% 50%; } -/* Brief checkmark flash after a refresh completes. Lasts ~900ms in JS; - the small bounce + accent color give a clear "done" cue. */ -#cal-sync.cal-sync-done svg, -#email-lib-refresh-btn.email-lib-refresh-done svg { - color: var(--accent, #2ea043); - animation: refresh-check-pop 0.32s ease-out; -} -@keyframes refresh-check-pop { - 0% { transform: scale(0.55); opacity: 0.4; } - 60% { transform: scale(1.18); opacity: 1; } - 100% { transform: scale(1); opacity: 1; } -} -#cal-sync.cal-syncing svg, -#email-lib-refresh-btn.email-lib-refreshing svg { animation: spin 0.6s linear infinite; } -.email-undone-toggle.active, -.email-attach-toggle.active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); - color: var(--accent, var(--red)); - font-weight: 600; -} -/* Hide the gallery-style reader-btn labels on desktop — only the mobile - @media block re-displays them under each icon. Desktop keeps compact - icon-only reader buttons. */ -.reader-btn-label { - display: inline-block; - font-size: 9px; - font-weight: 500; - line-height: 1; - letter-spacing: 0.02em; - white-space: nowrap; - opacity: 0.85; -} - -/* Inline attachment-filter toggle nested inside the search input. The - input has padding-right:34px set inline so its text doesn't slide - under the button. */ -.email-search-wrap { display: flex; align-items: center; } -/* Keep the email search input and the adjacent "Select" button identical in - height regardless of base-vs-mobile overrides — pin both explicitly. */ -.email-search-row .memory-search-input { - height: 32px; - min-height: 32px; - box-sizing: border-box; -} -.email-search-row .email-search-select-btn { - height: 32px; - min-height: 32px; - box-sizing: border-box; - margin-top: 6px; - flex-shrink: 0; -} -/* Select moved into the dropdown row — dock it to the right and match the - selects' vertical nudge (.memory-sort-select has top:3px). */ -.memory-category-filters .email-filter-select-btn, -.memory-category-filters .email-filter-refresh-btn { - position: relative; - top: 3px; - flex-shrink: 0; -} -.memory-category-filters .email-filter-refresh-btn { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0 8px; -} -.memory-category-filters .email-reminders-clear-btn { - position: relative; - top: -2px; - flex-shrink: 0; -} -.email-attach-toggle-inline { - position: absolute !important; - right: 4px !important; - top: 50%; - transform: translateY(calc(-50% - 5px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-attach-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-attach-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -/* Inline undone-toggle nested inside the search input, sits LEFT of the - attach toggle. Same visual treatment. */ -.email-undone-toggle-inline { - position: absolute !important; - right: 34px !important; - top: 50%; - transform: translateY(calc(-50% - 3px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-reminder-toggle-inline { - position: absolute !important; - right: 64px !important; - top: 50%; - transform: translateY(calc(-50% - 3px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-reminder-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-reminder-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -.email-undone-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-undone-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -/* Round (circular) toggles in the search bar, matching the app's default icon - buttons. Always fully visible (not dimmed until hover). */ -.email-attach-toggle-inline, -.email-undone-toggle-inline, -.email-reminder-toggle-inline { border-radius: 50% !important; opacity: 1 !important; } -.email-search-options-btn { - position: absolute !important; - right: 5px !important; - top: 50% !important; - transform: translateY(calc(-50% - 5px)); - width: 26px !important; - height: 26px !important; - min-width: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 999px !important; - background: transparent !important; - border: 0 !important; - color: inherit !important; - opacity: 0.7; - z-index: 2; -} -.email-search-options-btn:hover, -.email-search-options-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -.email-search-options-menu { - position: absolute; - top: calc(100% + 2px); - right: 0; - z-index: 75; - min-width: 140px; - padding: 4px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--panel, var(--bg)); - box-shadow: 0 8px 24px rgba(0,0,0,0.3); -} -.email-search-options-title { - padding: 5px 9px 4px; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - opacity: 0.65; - white-space: nowrap; -} -.email-search-options-menu button { - width: 100%; - border: 0; - background: transparent; - color: var(--fg); - display: flex; - align-items: center; - gap: 10px; - height: auto; - min-height: 0; - padding: 6px 8px; - border-radius: 8px; - font-family: inherit; - font-size: 11px; - font-weight: 400; - line-height: normal; - letter-spacing: normal; - text-align: left; - cursor: pointer; -} -.email-search-options-menu button:hover, -.email-search-options-menu button.active { - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - color: var(--accent, var(--red)); -} -.email-search-option-icon { - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - opacity: 0.5; -} -.email-search-option-icon svg { width: 14px; height: 14px; } -.email-search-date-panel { - width: 220px; - padding: 6px 7px 5px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.email-search-date-panel input { - width: 100%; - height: 26px; - box-sizing: border-box; - padding: 2px 6px; - border: 1px solid var(--border); - border-radius: 5px; - outline: none; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.email-search-date-panel input:focus { border-color: var(--accent, var(--red)); } -.email-search-date-actions { display: flex; justify-content: flex-end; gap: 5px; } -.email-search-options-menu .email-search-date-actions [data-email-date-apply] { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -@media (max-width: 768px) { - .email-search-options-menu button { - min-height: 44px; - padding: 12px 12px !important; - font-size: 14px !important; - gap: 10px !important; - } - .email-search-options-menu .email-search-option-icon { - width: 18px; - height: 18px; - } - .email-search-options-menu .email-search-option-icon svg { - width: 16px; - height: 16px; - } -} -/* Mobile: enlarge the icons inside the inline search-bar toggles - (done / attachment / reminders) — buttons themselves stay the same, - only the SVG glyph scales up so it's tappable + visible. */ -@media (max-width: 768px) { - .email-filter-btn { - height: 30px; - min-height: 30px; - font-size: 12px; - top: -6px; - padding: 0 9px; - } - .email-folder-btn { - top: -4px; - } - - #email-lib-folder, - .memory-category-filters .email-filter-select-btn, - .memory-category-filters .email-filter-refresh-btn, - .memory-category-filters .email-filter-settings-btn { - height: 30px !important; - min-height: 30px !important; - box-sizing: border-box; - } - - .memory-category-filters .email-filter-refresh-btn, - .memory-category-filters .email-filter-settings-btn { - width: 30px; - padding: 0 !important; - } - .memory-category-filters .email-filter-settings-btn > svg { - width: 15px; - height: 15px; - } - - .email-search-row .memory-search-input { - height: 38px; - min-height: 38px; - } - - #email-lib-chip-bar { - padding-right: 42px !important; - padding-left: 30px !important; - gap: 5px !important; - } - - .email-lib-chip-bar-icon { - width: 14px !important; - height: 14px !important; - left: 9px !important; - } - - #email-lib-search { - font-size: 14px !important; - min-width: 88px !important; - top: 0 !important; - } - - #email-lib-search::placeholder { - font-size: 14px !important; - } - - #email-lib-chip-bar.has-email-lib-pills #email-lib-search { - min-width: 24px !important; - flex: 1 1 24px !important; - } - - .email-lib-pill { - min-height: 24px !important; - height: 24px !important; - line-height: 24px !important; - font-size: 12px !important; - padding: 0 6px 0 8px !important; - max-width: 180px !important; - } - - .email-lib-pill svg { - width: 14px !important; - height: 14px !important; - } - - .email-lib-pill-x { - font-size: 13px !important; - top: -4.5px !important; - } - - .email-lib-filter-pill { - min-height: 24px !important; - height: 24px !important; - line-height: 24px !important; - gap: 4px !important; - padding: 0 6px 0 8px !important; - } - - .email-lib-filter-pill .email-lib-pill-icon { - width: 17px !important; - height: 17px !important; - } - - .email-lib-filter-pill svg { - width: 18px !important; - height: 18px !important; - } - - .email-lib-filter-pill .email-lib-pill-x { - font-size: 13px !important; - top: -4.5px !important; - } - - .email-tags-toggle-inline { - right: 6px; - height: 28px !important; - min-width: 42px; - padding: 0 8px !important; - font-size: 11px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-attach-toggle-inline { - right: 56px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-undone-toggle-inline { - right: 92px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-reminder-toggle-inline { - right: 128px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-attach-toggle-inline svg, - .email-undone-toggle-inline svg, - .email-reminder-toggle-inline svg, - .email-filter-refresh-btn svg { - width: 16px !important; - height: 16px !important; - } -} -.email-accounts-row { - display: flex; - align-items: center; - gap: 10px; - padding: 0 0 6px; -} -.email-accounts-row .doclib-desc { flex-shrink: 0; } -/* Only the direct-child compose button gets pushed right; nested chips - inside #email-lib-accounts pack to the left as normal flex items. */ -.email-accounts-row > .memory-toolbar-btn { flex-shrink: 0; margin-left: auto; } -.email-account-chip { - display: inline-flex; - align-items: center; - gap: 5px; -} -.email-account-chip.email-account-has-unread { - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - color: color-mix(in srgb, var(--accent, var(--red)) 86%, var(--fg)); -} -.email-account-unread-dot { - width: 6px; - height: 6px; - border-radius: 999px; - background: var(--accent, var(--red)); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - flex: 0 0 auto; -} -.email-account-chip-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} -.email-account-unread-count { - font-size: 9px; - line-height: 1; - font-weight: 700; - color: var(--accent, var(--red)); - opacity: 0.9; - position: relative; - top: 0; -} -.email-account-away-label { - color: var(--accent, var(--red)); - font-size: 9px; - font-weight: 700; - line-height: 1; - flex: 0 0 auto; -} -.email-accounts-loading-whirlpool { - width: 14px; - height: 14px; - margin: 3px 4px 0 1px; - display: inline-flex; - flex: 0 0 auto; -} -.email-accounts-loading-label { - font-size: 10px; - opacity: 0.55; - position: relative; - top: 2px; - white-space: nowrap; -} -.email-loading-with-label { - /* Fill the email list grid so the spinner lands in the middle of the - email window rather than the top of the empty list section. */ - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - text-align: center; - pointer-events: none; -} -.date-section-header { - display: flex; - align-items: center; - gap: 7px; - padding: 8px 9px 4px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 10px; - font-weight: 700; - letter-spacing: 0; - line-height: 1.2; - text-transform: uppercase; - user-select: none; -} -.date-section-header::after { - content: ''; - flex: 1 1 auto; - height: 1px; - background: color-mix(in srgb, var(--border) 72%, transparent); -} -#session-list .date-section-header { - padding-left: 10px; - padding-right: 8px; - cursor: pointer; -} -#session-list .date-section-header.collapsed { - color: color-mix(in srgb, var(--fg) 36%, transparent); -} -#session-list .date-section-header.collapsed::after { - opacity: 0.45; -} -#session-list .date-section-header:focus-visible { - outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); - outline-offset: 2px; - border-radius: 5px; -} -/* Bootstrap row shown while the session list hydrates, and on load failure. - Reads as a normal list row but is not selectable. */ -.session-list-bootstrap { - cursor: default; - pointer-events: none; -} -#email-lib-grid .date-section-header { - padding: 10px 5px 3px; -} -#email-lib-modal .email-lib-sync-status { - flex: 0 0 auto; - min-height: 14px; - padding: 1px 4px 0; - font-size: 10.5px; - line-height: 1.2; - text-align: center; - opacity: 0.58; - color: var(--muted-text, currentColor); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - position: relative; - top: 4px; -} -@media (max-width: 768px) { - #email-lib-modal .email-lib-sync-status { - min-height: 13px; - padding-right: 5px; - font-size: 10px; - } -} -/* Nudge the subject + sender/date meta line 4px right inside each - email card so they don't sit flush against the read/unread/done - markers on the left. Includes #email-lib-modal selector to - override the expanded card's `padding: 4px 0 6px` shorthand - which was zeroing out the title's padding-left. */ -#email-lib-grid .email-card-titlerow, -#email-lib-grid .memory-item-meta, -#email-lib-modal .doclib-card-expanded .email-card-titlerow { - padding-left: 4px; -} -.email-loading-label { - font-size: 11px; - opacity: 0.6; -} - -/* Refresh button now lives top-right in the modal header next to the close X. - Borderless (matches the close X), and a fixed square box so the spin and the - refresh→checkmark icon swap never change its size or nudge neighbours. */ -.email-lib-header-actions #email-lib-refresh-btn { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - border: none; - background: none; - padding: 0; - width: 24px; - height: 24px; -} -/* Bump the icon up on mobile for a bigger visual — but keep the button BOX at - 24px so it never exceeds the header's natural height (the title/close set - that). A taller box would grow the sticky header and push the list down. */ -@media (max-width: 768px) { - .email-lib-header-actions #email-lib-refresh-btn svg { - width: 17px; - height: 17px; - } -} -.cal-view-toggle { - display: inline-flex; - align-items: stretch; - border: 1px solid var(--border); - border-radius: 5px; - overflow: hidden; - vertical-align: middle; - box-sizing: border-box; - padding: 0; - margin: 0; - line-height: 1; -} -.cal-view-btn + .cal-view-btn { border-left: 1px solid var(--border); } -.cal-view-btn:hover { opacity: 0.75; } -.cal-view-btn.active { - background: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--accent, var(--red)); - opacity: 1; - font-weight: 600; -} -/* Toolbar holds only the toggle button; the chip row renders below the - toolbar on its own line and wraps freely. */ -.cal-filters { - display:flex; - gap:6px; - margin:6px 0 8px; - flex-wrap:wrap; - padding:6px 8px; - border:1px solid color-mix(in srgb, var(--fg) 8%, transparent); - border-radius:8px; - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 4%, transparent), transparent), - color-mix(in srgb, var(--fg) 2.5%, var(--bg)); - box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 4%, transparent); -} -/* Round the native color-input swatch in calendar settings. The outer - wraps a colored fill drawn by the browser; we need to round both - the wrapper and the fill so it actually appears circular. */ -.cal-s-color { border-radius: 50%; overflow: hidden; } -.cal-filter-item { display:inline-flex; align-items:center; gap:4px; cursor:pointer; font-size:10px; padding:2px 8px; line-height:1.4; border-radius:10px; background:color-mix(in srgb, var(--bg) 72%, transparent); border:1px solid color-mix(in srgb, var(--fg) 12%, transparent); transition:opacity 0.15s, background 0.15s, border-color 0.15s, transform 0.15s; } -.cal-filter-item:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); } -.cal-filter-item.cal-filter-important { font-weight:700; color:#e5a33a; border-color:color-mix(in srgb, #e5a33a 40%, transparent); } -/* Match the toolbar's other buttons (cal-nav, cal-view-btn, cal-add-btn-text): - 24px tall, 11px font, 5px corners. */ -.cal-filter-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font-size: 11px; - font-family: inherit; - font-weight: 500; - padding: 0 10px; - height: 24px; - line-height: 1; - border-radius: 5px; - box-sizing: border-box; - vertical-align: middle; - opacity: 0.65; - position: relative; - top: -3px; -} -.cal-filter-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } -.cal-filter-toggle-glyph { - display: inline-block; - position: relative; - top: 0; - margin-right: 3px; - color: var(--accent, var(--red)); -} -.cal-filter-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; } -/* The grid is now a flex column: one row of weekday headers + six - .cal-week-row rows. Each row holds the 7 day cells in its own - internal grid plus the absolute-positioned multi-day overlays. - --bars on a week-row is the number of multi-day events overlapping - it; cells in that row use it to reserve top padding so the bar - overlays don't cover the day numbers or single-event rows. */ -.cal-grid { display:flex; flex-direction:column; gap:1px; background:color-mix(in srgb, var(--fg) 8%, transparent); border-radius:6px; overflow:hidden; } -.cal-week-headers { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } -.cal-week-row { position:relative; display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } -.cal-weekday { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); text-align:center; font-size:10px; font-weight:600; opacity:0.4; padding:5px 0; } -.cal-day { - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 34px), - var(--bg); - min-height:78px; - padding:3px; - cursor:pointer; - position:relative; - transition:background 0.12s, box-shadow 0.12s; - overflow:hidden; -} -.cal-day:hover { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); } -.cal-day.cal-today { - box-shadow: inset 0 0 0 2px var(--accent, var(--red)); - background: var(--bg); - border-radius: 8px; -} -.cal-day.cal-selected:not(.cal-today) .cal-day-num { - color: var(--accent, var(--fg)); - opacity: 1; - font-weight: 700; -} -.cal-day.cal-drag-over { background:color-mix(in srgb, var(--accent) 18%, var(--bg)); } -.cal-day.cal-range-select { background:color-mix(in srgb, var(--accent, var(--red)) 25%, var(--bg)); box-shadow:inset 0 0 0 1px var(--accent, var(--red)); } -.cal-day-num { font-size:10px; font-weight:600; display:block; margin-bottom:1px; opacity:0.7; line-height:1.2; } -.cal-dots { display:flex; flex-direction:row; align-items:center; gap:2px; flex-wrap:nowrap; margin-bottom:1px; height:6px; } -.cal-dot { width:5px; height:5px; border-radius:50%; display:inline-block; cursor:grab; flex-shrink:0; } -.cal-dot-more { font-size:7px; opacity:0.4; } -.cal-event-row { - display:flex; - align-items:center; - gap:4px; - padding:2px 4px; - border-radius:4px; - cursor:grab; - line-height:1.2; - margin-bottom:2px; - min-height:17px; - background:color-mix(in srgb, var(--fg) 5%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); - animation:cal-event-enter 0.16s ease-out both; - transition:background 0.15s, box-shadow 0.15s, transform 0.15s; -} -.cal-event-row:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); transform:translateY(-1px); } -.cal-event-row-dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } -.cal-event-row-time { font-size:10px; opacity:0.72; font-variant-numeric:tabular-nums; flex-shrink:0; font-weight:600; } -.cal-event-row-name { font-size:10px; opacity:0.96; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; font-weight:600; } -.cal-event-reminder { - display: inline-flex; - width: 12px; - height: 12px; - margin-left: 5px; - vertical-align: -2px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - opacity: 0.9; - flex: 0 0 auto; -} -.cal-event-reminder svg { - width: 100%; - height: 100%; - display: block; -} -.cal-event-row .cal-event-reminder { - width: 10px; - height: 10px; - margin-left: 4px; -} -.cal-event-source { - display:inline-flex; - align-items:center; - justify-content:center; - width:12px; - height:12px; - margin-left:5px; - border-radius:50%; - font-size:7px; - font-weight:800; - line-height:1; - color:var(--cal-event-fg, var(--fg)); - background:color-mix(in srgb, currentColor 16%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); - vertical-align:1px; -} -.cal-event-source svg { - width: 10px; - height: 10px; - display: block; -} -.cal-event-row .cal-event-source { - width:11px; - height:11px; - font-size:7px; - margin-left:4px; - color:color-mix(in srgb, var(--fg) 72%, transparent); -} -.cal-multiday .cal-event-reminder, -.cal-wk-allday-event .cal-event-reminder { - color: var(--cal-event-fg, currentColor); - opacity: 0.88; -} -.cal-multiday .cal-event-source, -.cal-wk-allday-event .cal-event-source { - color: var(--cal-event-fg, currentColor); -} -.cal-event-more { font-size:9px; opacity:0.55; padding-left:6px; } -/* Multi-day bar — positioned as an absolute overlay inside its - .cal-week-row so it spans uninterrupted across every column it - covers. --col is the starting column (0–6), --span is the number - of days it covers within the row, --slot stacks parallel bars. */ -.cal-multiday { - position: absolute; - /* Fractional offsets let timed events that cross midnight render at - true proportional width. start-frac shifts the left edge into the - first day; end-frac trims the right edge inside the last day. - All-day events default to (0, 1) and still fill whole columns. */ - left: calc((var(--col, 0) + var(--start-frac, 0)) * (100% / 7)); - width: calc( - (var(--span, 1) - var(--start-frac, 0) - (1 - var(--end-frac, 1))) - * (100% / 7) - ); - top: calc(2px + var(--slot, 0) * 12px); - height: 12px; - font-size: 9px; - line-height: 12px; - padding: 0 5px; - border-radius: 4px; - color: var(--cal-event-fg, #fff); - font-weight: 700; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: grab; - opacity: 0.98; - z-index: 2; - pointer-events: auto; - box-shadow: 0 1px 2px color-mix(in srgb, #000 18%, transparent); -} -/* Splitter between the month/week grid and the day-tasks panel — drag - vertically to give the day panel more room. Height clamped by the - `--cal-detail-h` variable set on #cal-body by the splitter drag JS. */ -.cal-splitter { - height: 8px; - margin: 6px -10px 0; - cursor: row-resize; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - user-select: none; - touch-action: none; -} -/* Mobile: fat hitbox so the splitter is easy to grab with a thumb, - without changing how the grip itself looks. */ -@media (max-width: 768px) { - .cal-splitter { - height: 24px; - margin-top: 0; - margin-bottom: 0; - } - .cal-splitter-grip { - width: 56px; - height: 4px; - } -} -.cal-splitter-grip { - width: 42px; - height: 3px; - border-radius: 2px; - background: color-mix(in srgb, var(--fg) 25%, transparent); - transition: background 0.12s; -} -.cal-splitter:hover .cal-splitter-grip, -.cal-splitter-dragging .cal-splitter-grip { - background: var(--accent, var(--red)); -} -.cal-day-detail { - margin-top: 4px; - border-top: 1px solid var(--border); - padding-top: 8px; - height: var(--cal-detail-h, 240px); - overflow-y: auto; - overscroll-behavior: contain; - flex-shrink: 0; -} -.cal-detail-header { display:flex; justify-content:space-between; align-items:center; font-size:12px; font-weight:600; margin-bottom:6px; padding-right:8px; } -.cal-empty { font-size:11px; opacity:0.3; padding:4px 0; } -.cal-event-item { display:flex; gap:9px; padding:8px 9px; border-radius:6px; cursor:pointer; align-items:flex-start; background:color-mix(in srgb, var(--fg) 3.5%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); margin-bottom:4px; } -.cal-event-item:hover { background:color-mix(in srgb, var(--fg) 8%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); } -.cal-event-dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:4px; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } -.cal-event-info { flex:1; min-width:0; } -.cal-event-name { font-size:13px; font-weight:700; line-height:1.25; color:color-mix(in srgb, var(--fg) 96%, transparent); overflow-wrap:anywhere; } -.cal-event-tag { - display: inline-block; - font-size: 10px; - font-weight: 500; - padding: 1px 6px; - border-radius: 8px; - border: 1px solid; - margin-left: 6px; - vertical-align: 1px; - opacity: 0.85; - white-space: nowrap; -} -.cal-event-time { font-size:11px; opacity:0.68; line-height:1.35; margin-top:2px; font-weight:500; } -.cal-event-loc { font-size:11px; opacity:0.58; line-height:1.35; margin-top:1px; } -/* ── Week view: hour grid ────────────────────────────────────────── - Vertical hour rail on the left, 7 day columns on the right with their - own all-day strip and an absolute-positioned hour grid below it. */ -.cal-wk-wrap { - display: flex; - flex-direction: row; - gap: 0; - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 3%, transparent), transparent 120px), - var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - /* The wrap itself scrolls so the hour rail and day columns move - together; column headers and the rail spacer are sticky-pinned. */ - overflow: auto; - max-height: calc(100vh - 220px); - min-height: 360px; - position: relative; -} -.cal-wk-now-below-hint { - position: absolute; - height: 2px; - border-radius: 2px 2px 0 0; - background: var(--accent, var(--red)); - pointer-events: none; - z-index: 38; -} -@media (max-width: 768px) { - .cal-wk-wrap { - -webkit-overflow-scrolling: touch; - overscroll-behavior-y: contain; - } - .cal-wk-wrap > .cal-wk-rail, - .cal-wk-wrap > .cal-wk-cols { - transform: translateY(var(--wk-edge-pull, 0)); - } - .cal-wk-wrap.cal-wk-edge-release > .cal-wk-rail, - .cal-wk-wrap.cal-wk-edge-release > .cal-wk-cols { - transition: transform 0.14s cubic-bezier(0.2, 0.75, 0.25, 1); - } - .cal-wk-wrap.cal-wk-pull-top { - border-top-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); - } - .cal-wk-wrap.cal-wk-pull-bottom { - border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); - } -} -.cal-wk-rail { - flex: 0 0 56px; - border-right: 1px solid var(--border); - display: flex; - flex-direction: column; - background: color-mix(in srgb, var(--fg) 2%, var(--bg)); - position: sticky; - left: 0; - z-index: 5; -} -.cal-wk-rail-spacer { - /* Match the 32px day header. The 24px all-day row gets its own spacer - below, so the zoom controls sit high without shifting the hour grid. */ - height: 32px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 0; - background: color-mix(in srgb, var(--fg) 2%, var(--bg)); - z-index: 9; - display: flex; - align-items: center; - justify-content: center; - gap: 2px; - padding: 0 4px; -} -.cal-wk-rail-allday-spacer { - height: 24px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 32px; - z-index: 8; - background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); -} -.cal-wk-zoom { - position: relative; - top: -3px; - width: 22px; - height: 22px; - border: 1px solid var(--border); - background: var(--bg); - color: var(--fg); - border-radius: 50%; - font-family: inherit; - font-size: 14px; - font-weight: 600; - line-height: 1; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - opacity: 0.55; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.cal-wk-zoom:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--fg)) 50%, var(--border)); -} -.cal-wk-rail-cell { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 55%, transparent); - padding: 4px 8px 0; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); - flex-shrink: 0; - display: flex; - align-items: flex-start; - justify-content: flex-end; - font-variant-numeric: tabular-nums; -} -.cal-wk-cols { - flex: 1; - display: grid; - grid-template-columns: repeat(7, 1fr); - position: relative; -} -.cal-wk-col { - border-left: 1px solid var(--border); - display: flex; - flex-direction: column; - min-width: 0; -} -.cal-wk-col:first-child { border-left: none; } -.cal-wk-col-head { - height: 32px; - padding: 0 6px; - display: flex; - align-items: center; - gap: 6px; - font-size: 11px; - flex-shrink: 0; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); - position: sticky; - top: 0; - z-index: 40; -} -/* Today on a Sunday still wins the accent treatment. */ -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); } -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dn, -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dt { color: var(--accent, var(--fg)); } -.cal-wk-dn { font-weight: 600; opacity: 0.6; } -.cal-wk-allday { - height: 24px; - padding: 2px 4px; - display: flex; - flex-direction: row; - gap: 2px; - align-items: center; - background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); - border-bottom: 1px solid var(--border); - flex-shrink: 0; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: none; - position: sticky; - top: 32px; - z-index: 39; -} -.cal-wk-allday-event { - font-size: 10.5px; - font-weight: 700; - padding: 3px 6px; - border-radius: 4px; - color: var(--cal-event-fg, var(--fg)); - cursor: pointer; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Hour grid body */ -.cal-wk-grid { - position: relative; - flex: 0 0 auto; - cursor: cell; - user-select: none; - background: - repeating-linear-gradient( - to bottom, - transparent 0, - transparent calc(var(--wk-hour-px, 52px) / 2 - 1px), - color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--wk-hour-px, 52px) / 2), - transparent calc(var(--wk-hour-px, 52px) / 2 + 1px) - ); -} -.cal-wk-cell { - border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); - box-sizing: border-box; -} -.cal-wk-cell:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); } -/* "Now" line */ -.cal-wk-now { - position: absolute; - left: 0; right: 0; - height: 0; - border-top: 2px solid color-mix(in srgb, var(--accent, var(--red, #f87171)) 90%, transparent); - pointer-events: none; - z-index: 7; - filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent)); -} -/* Event blocks */ -.cal-wk-block { - position: absolute; - left: calc((var(--lane, 0) * (100% / var(--lane-count, 1))) + 2px); - width: calc((100% / var(--lane-count, 1)) - 4px); - right:auto; - border-left: 4px solid var(--fg); - border-radius: 5px; - padding: 5px 7px 7px; - font-size: 12px; - line-height: 1.3; - cursor: pointer; - /* `clip` preserves the rounded-card boundary without creating an overflow - container, allowing the label inside to stick to the week scroller. */ - overflow: clip; - z-index: 2; - box-shadow: 0 2px 6px color-mix(in srgb, #000 14%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); - animation:cal-event-enter 0.18s ease-out both; - transition: width 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), height 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), filter 0.12s, transform 0.12s, box-shadow 0.16s; - box-sizing: border-box; -} -.cal-wk-block:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); } -.cal-wk-block-label { - position: sticky; - top: 37px; - z-index: 1; - min-width: 0; - pointer-events: none; -} -.cal-wk-has-allday .cal-wk-block-label { - top: 61px; -} -.cal-wk-block-name { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color:color-mix(in srgb, var(--fg) 96%, transparent); } -.cal-wk-block-time { font-size: 10.5px; opacity: 0.82; font-variant-numeric: tabular-nums; margin-top: 2px; font-weight:600; } -@media (hover: hover) and (pointer: fine) { - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left { - left: auto; - right: calc(100% - var(--lane-right) + 2px); - } - .cal-wk-block.cal-wk-block-crowded:hover { - width: var(--hover-width, 170px); - height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; - overflow: clip; - z-index: 30; - filter: brightness(1.08); - transform: translateY(var(--hover-shift-y, -2px)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-hover-width-only:hover { - height: var(--event-height) !important; - transform: translateY(-1px); - } - .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-label { - position: absolute; - top: 5px; - width: calc(var(--hover-width, 170px) - 18px); - animation: cal-wk-hover-label-reveal 0.2s ease-out both; - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left:hover .cal-wk-block-label { - left: auto; - right: 7px; - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-right:hover .cal-wk-block-label { - left: 7px; - right: auto; - } - .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-name { - display: block; - white-space: normal; - overflow: visible; - text-overflow: clip; - overflow-wrap: anywhere; - } -} -@media (max-width: 768px) { - /* Mobile week view: the hour rail already shows the time, so drop it from the - card and let the title wrap to fill the freed space (more readable text). */ - .cal-wk-block-time { display: none; } - .cal-wk-block-name { - white-space: normal; - line-height: 1.15; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left { - left: auto; - right: calc(100% - var(--lane-right) + 2px); - } - .cal-wk-block.cal-wk-mobile-expanded { - width: var(--hover-width, 170px); - height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; - overflow: clip; - z-index: 30; - filter: brightness(1.08); - transform: translateY(var(--hover-shift-y, 0)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); - } - .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-label { - position: absolute; - top: 5px; - width: calc(var(--hover-width, 170px) - 18px); - animation: cal-wk-hover-label-reveal 0.2s ease-out both; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left .cal-wk-block-label { - left: auto; - right: 7px; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-right .cal-wk-block-label { - left: 7px; - right: auto; - } - .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-name { - display: block; - white-space: normal; - overflow: visible; - text-overflow: clip; - overflow-wrap: anywhere; - -webkit-line-clamp: unset; - } -} -/* Resize handle: a 6 px hit zone along the block's bottom edge. The - visible 2-px stripe only shows on hover so blocks at rest stay clean. */ -.cal-wk-block-resize { - position: absolute; - left: 4px; - right: 4px; - bottom: 0; - height: 6px; - cursor: ns-resize; - z-index: 3; -} -/* Drag-to-create ghost */ -.cal-wk-ghost { - position: absolute; - left: 2px; - right: 2px; - background: color-mix(in srgb, var(--accent, var(--fg)) 28%, transparent); - border: 1px dashed color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent); - border-radius: 4px; - pointer-events: none; - z-index: 3; - font-size: 10px; - font-weight: 600; - display: flex; - align-items: center; - justify-content: center; - color: var(--accent, var(--fg)); - font-variant-numeric: tabular-nums; -} -.cal-wk-slot-selected { - background: color-mix(in srgb, var(--accent, var(--fg)) 20%, var(--bg)); - border-style: solid; - border-color: var(--accent, var(--fg)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--fg)) 20%, transparent); - overflow: visible; - pointer-events: auto; - cursor: pointer; -} -.cal-wk-slot-resize { - position: absolute; - left: 14%; - right: 14%; - bottom: -7px; - height: 14px; - z-index: 4; - cursor: ns-resize; - touch-action: none; - pointer-events: auto; -} -.cal-year { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; } -.cal-year-month { background:color-mix(in srgb, var(--fg) 3%, var(--bg)); border:1px solid var(--border); border-radius:6px; padding:6px; cursor:pointer; transition:background 0.15s, border-color 0.15s, transform 0.15s; } -.cal-year-month:hover { background:color-mix(in srgb, var(--fg) 8%, var(--bg)); border-color:var(--accent); transform:translateY(-1px); } -.cal-year-month-title { font-size:11px; font-weight:600; text-align:center; margin-bottom:4px; opacity:0.7; } -.cal-year-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; } -.cal-year-wd { font-size:7px; text-align:center; opacity:0.3; padding:1px 0; } -.cal-year-cell { font-size:8px; text-align:center; padding:2px 0; min-height:14px; border-radius:2px; transition:transform 0.12s, background 0.12s; } -.cal-year-day:hover { background:var(--accent, var(--red)); color:#fff; opacity:1; transform:scale(1.15); font-weight:700; z-index:2; position:relative; } -.cal-year-today { color:var(--accent, var(--red)); font-weight:700; opacity:1; } -.cal-year-has { opacity:1; background:color-mix(in srgb, var(--accent) 15%, transparent); } -.cal-year-has.cal-year-today { background:color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } -.cal-loading { display:flex; justify-content:center; padding:40px 0; } -.cal-badge { background:var(--accent); border-radius:50%; width:6px; height:6px; margin-left:auto; display:inline-block; flex-shrink:0; } -/* Search input — in-panel variant lives inside the day-detail panel and - spans its width; no width animation since growing/shrinking on every - keystroke would jitter beside the events list. */ -.cal-search-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:0 8px; height:24px; color:var(--fg); font-size:11px; font-family:inherit; width:120px; box-sizing:border-box; transition:width 0.15s; } -.cal-search-input:focus { outline:none; border-color:var(--accent); width:180px; } -.cal-search-input.cal-day-search { width:100%; margin-bottom:0; transition:none; padding-left: 28px; } -/* Wrap for the search input so the magnifying-glass icon can be absolute- - positioned inside the field. */ -.cal-search-wrap { - position: relative; - display: block; - margin-bottom: 6px; -} -.cal-search-wrap .cal-search-icon { - position: absolute; - left: 8px; - top: 50%; - transform: translateY(-50%); - pointer-events: none; - color: var(--accent, var(--red)); -} -.cal-day-search-meta { font-size:10px; opacity:0.5; margin:0 2px 6px; } -.cal-search-results { display:flex; flex-direction:column; gap:4px; } -.cal-search-count { font-size:10px; opacity:0.5; padding:4px 2px 8px; } -/* Agenda view */ -.cal-agenda { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-left:4px; } -.cal-agenda-day { display:flex; flex-direction:column; gap:5px; position:relative; padding-left:14px; } -.cal-agenda-date { font-size:12px; font-weight:800; opacity:0.72; padding:5px 2px 3px; border-bottom:1px solid var(--border); } -.cal-agenda-today-badge { - display: inline-block; - margin-left: 6px; - font-size: 9px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - vertical-align: 1px; -} -.cal-agenda-empty { font-size:11px; opacity:0.4; padding:8px; font-style:italic; } -.cal-agenda-event { - display:flex; - gap:9px; - padding:9px 10px; - border-radius:6px; - cursor:pointer; - align-items:center; - position:relative; - background:color-mix(in srgb, var(--fg) 3.5%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); - animation:cal-event-enter 0.18s ease-out both; - transition:background 0.15s, box-shadow 0.15s, transform 0.15s; -} -.cal-agenda-event:hover { - background:color-mix(in srgb, var(--fg) 8%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); - transform:translateX(2px); -} -.cal-agenda-event .cal-event-more, -.cal-event-item .cal-event-more { opacity:0; transition:opacity 0.15s; } -.cal-event-item { position:relative; } - -/* ── Personal Assistant ── */ -.sidebar-assistant-entry { gap: 6px; min-height: 29px; box-sizing: border-box; align-items: center; } -.sidebar-assistant-entry .sidebar-action-icon { position: relative; left: -2px; } -#sidebar-assistant-gear:hover { opacity: 0.8 !important; } -.assistant-settings-form { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; } -.assistant-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: color-mix(in srgb, var(--fg) 70%, transparent); } -.assistant-field > span { font-size: 11px; opacity: 0.6; } -.assistant-field input[type="text"], -.assistant-field input[type="time"], -.assistant-field select, -.assistant-field textarea { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 5px; - padding: 7px 10px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - box-sizing: border-box; -} -.assistant-field textarea { resize: vertical; min-height: 60px; } -.assistant-field input:focus, -.assistant-field select:focus, -.assistant-field textarea:focus { outline: none; border-color: var(--accent, var(--red)); } -.assistant-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } -.assistant-field-row > .assistant-field { flex: 1 1 180px; } -.assistant-field-check { flex-direction: row !important; align-items: center; gap: 6px; padding-bottom: 7px; } -.assistant-checkins { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); } -.assistant-checkins h5 { margin: 0 0 2px; font-size: 12px; font-weight: 600; opacity: 0.8; } -.assistant-checkin-row { - display: flex; - flex-direction: column; - gap: 4px; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.assistant-checkin-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } -.assistant-checkin-head input[type="text"], -.assistant-checkin-head input[type="time"] { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 4px; - padding: 4px 8px; - color: var(--fg); - font-size: 12px; - font-family: inherit; -} -.assistant-checkin-head input[type="text"] { flex: 1; min-width: 120px; } -.assistant-checkin-head input[type="time"] { width: 90px; } -.assistant-checkin-run { - background: none; - border: 1px solid var(--border); - color: var(--fg); - font-family: inherit; - font-size: 11px; - padding: 4px 8px; - border-radius: 4px; - cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s; -} -.assistant-checkin-run:hover { opacity: 1; } -.assistant-checkin-prompt { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 4px; - padding: 6px 8px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - resize: vertical; - min-height: 48px; -} -.assistant-checkin-prompt:focus { outline: none; border-color: var(--accent, var(--red)); } -.assistant-checkin-meta { font-size: 10px; opacity: 0.45; } -.assistant-settings-actions { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 8px; - padding-top: 8px; - border-top: 1px solid var(--border); -} -.assistant-tools-grid { - display: flex; - flex-direction: column; - gap: 6px; - max-height: 240px; - overflow-y: auto; - padding: 6px; - border: 1px solid var(--border); - border-radius: 6px; - margin-top: 4px; -} -.assistant-tool-group { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.assistant-tool-group-label { - font-size: 10px; - font-weight: 600; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.03em; - width: 100%; - margin-bottom: 1px; -} -.assistant-tool-item { - display: inline-flex; - align-items: center; - gap: 3px; - font-size: 11px; - padding: 2px 6px; - border: 1px solid var(--border); - border-radius: 4px; - cursor: pointer; - transition: background 0.1s, border-color 0.1s; - user-select: none; -} -.assistant-tool-item:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.assistant-tool-item:has(input:checked) { - border-color: var(--accent); - background: color-mix(in srgb, var(--accent) 10%, transparent); -} -.assistant-tool-item input[type="checkbox"] { - width: 12px; - height: 12px; - margin: 0; -} - -/* Empty state */ -.cal-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px 20px; text-align:center; gap:10px; } -.cal-empty-title { font-size:14px; font-weight:600; opacity:0.8; } -.cal-empty-msg { font-size:12px; opacity:0.5; max-width:320px; line-height:1.5; margin-bottom:8px; } -.cal-allday-switch { margin:0; flex-shrink:0; } -/* Stack the "All day" label above its toggle so it doesn't get squeezed / - truncated next to the date inputs in the row. */ -.cal-allday-ctrl { display:flex; flex-direction:column; align-items:center; gap:3px; flex-shrink:0; } -.cal-allday-label { font-size:10px; opacity:0.5; white-space:nowrap; line-height:1; } -.cal-form { display:flex; flex-direction:column; gap:8px; } -.cal-form-title { font-size:13px; font-weight:600; margin-bottom:2px; } -.cal-title-wrap { position: relative; } -/* Calendar-picker select sits 2px higher; its border-left/background tint is - set in JS to the chosen calendar's colour. */ -.cal-f-cal-select { position: relative; top: -4px; } - -/* ── Bespoke event form ──────────────────────────────────────────── - Big clock-face date/time hero with the title under it. Details panel - stays collapsed until the title gets focus or "Add details" is clicked. -*/ -.cal-form-bespoke { - position: relative; - width: min(720px, calc(100% - 24px)); - max-width: none; - margin: 12px auto; - padding: 18px 28px 14px; - box-sizing: border-box; - background: var(--bg); - /* Optional per-event tint set by JS via --ev-color. When unset, falls - back to the theme's neutral border so the card looks like normal. */ - border: 1px solid var(--border); - border-radius: 14px; - box-shadow: 0 8px 24px color-mix(in srgb, var(--fg) 6%, transparent); - transition: border-color 0.18s, box-shadow 0.18s; -} -/* When a colour is picked, lift the card with a soft tinted border + glow - so the choice is unmistakable. */ -.cal-form-bespoke[style*="--ev-color"] { - border-color: color-mix(in srgb, var(--ev-color) 55%, var(--border)); - box-shadow: 0 8px 28px color-mix(in srgb, var(--ev-color) 22%, transparent); -} -.cal-form-bespoke[style*="--ev-color"] .cal-btn-primary { - background: var(--ev-color); - border-color: var(--ev-color); -} -.cal-form-bespoke.cal-form-bg-image .cal-btn-primary { - background: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); - color: var(--bg); -} -.cal-form-close { - position: absolute; top: 10px; right: 12px; - background: none; border: none; color: var(--fg); - opacity: 0.35; cursor: pointer; font-size: 20px; line-height: 1; - padding: 4px 8px; border-radius: 6px; -} -.cal-form-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cal-form-mobile-cancel { display: none; } - -/* "Today is …" header pinned at the top of the form. Keeps you oriented - even when the event date you're picking is way off in the future. */ -.cal-form-today { - text-align: center; - font-size: 11px; - letter-spacing: 0.08em; - text-transform: uppercase; - opacity: 0.45; - margin-bottom: 0; -} - -/* Hero — clock face + date label. Spacing between the two lives entirely - on the clock's bottom margin so it's predictable. */ -.cal-hero { - display: flex; flex-direction: column; align-items: center; - gap: 0; margin-bottom: 12px; -} -.cal-hero-time { - display: inline-flex; align-items: baseline; gap: 8px; - font-family: ui-monospace, SFMono-Regular, "Menlo", "Consolas", monospace; - font-variant-numeric: tabular-nums; - font-size: 56px; - letter-spacing: 0.02em; - font-weight: 200; - color: var(--fg); - line-height: 1; - /* Behaves like a button — strip native chrome but keep it tappable. */ - background: none; - border: none; - /* Pill that reads as a real hit target for a 56-px clock face. - Top padding kept tight so the clock sits close to the "Today is" line. */ - padding: 6px 38px 18px; - margin: 0 0 24px; - border-radius: 18px; - cursor: pointer; - line-height: 1; - transition: background 0.15s, color 0.15s; -} -/* The clock is a `` and a global `button:hover` rule paints - every button's background on hover. Suppress it explicitly. */ -.cal-hero-time:hover { background: transparent; border-color: transparent; } -.cal-hero-clock { font-feature-settings: "tnum"; display: inline-flex; align-items: baseline; } -/* Per-segment hover so it's clear hh vs mm are individually clickable. */ -.cal-hero-clock-hh, .cal-hero-clock-mm { - display: inline-block; - border-radius: 6px; - padding: 0 2px; - transition: background 0.12s; -} -.cal-hero-clock-hh:hover, .cal-hero-clock-mm:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); -} -.cal-hero-sep { padding: 0 2px; opacity: 0.55; pointer-events: none; } -.cal-hero-ampm { - font-size: 14px; - letter-spacing: 0.12em; - opacity: 0.55; - font-weight: 500; - text-transform: uppercase; -} -.cal-hero-date { - font-size: 16px; - opacity: 0.6; - letter-spacing: 0.04em; - background: none; - border: none; - color: inherit; - font-family: inherit; - padding: 4px 10px; - border-radius: 6px; - cursor: pointer; - margin: 7px 0 0; /* extra nudge below the clock */ - transition: background 0.15s, opacity 0.15s; -} -.cal-hero-date:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); opacity: 0.85; } - -/* Title input — flat, large, no chrome until focused */ -.cal-input.cal-hero-title { - font-size: 18px; - text-align: left; - padding: 10px 12px 10px 0; - background: transparent; - border: 1px solid transparent; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); - border-radius: 0; - margin-bottom: 4px; -} - -/* Details panel — animated reveal via max-height + opacity. */ -.cal-form-bespoke .cal-form-details { - display: block; - max-height: 0; - opacity: 0; - overflow: hidden; - transition: max-height 240ms ease, opacity 180ms ease, margin 220ms ease; - margin-top: 0; -} -/* The detail children themselves keep the existing flex/gap rules from - the regular .cal-form, so we don't restyle inputs/rows here. */ -.cal-form-bespoke .cal-form-details > * + * { margin-top: 8px; } - - -/* Location row: input + Apple Maps pin link. Pin only lights up when the - field has content; clicking opens maps.apple.com (native Maps app on - Apple devices, web fallback elsewhere). All theme vars. */ -.cal-loc-row { - display: flex; - align-items: stretch; - gap: 6px; -} -.cal-loc-input-wrap { - position: relative; - flex: 1; - min-width: 0; -} -.cal-loc-hint { - position: absolute; - inset: 0 auto 0 10px; - display: none; - align-items: center; - gap: 5px; - pointer-events: none; - color: color-mix(in srgb, var(--fg) 42%, transparent); - font-size: 12px; -} -.cal-loc-map { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - flex-shrink: 0; - border: 1px solid var(--border); - border-radius: 5px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - text-decoration: none; - transition: background 0.15s, border-color 0.15s, opacity 0.15s; -} -.cal-loc-map:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); border-color: var(--accent, var(--red)); } - -@media (max-width: 520px) { - .cal-hero-time { font-size: 44px; } - .cal-form-bespoke { margin: 16px 12px; padding: 32px 16px 14px; } - .cal-form-mobile-cancel { - display: inline-flex; - position: absolute; - top: 8px; - right: 8px; - width: 30px; - height: 30px; - align-items: center; - justify-content: center; - padding: 0; - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--bg) 82%, transparent); - color: var(--fg); - opacity: 0.72; - cursor: pointer; - z-index: 3; - -webkit-tap-highlight-color: transparent; - } - .cal-form-mobile-cancel:active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); - } -} -.cal-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:7px 10px; color:var(--fg); font-size:12px; width:100%; box-sizing:border-box; } -.cal-input:focus { outline:none; border-color:var(--accent); } -.cal-input-time { width:auto; flex:1; } -.cal-form-row { display:flex; gap:8px; align-items:center; } -.cal-form-actions { display:flex; gap:8px; justify-content:flex-end; margin-top:4px; } button.cal-btn { background:color-mix(in srgb, var(--fg) 8%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 14px; height:28px; font-size:11px; font-weight:500; cursor:pointer; font-family:inherit; display:inline-flex; align-items:center; justify-content:center; box-sizing:border-box; transition:background 0.15s, border-color 0.15s; } button.cal-btn:hover { background:color-mix(in srgb, var(--fg) 14%, transparent); } button.cal-btn.cal-btn-primary { background:var(--accent, var(--red)); color:var(--bg); border-color:var(--accent, var(--red)); } diff --git a/static/css/style-part-02.css b/static/css/style-part-02.css deleted file mode 100644 index a3ceff612..000000000 --- a/static/css/style-part-02.css +++ /dev/null @@ -1,6011 +0,0 @@ -/* Fragment 3/9 of the former static/style.css. - * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. - * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. - */ - .loading-dot:nth-child(1) { - animation: loading-bounce 1.4s infinite ease-in-out both; - } - .loading-dot:nth-child(2) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.32s; - } - .loading-dot:nth-child(3) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.64s; - } - .session-loading-state { - min-height: 100%; - display: flex; - flex-direction: column; - justify-content: flex-start; - gap: 12px; - color: var(--fg); - font-size: 12px; - pointer-events: none; - position: relative; - padding: 16px 14px 24px; - box-sizing: border-box; - overflow: hidden; - } - .session-loading-state::before { content: none; } - .session-skeleton-bubble { - width: min(78%, 680px); - border: 1px solid color-mix(in srgb, var(--border) 74%, transparent); - border-radius: 10px; - background: color-mix(in srgb, var(--panel, var(--bg)) 72%, transparent); - padding: 10px 12px 12px; - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.14); - } - .session-skeleton-bubble.is-user { - align-self: flex-end; - width: min(64%, 520px); - } - .session-skeleton-bubble.is-ai { - align-self: flex-start; - } - .session-skeleton-line { - position: relative; - overflow: hidden; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - border-radius: 999px; - } - .session-skeleton-line { - height: 10px; - margin-top: 8px; - opacity: 0.62; - } - .session-skeleton-line::after { - content: ''; - position: absolute; - inset: -1px; - transform: translateX(-120%); - background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--fg) 14%, transparent), transparent); - animation: session-skeleton-shimmer 1.25s ease-in-out infinite; - } - .session-skeleton-bubble:nth-child(2) .session-skeleton-line::after { animation-delay: 0.12s; } - .session-skeleton-bubble:nth-child(3) .session-skeleton-line::after { animation-delay: 0.24s; } - .session-skeleton-bubble:nth-child(4) .session-skeleton-line::after { animation-delay: 0.36s; } - @keyframes session-skeleton-shimmer { - 100% { transform: translateX(120%); } - } - @media (max-width: 768px) { - .session-loading-state { padding: 12px 10px 18px; gap: 10px; } - .session-skeleton-bubble { width: 86%; padding: 9px 10px 11px; border-radius: 9px; } - .session-skeleton-bubble.is-user { width: 72%; } - .session-skeleton-line { height: 9px; margin-top: 7px; } - } - @keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } - } - /* Modal styling */ - .modal { - position:fixed; - top:0; left:0; width:100%; height:100%; - background:none; - display:flex; align-items:center; justify-content:center; - z-index:250; - backdrop-filter:none; - pointer-events:none; - } - /* Cookbook always sits above Gallery so the "Serve a model in - Cookbook…" flow (and any other "open Cookbook from inside another - modal") is visible no matter which modal was opened first. */ - #cookbook-modal { z-index: 260; } - .modal.hidden { display:none; } - - /* Tool windows open centered in the CHAT AREA (the space right of the - sidebar + icon rail), rather than the full viewport. - We narrow the overlay to the chat area so its flex-centering lands the - window there; fullscreen / docked states use position:fixed so they - escape this narrowed overlay and still fill the screen. The - --sidebar-w / --icon-rail-w vars track collapse state live, so when a - window (e.g. Cookbook) hides the sidebar this naturally re-centers. - Desktop only — on mobile these are full-screen sheets. */ - @media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal { - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)); - width: calc(100% - (var(--icon-rail-w, 48px) + var(--sidebar-w, 0px))); - box-sizing: border-box; - /* Slide in sync with the sidebar's 0.25s collapse/expand so the - centered window glides instead of jumping when the nav toggles. */ - transition: left 0.25s ease, width 0.25s ease; - } - } - .modal-content { - background:var(--panel); - border:1px solid var(--border); - width:min(520px, 92vw); max-height:85vh; padding:10px; - box-sizing:border-box; font-size:14px; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - letter-spacing: -0.015em; - display:flex; flex-direction:column; - position:relative; - overflow-y:auto; - border-radius:10px; - box-shadow:0 8px 32px rgba(0,0,0,0.45); - pointer-events:auto; - animation: modal-enter 0.25s ease-out both; - } - .memory-modal-content, - .tasks-modal-content, - .preset-modal-content, - #cookbook-modal .modal-content, - #theme-popup, - .doclib-modal-content, - .gallery-modal-content { - container-type: inline-size; - } - .modal-header { - display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; - cursor:grab; user-select:none; - /* Pin the header (with its close button) to the top of the - scrollable modal-content so users can always dismiss the modal - even after scrolling far down — especially important on mobile - where the modal can be taller than the viewport. */ - position: sticky; - top: 0; - z-index: 5; - /* Inherit the modal-content's background so the header matches the - panel body. Many tool modals set their content to var(--bg) inline - while this header was hard-coded to var(--panel) — making the title - strip a different shade in every theme. `inherit` tracks whatever - the content uses (var(--bg) or the default var(--panel)) and stays - opaque, so the sticky header still masks scrolled content. */ - background-color: inherit; - } - .modal-header:active { cursor:grabbing; } - /* Edge/corner window resize (windowResize.js). While a resize is in - progress, suppress text selection and force the active resize cursor - across the whole document so it does not flicker as the pointer passes - over child elements mid-drag. */ - body.window-resizing-active { user-select:none !important; } - body.window-resizing-active * { cursor:inherit !important; } - /* Suppress only TRANSITIONS while resizing so the edge tracks the cursor - crisply. We deliberately do NOT toggle `animation` here: toggling - animation off→on re-triggers the modal open-animation (a scale-in) on - mouseup, which both mis-measures the final size and visibly "pops" the - window. windowResize.js instead kills the one-shot open animation inline - once, in begin(). */ - .window-resizing { - transition:none !important; - } - /* Cookbook opts out of the sticky header on mobile; the title bar scrolls - away with the content instead of following. Background is inherited from - the shared .modal-header rule so frosted/glass themes stay consistent. */ - #cookbook-modal .modal-header { - position: static; - top: auto; - } - .modal-header h4 { - margin:0; - /* Push every header control (opacity slider, minimize, close) into one - group on the right — works whether or not optional controls like the - opacity slider are present, so the minimize button never floats to - the centre when a sibling is hidden. */ - margin-right:auto; - font-size:1rem; - font-weight:600; - letter-spacing:-0.03em; - color:var(--red); - } - /* Headers with an optional Peek control already have the title's - auto-margin grouping all right-side controls. A second auto-margin on - the injected minimize button splits the group and makes it look - different from the other modal headers. */ - .modal-header .theme-opacity-wrap + .modal-minimize-btn { - margin-left: 0 !important; - } - .close-btn, - .modal-close { - background:transparent; - color:color-mix(in srgb, var(--fg) 58%, transparent); - border:1px solid transparent; - font-size:0; - width:24px; - height:24px; - padding:0; - display:inline-flex; - align-items:center; - justify-content:center; - line-height:1; - text-indent:0; - cursor:pointer; - border-radius:999px; - line-height:1; - flex-shrink:0; - position:relative; - } - .close-btn > svg, - .modal-close > svg { - display:none; - } - .close-btn::before, - .modal-close::before { - content:""; - width:10px; - height:10px; - display:block; - background:currentColor; - -webkit-mask: - url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") - center / contain no-repeat; - mask: - url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") - center / contain no-repeat; - } - .close-btn:hover, - .modal-close:hover { - background:color-mix(in srgb, var(--fg) 10%, transparent); - border-color:color-mix(in srgb, var(--fg) 12%, transparent); - color:var(--fg); - } - /* Minimize button — sits beside the close button on every modal */ - .minimize-btn { - background:transparent; - color:color-mix(in srgb, var(--fg) 58%, transparent); - border:1px solid transparent; - font-size:0; - font-weight:700; - width:24px; - height:24px; - padding:0; - display:inline-flex; - align-items:center; - justify-content:center; - line-height:1; - cursor:pointer; - border-radius:999px; - flex-shrink:0; - margin-left:auto; /* push to the right so it docks next to .close-btn */ - margin-right:4px; - } - .minimize-btn::before { - content:""; - width:9px; - height:2px; - border-radius:999px; - background:currentColor; - display:block; - transform:translateY(2px); - } - .modal-minimize-btn.minimize-btn::before { - content:none; - display:none; - } - .minimize-btn:hover { - background:color-mix(in srgb, var(--fg) 10%, transparent); - border-color:color-mix(in srgb, var(--fg) 12%, transparent); - color:var(--fg); - } - @media (max-width: 768px) { - .minimize-btn { display: none !important; } - #modal-dock { display: none !important; } - } - /* Minimized modals are hidden but stay in the DOM with their state intact */ - .modal.minimized { display:none !important; } - /* Bottom dock for minimized modals */ - #modal-dock { - position:fixed; - bottom:var(--composer-clearance, 0px); - left:0; - right:0; - display:flex; - flex-wrap:wrap; - gap:4px; - padding:4px 8px; - z-index:240; - pointer-events:none; - justify-content:flex-start; - } - #modal-dock:empty { display:none; } - .modal-dock-item { - background:var(--panel); - border:1px solid var(--border); - border-bottom:none; - border-radius:6px 6px 0 0; - padding:4px 4px 4px 10px; - display:inline-flex; - align-items:center; - gap:6px; - cursor:pointer; - pointer-events:auto; - font-size:12px; - color:var(--fg); - max-width:220px; - box-shadow:0 -2px 8px rgba(0,0,0,0.25); - transition:background 0.15s; - } - .modal-dock-item:hover { background:var(--bg); } - .modal-dock-label { - white-space:nowrap; - overflow:hidden; - text-overflow:ellipsis; - max-width:180px; - } - .modal-dock-close { - background:transparent; - border:none; - color:var(--fg); - cursor:pointer; - font-size:14px; - padding:0 4px; - line-height:1; - opacity:0.6; - } - .modal-dock-close:hover { color:var(--red); opacity:1; } - .modal-body { flex:1; overflow-y:auto; } - .modal-body button { margin-top:6px; } - /* Styled confirm dialog — keeps backdrop */ - #styled-confirm-overlay { - background:rgba(0,0,0,0.5); - backdrop-filter:blur(4px); - pointer-events:auto !important; - z-index: 99999 !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - top: 0; left: 0; width: 100%; height: 100%; - } - #styled-confirm-overlay .modal-content { - position: relative; - z-index: 10001; - } - .email-attachment-warning-modal { - z-index: 99999 !important; - } - .email-attachment-warning-modal .modal-content { - position: relative; - z-index: 100001; - } - .styled-confirm-box { - width:360px; max-width:90vw; - max-height:none; /* override modal-content's 85vh */ - padding:14px 18px; - } - .styled-confirm-box .modal-header { margin-bottom:4px; } - .styled-confirm-box .modal-body p { - margin:8px 0 12px; color:var(--fg); font-size:0.92rem; line-height:1.45; - white-space:pre-line; - } - .styled-confirm-box .modal-footer { - display:flex; justify-content:flex-end; gap:8px; padding-top:6px; - border-top:1px solid var(--border); margin-top:4px; - } - @media (max-width:768px) { - .styled-confirm-box { - width: 85vw; - padding: 12px 16px; - font-size: 0.88rem; - border-radius: 12px; - } - .styled-confirm-box .modal-header h4 { font-size: 0.9rem; } - .styled-confirm-box .modal-body p { font-size: 0.85rem; margin: 6px 0 10px; } - .styled-confirm-box .modal-footer { gap: 10px; } - .styled-confirm-box .confirm-btn { - flex: 1; - /* More bottom than top padding nudges the label up ~2px so it isn't - sitting low in the taller mobile buttons. */ - padding: 8px 12px 12px; - font-size: 0.85rem; - border-radius: 8px; - text-align: center; - } - } - .confirm-btn { - /* Asymmetric padding nudges the label UP ~2px from where it was (more - bottom than top padding), so the confirm-dialog text isn't sitting low. */ - padding:3px 16px 5px; border-radius:4px; font-size:0.85rem; - cursor:pointer; border:1px solid var(--border); - font-family:inherit; - } - @media (max-width: 820px) { - /* Mobile: flip the asymmetry to shift the text 1 px UP from - centre instead (the bigger touch targets on mobile read better - with the label sitting slightly high). */ - .confirm-btn { padding:2px 16px 4px; } - } - .confirm-btn-secondary { background:var(--bg); color:var(--fg); } - .confirm-btn-secondary:hover { background:var(--border); } - #styled-confirm-cancel .styled-confirm-cancel-label { - position:relative; - top:2px; - } - .confirm-btn-primary { background:var(--accent-primary, var(--red, #4a9eff)); color:#fff; border-color:transparent; } - .confirm-btn-primary:hover { filter:brightness(1.15); } - .confirm-btn-danger { background:var(--color-danger); color:#fff; border-color:transparent; } - .confirm-btn-danger:hover { background:var(--color-error); } - .cookbook-gguf-delete-box { - width:560px; - max-width:92vw; - } - .cookbook-gguf-delete-list { - display:flex; - flex-direction:column; - gap:6px; - max-height:42vh; - overflow:auto; - padding:2px 2px 4px; - } - .cookbook-gguf-delete-row { - display:grid; - grid-template-columns:18px minmax(0,1fr); - gap:7px 8px; - align-items:start; - padding:7px 8px; - border:1px solid var(--border); - border-radius:7px; - background:color-mix(in srgb, var(--panel, var(--bg)) 92%, var(--fg) 8%); - cursor:pointer; - } - .cookbook-gguf-delete-row:hover { - border-color:color-mix(in srgb, var(--accent-primary, var(--fg)) 45%, var(--border)); - } - .cookbook-gguf-delete-cb { - -webkit-appearance:none; - appearance:none; - width:8px !important; - height:8px !important; - min-width:8px; - min-height:8px; - padding:0; - margin:4px 0 0; - border:1px solid var(--border); - border-radius:50%; - background:transparent; - box-sizing:content-box; - cursor:pointer; - transition:background 0.15s, border-color 0.15s, transform 0.12s; - } - .cookbook-gguf-delete-cb:hover { - border-color:var(--accent, var(--red)); - transform:scale(1.12); - } - .cookbook-gguf-delete-cb:checked { - background:var(--accent, var(--red)); - border-color:var(--accent, var(--red)); - } - .cookbook-gguf-delete-main, - .cookbook-gguf-delete-path { - min-width:0; - overflow:hidden; - text-overflow:ellipsis; - white-space:nowrap; - } - .cookbook-gguf-delete-main { - font-size:0.86rem; - color:var(--fg); - } - .cookbook-gguf-delete-path { - grid-column:2; - margin-top:-2px; - font-size:0.74rem; - opacity:0.58; - } - /* Styled prompt — text-input dialog (used in place of window.prompt) */ - #styled-prompt-overlay { - background:rgba(0,0,0,0.5); - backdrop-filter:blur(4px); - pointer-events:auto !important; - z-index: 99999 !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - } - #styled-prompt-overlay .modal-content { - position: relative; - z-index: 10001; - } - .styled-prompt-box { width: min(400px, 92vw); max-width: 100%; box-sizing: border-box; } - .styled-prompt-box .modal-body { padding-top: 4px; } - .styled-prompt-input { - width:100%; - box-sizing:border-box; - margin-top:8px; - padding:9px 12px; - border:1px solid var(--border); - border-radius:6px; - background:var(--bg); - color:var(--fg); - font:inherit; - font-size:0.95rem; - outline:none; - transition: border-color 0.15s, box-shadow 0.15s; - } - .styled-prompt-input:focus { - border-color: var(--accent-primary, var(--red, #4a9eff)); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-primary, var(--red, #4a9eff)) 25%, transparent); - } - @media (max-width:768px) { - .styled-prompt-box { width: 85vw; } - .styled-prompt-input { font-size: 0.9rem; padding: 10px 12px; } - } - /* Scroll navigation buttons */ - .scroll-nav-btn { - position:fixed; - background:var(--panel); - color: var(--accent); - border:none; - border-radius:10px; - width:38px; height:38px; - padding:0; - display:flex; align-items:center; justify-content:center; - font-size:14px; - line-height:1; - font-family:inherit; - cursor:pointer; - opacity:0; - pointer-events:none; - transition: opacity .2s, transform .3s cubic-bezier(0.25, 1, 0.5, 1), background .15s; - z-index:100; - transform: translateY(0); - } - .scroll-nav-btn::before { - content: ''; - position: absolute; - inset: 0; - border-radius: 10px; - padding: 1px; - background: linear-gradient(to bottom, var(--border), color-mix(in srgb, var(--border) 30%, transparent)); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - pointer-events: none; - } - .scroll-nav-btn.agent-working::after, - body.chat-agent-working #scroll-bottom-btn.show::after { - content: ''; - position: absolute; - inset: 0; - border-radius: 10px; - padding: 1px; - background: conic-gradient( - from 0deg, - transparent 0deg, - transparent 245deg, - color-mix(in srgb, var(--accent, var(--red)) 78%, transparent) 315deg, - transparent 360deg - ); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - opacity: 0.65; - pointer-events: none; - animation: scrollAgentWorkingRing 1.05s linear infinite; - } - @keyframes scrollAgentWorkingRing { - to { transform: rotate(360deg); } - } - #scroll-bottom-btn.show { opacity:1; pointer-events:auto; } - .scroll-nav-caret { - display: inline-block; - font-family: Arial, Helvetica, sans-serif; - font-size: 16px; - line-height: 1; - transform: translateY(-1px); - pointer-events: none; - } - @media (hover: hover) and (pointer: fine) { - #scroll-bottom-btn.show:hover { - border-color: color-mix(in srgb, var(--fg) 25%, transparent); - } - } - #scroll-bottom-btn.slide-out { - transform: translateY(20px); - opacity: 0 !important; - pointer-events: none; - } - /* Focus outline for accessibility */ - :focus-visible { - outline: 2px solid var(--red); - outline-offset: 2px; - } - /* Hamburger menu button */ - .hamburger { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - background: none; - border: 1px solid transparent; - border-radius: 6px; - padding: 0; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; - } - .hamburger:hover { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: var(--border); - } - .hamburger span { - display: block; - width: 16px; - height: 2px; - background: var(--fg); - border-radius: 1px; - transition: transform 0.2s, opacity 0.2s; - } - .hamburger span + span { margin-top: 3px; } - /* Agent indicator */ - #agent-indicator { - position: fixed; - top: 20px; - right: 20px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - display: none; - z-index: 100; - cursor: pointer; - transition: all 0.2s ease; - } - #agent-indicator.active { - display: block; - border-color: var(--color-agent-active); - box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); - } - #agent-indicator:hover { - border-color: var(--color-agent-active); - background: var(--panel); - } - #research-toggle-btn:disabled { - opacity: 0.6; - cursor: not-allowed; - } - -/* ── Drag & Drop ── */ - -/* ---------- Color palette (dark / light) ---------- */ - - /* Drag and drop styling */ - .section[dnd-active="true"] { - background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: var(--red) !important; - } - - .section[dnd-over="true"] { - background: color-mix(in srgb, var(--red) 20%, transparent) !important; - border-color: var(--red) !important; - transform: scale(1.02); - } - - .drag-handle { - cursor: grab; - opacity: 0.5; - padding: 0 6px; - user-select: none; - } - - .drag-handle:hover { - opacity: 0.8; - } - - .drag-handle:active { - cursor: grabbing; - } - -/* ── UI Controls (Radio, Presets, Toolbar, Settings) ── */ - - - /* Custom radio button styling */ - .radio-option { - display: flex; - align-items: center; - gap: 10px; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - cursor: pointer; - transition: all 0.2s ease; - } - - .radio-option:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: var(--fg); - } - - .radio-option input[type="radio"] { - appearance: none; - -webkit-appearance: none; - width: 18px; - height: 18px; - border: 2px solid var(--border); - border-radius: 50%; - outline: none; - margin: 0; - background: var(--bg); - transition: all 0.2s ease; - position: relative; - flex-shrink: 0; - } - - .radio-option input[type="radio"]:checked { - border-color: var(--red); - background: var(--red); - } - - .radio-option input[type="radio"]:focus { - box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 30%, transparent); - } - - .radio-label { - color: var(--fg); - font-size: 14px; - user-select: none; - } - - /* Preset buttons */ - .preset-btn { - height: 27.2px; /* 15% smaller than 32px */ - padding: 0 8.5px; /* 15% smaller than 10px */ - margin-left: 4px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font-family: 'Fira Code', monospace; - font-size: 10.2px; /* 15% smaller than 12px */ - cursor: pointer; - transition: all 0.2s ease; - } - - .preset-btn:hover { - background: var(--panel); - border-color: var(--fg); - } - - .preset-btn.active { - background: var(--panel); - border-color: var(--fg); - box-shadow: 0 0 0 1px var(--fg), 0 0 8px color-mix(in srgb, var(--fg) 16%, transparent); - font-weight: 600; - } - - /* All preset buttons use the same blue color */ - .preset-btn { - border-color: var(--red); /* Blue color */ - } - - .preset-btn.active { - border-color: var(--red); /* Blue color when active */ - box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); - } - - /* Custom preset modal: the base .preset-modal-content sets overflow:hidden - (for desktop rounded-corner clipping), which on the mobile sheet clipped - the footer (Start/Cancel) off the bottom with no way to reach it. Let the - whole sheet scroll — same as every other mobile modal (.modal-content is - overflow-y:auto on mobile) — so the footer is always reachable. No flex - changes, so the body can't collapse. */ - #custom-preset-modal .preset-modal-content { - overflow-y: auto !important; - } - #custom-preset-modal .modal-body label { - font-size: 13px; - font-weight: 500; - color: var(--fg); - margin-top: 8px; - margin-bottom: 4px; - display: block; - } - - #custom-preset-modal .modal-body input, - #custom-preset-modal .modal-body textarea { - width: 100%; - margin-bottom: 8px; - box-sizing: border-box; - } - #custom-preset-modal .modal-body textarea, - #custom-preset-modal .modal-body input[type="text"] { - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - padding: 8px 10px; - font-size: 13px; - font-family: inherit; - transition: border-color 0.15s; - } - #custom-preset-modal .modal-body textarea { - resize: vertical; - } - #custom-preset-modal .modal-body textarea:focus, - #custom-preset-modal .modal-body input[type="text"]:focus { - outline: none; - border-color: var(--red); - } - - #custom-preset-modal .modal-footer { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 12px; - padding-top: 10px; - border-top: 1px solid var(--border); - } - - #custom-preset-modal .modal-footer button { - padding: 7px 14px; - border-radius: 6px; - font-size: 12px; - font-weight: 500; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: all 0.15s; - } - #custom-preset-modal .modal-footer button:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - } - - #custom-preset-modal .modal-footer button#save-custom-preset { - /* The theme's accent is stored in --red (theme.js sets --red = accentHex) - and --accent is undefined, so the canonical accent expression is - var(--accent, var(--red)). The old bare var(--accent) resolved to nothing - which, with color:var(--bg), made the button invisible. */ - background: var(--accent, var(--red)); - color: var(--bg); - border-color: var(--accent, var(--red)); - } - #custom-preset-modal .modal-footer button#save-custom-preset:hover { - opacity: 0.85; - } - /* Toolbar visibility tab */ - .toolbar-hint { - font-size: 12px; - color: color-mix(in srgb, var(--fg) 55%, transparent); - margin-bottom: 12px; - } - /* ── Appearance visibility toggles ── */ - .vis-toggles { - display: flex; - flex-direction: column; - } - .vis-row { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - padding: 5px 6px; - border-radius: 4px; - transition: background 0.12s; - } - .vis-row:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); - } - .vis-row input[type="checkbox"] { - display: none; - } - .vis-icon { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - color: color-mix(in srgb, var(--fg) 40%, transparent); - } - .vis-icon-text { - font-size: 10px; - font-weight: 700; - font-family: inherit; - letter-spacing: -0.5px; - } - .vis-label { - flex: 1; - font-size: 12px; - color: var(--fg); - user-select: none; - } - .vis-switch { - position: relative; - width: 30px; - height: 16px; - background: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 8px; - transition: background 0.2s; - flex-shrink: 0; - } - .vis-switch::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 12px; - height: 12px; - background: var(--panel); - border-radius: 50%; - transition: transform 0.2s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); - } - .vis-row input:checked + .vis-switch { - background: var(--red); - } - .vis-row input:checked + .vis-switch::after { - transform: translateX(14px); - } - .vis-row input:checked ~ .vis-icon, - .vis-row:has(input:checked) .vis-icon { - color: var(--fg); - } - .persona-display-name-row .vis-label { - position: relative; - top: -4px; - } - .persona-display-name-row .vis-switch { - position: relative; - left: 4px; - } - .md-attach-paperclip-icon { - position: relative; - top: 1px; - } - /* Compare model selector — match the calendar modal's clean header - (no border underline). */ - #compare-model-overlay .modal-header h4 { - pointer-events: none; - } - /* Compare model selector: keep manually-resized/tiny windows contained. - Picker dropdowns are appended to document.body, so the card itself can - clip and scroll without cropping the dropdown list. */ - #compare-model-overlay .modal-content { - display: flex; - flex-direction: column; - max-height: min(720px, calc(100dvh - 48px)); - overflow: hidden; - min-height: 180px; - } - #compare-model-overlay .modal-body { - overflow: auto; - flex: 1 1 auto; - min-height: 0; - } - .vis-hint { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 30%, transparent); - font-weight: 400; - margin-left: 2px; - } - /* Settings toggle — admin-only lock indicator */ - .ui-vis-lock { - display: none; - } - /* (legacy toolbar-toggle styles removed — now using .vis-* classes) */ - - /* Demo highlight pulse */ - .odysseus-highlight { - outline: 2px solid var(--accent, var(--red)) !important; - outline-offset: 1px; - box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - animation: ody-pulse 1.5s ease-in-out infinite; - z-index: 100; - position: relative; - } - @keyframes ody-pulse { - 0%, 100% { outline-color: var(--accent, var(--red)); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); } - 50% { outline-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); } - } - /* Floating breathing halo. Rendered as a body-level div positioned over - the target, so we don't fight the target's own outline / box-shadow / - overflow chain. JS keeps it in sync with the target's bounding rect. */ - .tour-halo { - position: fixed; - pointer-events: none; - border: 3px solid var(--accent, var(--red)); - border-radius: 10px; - z-index: 10000; - animation: ody-breathe 1.4s ease-in-out infinite; - opacity: 0; - transform: scale(0.94); - transition: opacity 0.35s ease-out, transform 0.35s ease-out; - } - .tour-halo.tour-fade-in { - opacity: 1; - transform: scale(1); - } - @keyframes ody-breathe { - 0%, 100% { - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent), - 0 0 22px 4px color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - border-color: var(--accent, var(--red)); - } - 50% { - box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 30%, transparent), - 0 0 40px 14px color-mix(in srgb, var(--accent, var(--red)) 70%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 80%, transparent); - } - } - /* While the tour is active, lift overflow:hidden on common clipping - ancestors so the halo around a highlighted child isn't cropped. */ - body.tour-active .sidebar, - body.tour-active .sidebar-inner, - body.tour-active .chat-input-bar, - body.tour-active .chat-input-top, - body.tour-active .chat-input-wrap, - body.tour-active .chat-input-right, - body.tour-active .mode-toggle, - body.tour-active .model-picker-wrap { - overflow: visible !important; - } - - /* ── Secret tour hint (drag-to-snap on first modal open) ── */ - .tour-hint { - position: fixed; - z-index: 10002; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 12px 14px 10px; - width: 240px; - font-size: 0.78rem; - line-height: 1.5; - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.32); - opacity: 0; - transform: translateY(-4px); - transition: opacity 0.28s ease-out, transform 0.28s ease-out; - pointer-events: auto; - } - .tour-hint.tour-hint-in { opacity: 1; transform: translateY(0); } - .tour-hint.tour-hint-out { opacity: 0; transform: translateY(-4px); } - .tour-hint-visual { - display: flex; - justify-content: center; - margin-bottom: 8px; - color: var(--accent, var(--red)); - } - .tour-hint-visual svg { display: block; } - .tour-hint-text { margin-bottom: 10px; opacity: 0.92; } - .tour-hint-text b { color: var(--accent, var(--red)); font-weight: 600; } - .tour-hint-dismiss { - display: block; - margin: 0 0 0 auto; - background: none; - border: 1px solid var(--border); - color: var(--fg); - border-radius: 6px; - padding: 3px 12px; - cursor: pointer; - font-family: inherit; - font-size: 0.72rem; - opacity: 0.85; - transition: opacity 0.15s, background 0.15s; - } - .tour-hint-dismiss:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, transparent); } - - /* SVG dance: cursor approaches title bar, drags right, modal snaps to a - right-half zone, holds, returns. 3.2s loop. */ - .th-cursor { animation: th-cursor 3.2s ease-in-out infinite; transform-origin: 0 0; } - .th-modal-group { animation: th-modal-slide 3.2s ease-in-out infinite; transform-origin: 39px 31px; } - .th-zone { animation: th-zone-flash 3.2s ease-in-out infinite; } - @keyframes th-cursor { - 0%, 5% { transform: translate(35px, 32px); } - 18% { transform: translate(35px, 22px); } - 48% { transform: translate(80px, 22px); } - 72% { transform: translate(80px, 22px); } - 90%, 100% { transform: translate(35px, 32px); } - } - @keyframes th-modal-slide { - 0%, 18% { transform: translate(0, 0) scale(1, 1); } - 48% { transform: translate(45px, 0) scale(1, 1); } - 58% { transform: translate(30px, -19px) scale(1.4, 2.4); } - 72% { transform: translate(30px, -19px) scale(1.4, 2.4); } - 90%, 100% { transform: translate(0, 0) scale(1, 1); } - } - @keyframes th-zone-flash { - 0%, 38% { opacity: 0; } - 48% { opacity: 0.22; } - 58%, 72% { opacity: 0; } - 100% { opacity: 0; } - } - @media (prefers-reduced-motion: reduce) { - .th-cursor, .th-modal-group, .th-zone { animation: none !important; } - } - .odysseus-hl-label { - position: absolute; - top: -22px; - left: 8px; - background: var(--red); - color: var(--bg); - font-size: 0.7rem; - padding: 2px 8px; - border-radius: 4px; - white-space: nowrap; - z-index: 101; - pointer-events: none; - } - - /* Generated images inside chat bubbles */ - .msg.generated-image-wrap .body { text-align: center; } - .generated-image { - max-width: 100%; - max-height: 512px; - border-radius: 8px; - cursor: pointer; - transition: transform 0.2s; - display: inline-block; - margin: 0 auto; - } - .generated-image:hover { transform: scale(1.02); } - .generated-image-caption { - font-size: 0.8rem; - opacity: 0.5; - margin-top: 6px; - font-style: italic; - text-align: center; - } - - /* Setup wizard */ - .setup-wizard { padding: 16px; max-width: 500px; } - .setup-wizard .setup-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 12px; color: var(--fg); } - .setup-wizard .setup-label { font-size: 0.85rem; color: var(--fg); opacity: 0.7; margin-bottom: 8px; } - .setup-wizard .setup-presets { display: flex; flex-wrap: wrap; gap: 8px; } - .setup-wizard .setup-preset-btn { - padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; - font-size: 0.85rem; transition: all 0.2s; - } - .setup-wizard .setup-preset-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .setup-wizard .setup-input { - display: block; width: 100%; padding: 8px 12px; margin-bottom: 8px; - background: var(--panel); border: 1px solid var(--border); - border-radius: 6px; color: var(--fg); font-size: 0.85rem; box-sizing: border-box; - } - .setup-wizard .setup-input:focus { border-color: var(--red); outline: none; } - .setup-wizard .setup-connect-btn { - padding: 8px 20px; background: var(--red); color: var(--bg); - border: none; border-radius: 6px; cursor: pointer; font-weight: 600; margin-top: 4px; - } - .setup-wizard .setup-connect-btn:hover { opacity: 0.9; } - .setup-wizard .setup-status { font-size: 0.8rem; margin-top: 8px; color: var(--fg); opacity: 0.7; } - .setup-wizard .setup-model-list { display: flex; flex-wrap: wrap; gap: 8px; } - .setup-wizard .setup-model-btn { - padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; - font-size: 0.85rem; transition: all 0.2s; - } - .setup-wizard .setup-model-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .setup-wizard .setup-step.hidden { display: none; } - - /* Dropdown menu styles */ - .dropdown { - position: absolute; - top: calc(100% + 6px); - right: 0; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 10px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); - z-index: 1000; - display: none; - min-width: 220px; - padding: 6px; - backdrop-filter: blur(12px); - } - - .dropdown.show { - display: block; - animation: dropdown-in 0.15s ease-out; - } - @keyframes dropdown-in { - from { opacity:0; transform:translateY(-6px) scale(0.97); } - to { opacity:1; transform:translateY(0) scale(1); } - } - - .dropdown-item { - padding: 8px 10px; - border-radius: 6px; - cursor: pointer; - transition: background 0.12s ease; - display: flex; - align-items: center; - gap: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - } - - .dropdown-item:last-child { - border-bottom: none; - } - - .dropdown-item .menu-icon { - width: 28px; - height: 28px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - flex-shrink: 0; - } - - .dropdown-item .menu-text h4 { - margin: 0; - font-size: 12.5px; - font-weight: 500; - color: var(--fg); - line-height: 1.3; - } - - .dropdown-item .menu-text p { - margin: 0; - font-size: 10.5px; - color: var(--color-subheader); - line-height: 1.3; - } - - .dropdown-item:hover { - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .dropdown-item:hover .menu-icon { - background: color-mix(in srgb, var(--red) 15%, transparent); - } - - /* Compact dropdown items (session/model context menus) */ - .dropdown-item-compact { - cursor: pointer; - padding: 6px 8px; - font-size: 11px; - border-radius: 8px; - display: flex; - align-items: center; - gap: 10px; - color: var(--fg); - transition: background 0.1s; - } - .dropdown-item-compact:hover { - background: color-mix(in srgb, var(--accent) 10%, transparent); - } - .dropdown-item-compact .dropdown-icon { - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - opacity: 0.5; - } - .dropdown-item-compact .dropdown-icon svg { - width: 14px; - height: 14px; - } - .dropdown-item-compact .dropdown-shortcut { - margin-left: auto; - font-size: 9.5px; - opacity: 0.35; - font-weight: 400; - } - /* Keyboard shortcut hints (⌘+Alt+D etc.) are meaningless on touch — - hide them in the per-chat actions menu on mobile. */ - @media (max-width: 768px) { - .session-dropdown .dropdown-shortcut { display: none; } - } - .dropdown-item-danger { - color: var(--red) !important; - } - .dropdown-item-danger .dropdown-icon { - opacity: 0.7; - } - - /* Inline rename input for sessions */ - .session-rename-input { - width: 100%; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--accent, var(--accent-primary)); - border-radius: 4px; - padding: 2px 6px; - font-family: inherit; - font-size: 12px; - line-height: 1.3; - outline: none; - } - - /* Session dropdown container */ - .session-dropdown-menu { - position: fixed; - z-index: 1000; - display: none; - min-width: auto; - width: max-content; - padding: 4px; - animation: dropdown-in 0.15s ease-out; - } - - /* Folder move submenu */ - .session-folder-submenu { - position: fixed; - z-index: 1001; - display: none; - min-width: auto; - width: max-content; - padding: 4px; - } - - .dropdown-divider { - height: 1px; - background: var(--border); - margin: 4px 8px; - } - - /* Search toggle styles */ - .search-toggle { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 0; - border-bottom: 1px solid var(--border); - margin-bottom: 8px; - } - - .search-provider-label { - font-size: 14px; - color: var(--fg); - } - -/* ── Voice, Search, Themes, Comparison, Censor, Print ── */ - - /* Voice recording styles */ - #mic-btn { - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - transition: all 0.2s ease; - } - - #mic-btn:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: var(--fg); - } - - #mic-btn.recording { - background: var(--color-recording); - border-color: var(--color-recording); - animation: pulse 1.5s infinite; - } - - @keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } - } - - #recording-indicator { - position: fixed; - top: 10px; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin: 10px; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - } - - #recording-indicator.hidden { - display: none !important; - } - - .recording-content { - display: flex; - align-items: center; - gap: 12px; - color: white; - } - - .recording-icon { - color: var(--color-recording); - font-size: 20px; - animation: pulse 1.5s infinite; - } - - .recording-text { - font-size: 16px; - font-weight: 500; - } - - #stop-recording { - background: var(--color-recording); - color: white; - border: none; - border-radius: 6px; - padding: 6px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s ease; - } - - #stop-recording:hover { - background: var(--color-recording-hover); - } - - /* Error state for recording */ - #recording-indicator.error { - background: rgba(173, 26, 26, 0.9); - } - - .recording-error { - color: var(--color-recording); - font-size: 14px; - margin-top: 4px; - } - - @media (max-width: 768px) { - #recording-indicator { - margin: 8px; - padding: 10px; - } - - .recording-text { - font-size: 14px; - } - - #stop-recording { - padding: 6px 10px; - font-size: 12px; - } - } -/* SYNTAX HIGHLIGHTING — uses theme vars from style.css, no hardcoded overrides */ -.hljs { color: var(--hl-fg, #9cdef2); background: none !important; } -pre { background: var(--code-bg, var(--hl-bg, #282c34)) !important; } - - /* ---------- Search overlay (Ctrl+K command palette) ---------- */ - .search-overlay { - position: fixed; - top: 0; left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: flex-start; - justify-content: center; - padding-top: var(--search-overlay-top, 15vh); - z-index: 300; - backdrop-filter: blur(6px); - } - .search-overlay.hidden { display: none; } - .search-popup { - width: 520px; - max-width: 90vw; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 12px; - box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5), 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); - overflow: hidden; - display: flex; - flex-direction: column; - max-height: 60vh; - } - .search-popup input#search-input { - width: 100%; - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - outline: none; - color: var(--fg); - font-size: 16px; - font-family: 'Fira Code', monospace; - padding: 14px 16px; - box-sizing: border-box; - } - .search-popup input#search-input::placeholder { - color: color-mix(in srgb, var(--fg) 30%, transparent); - } - .search-results { - overflow-y: auto; - flex: 1; - padding: 4px; - } - .search-results:empty { - display: none; - } - .search-group-header { - font-size: 10px; - font-weight: 600; - color: var(--color-subheader); - text-transform: uppercase; - letter-spacing: 0.5px; - padding: 10px 12px 4px; - } - .search-result-item { - display: flex; - align-items: baseline; - gap: 8px; - padding: 8px 12px; - border-radius: 6px; - cursor: pointer; - transition: background 0.1s; - } - .search-result-item:hover, - .search-result-item.selected { - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .search-result-role { - font-size: 10px; - font-weight: 600; - color: var(--color-subheader); - flex-shrink: 0; - min-width: 24px; - } - .search-result-snippet { - flex: 1; - font-size: 12px; - color: var(--fg); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .search-result-time { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 35%, transparent); - flex-shrink: 0; - white-space: nowrap; - } - mark.search-highlight { - /* Was orange-on-orange (0.35 alpha bg + orange text) — too low-contrast - to read. Solid accent fill with bg-colored text reads clearly. */ - background: var(--accent, #e8a830); - color: var(--bg, #1a1a1a); - border-radius: 2px; - padding: 0 2px; - font-weight: 600; - } - .search-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 24px; - font-size: 13px; - } - - /* ---------- Theme popup (uses .modal > .modal-content frame) ---------- */ - #theme-modal { z-index: 260; } - #theme-popup .theme-popup-sub { color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 11px; margin-bottom: 10px; } - /* `max-height` instead of a fixed height so the popup shrinks to fit its - content (avoids the leftover whitespace after the time-based switching - card was removed). The inner .theme-tab-panel keeps overflow:auto, so - any taller content still scrolls inside. */ - #theme-popup { overflow-y: hidden; max-height: min(85vh, 600px); } - #theme-popup .modal-header { flex-shrink: 0; } - #theme-popup .admin-tabs { margin: 0 -10px 8px; padding: 0 10px; flex-shrink: 0; } - .theme-tab-panel { overflow-y: auto; min-height: 0; flex: 1; padding-bottom: 10px; } - - .theme-grid { - display: grid; grid-template-columns: repeat(auto-fill, minmax(66px, 1fr)); - gap: 6px; margin-bottom: 12px; - } - .theme-swatch { - border: 2px solid var(--border); border-radius: 8px; cursor: pointer; - padding: 5px; text-align: center; font-size: 0.65rem; color: var(--fg); - transition: border-color 0.15s, transform 0.15s; - } - .theme-swatch:hover { transform: scale(1.06); } - .theme-swatch.active { border-color: var(--red); box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 33%, transparent); } - .theme-swatch-colors { - display: flex; justify-content: center; margin-bottom: 3px; - } - .theme-swatch-colors span { - width: 15px; height: 15px; border-radius: 50%; - margin-left: -5px; - border: 1.5px solid color-mix(in srgb, var(--fg) 12%, transparent); - } - .theme-swatch-colors span:first-child { margin-left: 0; } - .theme-custom-label { font-size: 11px; font-weight: 600; color: color-mix(in srgb, var(--fg) 50%, transparent); text-transform: uppercase; letter-spacing: 0.06em; margin: 10px 0 6px; } - .theme-custom { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; } - .color-row { display: flex; align-items: center; gap: 4px; position: relative; } - .color-row label { font-size: 13px; font-weight: 500; color: var(--fg); opacity: 0.7; flex: 1; } - .color-row input[type="color"], - .color-row input.cp-swatch-input { - width: 24px; height: 24px; border: 1px solid var(--border); border-radius: 50%; - background: none; cursor: pointer; padding: 0; flex-shrink: 0; - overflow: hidden; - -webkit-appearance: none; - appearance: none; - } - .color-row input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } - .color-row input[type="color"]::-webkit-color-swatch { border: none; border-radius: 50%; } - .color-row input[type="color"]::-moz-color-swatch { border: none; border-radius: 50%; } - #theme-bg-effect-color { position: relative; top: 2px; } - .color-row input.cp-swatch-input { - color: transparent; - text-shadow: none; - caret-color: transparent; - font-size: 0; - user-select: none; - } - .color-row input.cp-swatch-input::selection { background: transparent; } - .color-row input.cp-swatch-input:focus { outline: 1px solid var(--red); outline-offset: 1px; } - .color-reset-btn { - width: 24px; height: 24px; border: none; background: none; cursor: pointer; - color: var(--fg); opacity: 0; font-size: 1.15rem; padding: 0; line-height: 1; - transition: opacity 0.15s, color 0.15s; flex-shrink: 0; pointer-events: none; - position: absolute; right: 6px; top: 50%; transform: translateY(-50%); - } - .color-row input[type="color"], - .color-row input.cp-swatch-input { margin-left: auto; } - /* Keep the harmony hex readout on the left, with the swatch dot anchored - to the right like the other color controls. */ - #theme-harmony-card #harmony-accent-hex { - order: -1; - margin-right: auto; - } - .color-row:has(.color-reset-btn.changed) input[type="color"], - .color-row:has(.color-reset-btn.changed) input.cp-swatch-input { margin-right: 28px; } - .color-reset-btn.changed { opacity: 0.4; pointer-events: auto; } - .color-reset-btn.changed:hover { opacity: 1; color: var(--red); } - .theme-custom-divider { - grid-column: 1 / -1; font-size: 11px; color: var(--fg); opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.04em; margin: 6px 0 2px; - border-top: 1px solid var(--border); padding-top: 6px; - } - .theme-swatch[data-custom] { position: relative; overflow: visible; } - /* Accent-coloured circular X — always visible (mobile-friendly), sits - INSIDE the swatch's top-right corner so it never crops into a - neighbouring swatch. */ - .theme-delete-btn { - position: absolute; - top: -2px; - right: -2px; - width: 20px; - height: 20px; - padding: 0; - border: none; - border-radius: 50%; - background: var(--accent, var(--red, #d92534)); - color: #fff; - cursor: pointer; - z-index: 2; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); - transition: transform 0.12s, background 0.12s; - } - .theme-delete-btn:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 80%, white); - transform: scale(1.12); - } - .theme-delete-btn:active { transform: scale(0.95); } - .theme-delete-btn svg { - display: block; - /* Optical nudge — the X reads off-center inside the circle without it. */ - position: relative; - left: 1px; - top: -1px; - } - .theme-save-row { - display: flex; gap: 6px; margin-top: 8px; - } - .theme-save-row input { - flex: 1; padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 12px; font-family: inherit; - } - .theme-save-row input:focus { outline: none; border-color: var(--red); } - .theme-save-row input::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } - .theme-save-row button { - padding: 6px 12px; border: 1px solid var(--red); border-radius: 6px; - background: transparent; color: var(--red); cursor: pointer; - font-size: 12px; font-family: inherit; white-space: nowrap; transition: all 0.15s; - } - .theme-save-row button:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } - .theme-save-error { - font-size: 11px; color: var(--red); margin-top: 2px; display: none; - } - /* Import/Export */ - .theme-io-row { display: flex; gap: 6px; margin-top: 6px; } - .theme-io-btn { - flex: 1; padding: 5px 10px; border: 1px solid var(--border); border-radius: 6px; - background: transparent; color: var(--fg); cursor: pointer; - font-size: 12px; opacity: 0.7; transition: all 0.15s; font-family: inherit; - } - .theme-io-btn:hover { opacity: 1; border-color: var(--fg); background: color-mix(in srgb, var(--fg) 5%, transparent); } - - #memory-import-btn svg, - #memory-export-btn svg { - color: var(--accent, var(--red)); - } - .theme-import-area { - width: 100%; margin-top: 6px; padding: 6px 8px; - border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 0.7rem; - font-family: inherit; resize: vertical; min-height: 48px; - } - .theme-import-area:focus { outline: none; border-color: var(--red); } - .theme-import-area.hidden { display: none; } - .theme-import-actions { display: flex; gap: 6px; margin-top: 4px; } - .theme-import-actions.hidden { display: none; } - /* Font & Density */ - .theme-fd-row { display: flex; gap: 8px; margin-bottom: 8px; } - .theme-fd-group { flex: 1; display: flex; flex-direction: column; gap: 3px; } - .theme-fd-label { font-size: 12px; font-weight: 500; color: var(--fg); opacity: 0.6; } - .theme-fd-select { - padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 12px; - font-family: inherit; cursor: pointer; - } - .theme-fd-select:focus { outline: none; border-color: var(--red); } - @media (max-width: 768px) { - /* The switch is too cramped beside the Font and Density selects on - narrow screens; give Frosted a dedicated row instead. */ - .theme-fd-row:first-of-type { - flex-wrap: wrap; - } - .theme-fd-row:first-of-type #theme-frosted-group { - flex: 1 1 100%; - flex-direction: row; - align-items: center; - justify-content: space-between; - min-width: 0; - } - } - .theme-fd-range { - width: 100%; - max-width: 100%; - box-sizing: border-box; - margin: 0; - padding: 0; - height: 24px; - background: transparent; - cursor: pointer; - -webkit-appearance: none; - appearance: none; - accent-color: var(--red); - } - .theme-fd-range::-webkit-slider-runnable-track { - height: 4px; background: var(--border); border-radius: 2px; - } - .theme-fd-range::-moz-range-track { - height: 4px; background: var(--border); border-radius: 2px; - } - .theme-fd-range::-webkit-slider-thumb { - -webkit-appearance: none; appearance: none; - width: 14px; height: 14px; border-radius: 50%; - background: var(--red); border: none; margin-top: -5px; cursor: pointer; - } - .theme-fd-range::-moz-range-thumb { - width: 14px; height: 14px; border-radius: 50%; - background: var(--red); border: none; cursor: pointer; - } - .theme-fd-range:focus { outline: none; } - /* Color Harmony Generator */ - .theme-harmony-row { display: flex; gap: 8px; align-items: flex-end; } - .harmony-generate-btn { - padding: 5px 14px; border: 1px solid var(--red); border-radius: 6px; - background: transparent; color: var(--red); cursor: pointer; - font-size: 12px; white-space: nowrap; transition: all 0.15s; - font-family: inherit; width: 100%; - } - .harmony-generate-btn:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } - .harmony-preview { - display: flex; height: 20px; border-radius: 6px; overflow: hidden; - margin-top: 8px; border: 1px solid var(--border); - } - .harmony-preview:empty { display: none; border: none; } - .harmony-preview span { flex: 1; } - #theme-reset-btn { - margin-top: 6px; width: 100%; padding: 6px; border: 1px solid var(--border); - border-radius: 6px; background: var(--bg); color: var(--fg); cursor: pointer; - font-size: 12px; font-family: inherit; opacity: 0.7; transition: opacity 0.15s; - } - #theme-reset-btn:hover { opacity: 1; } - .theme-adv-toggle { - margin-top: 10px; margin-bottom: 10px; - padding: 6px 8px; cursor: pointer; - font-size: 11px; color: var(--red); opacity: 0.8; - border: 1px solid var(--border); border-radius: 6px; - transition: opacity 0.15s, background 0.15s; - user-select: none; - } - .theme-adv-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 4%, transparent); } - .theme-adv-toggle .theme-adv-arrow { - display: inline-block; transition: transform 0.2s; font-size: 10px; - margin-right: 4px; - } - .theme-adv-toggle.open .theme-adv-arrow { transform: rotate(90deg); } - .theme-adv-section { margin-top: 8px; margin-bottom: 10px; } - .theme-adv-section.hidden { display: none; } - .theme-adv-group { margin-bottom: 8px; } - .theme-adv-group-label { - font-size: 10px; color: var(--fg); opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; - } - .theme-adv-clear-btn { - margin-top: 6px; width: 100%; padding: 5px; border: 1px solid var(--border); - border-radius: 6px; background: transparent; color: var(--fg); opacity: 0.5; - cursor: pointer; font-size: 12px; font-family: inherit; transition: opacity 0.15s; - } - .theme-adv-clear-btn:hover { opacity: 1; } - - /* Customization is an editor, so keep its sections visually related while - preserving the existing controls and their order. */ - #theme-tab-customize { - display: flex; - flex-direction: column; - gap: 10px; - } - #theme-tab-customize > .admin-card, - #theme-tab-customize > .theme-adv-toggle, - #theme-tab-customize > .theme-adv-section { - margin-top: 0; - margin-bottom: 0; - } - #theme-tab-customize > .admin-card { - padding: 12px; - } - #theme-tab-customize > .admin-card > h2 { - display: flex; - align-items: center; - min-height: 20px; - margin: 0 0 10px; - padding-bottom: 7px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 65%, transparent); - font-size: 13px; - } - #theme-tab-customize .theme-custom { - gap: 6px 14px; - } - #theme-tab-customize .color-row { - min-height: 30px; - padding: 3px 6px; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--border) 55%, transparent); - background: color-mix(in srgb, var(--fg) 2%, transparent); - } - #theme-tab-customize .color-row label { - font-size: 12px; - } - #theme-tab-customize .theme-adv-toggle { - flex-shrink: 0; - margin: 0; - background: color-mix(in srgb, var(--fg) 2%, transparent); - } - #theme-tab-customize .theme-adv-section { - padding: 10px 12px 2px; - border: 1px solid color-mix(in srgb, var(--border) 75%, transparent); - border-radius: 7px; - background: color-mix(in srgb, var(--panel) 55%, transparent); - } - #theme-tab-customize .theme-adv-group { - margin: 0; - padding: 0 0 9px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 48%, transparent); - } - #theme-tab-customize .theme-adv-group + .theme-adv-group { - padding-top: 9px; - } - #theme-tab-customize .theme-adv-group:last-of-type { - border-bottom: 0; - } - #theme-tab-customize .theme-adv-group-label { - margin-bottom: 6px; - color: var(--red); - opacity: 0.8; - font-weight: 600; - } - #theme-tab-customize .theme-fd-row, - #theme-tab-customize .theme-harmony-row { - gap: 12px; - margin-bottom: 10px; - } - #theme-tab-customize .theme-fd-row:last-child, - #theme-tab-customize .theme-harmony-row:last-child { - margin-bottom: 0; - } - #theme-tab-customize .theme-fd-group { - min-width: 0; - } - #theme-tab-customize .theme-fd-select { - min-height: 30px; - } - #theme-tab-customize #theme-harmony-card .theme-fd-label { - font-size: 11px; - } - #theme-tab-customize .theme-save-row, - #theme-tab-customize .theme-io-row { - gap: 8px; - } - #theme-tab-customize #theme-reset-btn { - margin-top: 8px; - } - @media (max-width: 560px) { - #theme-tab-customize .theme-custom { - grid-template-columns: 1fr; - } - #theme-tab-customize .theme-fd-row, - #theme-tab-customize .theme-harmony-row { - flex-direction: column; - align-items: stretch; - } - #theme-tab-customize .theme-fd-group, - #theme-tab-customize .theme-harmony-row .theme-fd-group { - width: 100%; - } - } - - - /* Mobile: bottom sheet modals — slide up from bottom */ - @media (max-width: 768px) { - .modal { - align-items: flex-end; - background: rgba(0,0,0,0.4); - pointer-events: auto; - /* Anchor the bottom sheet to the DYNAMIC viewport. The base overlay is - position:fixed; height:100% = the layout viewport, whose bottom sits - UNDER Firefox Android's bottom URL bar — so flex-end parked the sheet - (and its footer / Start button) beneath the bar. dvh excludes the bar - so the footer lands above it. Extra safe-area pad for iOS home bar. */ - height: 100dvh; - padding-bottom: env(safe-area-inset-bottom, 0px); - } - /* Confirm dialog stays centered, not a bottom sheet */ - #styled-confirm-overlay { - align-items: center; - } - .styled-confirm-box { - border-radius: 12px !important; - border: 1px solid var(--border) !important; - animation: modal-enter 0.25s ease-out both !important; - max-height: none !important; - } - .styled-confirm-box.modal-closing { - animation: modal-exit 0.18s ease-in both !important; - } - .styled-confirm-box::before { - display: none !important; - } - .styled-confirm-box .close-btn { - display: none !important; - } - #theme-popup { - width: 100% !important; - height: 65vh !important; - max-height: 65vh !important; - top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important; - position: fixed !important; - border-radius: 14px 14px 0 0; - border: none; - border-top: 1px solid var(--border); - padding: 6px 12px 12px; - animation: sheet-enter 0.2s ease-out forwards; - overflow-y: hidden !important; - } - #theme-popup .theme-tab-panel { - touch-action: pan-y; - overscroll-behavior: contain; - -webkit-overflow-scrolling: touch; - } - #theme-popup.sheet-ready { - animation: none; - } - #theme-popup.modal-closing { - animation: sheet-exit 0.15s ease-in both !important; - } - .modal-content, - .memory-modal-content, - .settings-modal-content, - #compare-model-overlay .modal-content { - width: 100% !important; - /* 85dvh leaves comfortable headroom above the sheet so the user can - still see the chat behind it and the drag handle has breathing - room. Was 65vh (too short, content clipped) → 90vh (too tall, top - hugged the status bar). */ - max-height: 85dvh !important; - max-height: 85vh !important; /* fallback for browsers without dvh */ - height: auto !important; - border-radius: 14px 14px 0 0; - border: none; - border-top: 1px solid var(--border); - padding-top: 6px !important; - /* Clip children to the rounded top corners — otherwise the sticky - modal-header's var(--panel) background paints a darker - rectangle past the radius and the corners look square again. */ - overflow: hidden; - animation: sheet-enter 0.2s ease-out forwards; - } - /* Keep the tall tool drawers on a shared mobile height ceiling. The - final mobile drawer rule below makes this content-sized so shorter - tools remain compact while tall ones leave a visible top gap. */ - #cookbook-modal .modal-content, - #tasks-modal .modal-content, - #calendar-modal .modal-content, - #memory-modal .memory-modal-content, - #settings-modal .settings-modal-content, - #email-lib-modal .modal-content, - #doclib-modal .doclib-modal-content { - max-height: 90vh !important; - max-height: 90dvh !important; - height: auto !important; - /* Reserve the iOS home-indicator / bottom-bar strip so an expanded - skill card's footer (Delete / Edit / Run) isn't hidden under it. - dvh already excludes the URL bar; this handles the safe area. */ - padding-bottom: env(safe-area-inset-bottom, 0px) !important; - } - /* Grab handle pill on the non-standard content classes too. Memory's - desktop rule defines a radial-glow ::before later in the file — - these body-prefixed selectors win the specificity battle on mobile. */ - body .memory-modal-content::before, - body .settings-modal-content::before { - content: ''; - display: block; - position: static; - inset: auto; - width: 36px; height: 4px; - background: var(--fg); - opacity: 0.25; - border-radius: 2px; - margin: 0 auto 4px; - flex-shrink: 0; - padding: 0; - border-top: 10px solid transparent; - border-bottom: 6px solid transparent; - background-clip: padding-box; - animation: none; - } - .memory-modal-content .close-btn, - .memory-modal-content .modal-close, - .settings-modal-content .close-btn, - .settings-modal-content .modal-close { - display: none !important; - } - .memory-modal-content, - .settings-modal-content { - touch-action: pan-y; - overscroll-behavior: contain; - } - /* Library modals use the same bottom-sheet sizing as the other tools. - The shared mobile drawer rule below provides the top breathing room. */ - #cookbook-modal .modal-content, - #calendar-modal .modal-content, - #email-lib-modal .modal-content, - #doclib-modal .modal-content, - #gallery-modal .modal-content, - #tasks-modal .modal-content { - /* vh-first as fallback for very old browsers, then dvh wins so - the modal shrinks/grows with the mobile URL bar. Wrong order - previously made the expanded chat preview extend below the - visible viewport on Chrome/Safari mobile (URL bar covered the - action buttons row). */ - max-height: 90vh !important; - max-height: 90dvh !important; - height: auto !important; - } - /* Anchor those modals to the top so the full height is usable */ - #cookbook-modal, - #calendar-modal, - #email-lib-modal, - #doclib-modal, - #gallery-modal, - #tasks-modal { - padding-top: 0 !important; - align-items: stretch !important; - } - /* Deep Research already gets the swipe grab-handle pill from the - shared `.modal-content::before` rule (the pane carries that class). - The previous header-level pill was redundant and rendered as a - SECOND pill stacked above the real one — removed. */ - /* The inner body must flex to fill the new full height, and the - tasks list inside it must scroll independently — otherwise the - list crops at whatever pre-mobile height the desktop layout had. */ - #tasks-modal .modal-content { - display: flex !important; - flex-direction: column !important; - } - #tasks-modal .modal-body { - flex: 1 1 auto !important; - min-height: 0 !important; - } - /* Memory/Skills: the body lacks flex:1, so on the fixed-height mobile - sheet (overflow:hidden) it grew past the viewport and clipped the - bottom of the skills list + its row action buttons with no way to - scroll there. Bound the body so the inner list scrolls internally. */ - #memory-modal .memory-modal-content { - display: flex !important; - flex-direction: column !important; - } - /* flex-basis MUST be 0 (not auto) — matches the working #doclib-modal - .modal-body. With basis:auto, Firefox (incl. mobile) sizes the body - to its content (~half height) instead of filling, while Chromium - fills it regardless — which is exactly why the skill expand worked - on desktop/Chromium but stuck at ~50% on Firefox mobile. */ - #memory-modal .memory-modal-body { - flex: 1 1 0 !important; - min-height: 0 !important; - overflow: hidden !important; - } - /* Same basis:0 fix down the rest of the skills chain so every layer - fills instead of sizing to content under Firefox. */ - #memory-modal .memory-tab-panel[data-memory-panel="skills"] { - flex: 1 1 0 !important; - min-height: 0 !important; - } - #memory-modal .memory-tab-panel[data-memory-panel="skills"] > .admin-card { - flex: 1 1 0 !important; - min-height: 0 !important; - } - /* The skills modal carries an extra toolbar row (search/sort/select + - bulk bar) the doc/email libraries don't, so the shared 82dvh preview - min-height overflowed here and pushed the expanded footer (Delete / - Edit / Run) off the bottom of the screen. Let flexbox size it from - the resolved card height instead, so the footer always pins inside - the visible area. */ - #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview { - min-height: 0 !important; - } - /* Once enter animation finishes, clear it so inline transform works for dragging */ - .modal-content.sheet-ready { - animation: none; - } - .modal-content.modal-closing { - animation: sheet-exit 0.15s ease-in both !important; - } - /* Grab handle — large touch target, visible pill */ - #theme-popup::before, - .modal-content::before { - content: ''; - display: block; - width: 36px; height: 4px; - background: var(--fg); opacity: 0.25; - border-radius: 2px; - margin: 0 auto 4px; - flex-shrink: 0; - padding: 0; - /* Expand touch target without changing visual size */ - border-top: 10px solid transparent; - border-bottom: 6px solid transparent; - background-clip: padding-box; - } - /* Hide close X on mobile — swipe down to dismiss */ - .modal-content .close-btn, - .modal-content .modal-close, - #theme-popup .close-btn { - display: none !important; - } - /* Hide the auto-injected minimize (_) button on mobile — the dock - chip already represents the minimized state, and the swipe-down - gesture is the canonical minimize action. */ - .modal-minimize-btn, - .minimize-btn, - [data-minimize] { - display: none !important; - } - /* Lock modals to vertical touch only, prevent horizontal dragging */ - .modal-content { - touch-action: pan-y; - overscroll-behavior: contain; - } - .modal-content .cookbook-body, - .modal-content .modal-body { - touch-action: pan-y; - overscroll-behavior: contain; - } - .modal-header { - cursor: default; - touch-action: none; - } - @keyframes sheet-enter { - from { transform: translateY(100%); } - to { transform: translateY(0); } - } - @keyframes sheet-exit { - from { transform: translateY(0); } - to { transform: translateY(100%); } - } - } - - /* ── Model A/B Comparison ── */ - - /* -- Extracted inline-style classes -- */ - .cmp-header-action-btn { - background: none; border: 1px solid var(--border); color: var(--fg); - cursor: pointer; padding: 3px 10px; font-size: 11px; font-weight: 600; - opacity: 0.7; transition: all 0.15s; line-height: 1; border-radius: 4px; - display: inline-flex; align-items: center; font-family: inherit; - } - .cmp-form-control { - padding: 8px; background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; font-size: 0.85em; - } - .cmp-btn-secondary { - background: transparent; color: var(--fg); border: 1px solid var(--border); - border-radius: 6px; cursor: pointer; - } - .cmp-btn-primary { - background: var(--fg); color: var(--bg); border: none; - border-radius: 6px; cursor: pointer; font-weight: 600; - transition: filter 0.12s, background 0.12s, color 0.12s; - } - /* Override global button:hover (which switches bg to var(--panel) = - very dark) — keep the bright primary look and just brighten slightly. */ - .cmp-btn-primary:hover:not(:disabled) { - background: var(--fg); color: var(--bg); - filter: brightness(1.1); - } - .cmp-btn-primary:active:not(:disabled) { filter: brightness(0.95); } - .cmp-btn-secondary:hover:not(:disabled) { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); color: var(--fg); - } - .cmp-model-row { - display: flex; align-items: center; gap: 8px; margin-bottom: 8px; - flex-wrap: wrap; - padding: 2px 0; - transition: margin-left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.15s, border-color 0.15s; - } - .cmp-model-row-duplicate { - margin-left: -4px; - margin-right: -4px; - padding: 4px; - border-radius: 6px; - background: color-mix(in srgb, var(--color-error) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--color-error) 20%, transparent); - } - .cmp-model-row-swap-target { - margin-left: -4px; - margin-right: -4px; - padding: 4px; - border-radius: 6px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); - } - .cmp-model-row-swap-target .cmp-row-label { - color: var(--accent, var(--red)); - opacity: 1; - } - .cmp-row-label { - color: var(--fg); font-size: 0.82em; font-weight: 600; min-width: 20px; - opacity: 0.4; text-align: center; flex-shrink: 0; - } - .cmp-model-meta { - order: 3; - flex: 1 0 calc(100% - 28px); - margin-left: 28px; - margin-top: -4px; - display: flex; - align-items: center; - gap: 5px; - min-width: 0; - font-size: 10px; - line-height: 1.2; - } - .cmp-model-meta-chip, - .cmp-model-meta-warning, - .cmp-model-meta-muted { - display: inline-flex; - align-items: center; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - border-radius: 999px; - padding: 1px 6px; - letter-spacing: 0; - } - .cmp-model-meta-chip { - color: color-mix(in srgb, var(--fg) 60%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - } - .cmp-model-meta-warning { - color: var(--color-error); - background: color-mix(in srgb, var(--color-error) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--color-error) 24%, transparent); - font-weight: 600; - } - .cmp-model-meta-muted { - color: color-mix(in srgb, var(--fg) 36%, transparent); - } - .cmp-rm-btn { - background: none; border: none; color: var(--accent, var(--red)); cursor: pointer; - min-width: 20px; font-size: 16px; font-weight: 600; opacity: 0.3; - transition: all 0.15s; padding: 0; line-height: 1; text-align: center; - position: relative; top: -2px; - order: 2; - margin-left: auto; - align-self: center; - } - .cmp-prov-select { - flex: 0 0 auto; width: 120px; font-size: 0.8em; - } - /* Eval-prompts picker — only shown during compare; absolute top-right - inside .chat-input-top, matching .model-picker-wrap's slot. */ - .chat-input-top > .cmp-eval-wrap { - position: relative; - grid-column: 2; - grid-row: 1; - top: -2px; right: 0; - z-index: 2; - } - .cmp-eval-btn { - display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 11px; font-weight: 500; - font-family: inherit; - cursor: pointer; opacity: 0.75; - transition: opacity 0.15s, border-color 0.15s; - } - .cmp-eval-btn:hover { opacity: 1; border-color: var(--fg); } - .cmp-eval-clear { - display: inline-flex; align-items: center; gap: 4px; margin-left: 4px; - border: 0; background: transparent; color: var(--fg); opacity: .55; - padding: 3px 5px; font: inherit; font-size: 10px; cursor: pointer; - } - .cmp-eval-clear:hover { opacity: 1; } - .cmp-eval-caret { opacity: 0.7; transform: rotate(180deg); } - .cmp-eval-menu { - position: absolute; bottom: calc(100% + 4px); right: 0; - min-width: 220px; max-width: 280px; - max-height: 360px; overflow-y: auto; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 -4px 16px rgba(0,0,0,0.3); - padding: 4px; - z-index: 1000; - } - .cmp-eval-menu.hidden { display: none; } - .cmp-eval-group-label { - font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px; - opacity: 0.45; font-weight: 600; - padding: 6px 8px 2px; - } - .cmp-eval-item { - display: block; width: 100%; - text-align: left; - padding: 5px 8px; - background: none; border: none; - color: var(--fg); font-size: 11px; - font-family: inherit; - border-radius: 4px; - cursor: pointer; - } - .cmp-eval-item:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - } - .cmp-eval-empty { - padding: 10px; text-align: center; - font-size: 11px; opacity: 0.5; - } - /* Tick on items that ship a known expected answer */ - .cmp-eval-item-tick { - float: right; - margin-left: 6px; - font-size: 10px; - color: var(--color-success, #4caf50); - opacity: 0.8; - } - /* Expected-answer panel — floats as its own little window above the - chat-input-bar. Distinct surface, padded, drop-shadow, slightly - lifted so it reads as a separate UI element, not part of the input. */ - .chat-input-bar:has(.cmp-eval-expected) { position: relative; } - .cmp-eval-expected { - position: absolute; - bottom: calc(100% + 8px); - right: 0; - display: inline-flex; align-items: center; gap: 8px; - padding: 8px 12px; - font-size: 11px; - background: var(--panel); - border: 1px solid color-mix(in srgb, var(--color-success, #4caf50) 50%, transparent); - border-radius: 8px; - box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2); - color: var(--fg); - width: fit-content; - z-index: 5; - pointer-events: auto; - } - .cmp-eval-expected.hidden { display: none; } - .cmp-eval-expected-label { - opacity: 0.6; - text-transform: uppercase; - font-size: 9px; - letter-spacing: 0.5px; - font-weight: 600; - } - .cmp-eval-expected-value { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 11px; - } - .cmp-eval-expected-close { - background: none; border: none; color: var(--fg); - font-size: 14px; line-height: 1; padding: 0 0 0 4px; - opacity: 0.5; cursor: pointer; font-family: inherit; - } - .cmp-eval-expected-close:hover { opacity: 1; } - /* Auto-grade badge — stamped on a pane after stream completes when an - eval prompt with a known expected answer was used. */ - .pane-grade-badge { - display: inline-flex; align-items: center; justify-content: center; - width: 18px; height: 18px; - margin: 0 4px; - font-size: 12px; font-weight: 700; - border-radius: 50%; - border: 1px solid currentColor; - flex-shrink: 0; - } - .pane-grade-badge.pass { - color: var(--color-success, #4caf50); - background: color-mix(in srgb, var(--color-success, #4caf50) 12%, transparent); - } - .pane-grade-badge.fail { - color: var(--color-error, #e55); - background: color-mix(in srgb, var(--color-error, #e55) 12%, transparent); - } - - /* Compare probe overlay */ - .compare-probe-overlay { - position: fixed; inset: 0; z-index: 300; - background: rgba(0,0,0,0.5); display: flex; - align-items: center; justify-content: center; - padding: 12px; - box-sizing: border-box; - } - .compare-probe-card { - width: min(460px, calc(100% - 16px)); - max-height: min(620px, calc(100dvh - 24px)); - box-sizing: border-box; - background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px; - display: flex; flex-direction: column; align-items: stretch; gap: 10px; - min-width: 280px; box-shadow: 0 8px 28px rgba(0,0,0,0.28); - overflow: hidden; - font-family: inherit; - } - .compare-probe-title { - font-size: 13px; font-weight: 400; color: var(--fg); - } - .compare-probe-title-label { font-weight: 600; } - .compare-probe-title-status { font-weight: 400; } - .compare-probe-list { - display: grid; grid-template-columns: 1fr 1fr; gap: 6px; min-width: 0; width: 100%; - overflow-y: auto; - } - .compare-probe-row { - display: flex; align-items: center; gap: 6px; padding: 6px 10px; - border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent); - font-size: 12px; transition: background 0.2s; overflow: hidden; - } - .compare-probe-row.fail { - background: color-mix(in srgb, var(--color-error, #f44) 8%, transparent); - } - .compare-probe-spinner { - width: 24px; text-align: center; font-size: 10px; flex-shrink: 0; - letter-spacing: -1px; opacity: 0.6; - } - .compare-probe-spinner.ok { color: var(--color-success); animation: none; } - .compare-probe-spinner.fail { color: var(--color-error, #f44); animation: none; } - .compare-probe-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .compare-probe-status { font-size: 11px; opacity: 0.5; flex-shrink: 0; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .compare-probe-status.ok { color: var(--color-success); opacity: 1; } - .compare-probe-status.fail { color: var(--color-error, #f44); opacity: 1; } - .compare-probe-action-btn { - display: inline-flex; align-items: center; gap: 5px; - min-height: 25px; padding: 3px 8px; background: transparent; color: var(--fg); border: 1px solid var(--border); - border-radius: 5px; cursor: pointer; font-size: 10px; font-family: inherit; - opacity: 0.78; transition: opacity 0.15s, border-color 0.15s, background 0.15s; white-space: nowrap; flex-shrink: 0; - } - .compare-probe-action-btn svg, - .compare-probe-footer-btn svg { width: 12px; height: 12px; flex: 0 0 auto; color: var(--accent, var(--red)); } - .compare-probe-action-btn:hover { opacity: 1; border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--fg) 5%, transparent); } - .compare-probe-feedback { display: flex; flex-direction: column; gap: 5px; } - .compare-probe-feedback:empty { display: none; } - .compare-probe-detail { - display: flex; align-items: center; gap: 6px; min-width: 0; - padding: 6px 8px; font-size: 10px; - background: color-mix(in srgb, var(--color-error, #f44) 6%, transparent); - border-left: 2px solid color-mix(in srgb, var(--color-error, #f44) 72%, transparent); - border-radius: 4px; - } - .compare-probe-detail-icon { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - width: 14px; - height: 14px; - color: var(--accent, var(--red)); - font-family: inherit; - font-size: 11px; - font-weight: 700; - line-height: 1; - } - .compare-probe-detail-message { flex: 1 1 auto; min-width: 0; line-height: 1.35; color: color-mix(in srgb, var(--fg) 72%, transparent); overflow-wrap: anywhere; } - .compare-probe-footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; margin-top: 2px; } - .compare-probe-footer-btn { - display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 5px 10px; - font-family: inherit; font-size: 11px; font-weight: 600; line-height: 1; - } - .compare-probe-start-anyway, - .compare-probe-start-anyway:hover:not(:disabled) { - background: var(--accent, var(--red)); - border: 1px solid var(--accent, var(--red)); - color: var(--bg); - } - .compare-probe-start-anyway svg { color: currentColor; } - .compare-probe-start-anyway span { font-weight: 700; } - @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } - @keyframes pane-shake { - 0%, 100% { transform: translateX(0); } - 15% { transform: translateX(-3px) rotate(-0.5deg); } - 30% { transform: translateX(3px) rotate(0.5deg); } - 45% { transform: translateX(-2px); } - 60% { transform: translateX(2px); } - 75% { transform: translateX(-1px); } - } - - .chat-container.compare-active { - display: flex; - flex-direction: column; - padding: 0; - overflow: hidden; - animation: compare-enter 0.3s ease-out; - } - @keyframes compare-enter { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } - } - .chat-container.compare-active .chat-input-bar { - margin-bottom: 0; - } - .chat-container.compare-active #model-picker-wrap { - display: none !important; - } - .compare-grid { - display: grid; - gap: 4px; - flex: 1 1 0; - min-height: 0; - overflow: hidden; - position: relative; - } - .compare-mobile-tabs { display: none; } - .compare-grid[data-cols="2"] { grid-template-columns: 1fr 1fr; } - .compare-grid[data-cols="3"] { grid-template-columns: 1fr 1fr 1fr; } - .compare-grid[data-cols="4"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="5"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="6"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="7"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="8"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid { grid-auto-rows: 1fr; } - .compare-shuffle-notice { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%) scale(0.94); - z-index: 30; - display: inline-flex; - align-items: center; - gap: 8px; - padding: 10px 14px; - border: 1px solid color-mix(in srgb, var(--red) 28%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 92%, transparent); - color: var(--fg); - box-shadow: 0 10px 30px color-mix(in srgb, #000 24%, transparent); - font-size: 12px; - font-weight: 700; - line-height: 1; - opacity: 0; - pointer-events: none; - transition: opacity 0.14s ease, transform 0.14s ease; - } - .compare-shuffle-notice.show { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - } - .compare-shuffle-notice-icon { - display: inline-flex; - color: var(--red); - animation: compare-shuffle-dice 0.56s ease both; - } - .compare-shuffle-notice-icon svg { - display: block; - width: 18px; - height: 18px; - } - @keyframes compare-shuffle-dice { - 0% { transform: rotate(-18deg) scale(0.9); } - 55% { transform: rotate(380deg) scale(1.08); } - 100% { transform: rotate(360deg) scale(1); } - } - /* Sequential waterfall layout — stacked rows, staggered left, flush right */ - .compare-grid.sequential-layout { - display: flex !important; - flex-direction: column !important; - grid-template-columns: none !important; - gap: 4px; - overflow-y: auto; - } - .compare-grid.sequential-layout .compare-pane { - flex-shrink: 0; - min-height: 200px; - transition: margin-left 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease; - } - /* Herringbone diagonal cut on left side of sequential pane headers */ - .compare-grid.sequential-layout .compare-pane .pane-header { - clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%); - padding-left: 26px; - } - .compare-pane { - display: flex; - flex-direction: column; - border: 1px solid var(--border); - border-radius: 6px; - overflow: hidden; - min-height: 0; - min-width: 0; - position: relative; - transition: border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; - } - .compare-pane::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - z-index: 2; - pointer-events: none; - background: transparent; - transform-origin: left center; - } - .compare-pane.is-streaming { - border-color: color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - } - .compare-pane.is-streaming::before { - background: linear-gradient(90deg, transparent 0%, var(--accent, var(--red)) 35%, var(--red) 50%, var(--accent, var(--red)) 65%, transparent 100%); - animation: compare-pane-progress 1.25s linear infinite; - } - .compare-pane.is-awaiting-input { - border-color: color-mix(in srgb, var(--red) 42%, var(--border)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--red) 12%, transparent); - } - .compare-pane.is-awaiting-input::before { - background: var(--red); - animation: none; - } - .compare-pane.is-done { - border-color: color-mix(in srgb, var(--green, #50fa7b) 20%, var(--border)); - } - .compare-pane.is-failed { - border-color: color-mix(in srgb, var(--color-error) 42%, var(--border)); - } - .compare-pane.is-failed::before { - background: var(--color-error); - } - @keyframes compare-pane-progress { - from { transform: translateX(-100%); } - to { transform: translateX(100%); } - } - .compare-pane .pane-header { - display: flex; - flex-direction: column; - align-items: stretch; - gap: 4px; - padding: 4px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-bottom: 1px solid var(--border); - font-size: 0.82em; - font-weight: 600; - color: var(--fg); - transition: background 0.4s; - flex-shrink: 0; - overflow: visible; - min-width: 0; - flex-wrap: nowrap; - } - .pane-header-row { display: flex; align-items: center; width: 100%; min-width: 0; } - .pane-header-primary { min-height: 24px; } - .pane-header-secondary { min-height: 20px; } - .pane-primary-actions, .pane-stats { display: flex; align-items: center; gap: 4px; min-width: 0; } - .pane-primary-actions { margin-left: auto; flex: 0 0 auto; } - .pane-stats { flex: 1 1 auto; overflow: hidden; } - .compare-header-bar button:hover, - .compare-header-bar button:focus-visible { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)) !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-header-bar button:active { - background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent) !important; - transform: scale(0.96); - } - .compare-more-wrap { position: relative; display: inline-flex; } - .compare-more-btn { - display: inline-flex; align-items: center; justify-content: center; - width: 28px; height: 24px; padding: 0 !important; - border: 1px solid transparent; border-radius: 5px; - background: none; color: var(--fg); cursor: pointer; - opacity: 0.5; transition: opacity .15s, background .15s; - } - .compare-header-bar .compare-close-btn { - width: 24px; height: 24px; min-width: 24px; padding: 0; - border-radius: 999px; font-size: 0; display: inline-flex; - align-items: center; justify-content: center; - } - .compare-header-bar .compare-close-btn:hover, - .compare-header-bar .compare-close-btn:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-header-bar .compare-more-btn:hover, - .compare-header-bar .compare-more-btn:focus-visible, - .compare-header-bar .compare-more-btn[aria-expanded="true"] { - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-more-menu { - position: absolute; top: calc(100% + 5px); right: 0; z-index: 10002; - display: none; flex-direction: column; gap: 3px; min-width: 132px; - padding: 5px; border: 1px solid var(--border); border-radius: 8px; - background: var(--panel, var(--bg)); box-shadow: 0 8px 24px rgba(0,0,0,.3); - } - .compare-more-menu.is-open { display: flex; } - .compare-more-menu > .compare-export-wrap, - .compare-more-menu > button { - width: 100%; display: flex; align-items: center; - } - .compare-more-menu #compare-export-btn, - .compare-more-menu #compare-shuffle-btn, - .compare-more-menu #compare-check-btn, - .compare-more-menu #compare-add-menu-btn { - width: 100%; min-height: 32px; height: 32px; - box-sizing: border-box; justify-content: flex-start; gap: 7px; - padding: 7px 9px !important; - border: 1px solid transparent !important; border-radius: 6px; - background: transparent !important; color: var(--fg) !important; - font-size: 11px; font-weight: 500; opacity: .72; - transition: background .12s ease, opacity .12s ease, color .12s ease; - } - .compare-more-menu #compare-export-btn:hover, - .compare-more-menu #compare-shuffle-btn:hover, - .compare-more-menu #compare-check-btn:hover, - .compare-more-menu #compare-add-menu-btn:hover, - .compare-more-menu #compare-export-btn:focus-visible, - .compare-more-menu #compare-shuffle-btn:focus-visible, - .compare-more-menu #compare-check-btn:focus-visible, - .compare-more-menu #compare-add-menu-btn:focus-visible { - opacity: 1; background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: transparent !important; color: var(--fg) !important; outline: none; - } - .compare-more-menu #compare-export-btn svg, - .compare-more-menu #compare-shuffle-btn svg, - .compare-more-menu #compare-check-btn svg, - .compare-more-menu #compare-add-menu-btn svg { flex: 0 0 auto; opacity: .65; } - .compare-more-menu #compare-export-btn span, - .compare-more-menu #compare-shuffle-btn span, - .compare-more-menu #compare-check-btn span, - .compare-more-menu #compare-add-menu-btn span { - font-size: 11px !important; margin-left: 0 !important; - color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; - } - .compare-more-menu #compare-export-btn, - .compare-more-menu #compare-shuffle-btn, - .compare-more-menu #compare-check-btn, - .compare-more-menu #compare-add-menu-btn { - color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; - } - .compare-more-menu #compare-export-btn svg, - .compare-more-menu #compare-shuffle-btn svg, - .compare-more-menu #compare-check-btn svg, - .compare-more-menu #compare-add-menu-btn svg { color: var(--fg) !important; opacity: .65 !important; } - .compare-export-item:hover, - .compare-export-item:focus-visible { - background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: transparent !important; outline: none; - } - .compare-export-item { display: flex; align-items: center; gap: 8px; } - .compare-export-item > svg { flex: 0 0 auto; opacity: .65; } - .compare-export-menu .compare-export-item, - .compare-export-menu .compare-export-item > span { - color: var(--fg) !important; - opacity: 1 !important; - } - .compare-add-flap { - position: absolute; z-index: 20; right: -8px; top: 50%; - width: 34px; min-width: 34px; height: 30px; box-sizing: border-box; - padding: 0 !important; - display: inline-flex; flex-direction: row; align-items: center; justify-content: center; - gap: 0; border: 1px solid var(--border); - border-radius: 0 7px 7px 0; background: var(--panel, var(--bg)); - color: var(--fg); cursor: pointer; opacity: .7; - box-shadow: 3px 2px 9px color-mix(in srgb, var(--fg) 12%, transparent); - transform: translate(0, -50%); transition: width .18s ease, opacity .15s, background .15s, box-shadow .15s; - } - .compare-add-flap:hover, .compare-add-flap:focus-visible { - width: 70px; opacity: 1; background: color-mix(in srgb, var(--fg) 8%, var(--panel)); - box-shadow: 4px 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); - outline: none; - } - .compare-add-flap span { - max-width: 0; overflow: hidden; white-space: nowrap; margin-left: 0 !important; - font-size: 10px; opacity: 0; transition: max-width .18s ease, margin-left .18s ease, opacity .12s; - } - .compare-add-flap:hover span, .compare-add-flap:focus-visible span { - max-width: 32px; margin-left: 3px !important; opacity: 1; - } - .compare-add-flap svg { flex-shrink: 0; position: relative; left: -3px; } - @media (max-width: 768px) { - .compare-add-flap { - top: 8px; right: 8px; width: auto; min-width: 32px; height: 32px; - padding: 5px 8px !important; flex-direction: row; transform: none; - border-right: 1px solid var(--border); border-radius: 6px; - } - .compare-add-flap span { opacity: 1; } - } - .pane-actions { - display: flex; gap: 4px; align-items: center; margin-left: auto; flex-shrink: 0; - } - .compare-pane-footer { - font-size: 0.72em; opacity: 0.4; padding: 4px 10px; - border-top: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; flex-shrink: 0; - } - /* Per-pane vote button. Sits at the bottom of each compare pane so - the action lives right under the response it judges. */ - .pane-vote-footer { - padding: 6px 8px; - border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex-shrink: 0; - } - .pane-vote-btn { - width: 100%; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 6px 10px; - font-family: inherit; - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: background 0.15s, border-color 0.15s, opacity 0.15s; - } - .pane-vote-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); - border-color: var(--accent, var(--fg)); - } - .pane-vote-btn:disabled { cursor: not-allowed; } - .pane-vote-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; - } - .pane-action-btn { - background: none; border: none; color: var(--fg); cursor: pointer; - opacity: 0.3; padding: 2px; border-radius: 4px; - display: flex; align-items: center; transition: all 0.15s; - } - .pane-action-btn:hover { opacity: 0.8; background: color-mix(in srgb, var(--fg) 6%, transparent); } - /* Pane title as clickable model-swap button */ - .pane-title-btn { - background: none; border: none; cursor: pointer; - font-size: 10px; font-weight: 400; font-family: inherit; - color: var(--fg); padding: 0; - text-align: left; display: flex; align-items: center; gap: 4px; - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - transition: opacity 0.15s; - min-width: 0; flex: 1 1 0; - } - .pane-title-btn:hover { opacity: 0.7; } - .pane-title-caret { font-size: 0.6em; opacity: 0.35; flex-shrink: 0; position: relative; top: 2px; } - .pane-title-btn:hover .pane-title-caret { opacity: 0.7; } - .pane-mode-badge { - display: inline-flex; - align-items: center; - gap: 4px; - flex: 0 0 auto; - height: 18px; - padding: 0 6px; - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - color: color-mix(in srgb, var(--fg) 76%, transparent); - font-size: 9px; - font-weight: 600; - line-height: 1; - letter-spacing: 0; - white-space: nowrap; - } - .pane-mode-dot { - width: 5px; - height: 5px; - border-radius: 50%; - background: currentColor; - opacity: 0.75; - flex: 0 0 auto; - } - .pane-mode-detail { - opacity: 0.52; - font-weight: 500; - } - .pane-mode-badge.pane-mode-icon-only { - width: 20px; - padding: 0; - justify-content: center; - } - .pane-mode-settings { - opacity: 0.65; - cursor: pointer; - } - .pane-settings-btn { width: 20px; height: 20px; justify-content: center; } - .pane-settings-btn[aria-expanded="true"] { opacity: 1; background: color-mix(in srgb, var(--fg) 9%, transparent); } - .response-metrics[data-action="settings"] { cursor: pointer; } - .response-thinking-state { color: color-mix(in srgb, var(--fg) 55%, transparent); } - .pane-mode-settings:hover, - .pane-mode-settings:focus-visible, - .pane-mode-settings[aria-expanded="true"] { - opacity: 1; - color: var(--fg); - background: color-mix(in srgb, var(--fg) 9%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--fg)) 45%, var(--border)); - outline: none; - } - .pane-mode-icon, - .pane-mode-icon svg { - display: block; - width: 12px; - height: 12px; - } - .pane-mode-agent { - color: color-mix(in srgb, var(--fg) 76%, transparent); - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - border-color: color-mix(in srgb, var(--border) 70%, transparent); - } - .pane-mode-search { - color: color-mix(in srgb, var(--accent, var(--red)) 82%, var(--fg)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, var(--border)); - } - .pane-mode-research { - color: color-mix(in srgb, #36c48f 82%, var(--fg)); - background: color-mix(in srgb, #36c48f 10%, var(--bg)); - border-color: color-mix(in srgb, #36c48f 28%, var(--border)); - } - .compare-check-label { - position: relative; - top: -2px; - font-size: 11px; - margin-left: 3px; - } - .compare-check-icon { - position: relative; - top: 1px; - } - #compare-check-btn > .spinner-whirlpool { - position: relative; - top: -2px; - transform: none; - } - .compare-pane .pane-close-btn { opacity: 0.3; } - .compare-pane .pane-close-btn:hover { opacity: 1; color: var(--color-error); } - /* Model swap dropdown under pane title */ - .pane-model-dropdown { - position: absolute; - z-index: 1000; - min-width: 220px; - max-height: 300px; - overflow-y: auto; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 16px rgba(0,0,0,0.3); - padding: 4px; - } - .pane-model-item { - display: block; width: 100%; - box-sizing: border-box; - padding: 8px 10px; font-size: 11px; line-height: 1.3; - text-align: left; background: none; border: none; border-radius: 6px; - color: var(--fg); cursor: pointer; transition: background 0.12s ease; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - } - .pane-model-item:hover, - .pane-model-item:focus-visible { background: color-mix(in srgb, var(--red) 10%, transparent); outline: none; } - .pane-model-item.current { color: var(--red); font-weight: 600; } - .pane-timer { - font-size: 10px; font-weight: 400; opacity: 0.45; font-variant-numeric: tabular-nums; - white-space: nowrap; padding-right: 4px; - } - .pane-summary { - display: inline-flex; - align-items: center; - position: relative; - top: 1px; - min-height: 16px; - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding: 0 5px; - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 9px; - font-weight: 500; - line-height: 1; - font-variant-numeric: tabular-nums; - cursor: pointer; - transition: max-width .15s ease, white-space .15s ease; - } - .pane-summary:empty { display: none; } - .pane-stats-popup { - position: fixed; z-index: 10040; width: 210px; padding: 6px; - background: var(--panel, var(--bg)); color: var(--fg); - border: 1px solid var(--border); border-radius: 7px; - box-shadow: 0 8px 24px rgba(0,0,0,.3); font-size: 11px; - } - .pane-stats-popup-title { padding: 4px 6px 7px; font-weight: 600; border-bottom: 1px solid var(--border); } - .pane-stats-popup-row { display: flex; justify-content: space-between; gap: 12px; padding: 6px; } - .pane-stats-popup-row span { color: color-mix(in srgb, var(--fg) 55%, transparent); } - .pane-stats-popup-row strong { font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; } - /* Metrics stay on the lower row even in dense comparisons. */ - .compare-grid[data-cols="4"] .pane-timer, - .compare-grid[data-cols="5"] .pane-timer, - .compare-grid[data-cols="6"] .pane-timer { - width: auto; order: initial; margin-top: 0; padding-bottom: 0; padding-left: 0; - } - .pane-finish-badge { - font-weight: 600; color: var(--green, #50fa7b); - position: relative; - top: 2px; - } - .compare-pane.winner .pane-header { - background: color-mix(in srgb, var(--green, #50fa7b) 12%, transparent); - border-bottom-color: color-mix(in srgb, var(--green, #50fa7b) 30%, var(--border)); - } - .compare-pane.winner .pane-title { - color: var(--green, #50fa7b); - } - .compare-pane.loser .pane-header { - opacity: 0.5; - } - .confetti-piece { - position: fixed; - pointer-events: none; - z-index: 1000000; - } - .compare-pane.expanded { grid-column: 1 / -1; } - .compare-pane .chat-history { - flex: 1 1 0; - min-height: 0; - overflow-y: auto !important; - overflow-x: hidden; - padding: 8px; - display: flex; - flex-direction: column; - } - .compare-pane .chat-history .msg { - flex-shrink: 0; - } - .compare-gen-image { - max-width: 100%; - max-height: 100%; - object-fit: contain; - border-radius: 4px; - cursor: pointer; - } - .compare-section { - margin-bottom: 14px; - } - .compare-section:last-child { - margin-bottom: 0; - } - .compare-section-label { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.6px; - opacity: 0.5; - margin-bottom: 5px; - font-weight: 600; - } - /* Active-type/mode readout next to "Type:"/"Mode:" — only needed on mobile - (where the tab/toggle text labels are hidden); hidden on desktop. */ - .compare-type-current, - .compare-mode-current { display: none; } - /* Contextual one-liner under the mode toggles describing what you just - changed — empty until the first toggle, then a subtle hint. */ - .compare-mode-hint { - font-size: 11px; - opacity: 0.55; - margin-top: 6px; - min-height: 0; - line-height: 1.3; - } - .compare-mode-hint:empty { display: none; } - .compare-type-tabs .compare-mode-tab > svg, - .compare-type-current > svg { - color: var(--accent, var(--red)); - } - - .compare-mode-tabs { - display: flex; - gap: 4px; - flex-wrap: wrap; - min-width: 0; - } - /* Type tabs match Mode toggles 1:1 (same flex column layout, same metrics) */ - .compare-mode-tab { - display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 56px; height: 38px; flex: 1 1 0; - padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; - cursor: pointer; user-select: none; transition: all 0.15s; - background: none; color: var(--fg); opacity: 0.35; - flex-shrink: 0; gap: 0; - font-family: inherit; - } - .compare-mode-tab > svg { - width: 13px; height: 13px; flex: 0 0 13px; - } - .compare-mode-tab:hover { - opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); - } - .compare-mode-tab.active { - opacity: 1; border-color: var(--fg); - background: color-mix(in srgb, var(--fg) 10%, transparent); - } - .compare-sources-box { - display: flex; align-items: center; gap: 6px; - padding: 6px 10px; margin-bottom: 8px; - border-radius: 6px; font-size: 0.78em; - background: color-mix(in srgb, var(--red) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - cursor: default; - } - .compare-sources-box .sources-label { font-weight: 600; } - /* Compact tool blocks inside compare panes */ - .compare-pane .agent-thread-node { - margin: 4px 0; font-size: 0.85em; - } - .compare-pane .agent-thread-cmd { - max-height: 80px; overflow-y: auto; - } - .compare-pane .agent-tool-output pre { - max-height: 120px; overflow-y: auto; - } - .compare-vote-bar { - display: flex; justify-content: center; gap: 8px; - padding: 8px; border-top: 1px solid var(--border); - flex-shrink: 0; flex-wrap: wrap; - } - .compare-vote-bar.hidden { display: none; } - .compare-vote-btn { - padding: 6px 13px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; font-size: 0.8em; - transition: all 0.15s; white-space: nowrap; - } - .compare-score-label { - position: relative; - top: -2px; - display: inline-block; - } - .compare-vote-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .compare-vote-tie { opacity: 0.7; } - .compare-rematch-btn { - display: flex; align-items: center; gap: 6px; margin-left: 8px; - border-color: color-mix(in srgb, var(--accent, var(--red)) 60%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--fg); opacity: 1; font-weight: 600; - } - .compare-rematch-btn:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent); } - /* Preview button accent in pane header */ - .pane-preview-btn.active { color: var(--red); } - /* Full-pane iframe for HTML preview */ - .compare-pane-iframe { - flex: 1; - width: 100%; - border: none; - border-radius: 0 0 8px 8px; - background: #fff; - } - /* ---- Add-pane "+" button in pane header (last pane only) ---- */ - .pane-add-btn { display: none !important; font-size: 16px; font-weight: 600; } - .compare-pane:last-child .pane-add-btn { display: flex !important; } - /* Dropdown for adding a pane */ - .add-pane-dropdown { - position: absolute; - right: 0; - z-index: 100; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 10px; - max-height: 300px; - overflow-y: auto; - min-width: 220px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); - padding: 6px; - backdrop-filter: blur(12px); - animation: dropdown-in 0.15s ease-out; - } - .add-pane-search { - width: 100%; - padding: 6px 8px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font-size: 0.85em; - box-sizing: border-box; - margin-bottom: 4px; - position: sticky; - top: 0; - z-index: 1; - } - /* Compare toggle buttons — icon + label stacked */ - .compare-toggle-label { - display: block; - font-size: 9px; - line-height: 1; - margin-top: 2px; - font-weight: 500; - } - .compare-blind-toggle, - .compare-save-toggle, - .compare-dice-toggle, - .compare-parallel-toggle, - .compare-reset-toggle { - display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 56px; height: auto; flex: 1 1 0; - padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; - cursor: pointer; user-select: none; transition: all 0.15s; - background: none; color: var(--fg); opacity: 0.35; - flex-shrink: 0; gap: 0; - } - .compare-blind-toggle:hover, - .compare-save-toggle:hover, - .compare-dice-toggle:hover, - .compare-parallel-toggle:hover, - .compare-reset-toggle:hover { - opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); - } - .compare-blind-toggle.active { - opacity: 1; color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - } - .compare-blind-toggle.active > svg { - color: var(--accent, var(--red)); - } - .compare-save-toggle.active { - opacity: 1; color: var(--color-save-green); border-color: var(--color-save-green); - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .compare-dice-toggle.active { - opacity: 1; color: var(--red); border-color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .compare-parallel-toggle { - opacity: 1; color: #e0a050; border-color: #e0a050; - background: rgba(224, 160, 80, 0.1); - } - .compare-parallel-toggle.active { - color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - } - @media (max-width: 520px) { - .compare-toggle-label { display: none; } - /* Tab text labels are hidden on mobile, so spell out the active type next - to "Type:" with its icon. */ - .compare-type-current { - display: inline-flex; - align-items: center; - gap: 4px; - margin-left: 5px; - font-weight: 600; - vertical-align: -3px; - } - .compare-type-current svg { width: 14px; height: 14px; } - /* Mode list — comma-separated, each name already coloured inline. */ - .compare-mode-current { - display: inline; - margin-left: 5px; - font-weight: 600; - } - .compare-blind-toggle, - .compare-save-toggle, - .compare-dice-toggle, - .compare-parallel-toggle, - .compare-reset-toggle { - width: 32px; height: 32px; min-width: 32px; padding: 0; - } - /* The Group tab's seq/parallel toggle is a single full-width button (not - a row of compact icons like the Compare header), so keep its label and - make it a comfortable touch target with the text beside the icon. */ - #group-mode-btn { - flex-direction: row !important; - width: auto !important; - height: auto !important; - min-height: 44px; - padding: 8px 14px !important; - gap: 8px !important; - font-size: 13px; - } - #group-mode-btn .compare-toggle-label { - display: inline !important; - font-size: 13px; - margin-top: 0; - } - #group-mode-btn svg { width: 18px; height: 18px; } - /* Compare header: hide labels + close button, show icons only */ - #compare-shuffle-btn span { - display: none; - } - #compare-shuffle-btn, - #compare-check-btn, - #compare-add-btn { - padding: 3px 6px; - } - /* Save space so the header buttons fit on one row: tighter padding + - smaller labels (icons kept full size). (Score now lives in the vote bar.) */ - .compare-header-bar button { padding: 3px 5px !important; } - .compare-header-bar #compare-check-btn > span, - .compare-header-bar #compare-add-btn > span { - font-size: 10px; margin-left: 2px; - } - /* Compare mobile: keep the close X visible — it's the only way out - now that the hamburger is hidden during compare mode. */ - .compare-header-bar { - padding: 14px 8px 10px 8px !important; - min-height: 44px; - } - .compare-pane .pane-header { - align-items: stretch; - row-gap: 3px; - } - .compare-pane .pane-title-btn { - flex: 1 1 auto; - width: auto; - padding-right: 2px; - } - /* Compare is already visibly in chat mode; the Chat speech-bubble - badge adds a redundant second row on narrow screens. */ - .compare-pane .pane-mode-chat { display: none; } - .compare-pane .pane-timer, - .compare-pane .pane-summary, - .compare-pane .pane-finish-badge, - .compare-pane .pane-mode-badge, - .compare-pane .pane-actions { - order: 1; - align-self: center; - } - .compare-pane .pane-finish-badge { top: 0; } - .compare-pane .pane-summary { - max-width: 110px; - } - .compare-pane .pane-mode-detail { - display: none; - } - .compare-grid[data-cols="4"] .compare-pane .pane-timer, - .compare-grid[data-cols="5"] .compare-pane .pane-timer, - .compare-grid[data-cols="6"] .compare-pane .pane-timer { - width: auto; - order: 1; - margin-top: 0; - padding-bottom: 0; - padding-left: 0; - } - .compare-pane .pane-actions { - margin-left: auto; - } - /* Mode tabs: icons only, centered */ - .compare-mode-tab span { display: none; } - .compare-mode-tabs { justify-content: center; } - /* Header action buttons: hide text labels on Export / Shuffle / - Model on mobile so the close X fits on the right. Score keeps - its "Score" label because its icon (4-square grid) reads too - similar to Shuffle's icon without text. */ - .compare-header-bar #compare-export-btn > span, - .compare-header-bar #compare-shuffle-btn > span { - display: none; - } - /* Override the desktop override: on mobile the model picker overlay - MUST cap its height to the viewport so the dropdown doesn't run - past the bottom of the screen. */ - #compare-model-overlay .modal-content { - max-height: 85dvh !important; - max-height: 85vh !important; - overflow: hidden !important; - } - #compare-model-overlay .modal-body { - overflow: auto !important; - flex: 1 1 auto !important; - min-height: 0 !important; - } - } - /* Hide number input spinners */ - input[type="number"]::-webkit-inner-spin-button, - input[type="number"]::-webkit-outer-spin-button { - -webkit-appearance: none; margin: 0; - } - - /* ── Sensitive info censor ── */ - .censored-item { - filter: blur(5px); - cursor: pointer; - transition: filter 0.2s; - border-radius: 2px; - padding: 0 2px; - background: rgba(255,100,100,0.08); - user-select: none; - } - .censored-item:hover { filter: blur(3px); background: rgba(255,100,100,0.15); } - .censored-item.revealed { - filter: none; - background: rgba(100,255,100,0.08); - user-select: auto; - cursor: text; - } - - #settings-menu-list .list-item.active { - background: color-mix(in srgb, var(--accent) 10%, transparent); - border-color: var(--accent); - } - - /* ── Print / PDF Export ── */ - @media print { - body { background: #fff !important; color: #000 !important; } - #sidebar, .sidebar, #icon-rail, .hamburger-btn, #sidebar-backdrop, .chat-input-bar, .input-bar-wrapper, - #welcome-screen, .chat-top-bar, .chat-meta-overlay, .msg-footer, - .modal, .toast, .overflow-wrapper, .mode-toggle, .incognito-btn, - button, .dropdown, .session-dropdown, - .agent-tool-spinner, .agent-thread-node.running { display: none !important; } - main.chat-container { width: 100% !important; margin: 0 !important; padding: 0 !important; max-height: none !important; overflow: visible !important; } - #chat-history { max-height: none !important; overflow: visible !important; height: auto !important; padding: 0 !important; } - .msg { break-inside: avoid; page-break-inside: avoid; border: none !important; box-shadow: none !important; } - .msg-ai { background: #f5f5f5 !important; color: #000 !important; } - .msg-user { background: #e8e8e8 !important; color: #000 !important; } - .msg .role { color: #333 !important; font-weight: bold; } - .msg .body { color: #000 !important; } - pre, code { background: #f0f0f0 !important; color: #000 !important; border: 1px solid #ccc !important; } - details { display: block !important; } - details[open] summary ~ * { display: block !important; } - details > summary { list-style: none; } - details > summary::before { content: "" !important; } - #chat-history::before { content: attr(data-print-title); display: block; font-size: 1.3em; font-weight: bold; margin-bottom: 1em; color: #000; } - a { color: #000 !important; text-decoration: underline; } - } - - -/* ── Components (from style.css) ── */ - -/* Self-hosted Fira Code font */ -@font-face { font-family: 'Fira Code'; font-weight: 300; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Light.woff2') format('woff2'); } -@font-face { font-family: 'Fira Code'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Regular.woff2') format('woff2'); } -@font-face { font-family: 'Fira Code'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-SemiBold.woff2') format('woff2'); } - -/* Scrollbar styling */ - -/* Code block styling */ -pre, code, .hljs { - font-size: 11px; - line-height: 1.5; -} - -/* WebKit (Chrome, Edge, Safari) */ -/* Utility class for red text */ -.red-text { - color: var(--red); -} - -/* Internal chat links (search results, session references) */ -a.chat-link { - color: var(--hl-function); - text-decoration: none; - border-bottom: 1px dotted var(--hl-function); - cursor: pointer; -} -a.chat-link:hover { - opacity: 0.8; - border-bottom-style: solid; -} - -a.chat-link[href^="#email-"] { - display: inline; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, transparent); - border-radius: 5px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - color: var(--fg); - font-weight: 600; - border-bottom-style: solid; -} -a.chat-link[href^="#email-"]:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -a.chat-link[href^="#notes-more-"], -a.chat-link[href^="#skills-more-"], -a.chat-link[href^="#memories-more-"], -a.chat-link[href^="#events-more-"], -a.chat-link[href^="#sessions-more-"] { - color: var(--muted); - border-bottom: 1px dotted color-mix(in srgb, var(--muted) 55%, transparent); - font-size: 11px; - font-weight: 500; -} -a.chat-link[href^="#notes-more-"]:hover, -a.chat-link[href^="#skills-more-"]:hover, -a.chat-link[href^="#memories-more-"]:hover, -a.chat-link[href^="#events-more-"]:hover, -a.chat-link[href^="#sessions-more-"]:hover { - color: var(--fg); - opacity: 1; - border-bottom-style: solid; -} -.more-list-expanded { - display: block; - max-height: 0; - overflow: hidden; - opacity: 0; - color: inherit; - font: inherit; - line-height: inherit; - transition: max-height 220ms ease, opacity 160ms ease; -} -.more-list-expanded.is-open { - opacity: 1; -} -.more-list-expanded-content { - color: inherit; - font: inherit; - line-height: inherit; -} - -/* Session items */ -.session-item { position: relative; } -.text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.session-menu-btn { padding: 0 2px !important; min-width: 20px; height: 20px; display: inline-flex !important; align-items: center; justify-content: center; background: none !important; border-color: transparent !important; } -.session-menu-btn:hover { background: none !important; border-color: transparent !important; } -@media (max-width: 768px) { - .session-menu-btn { display: none !important; } - .item-drag-handle { display: none !important; } -} -.session-menu-btn svg { transition: transform 0.2s ease; } - -/* First-time swipe hint */ -.swipe-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 0.7rem; - color: var(--color-error, #f44); - opacity: 0.8; - transition: opacity 0.5s ease; - pointer-events: none; - display: flex; - align-items: center; - gap: 4px; - z-index: 2; -} -.swipe-hint-arrow { - animation: swipe-nudge 1s ease-in-out infinite; -} -@keyframes swipe-nudge { - 0%, 100% { transform: translateX(0); } - 50% { transform: translateX(-6px); } -} - -/* Utility classes */ -.muted { opacity: 0.5; } -.muted-sm { opacity: 0.35; font-size: 0.8em; } -.accent-link { color: var(--accent-primary, var(--color-accent)); cursor: pointer; font-size: 0.85em; } -.models-empty-state { text-align: center; padding: 12px 8px; line-height: 1.6; } - -/* Provider logo inside favorite dot */ -.provider-logo { - border: none !important; - background: none !important; - width: 14px !important; height: 14px !important; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.15s; -} -.provider-logo svg { width: 14px; height: 14px; display: block; } -.provider-logo:hover { opacity: 1 !important; transform: scale(1.2); } - -/* Hide session menu button until hover — use width:0 so it doesn't steal space from text */ -.list-item .hamburger { opacity: 0; width: 0; min-width: 0; overflow: hidden; padding: 0 !important; transition: opacity 0.15s, width 0.15s, padding 0.15s; flex-shrink: 0; display: flex; align-items: center; justify-content: center; } -.list-item:hover .hamburger { opacity: 1; width: 24px; min-width: 24px; padding: 0 4px !important; } -@media (max-width: 768px) { - .list-item .hamburger { opacity: 0.5; width: 28px; min-width: 28px; padding: 0 4px !important; } - .list-item .hamburger:active { opacity: 1; } -} - -/* Hamburger menu button styling (overrides default button appearance) */ -button.hamburger { - background: none; - border: none; - padding: 0; - cursor: pointer; -} - -/* ============================================ */ -/* HEADER SIZING FOR CHAT MESSAGES */ -/* ============================================ */ -/* Markdown in chat messages — colorful, scannable */ -.msg h1, .msg h2, .msg h3, .msg h4, .msg h5, .msg h6 { - margin: 0.6em 0 0.3em 0; - line-height: 1.3; - border-bottom: none; - padding-bottom: 0; -} - -.msg h1 { - font-size: 1.15em; - font-weight: 700; - color: var(--hl-keyword, #c678dd); -} - -.msg h2 { - font-size: 1.1em; - font-weight: 600; - color: var(--hl-function, #5b8def); -} - -.msg h3 { - font-size: 1.05em; - font-weight: 600; - color: var(--hl-string, #98c379); -} - -.msg h4 { - font-size: 1.02em; - font-weight: 600; - color: var(--hl-builtin, #e5c07b); -} - -.msg h5 { - font-size: 1em; - font-weight: 600; - color: var(--hl-variable, #61afef); -} - -.msg h6 { - font-size: 0.95em; - font-weight: 600; - color: var(--hl-number, #d19a66); -} - -/* Bold text — subtle accent color */ -.msg strong, .msg b { - color: var(--hl-builtin, #e5c07b); - font-weight: 600; -} - -/* Italic — softer highlight */ -.msg em, .msg i { - color: var(--hl-params, #abb2bf); - font-style: italic; -} - -/* Bold + italic */ -.msg strong em, .msg em strong, -.msg b i, .msg i b, -.msg b em, .msg em b, -.msg strong i, .msg i strong { - color: var(--hl-keyword, #c678dd); -} - -/* Strikethrough */ -.msg del { - color: color-mix(in srgb, var(--fg) 45%, transparent); - text-decoration: line-through; -} - -/* Inline code */ -.msg code:not(pre code) { - color: var(--hl-string, #98c379); - background: color-mix(in srgb, var(--fg) 6%, transparent); - padding: 1px 5px; - border-radius: 4px; - font-size: 0.9em; -} - -/* Blockquotes */ -.msg blockquote { - border-left: 3px solid var(--hl-function, #5b8def); - padding: 4px 12px; - margin: 0.5em 0; - color: color-mix(in srgb, var(--fg) 75%, transparent); - background: var(--bg); - border-radius: 0 6px 6px 0; -} -.msg blockquote p { margin: 0.3em 0; } - -/* Horizontal rules */ -.msg hr { - border: none; - height: 1px; - background: linear-gradient(90deg, transparent, var(--border), transparent); - margin: 0.8em 0; -} - -/* Lists */ -.msg ul, .msg ol { - margin: 0.3em 0 0.3em 1.2em; - padding: 0; -} -.msg li { - margin: 0.15em 0; -} -.msg li::marker { - color: var(--hl-function, #5b8def); -} - -/* Links */ -.msg a { - color: var(--hl-function, #5b8def); - text-decoration: none; - border-bottom: 1px solid rgba(91, 141, 239, 0.3); - transition: border-color 0.15s; -} -.msg a:hover { - border-bottom-color: var(--hl-function, #5b8def); -} -.msg a.is-loading { - opacity: 0.65; - border-bottom-color: var(--accent, #bd93f9); - cursor: progress; -} - -/* Tables */ -.msg table { - border-collapse: collapse; - margin: 0.5em 0; - font-size: 0.9em; - display: block; - width: max-content; - min-width: 100%; - max-width: 100%; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - table-layout: auto; -} -.msg th { - background: color-mix(in srgb, var(--fg) 7%, transparent); - color: var(--hl-keyword, #c678dd); - font-weight: 600; - padding: 6px 12px; - border: 1px solid var(--border); - text-align: left; - min-width: 9ch; - word-break: normal; - overflow-wrap: break-word; -} -.msg td { - padding: 5px 12px; - border: 1px solid var(--border); - min-width: 9ch; - word-break: normal; - overflow-wrap: break-word; -} - -/* Agent UI Styling */ -.agent-controls { - background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 12px; - margin-bottom: 12px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.agent-toggle label { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-weight: 500; - margin-bottom: 8px; -} - -#workflow-selector { - margin-top: 8px; -} - -#workflow-type { - width: 100%; - padding: 6px 12px; - border: 1px solid #ced4da; - border-radius: 4px; - background: white; - font-size: 14px; -} - -.agent-progress { - background: #ffebee; - border: 1px solid #ef9a9a; - border-radius: 6px; - padding: 12px; - margin: 8px 0; - text-align: center; -} - -.agent-working { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - font-style: italic; - color: var(--red); -} - -.loading-dots::after { - content: '...'; - animation: dots 1.5s infinite; -} - -@keyframes dots { - 0%, 20% { opacity: 0; } - 40% { opacity: 0.5; } - 60%, 100% { opacity: 1; } -} - -.workflow-info { - background: #f8f9fa; - border: 1px solid var(--red); - border-radius: 6px; - padding: 8px 12px; - margin: 4px 0; - font-size: 0.9em; - color: var(--red); - text-align: center; -} - -/* Scrollbar uses --red from :root (set at top of file) */ - -/* Loading spinner */ -@keyframes spin { - to { transform: rotate(360deg); } -} -.spinner { - width: 24px; - height: 24px; - margin: 8px auto; - border: 3px solid var(--border); - border-top-color: var(--red); - border-radius: 50%; - animation: spin 0.9s linear infinite; -} - -/* Inline spinner for buttons */ -.btn-spinner { - display: inline-block; - width: 12px; - height: 12px; - border: 2px solid transparent; - border-top-color: currentColor; - border-radius: 50%; - animation: spin 1s linear infinite; - margin-right: 6px; -} - -/* Loading indicator for messages */ -.loading-indicator { - display: flex; - align-items: center; - justify-content: center; - padding: 10px; -} -.loading-dots { - display: flex; - gap: 4px; -} -.loading-dot { - width: 6px; - height: 6px; - background-color: var(--fg); - border-radius: 50%; - opacity: 0.6; -} -.loading-dot:nth-child(1) { - animation: loading-bounce 1.4s infinite ease-in-out both; -} -.loading-dot:nth-child(2) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.32s; -} -.loading-dot:nth-child(3) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.64s; -} -@keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } -} - -/* Hamburger menu button */ -.hamburger { - display: inline-flex; - flex-direction: column; - justify-content: space-between; - width: 24px; - height: 18px !important; - background: none; - border: none; - padding: 0; - cursor: pointer; -} -.hamburger span { - display: block; - width: 100%; - height: 3px; - background: var(--fg); - border-radius: 2px; -} - -/* Agent indicator */ -#agent-indicator { - position: fixed; - top: 20px; - right: 20px; - background: var(--sidebar-bg, var(--panel)); - color: var(--fg); - border: 1px solid var(--border); - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - display: none; - z-index: 100; - cursor: pointer; - transition: all 0.2s ease; -} -#agent-indicator.active { - display: block; - border-color: var(--color-agent-active); - box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); -} -#agent-indicator:hover { - border-color: var(--color-agent-active); - background: var(--panel); -} - -/* Preset buttons */ -.preset-btn { - height: 27.2px; - padding: 0 8.5px; - margin-left: 4px; - border: 1px solid var(--red); - border-radius: 4px; - background: var(--sidebar-bg, var(--panel)); - color: var(--fg); - font-family: inherit; - font-size: 10.2px; - cursor: pointer; - transition: all 0.2s ease; -} - -.preset-btn:hover { - background: var(--panel); - border-color: var(--fg); -} - -.preset-btn.active { - background: var(--panel); - border-color: var(--red); - box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); - font-weight: 600; -} - -/* Custom preset modal — inherits from .modal base class */ - -/* Unified chat input area */ -.chat-input-area { - display: flex; - flex-direction: column; - gap: 8px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin-top: 12px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.chat-controls-row { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; -} - -.chat-controls-left { - display: flex; - align-items: center; - gap: 8px; -} - -.chat-controls-right { - display: flex; - align-items: center; - gap: 8px; -} - -.control-group { - display: flex; - align-items: center; - gap: 4px; -} - -.control-label { - font-size: 11px; - color: var(--fg); - opacity: 0.8; -} - -.toggle-switch { - position: relative; - display: inline-block; - width: 30px; - height: 16px; -} - -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; -} - -.toggle-slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 8px; - transition: background 0.08s; -} - -.toggle-slider:before { - position: absolute; - content: ""; - height: 12px; - width: 12px; - left: 4px; - top: 2px; - background-color: var(--panel); - border-radius: 50%; - transition: transform 0.08s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); -} - -.toggle-switch input:checked + .toggle-slider { - background-color: var(--toggle-active, var(--red)); -} - -.toggle-switch input:checked + .toggle-slider:before { - transform: translateX(14px); -} - -.preset-buttons-row { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: wrap; -} - -.chat-input-form { - display: flex; - gap: 8px; - align-items: flex-end; -} - -#message { - flex: 1; - min-height: 34px; - max-height: 120px; - resize: none; - font-size: 13px !important; - overflow-y: auto !important; - line-height: 1.4 !important; - font-family: inherit !important; -} - -.action-button { - width: 34px; - height: 34px; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - margin: 0; - background: none; - border: 1px solid var(--border); - border-radius: 4px; - cursor: pointer; - color: var(--fg); - transition: all 0.2s ease; -} - -.action-button:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: var(--fg); -} - -.action-button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.action-button.recording { - background: var(--color-recording); - border-color: var(--color-recording); - animation: pulse 1.5s infinite; -} - -@keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } -} - -#stop-icon { - display: none; - width: 14px; - height: 14px; - background: var(--color-recording); - border-radius: 2px; -} - -/* Attachment strip — centered + max-width to match the chat-input-bar below, - otherwise the chip floats flush-left while the input is centered (visible on - desktop where the chat area is wider than 800px). */ -.attach-strip { - display: flex; - gap: 6px; - flex-wrap: wrap; - padding: 2px 8px; - max-width: 800px; - width: 100%; - margin-left: auto; - margin-right: auto; - box-sizing: border-box; -} -.attach-strip:empty { display: none; } - -/* Upload-in-progress feedback: the message bubble shows immediately, so while - the files are still uploading we put a whirlpool ON each attachment chip and - dim the chip's content — making it obvious that file is being sent, not stuck. */ -.attach-strip.attach-uploading .thumb { - position: relative; - pointer-events: none; -} -.attach-strip.attach-uploading .thumb > :not(.thumb-upload-spinner) { - opacity: 0.4; -} -.thumb-upload-spinner { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; - z-index: 3; -} - -.thumb { - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 11%, transparent); - padding: 3px 6px; - font-size: 12px; - display: flex; - gap: 6px; - align-items: center; - border-radius: 4px; - transition: all 0.2s ease; - max-width: 180px; -} -.thumb-img { - max-width: 60px; - max-height: 40px; - border-radius: 3px; - object-fit: cover; -} -.attach-image-preview { - margin: 4px 0; -} -.attach-image-preview img { - box-shadow: 0 1px 4px rgba(0,0,0,0.2); - /* Same border as the chat bubbles. */ - border: 1px solid var(--bubble-border, var(--border)); -} -/* Image chips: image fills the chip, X overlays as a corner accent badge. - Same on desktop and mobile — doc/text chips keep the beside-X layout. */ -.thumb.thumb-image { - position: relative; - padding: 0; -} -.thumb.thumb-image .thumb-img { - max-height: 56px; - display: block; -} -@media (max-width: 768px) { - .thumb.thumb-image .thumb-img { - cursor: pointer; - touch-action: manipulation; - } -} -.thumb.thumb-image button { - position: absolute; - /* Sit on the top-right corner edge as an accent badge. */ - top: -7px; - right: -7px; - width: 24px; - height: 24px; - min-width: 0; - padding: 0; - border: 2px solid var(--bg); - border-radius: 50%; - background: var(--accent-primary, var(--red)); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 15px; - line-height: 1; - z-index: 3; - transition: transform 0.12s ease, filter 0.12s ease, box-shadow 0.12s ease; -} -.thumb.thumb-image button:hover { - transform: scale(1.12); - filter: brightness(1.12); - box-shadow: 0 2px 8px rgba(0,0,0,0.25); -} -.thumb.thumb-image button:active { - transform: scale(0.96); -} - -.attach-crop-overlay { - position: fixed; - inset: 0; - z-index: var(--attach-crop-z, 100001); - background: rgba(0, 0, 0, 0.72); - display: flex; - align-items: center; - justify-content: center; - padding: 14px; - box-sizing: border-box; -} -.attach-crop-panel { - width: min(94vw, 520px); - max-height: min(86vh, 720px); - background: var(--panel, #111); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 18px 50px rgba(0,0,0,0.45); - overflow: hidden; - display: flex; - flex-direction: column; -} -.attach-crop-stage { - position: relative; - min-height: 280px; - height: min(66vh, 560px); - background: #050505; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - touch-action: none; -} -.attach-crop-img { - max-width: 100%; - max-height: 100%; - object-fit: contain; - user-select: none; - pointer-events: none; -} -.attach-crop-box { - position: absolute; - border: 2px solid var(--accent, var(--red)); - box-shadow: 0 0 0 9999px rgba(0,0,0,0.42); - cursor: move; - touch-action: none; - box-sizing: border-box; -} -.attach-crop-box::before, -.attach-crop-box::after { - content: ''; - position: absolute; - inset: 33.333% 0 auto; - border-top: 1px solid rgba(255,255,255,0.38); -} -.attach-crop-box::after { - inset: 66.666% 0 auto; -} -.attach-crop-handle { - position: absolute; - right: -9px; - bottom: -9px; - width: 20px; - height: 20px; - border-radius: 50%; - background: var(--accent, var(--red)); - border: 2px solid var(--bg); - box-sizing: border-box; - cursor: nwse-resize; -} -.attach-crop-actions { - display: flex; - gap: 8px; - padding: 10px; - background: color-mix(in srgb, var(--bg) 78%, transparent); -} -.attach-crop-btn { - flex: 1; - height: 34px; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.attach-crop-primary { - background: var(--accent, var(--red)); - color: #fff; - border-color: var(--accent, var(--red)); -} -@media (max-width: 768px) { - /* Collapsed "N files" badge: use the same corner-X accent badge as image thumbs. */ - .thumb-collapsed { position: relative; } - .thumb-collapsed .thumb-collapsed-x { - position: absolute; - top: -7px; - right: -7px; - width: 24px; - height: 24px; - min-width: 0; - padding: 0; - border: 2px solid var(--bg); - border-radius: 50%; - background: var(--accent-primary, var(--red)); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 15px; - line-height: 1; - z-index: 3; - opacity: 1; - } - /* Bigger remove-X tap target for non-image (doc/text) chips on mobile too. */ - .thumb button { - height: 28px; - min-width: 28px; - font-size: 15px; - } -} -.thumb span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.thumb:hover { - background: color-mix(in srgb, var(--fg) 16%, transparent); - border-color: var(--fg); -} - -.thumb button { - height: 24px; - padding: 0 5px; - font-size: 0.95em; - border-radius: 4px; - color: var(--accent-primary, var(--red)); -} - -.thumb-collapsed { - cursor: pointer; - color: var(--red); - border-color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - font-weight: 600; - gap: 8px; - border-radius: 999px; /* pill — rounder than the square file chips */ - padding-left: 12px; -} -.thumb-collapsed:hover { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.thumb-collapsed-label { white-space: nowrap; } -.thumb-collapsed-x { - height: 24px; padding: 0 7px; font-size: 13px; border-radius: 4px; - color: var(--accent-primary, var(--red)); - background: none; border: none; cursor: pointer; opacity: 0.6; -} -.thumb-collapsed-x:hover { opacity: 1; } - -/* Recording indicator */ -#recording-indicator { - position: fixed; - top: 10px; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin: 10px; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); -} - -#recording-indicator.hidden { - display: none !important; -} - -.recording-content { - display: flex; - align-items: center; - gap: 12px; - color: white; -} - -.recording-icon { - color: var(--color-recording); - font-size: 20px; - animation: pulse 1.5s infinite; -} - -.recording-text { - font-size: 16px; - font-weight: 500; -} - -.stop-recording-btn { - background: var(--color-recording); - color: white; - border: none; - border-radius: 6px; - padding: 6px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s ease; -} - -.stop-recording-btn:hover { - background: var(--color-recording-hover); -} - -#recording-indicator.error { - background: rgba(173, 26, 26, 0.9); -} - -.recording-error { - color: var(--color-recording); - font-size: 14px; - margin-top: 4px; -} - -/* Mermaid diagram containers */ -.mermaid-container { - margin: 12px 0; - padding: 16px; - background: color-mix(in srgb, var(--bg) 95%, var(--fg)); - border: 1px solid var(--border); - border-radius: 8px; - overflow-x: auto; - text-align: center; -} -.mermaid-container svg { max-width: 100%; height: auto; } - -/* KaTeX math overrides */ -.katex-display { margin: 0.8em 0; overflow-x: auto; overflow-y: hidden; } -.katex { font-size: 1.1em; } - -/* Hide thinking sections globally via settings toggle */ -body.hide-thinking .thinking-section { display: none !important; } - -/* Widen chat area via settings toggle */ -body.fullwidth-chat .chat-history { - padding-left: 0 !important; - padding-right: 12px !important; -} - -/* Thinking process styles — colors follow theme accent */ -.msg .body .stream-content { - width: 100%; -} -.thinking-section { - margin: 12px 0; - width: 100%; - max-width: 100%; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - overflow: hidden; - transition: all 0.3s ease; -} - -.thinking-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 6px 12px; - cursor: pointer; - user-select: none; - background: color-mix(in srgb, var(--red) 8%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - transition: background 0.2s ease; -} - -.thinking-header > .token-new { - display: none; -} - -.thinking-header > div:last-child { - flex-shrink: 0; -} - -.thinking-header:hover { - background: color-mix(in srgb, var(--red) 12%, transparent); -} - -.thinking-header-left { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9em; - color: var(--red); - font-weight: 500; - overflow: hidden; - min-width: 0; -} -.thinking-header-left span { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - transition: opacity 0.2s ease; -} - -.thinking-icon { - font-size: 1.1em; -} - -.thinking-toggle { - font-size: 0.9em; - color: var(--red); - transition: transform 0.3s ease; -} -.thinking-toggle::after { - content: '\25BC'; /* ▼ */ -} - -.thinking-toggle.expanded { - transform: rotate(180deg); -} - -.thinking-content { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; - padding: 0 12px; -} - -.thinking-content.expanded { - max-height: 300px; - overflow-y: auto; - padding: 12px; -} - -.thinking-content-inner { - font-size: 0.85em; - color: var(--fg); - opacity: 0.9; - line-height: 1.5; -} -.live-reply-content { - animation: fadeSlideIn 0.3s ease-out; -} -@keyframes fadeSlideIn { - from { opacity: 0; transform: translateY(4px); } - to { opacity: 1; transform: translateY(0); } -} - -/* Thinking indicator animation */ -.thinking-indicator { - display: flex; - align-items: center; - gap: 4px; - color: var(--red); - font-style: italic; - padding: 8px 0; -} - -.thinking-dots::after { - content: '...'; - animation: thinking-dots 1.5s infinite; - display: inline-block; - width: 20px; - text-align: left; -} - -@keyframes thinking-dots { - 0%, 20% { content: '.'; } - 40% { content: '..'; } - 60%, 100% { content: '...'; } -} - -.thinking-complete { - color: var(--red); - font-size: 0.9em; - padding: 4px 0; - opacity: 0.8; -} - -.research-context-continue-hint { - display: inline-flex; - align-items: center; - gap: 4px; - margin: 5px 2px 0; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - opacity: 0.55; -} -.research-context-continue-hint svg { - flex: 0 0 auto; - opacity: 0.8; -} - -/* ── Sources section — collapsible source citations ── */ -.sources-section { - margin: 8px 0 12px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - overflow: hidden; - transition: all 0.3s ease; -} -.sources-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 12px; - cursor: pointer; - background: color-mix(in srgb, var(--red) 8%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - transition: background 0.2s ease; - user-select: none; -} -.sources-header:hover { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.sources-header-left { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.85em; - font-weight: 500; - color: var(--red); -} -.sources-header-left svg { - width: 14px; - height: 14px; - flex-shrink: 0; - opacity: 0.7; -} -.sources-toggle { - font-size: 0.8em; - color: var(--red); - opacity: 0.7; - transition: none; -} -.sources-toggle::after { - content: '\25B6'; /* ▶ right arrow */ -} -.sources-toggle[data-arrow="down"]::after { - content: '\25BC'; /* ▼ down arrow */ -} -.sources-content { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; - padding: 0 10px; -} -.sources-content.expanded { - max-height: 3000px; - padding: 8px 10px; -} -.sources-content-inner { - display: flex; - flex-direction: column; - gap: 4px; -} -.source-link { - display: flex; - align-items: center; - gap: 8px; - padding: 5px 8px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - text-decoration: none; - color: var(--fg); - transition: background 0.15s ease; -} -.source-link:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.source-num { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 20px; - height: 20px; - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 15%, transparent); - color: var(--fg); - font-size: 0.7em; - font-weight: 600; - flex-shrink: 0; -} -.source-title { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 0.82em; -} -.source-domain { - font-size: 0.72em; - opacity: 0.45; - flex-shrink: 0; -} - -.ai-spinner { - color: var(--red); -} -/* Nudge the Tidy button 2px left. */ -#memory-tidy-btn { position: relative; left: -2px; } -/* Tidy button's whirlpool nudge — sits 1px lower so it visually centers on - the Tidy label baseline. */ -#memory-tidy-btn .ai-spinner-whirlpool, -#memory-tidy-btn .spinner-whirlpool { - position: relative; - top: 1px; -} -.list-item.stream-complete { - animation: none; -} -.session-run-state { - flex: 0 0 auto; - margin-left: 5px; - font-size: 10px; - line-height: 1; - color: var(--fg-dim, #888); - opacity: 0.9; -} -.session-run-state.is-working { - color: var(--accent, var(--red)); -} -.session-run-state .session-run-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 !important; - display: inline-flex; - align-items: center; -} -.session-run-state .session-run-whirlpool .ai-spinner-whirlpool { - display: inline-flex; - width: 12px; - height: 12px; -} -.session-run-state.is-done { - color: var(--color-success, #4caf50); - font-weight: 600; -} -.cookbook-notif-active svg { opacity: 1 !important; } - -/* Rail notification dot — pulsing indicator on icon-rail buttons */ -.icon-rail-btn.rail-notify { - opacity: 1 !important; - position: relative; -} -.icon-rail-btn.rail-notify::before { - content: ''; - position: absolute; - top: 5px; - right: 4px; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--accent, var(--red)); - animation: rail-notif-pulse 2s ease-in-out infinite; - z-index: 1; -} -.icon-rail-btn.rail-notify.rail-notify-success::before { - background: var(--color-success, #4caf50); -} -@keyframes rail-notif-pulse { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.4; transform: scale(0.8); } -} -@keyframes stream-complete-pulse { - 0%, 100% { box-shadow: none; } - 50% { box-shadow: inset 0 0 0 1.5px var(--accent); } -} - -/* ===== SLASH COMMAND RESPONSES ===== */ -.msg.msg-system { - padding: 6px 12px; margin: 4px auto 4px 8px; - max-width: 85%; background: none; border-left: 2px solid var(--border); -} -.msg.msg-system .body { padding: 0; } -.msg.msg-system pre { margin: 4px 0; white-space: pre-wrap; font-size: 0.85em; } -.dropdown-divider { - height: 1px; - margin: 3px 6px; - background: color-mix(in srgb, var(--border) 60%, transparent); -} -.msg.stream-done-toast { - cursor: pointer; - border-left-color: var(--accent, var(--border)); - border-radius: 6px; - background: color-mix(in srgb, var(--accent) 6%, transparent); - transition: background 0.15s; -} -.msg.stream-done-toast:hover { - background: color-mix(in srgb, var(--accent) 12%, transparent); -} -.msg.stream-done-toast .body { - display: flex; - align-items: center; - gap: 8px; -} -.stream-done-indicator { - font-family: monospace; - font-size: 1.1em; - line-height: 1; - color: var(--accent); - flex-shrink: 0; - animation: bar-pulse 1.2s ease-in-out infinite; -} -@keyframes bar-pulse { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } -} - -/* ===== AGENT MULTI-BUBBLE ===== */ -.msg.msg-tool { - display: none; /* legacy — hidden, replaced by agent-thread */ -} -.msg.msg-continuation { - margin-top: 2px; -} - -/* ===== AGENT THREAD TIMELINE ===== */ -.agent-thread { - position: relative; - margin: 2px 0 2px 28px; - padding: 4px 0 4px 22px; - max-width: calc(85% - 20px); - box-sizing: border-box; -} -.agent-thread::before { - content: ''; - position: absolute; - left: 5px; - top: 14px; - bottom: 14px; - width: 2px; - background: color-mix(in srgb, var(--red) 18%, transparent); - border-radius: 1px; -} -/* Extend line to connect to chat bubble above/below */ -.agent-thread.has-top::before { - top: -6.5px; -} -.agent-thread.has-bottom::before { - bottom: -5px; -} -.agent-thread.has-ask-user-bottom::before { - bottom: -12px; -} -/* Terminating dot at bottom when no bubble below and last node is expanded */ -.agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { - content: ''; - position: absolute; - /* Centre the 6px dot on the rail: the rail sits at left:5px + 2px wide, - so its centre is 6px from the thread's left edge and the dot's left - edge belongs at 3px — i.e. 3px - padding-left. Keep this in step with - .agent-thread-dot and with the mobile padding override below. */ - left: -19px; - bottom: 5px; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--red); - box-shadow: 0 0 4px 1px color-mix(in srgb, var(--red) 55%, transparent), - 0 0 0 3px color-mix(in srgb, var(--red) 18%, transparent); -} - -/* Synapse pulse — a bright dot traveling down the line */ -.agent-thread::after { - content: ''; - position: absolute; - left: 4px; - width: 4px; - height: 4px; - border-radius: 50%; - background: var(--red); - box-shadow: 0 0 3px 1px color-mix(in srgb, var(--red) 50%, transparent); - pointer-events: none; - top: 0%; - opacity: 0; -} -.agent-thread.streaming::after { - animation: synapse-capped-short 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-top::after { - animation: synapse-capped 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-bottom::after { - animation: synapse-travel-short 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-top.has-bottom::after { - animation: synapse-travel 0.8s ease-in-out infinite; -} -@keyframes synapse-travel { - 0% { top: 0%; opacity: 0; } - 5% { opacity: 0.5; } - 85% { opacity: 0.35; } - 100% { top: 100%; opacity: 0; } -} -@keyframes synapse-capped { - 0% { top: 0%; opacity: 0; } - 5% { opacity: 0.5; } - 70% { opacity: 0.35; top: calc(100% - 20px); } - 100% { opacity: 0; top: calc(100% - 20px); } -} -@keyframes synapse-travel-short { - 0% { top: 14px; opacity: 0; } - 5% { opacity: 0.5; } - 85% { opacity: 0.35; } - 100% { top: 100%; opacity: 0; } -} -@keyframes synapse-capped-short { - 0% { top: 14px; opacity: 0; } - 5% { opacity: 0.5; } - 70% { opacity: 0.35; top: calc(100% - 20px); } - 100% { opacity: 0; top: calc(100% - 20px); } -} -.agent-thread-node { - position: relative; - padding: 5px 0; -} -.agent-thread-node + .agent-thread-node { - margin-top: 2px; -} -/* Supervisor ladder cards — same chrome as tool cards but tinted so the - user can tell at a glance "this is the agent recovering" vs "this is - the agent doing work". Stop rung gets the red accent. */ -.agent-thread-node.supervisor-step .agent-thread-tool { - color: color-mix(in srgb, var(--accent, #c08a3e) 80%, var(--fg)); - font-style: italic; -} -.agent-thread-node.supervisor-step .agent-thread-dot { - background: color-mix(in srgb, var(--accent, #c08a3e) 60%, transparent); -} -.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-tool { - color: var(--red, #d65a5a); -} -.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-dot { - background: color-mix(in srgb, var(--red, #d65a5a) 60%, transparent); -} -.agent-thread-dot { - position: absolute; - left: -20px; - top: 10px; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--red); - border: 2px solid var(--bg); - z-index: 1; -} -.agent-thread-node.running .agent-thread-dot { - background: var(--red); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 25%, transparent); - animation: thread-pulse 1.5s ease-in-out infinite; - top: 10px; -} -@keyframes thread-pulse { - 0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 20%, transparent); } - 50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--red) 10%, transparent); } -} -.agent-thread-node.error .agent-thread-dot { - background: var(--color-error); -} -.agent-thread-header { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 70%, transparent); - user-select: none; - padding: 2px 0; -} -.agent-thread-header:hover { - color: var(--fg); -} -.agent-thread-icon { - font-size: 0.9em; - color: var(--red); -} -.agent-thread-tool-icon { - width: 14px; - height: 14px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - color: color-mix(in srgb, var(--fg) 64%, transparent); - opacity: 0.85; -} -.agent-thread-tool-icon svg { - display: block; - width: 12px; - height: 12px; -} -.agent-thread-tool-icon-link { - color: inherit; - text-decoration: none; - cursor: pointer; -} -.agent-thread-tool-icon-link:hover { - color: var(--red); - opacity: 1; -} -.agent-thread-node.error .agent-thread-icon { - color: var(--color-error); -} -.agent-thread-tool { - font-weight: 600; - color: var(--red); - text-transform: uppercase; - letter-spacing: 0.3px; - font-size: 0.9em; -} -.agent-thread-header-link { - width: 17px; - height: 17px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 17px; - margin-left: -3px; - color: var(--red); - opacity: 0.72; - text-decoration: none; - border: 0; - background: transparent; -} -.agent-thread-header-link:hover { - opacity: 1; - color: var(--red); -} -.agent-thread-header-link svg { - display: block; -} -.agent-thread-header-link-calendar { - transform: translateY(-2px); -} -.msg-ai .body a[href^="#event-"], -.msg-ai .body a[href^="#email-"], -.msg-ai .body a[href^="#document-"], -.msg-ai .body a[href^="#note-"], -.msg-ai .body a[href^="#task-"], -.msg-ai .body a[href^="#skill-"], -.msg-ai .body a[href^="#memory-"], -.msg-ai .body a[href^="#session-"] { - display: inline-flex; - align-items: baseline; - max-width: 100%; - padding: 1px 5px; - border-radius: 5px; - background: color-mix(in srgb, var(--red) 12%, transparent); - color: var(--red); - font-weight: 600; - text-decoration: none; - box-decoration-break: clone; - -webkit-box-decoration-break: clone; -} -.msg-ai .body a[href^="#event-"], -.msg-ai .body a[href^="#note-"], -.msg-ai .body a[href^="#skill-"], -.msg-ai .body a[href^="#memory-"], -.msg-ai .body a[href^="#session-"] { - font-size: 11px; - line-height: 1.35; -} -.msg-ai .body p:has(> .emoji:first-child):has(> a.chat-link[href^="#note-"]) { - margin-top: 0.15em; - margin-bottom: 0.15em; - line-height: 1.35; -} -.msg-ai .body a[href^="#event-"]:hover, -.msg-ai .body a[href^="#email-"]:hover, -.msg-ai .body a[href^="#document-"]:hover, -.msg-ai .body a[href^="#note-"]:hover, -.msg-ai .body a[href^="#task-"]:hover, -.msg-ai .body a[href^="#skill-"]:hover, -.msg-ai .body a[href^="#memory-"]:hover, -.msg-ai .body a[href^="#session-"]:hover { - background: color-mix(in srgb, var(--red) 18%, transparent); - text-decoration: underline; -} -.agent-thread-status { - font-size: 0.85em; - opacity: 0.5; -} -.agent-thread-chevron { - width: 12px; - height: 12px; - flex: 0 0 12px; - display: inline-grid; - place-items: center; - opacity: 0.4; -} -.agent-thread-chevron::before { - content: ""; - width: 0; - height: 0; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 6px solid currentColor; - transform-origin: 50% 50%; - transition: transform 0.2s ease; -} -.agent-thread-node.open .agent-thread-chevron::before { - transform: rotate(90deg); -} -.agent-thread-node.browser-preview-node .agent-thread-chevron { - display: none; -} -.agent-thread-wave { - font-family: monospace; - font-size: 0.85em; - color: var(--red); - letter-spacing: -1px; -} -/* Live "cooking" timer on a running tool — prominent (accent, tabular) so a - long-running command always reads as alive, not frozen. */ -.agent-thread-elapsed { - margin: 0 6px 0 4px; - font-size: 11px; - font-weight: 600; - font-variant-numeric: tabular-nums; - color: var(--accent, var(--red)); -} -/* Stall watchdog banner — shown when the stream has been silent for ~1min. */ -.stall-banner { - display: flex; - align-items: center; - gap: 8px; - margin: 8px auto; - padding: 8px 12px; - max-width: 90%; - font-size: 12px; - border-radius: 8px; - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, var(--bg)); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 40%, transparent); -} -.stall-banner-txt { flex: 1; opacity: 0.85; } -.stall-banner-btn { - font-size: 11px; - font-weight: 600; - padding: 4px 10px; - border-radius: 6px; - border: none; - background: var(--accent, var(--red)); - color: #fff; - cursor: pointer; - flex-shrink: 0; -} -.stall-banner-stop { - background: none; - color: var(--fg); - border: 1px solid var(--border); -} -.agent-thread-content { - display: none; - padding: 4px 0 2px 0; - overflow-x: auto; - overflow-y: hidden; -} -.agent-thread-node.open .agent-thread-content { - display: block; -} -.agent-thread-cmd { - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - padding: 8px 12px; - margin: 4px 0; - color: var(--fg); - font-size: 0.85em; - white-space: pre-wrap; - word-break: break-word; - line-height: 1.4; - overflow-x: auto; -} -.agent-thread-summary { - display: inline-flex; - align-items: center; - gap: 6px; - max-width: 100%; - margin: 4px 0; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 26%, var(--border)); - border-radius: 6px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - color: color-mix(in srgb, var(--fg) 84%, var(--accent, var(--red))); - font-size: 12px; - font-variant-numeric: tabular-nums; - line-height: 1.35; -} -.agent-thread-summary-link { - cursor: pointer; - text-decoration: none; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-thread-summary-link:hover { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} - -.agent-image-progress { - margin: 6px 0 0; -} -.agent-image-progress-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - font-size: 11px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 72%, transparent); -} -.agent-image-progress-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-image-progress-value { - flex-shrink: 0; - font-variant-numeric: tabular-nums; - color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); -} -.agent-image-progress-track { - display: none !important; -} - -.private-browser-preview { - width: 100%; - max-height: min(300px, 36vh); - margin: 6px 0 2px; - display: flex; - flex-direction: column; - overflow: hidden; - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - color: var(--fg); - box-shadow: none; -} -.private-browser-preview-header { - height: 32px; - min-height: 32px; - display: flex; - align-items: center; - gap: 8px; - padding: 0 6px 0 10px; - border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); - background: color-mix(in srgb, var(--red) 10%, transparent); - font-size: 0.85em; - font-weight: 500; - cursor: pointer; -} -.private-browser-preview-title { - flex-shrink: 0; - color: var(--red); - font-size: inherit; - font-weight: inherit; - line-height: 1; - letter-spacing: 0; -} -.private-browser-preview-status { - min-width: 0; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-size: inherit; - line-height: 1; -} -.private-browser-preview-fold { - width: 18px; - height: 18px; - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - left: 2px; - flex-shrink: 0; - padding: 0; - border: 0; - border-radius: 0; - background: transparent; - color: var(--red); - cursor: pointer; - font-size: 0.95em; - font-family: inherit; - font-weight: 500; - line-height: 1; - text-shadow: none; - -webkit-text-stroke: 0; -} -.private-browser-preview-fold:hover { - background: transparent; - color: var(--red); -} -.private-browser-preview-frame { - position: relative; - min-height: 130px; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - background: color-mix(in srgb, #000 72%, var(--bg)); - box-shadow: - inset 0 1px 0 color-mix(in srgb, #fff 8%, transparent), - inset 0 0 0 1px color-mix(in srgb, #000 35%, transparent), - 0 8px 18px color-mix(in srgb, #000 18%, transparent); -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame { - cursor: zoom-in; -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame::after { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - opacity: 0.55; - transition: opacity 150ms ease; - background: - radial-gradient(circle at center, transparent 42%, color-mix(in srgb, #000 30%, transparent) 100%), - color-mix(in srgb, var(--bg) 10%, transparent); -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame:hover::after { - opacity: 0.9; -} -.private-browser-preview.folded .private-browser-preview-frame { - display: none; -} -.private-browser-preview.folded .private-browser-preview-header { - border-bottom: 0; -} -.private-browser-preview-frame img { - width: 100%; - height: auto; - max-height: calc(min(300px, 36vh) - 32px); - display: block; - object-fit: contain; - transform: scale(1.1); - transform-origin: center center; - animation: private-browser-image-in 180ms ease-out both; -} -.private-browser-preview-frame img:not([src]), -.private-browser-preview-frame img[src=""] { - display: none; -} -.private-browser-preview-empty { - padding: 22px 14px; - width: auto; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 7px; - color: color-mix(in srgb, var(--fg) 48%, transparent); - font-size: 11px; - line-height: 1.35; -} -.private-browser-preview-spinner { - width: 13px; - height: 13px; - flex: 0 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.private-browser-preview-spinner .spinner-whirlpool, -.private-browser-preview-spinner .ai-spinner-whirlpool { - margin: 0 !important; -} -@keyframes private-browser-image-in { - from { opacity: 0; transform: scale(1.08); } - to { opacity: 1; transform: scale(1.1); } -} - -@media (max-width: 700px) { - .private-browser-preview-frame img { - max-height: calc(36vh - 32px); - } -} - -/* Mobile: constrain thread content */ -@media (max-width: 768px) { - .agent-thread { - margin-left: 16px; - padding-left: 18px; - max-width: calc(90% - 16px); - } - .agent-thread-cmd { - font-size: 0.78em; - padding: 6px 8px; - max-width: 100%; - overflow-x: auto; - } - .agent-thread-content { - max-width: calc(100vw - 90px); - } - .agent-tool-output pre { - font-size: 0.8em; - max-width: 100%; - overflow-x: auto; - } - .agent-thread-header { - font-size: 0.8em; - } - .agent-thread-dot { - left: -16px; - top: 10px; - } - /* padding-left drops to 18px here, so the terminating dot needs its own - offset (3px - 18px) to stay centred on the rail. */ - .agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { - left: -15px; - } -} - -/* ===== AGENT TOOL OUTPUT (inside thread nodes) ===== */ -.agent-tool-output { - margin-top: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 8px; - overflow: hidden; -} -.agent-tool-output summary { - color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); - cursor: pointer; - font-size: 0.85em; - user-select: none; - padding: 6px 10px; - font-weight: 500; - /* Chevron on the right (like the thinking section) instead of the default - left-side disclosure triangle. */ - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - list-style: none; -} -.agent-tool-output summary::-webkit-details-marker { display: none; } -/* File-write diff — neutral chrome (not the red error tint) + colored lines */ -.agent-tool-diff { - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.agent-tool-diff summary { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-bottom-color: color-mix(in srgb, var(--fg) 12%, transparent); -} -.agent-tool-diff .diff-stat { - font-weight: 600; - opacity: 0.7; - font-family: var(--mono, monospace); -} -/* Collapsed diff summary: filename + +adds/−dels (theme green/red). */ -.agent-tool-diff summary { - display: flex; - align-items: center; - gap: 8px; -} -.agent-tool-diff .diff-file { - font-family: var(--mono, monospace); - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-tool-diff .diff-summary-stats { - margin-left: auto; - font-family: var(--mono, monospace); - font-weight: 600; - flex-shrink: 0; -} -.agent-tool-diff .diff-summary-stats .diff-stat-add { color: var(--green, #2ecc71); } -.agent-tool-diff .diff-summary-stats .diff-stat-del { color: var(--red, #e74c3c); } -.agent-tool-diff .diff-summary-stats .diff-stat-new { color: var(--accent, var(--red)); opacity: 0.85; } -.diff-pre { - margin: 0; - padding: 8px 10px; - overflow-x: auto; - font-family: var(--mono, monospace); - font-size: 0.82em; - line-height: 1.45; -} -.diff-pre span { display: block; white-space: pre; } -.diff-pre .diff-add { background: color-mix(in srgb, #2ecc71 22%, transparent); } -.diff-pre .diff-del { background: color-mix(in srgb, #e74c3c 22%, transparent); } -.diff-pre .diff-hunk { color: var(--accent); opacity: 0.85; } -.diff-pre .diff-meta { opacity: 0.55; } -.diff-pre .diff-ctx { opacity: 0.8; } -/* Suppress the global `summary::before { content: '▶' }` left arrow — this - section uses a right-side chevron instead. */ -.agent-tool-output summary::before { content: none; } -.agent-tool-output summary::after { - content: '\25BC'; /* ▼ */ - color: var(--red); - font-size: 0.9em; - transition: transform 0.3s ease; -} -.agent-tool-output[open] > summary::after { - transform: rotate(180deg); -} -.agent-tool-output summary:hover { - background: color-mix(in srgb, var(--red) 15%, transparent); -} -.agent-thinking-dots .ai-spinner { - font-size: 12px; - letter-spacing: 0.5px; -} - -.agent-tool-output[open] { - background: color-mix(in srgb, var(--red) 6%, transparent); -} -.agent-tool-output[open] > :not(summary) { - animation: detail-reveal 0.25s ease-out both; -} -@keyframes detail-reveal { - from { opacity: 0; transform: translateY(-4px); } - to { opacity: 1; transform: translateY(0); } -} -.agent-tool-output pre { - background: transparent; - border: none; - border-radius: 0; - padding: 10px 14px; - margin-top: 0; - max-height: 300px; - overflow-y: auto; - font-size: 0.95em; - color: var(--fg); - opacity: 0.85; - white-space: pre-wrap; - word-break: break-all; - line-height: 1.5; -} - -/* Mobile responsive styles */ -@media (max-width: 768px) { - #recording-indicator { - margin: 8px; - padding: 10px; - } - - .recording-text { - font-size: 14px; - } - - .stop-recording-btn { - padding: 6px 10px; - font-size: 12px; - } -} -/* ===== PANE STYLES (shared by compare) ===== */ - -.pane-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 0 4px; - border-bottom: 1px solid var(--border); -} -.close-split-btn, -.pane-close-btn { - background: none; - border: none; - color: var(--color-error); - font-size: 18px; - cursor: pointer; - padding: 0 4px; - line-height: 1; - opacity: 0; - transition: opacity 0.15s; -} -.pane-header:hover .close-split-btn, -.pane-header:hover .pane-close-btn { - opacity: 1; -} -.close-split-btn:hover, -.pane-close-btn:hover { - color: var(--color-error-light); -} - -/* Compare pane close controls use the same shared circular close treatment - as modal/title close buttons. */ -.compare-pane .pane-close-btn.close-btn { - width: 24px; - height: 24px; - min-width: 24px; - padding: 0; - border: 1px solid transparent; - border-radius: 999px; - background: transparent; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 0; - opacity: 0.5; - display: inline-flex; - align-items: center; - justify-content: center; - transition: background 0.15s, color 0.15s, opacity 0.15s; -} -.compare-pane .pane-close-btn.close-btn:hover, -.compare-pane .pane-close-btn.close-btn:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); - opacity: 1; - outline: none; -} -.compare-pane .pane-action-btn[data-action="expand"] { - width: 24px; - height: 24px; - min-width: 24px; - padding: 0; - border: 1px solid transparent; - border-radius: 999px; - justify-content: center; - transition: background 0.15s, color 0.15s, opacity 0.15s; -} -.compare-pane .pane-action-btn[data-action="expand"]:hover, -.compare-pane .pane-action-btn[data-action="expand"]:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); - opacity: 1; - outline: none; -} - - -/* ============================================ */ -/* RESEARCH DETAILS EXPANDABLE SECTION - UPDATED */ -/* ============================================ */ - -/* Style the details element */ -details { - background: color-mix(in srgb, var(--hl-string) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--hl-string) 30%, transparent); - border-radius: 8px; - margin: 12px 0; - padding: 0; - overflow: hidden; - transition: all 0.3s ease; -} - -details[open] { - background: color-mix(in srgb, var(--hl-string) 8%, transparent); -} -details[open] > :not(summary) { - animation: detail-reveal 0.25s ease-out both; -} - -/* Style the summary (clickable header) - NO CURSIVE, NORMAL SIZE */ -summary { - cursor: pointer; - padding: 10px 14px; - background: color-mix(in srgb, var(--hl-string) 10%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--hl-string) 20%, transparent); - font-weight: 500; - font-size: 0.95em; - font-style: normal; - font-family: inherit; - color: var(--hl-string); - user-select: none; - list-style: none; - display: flex; - align-items: center; - gap: 8px; - transition: background 0.2s ease; -} - -summary:hover { - background: color-mix(in srgb, var(--hl-string) 15%, transparent); -} - -/* Add custom arrow */ -summary::before { - content: '▶'; - display: inline-block; - transition: transform 0.3s ease; - font-size: 0.75em; /* Smaller arrow */ -} - -details[open] summary::before { - transform: rotate(90deg); -} - -/* Hide default marker in webkit browsers */ -summary::-webkit-details-marker { - display: none; -} - -/* Style the content inside details - SMALLER, MORE COMPACT */ -details > div, -details > p, -details > ul, -details > ol { - padding: 12px 14px; /* Less padding */ - animation: fadeIn 0.3s ease; - font-size: 0.9em; /* Smaller text */ - line-height: 1.5; -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Style research findings inside details - SMALLER HEADINGS */ -details h3 { - margin-top: 12px; - margin-bottom: 6px; - color: var(--hl-string); - font-size: 0.95em; - font-weight: 500; -} - -details h4 { - margin-top: 10px; - margin-bottom: 5px; - color: var(--hl-string); - font-size: 0.9em; - font-weight: 500; -} - -details ul { - margin-left: 18px; - margin-bottom: 10px; -} - -details li { - margin-bottom: 5px; - line-height: 1.4; - font-size: 0.85em; /* Smaller list items */ -} - -details strong { - color: var(--hl-string); - font-weight: 500; -} - -details a { - color: var(--accent); - text-decoration: none; - transition: color 0.2s ease; -} - -details a:hover { - color: var(--color-link-hover); - text-decoration: underline; -} - -/* Research metadata - SMALLER */ -details .research-meta { - font-size: 0.8em; - color: var(--fg); - opacity: 0.8; - margin-top: 4px; -} - -/* Source links - SMALLER */ -details .source-link { - display: inline-block; - margin-top: 3px; - padding: 2px 5px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); - border-radius: 4px; - font-size: 0.68em; - color: var(--accent); -} - -details .source-link:hover { - background: color-mix(in srgb, var(--accent) 20%, transparent); - border-color: var(--accent); -} - -/* Research report links - clean and subtle */ -details a { - color: var(--accent); - text-decoration: none; - font-weight: normal; /* Remove bold */ - font-size: 0.85em; - opacity: 0.9; - transition: all 0.2s ease; - word-break: break-all; /* Break long URLs nicely */ -} - -details a:hover { - color: var(--color-link-hover); - text-decoration: underline; - opacity: 1; -} - -/* ============================================ */ -/* CUSTOM SYNTAX HIGHLIGHTING - Theme-Reactive */ -/* ============================================ */ -.hljs { - background: var(--code-bg, var(--hl-bg, var(--panel))); - color: var(--code-fg, var(--hl-fg, var(--fg))); - padding: 8px; - border-radius: 4px; -} - -/* Keywords & control flow — purple/magenta */ -.hljs-keyword, -.hljs-selector-tag { color: var(--hl-keyword); } - -/* Strings & regex — warm yellow/orange */ -.hljs-string, -.hljs-regexp, -.hljs-addition { color: var(--hl-string); } - -/* Comments & docs — muted. font-style intentionally omitted: italics shift - glyph widths in the highlight overlay relative to the transparent textarea - above it, which makes the caret drift away from the visible character. */ -.hljs-comment, -.hljs-quote, -.hljs-meta { color: var(--hl-comment); } - -/* Functions & method names — blue */ -.hljs-function, -.hljs-title, -.hljs-title.function_, -.hljs-section { color: var(--hl-function); } - -/* Numbers & constants — distinct from strings */ -.hljs-number, -.hljs-literal { color: var(--hl-number, var(--hl-string)); } - -/* Built-ins & types — teal/cyan tint */ -.hljs-built_in, -.hljs-type, -.hljs-class, -.hljs-title.class_ { color: var(--hl-builtin, var(--hl-function)); } - -/* Variables & identifiers — fg with slight distinction */ -.hljs-variable, -.hljs-template-variable, -.hljs-attr { color: var(--hl-variable, var(--hl-fg, var(--fg))); } - -/* Operators & punctuation — slightly dimmed fg */ -.hljs-operator, -.hljs-punctuation { color: var(--hl-fg, var(--fg)); opacity: 0.8; } - -/* Parameters */ -.hljs-params { color: var(--hl-params, var(--hl-fg, var(--fg))); } - -/* Property access, attributes in HTML/XML */ -.hljs-property, -.hljs-selector-class, -.hljs-selector-id { color: var(--hl-variable, var(--hl-function)); } - -/* Tags (HTML) */ -.hljs-tag { color: var(--hl-keyword); } -.hljs-name { color: var(--hl-keyword); } - -/* Deletion/diff */ -.hljs-deletion { color: var(--red); } - -/* Symbol, special */ -.hljs-symbol { color: var(--hl-string); } -.hljs-link { color: var(--hl-function); text-decoration: underline; } - -/* Emphasis — applied globally (chat messages, rendered markdown in docs - preview, etc.) but explicitly NEUTRALIZED in the doc editor's overlay so - the textarea-and-overlay caret alignment stays bit-perfect. Bold / italic - shift glyph widths and that drifts the click-to-row mapping. */ -.hljs-emphasis { font-style: italic; } -.hljs-strong { font-weight: bold; } -.doc-editor-highlight .hljs-emphasis, -.doc-editor-highlight .hljs-strong { - font-style: normal !important; - font-weight: inherit !important; -} diff --git a/static/css/style-part-04.css b/static/css/style-part-04.css deleted file mode 100644 index ce6a9add4..000000000 --- a/static/css/style-part-04.css +++ /dev/null @@ -1,6011 +0,0 @@ -/* Fragment 5/9 of the former static/style.css. - * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. - * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. - */ -.admin-ep-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} - -/* Status messages */ -.admin-error { color: var(--color-error); font-size: 11px; margin-top: 4px; } -.admin-success { color: var(--color-success); font-size: 11px; margin-top: 4px; } -.admin-empty { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 12px; - padding: 10px 0; - text-align: center; -} -/* Endpoint-list empty states ("No endpoints configured" / "None") get a - left-aligned, accent-colored treatment so they read as "this row is your - placeholder, click Add" rather than a centered "nothing here" grey blob. */ -#adm-epList-local .admin-empty, -#adm-epList-api .admin-empty { - text-align: left; - padding: 8px 4px; - color: var(--accent-primary, var(--accent, var(--red))); -} - -/* RAG upload zone */ -.admin-rag-upload-zone { - border: 2px dashed var(--border); - border-radius: 8px; - padding: 14px; - text-align: center; - cursor: pointer; - transition: border-color 0.2s, background 0.2s; - margin-bottom: 8px; -} -.admin-rag-upload-zone:hover, -.admin-rag-upload-zone.dragover { - border-color: var(--red); - background: color-mix(in srgb, var(--red) 7%, transparent); -} -.admin-rag-upload-zone p { - color: color-mix(in srgb, var(--fg) 50%, transparent); - font-size: 11px; - margin-top: 4px; -} -.admin-rag-icon { font-size: 18px; } -.admin-rag-dir-row { - display: flex; - gap: 6px; - margin-bottom: 8px; -} -.admin-rag-dir-row input { - flex: 1; - padding: 5px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-rag-dir-row input:focus { outline: none; border-color: var(--red); } -.admin-rag-dir-row button { - padding: 5px 8px; - border: none; - border-radius: 6px; - background: var(--red); - color: #fff; - cursor: pointer; - font-size: 12px; - white-space: nowrap; -} -.admin-rag-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: 6px; -} -.admin-rag-section-label { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin: 8px 0 4px; -} -.admin-rag-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 8px; - background: var(--bg); - border-radius: 6px; - margin-bottom: 4px; - font-size: 12px; -} -.admin-rag-item-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--fg); -} -.admin-rag-item-meta { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 10px; - margin: 0 6px; - flex-shrink: 0; -} -.admin-rag-status { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-top: 6px; -} - -/* Token reveal */ -.admin-token-reveal { - margin-top: 8px; - padding: 8px; - background: color-mix(in srgb, var(--red) 8%, var(--bg)); - border: 1px solid color-mix(in srgb, var(--red) 25%, var(--border)); - border-radius: 8px; -} - -/* Selects in admin cards */ -.admin-card select { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-card input { - font-family: inherit; - font-size: 12px; -} -.admin-card label { - font-size: 12px; -} - -/* ---- Document Library ---- */ - -.doclib-modal-content { - width: min(600px, 92vw); - max-height: 85vh; - font-size: 12px; - background: var(--bg); -} -/* Anchor doclib modal to top so only the bottom edge moves on resize */ -#doclib-modal { - align-items: flex-start; - padding-top: 8vh; -} -.doclib-modal-content .modal-header h4 { - font-size: 1rem; -} -/* The "open in new tab" email modal inherited the 1rem doclib header, which - read way bigger than the email subject in the regular (inline) reader. Pin - it smaller so the two views are consistent. */ -.email-reader-tab-modal .modal-header h4 { - font-size: 13px; - font-weight: 600; -} -/* Match the sticky modal-header to the doclib modal-content body color - (var(--bg)) instead of the global var(--panel) default — otherwise the - header reads as a darker stripe above the email list. */ -.doclib-modal-content > .modal-header { - background: var(--bg); -} -.doclib-stats { - font-size: 11px; - color: var(--fg-muted); - margin-bottom: 8px; -} -.doclib-toolbar { - display: flex; - gap: 8px; - margin-bottom: 8px; - align-items: center; -} -.doclib-search { - flex: 1; - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - outline: none; - transition: border-color 0.15s; -} -#archive-select-btn { height: auto; padding: 5px 8px; } -.doclib-search:focus { - border-color: var(--red); -} -.doclib-sort { - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - outline: none; - cursor: pointer; -} -.doclib-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 8px; -} -.doclib-chip { - padding: 2px 10px; - border-radius: 12px; - font-size: 10px; - border: 1px solid var(--border); - background: transparent; - color: var(--fg-muted); - cursor: pointer; - user-select: none; - transition: background 0.15s, border-color 0.15s; -} -.doclib-chip:hover { - border-color: var(--red); -} -.doclib-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Document library — language chip row */ -.doclib-lang-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - padding: 2px 0; - max-height: 52px; - overflow-y: auto; -} -.doclib-lang-chips:empty { display: none; } -/* Mobile: keep tag chips on ONE row and scroll horizontally instead of wrapping - into a tall multi-line block (Serve, library — anywhere .doclib-lang-chips is used). */ -@media (max-width: 768px) { - .doclib-lang-chips { - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - max-height: none; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .doclib-lang-chips::-webkit-scrollbar { display: none; } - .doclib-lang-chips > * { flex-shrink: 0; } -} -#doclib-tidy-btn, #doclib-select-btn, #doclib-chats-tidy-btn { - position: relative; - top: -3px; -} -/* Document library — list layout */ -.doclib-grid { - display: flex; - flex-direction: column; - gap: 4px; - max-height: 400px; - overflow-y: auto; - padding: 0; - position: relative; -} - -/* Gallery grid domino-in cascade on open — same recipe as the document/email - libraries. Applied to #gallery-grid in gallery.js on the first render after - each open, removed after the longest delay so re-renders feel instant. */ -.gallery-just-opened > .gallery-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.gallery-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.gallery-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.gallery-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.gallery-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.gallery-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.gallery-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.gallery-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.gallery-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.gallery-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.gallery-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.gallery-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.gallery-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.gallery-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.gallery-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.gallery-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.gallery-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.gallery-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.gallery-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.gallery-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.gallery-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.gallery-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Tasks list domino-in cascade on open — mirrors gallery / doclib. Applied to - #tasks-list in tasks.js on the first render after each open, removed after - the longest delay so re-renders feel instant. */ -.tasks-just-opened > .task-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.tasks-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.tasks-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.tasks-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.tasks-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.tasks-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.tasks-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.tasks-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.tasks-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.tasks-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.tasks-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.tasks-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.tasks-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.tasks-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.tasks-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.tasks-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.tasks-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.tasks-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.tasks-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.tasks-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.tasks-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.tasks-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Document library cascade — same recipe as email library, applied per-tab - the first time content loads (chats / archive / research / documents). - Module-level Set in documentLibrary.js prevents re-firing on tab swaps - or filter/sort re-renders within the same page session. */ -.doclib-just-opened > .memory-item, -.doclib-just-opened > .doclib-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.doclib-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.doclib-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.doclib-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.doclib-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.doclib-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.doclib-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.doclib-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.doclib-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.doclib-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.doclib-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.doclib-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.doclib-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.doclib-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.doclib-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.doclib-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.doclib-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.doclib-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.doclib-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.doclib-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.doclib-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.doclib-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Domino cascade on first open of the email library — mirrors the sidebar - .section-just-expanded recipe so list items spring in staggered. Class - is applied to #email-lib-grid in emailLibrary.js on the first render - only, then removed after the longest delay so re-renders feel instant. */ -.email-lib-just-opened :is(.doclib-card, .date-section-header) { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(1) { animation-delay: 0.02s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(2) { animation-delay: 0.04s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(3) { animation-delay: 0.06s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(4) { animation-delay: 0.08s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(5) { animation-delay: 0.10s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(6) { animation-delay: 0.12s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(7) { animation-delay: 0.14s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(8) { animation-delay: 0.16s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(9) { animation-delay: 0.18s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(10) { animation-delay: 0.20s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(11) { animation-delay: 0.22s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(12) { animation-delay: 0.24s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(13) { animation-delay: 0.26s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(14) { animation-delay: 0.28s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(15) { animation-delay: 0.30s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(16) { animation-delay: 0.32s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(17) { animation-delay: 0.34s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(18) { animation-delay: 0.36s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(19) { animation-delay: 0.38s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(20) { animation-delay: 0.40s; } -/* Cap the cascade at 20 — beyond that they animate together so opening a - long inbox doesn't take forever to settle. */ -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(n+21) { animation-delay: 0.42s; } - -/* Inside the email library modal, the grid needs to grow with the modal — - but `flex: 1 1 0` collapses to 0 when the parent isn't height-constrained - (which is the non-fullscreen default). Use `auto` basis + a sensible - `min-height` floor so it shows ~400px naturally and absorbs more when - the modal is resized / fullscreened. */ -#email-lib-modal .doclib-grid { - max-height: none; - height: auto; - flex: 1 1 auto; - min-height: 400px; -} - -/* ── Mobile compose FAB (email) ── - Replaces the top-right "New" button on phones with a round mail-icon button - bottom-right that collapses to a circle while the list scrolls and springs - back out to "New" when scrolling stops. Desktop is unchanged. */ -.email-lib-fab { display: none; } -@media (max-width: 768px) { - /* The absolute FAB anchors to the email panel card. */ - #email-lib-modal .admin-card { position: relative; } - /* Hide the toolbar "New" button — the FAB is the mobile entry point. */ - #email-lib-compose-btn { display: none; } - - #email-lib-modal .email-lib-fab { - --fab-size: 56px; - display: flex; - align-items: center; - gap: 9px; - /* Hidden until the email list has rendered; JS adds .fab-revealed to pop - it in (scale-from-center). Avoids the button flashing at the top and - sliding down before _positionFab() places it. */ - transform: scale(0); - opacity: 0; - transform-origin: center center; - position: absolute; - right: calc(16px + env(safe-area-inset-right, 0px)); - bottom: calc(18px + env(safe-area-inset-bottom, 0px)); - height: var(--fab-size); - min-width: var(--fab-size); - padding: 0 20px 0 18px; - border: none; - border-radius: 999px; - background: var(--accent-primary, var(--red)); - color: #fff; - font-family: inherit; - font-size: 15px; - font-weight: 600; - line-height: 1; - cursor: pointer; - box-shadow: 0 6px 18px rgba(0,0,0,.38), 0 2px 6px rgba(0,0,0,.28); - z-index: 30; - pointer-events: auto; - /* This (base) transition governs the EXPAND — slower + graceful. */ - transition: - padding 420ms cubic-bezier(0.22, 1, 0.36, 1), - box-shadow 240ms ease, - transform 140ms ease, - background 140ms ease; - will-change: padding, transform; - } - #email-lib-modal .email-lib-fab svg { flex: 0 0 auto; display: block; } - #email-lib-modal .email-lib-fab .email-lib-fab-label { - overflow: hidden; - white-space: nowrap; - max-width: 120px; - opacity: 1; - /* EXPAND timing — label eases out a touch after the width opens up. */ - transition: - max-width 420ms cubic-bezier(0.22, 1, 0.36, 1), - opacity 300ms 60ms cubic-bezier(0.22, 1, 0.36, 1), - margin 420ms cubic-bezier(0.22, 1, 0.36, 1); - } - /* Collapsed (while scrolling) → pure circle, icon only. We DON'T set an - explicit width: the container is auto-width and shrinks smoothly as the - padding + label max-width animate (min-width keeps it circular). Setting a - fixed width here made the collapse snap, since auto↔px width can't tween. - This (.collapsed) transition governs the COLLAPSE — kept snappy. */ - #email-lib-modal .email-lib-fab.collapsed { - padding: 0; - justify-content: center; - transition: - padding 230ms cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 200ms ease, - transform 140ms ease; - } - #email-lib-modal .email-lib-fab.collapsed .email-lib-fab-label { - max-width: 0; - opacity: 0; - margin-left: -9px; - transition: - max-width 230ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), - margin 230ms cubic-bezier(0.4, 0, 0.2, 1); - } - /* Entrance: circle-expand-from-middle pop, played once when revealed. No - `forwards` fill so the resting transform (scale 1) comes from this rule — - that keeps the :active press transform working afterwards. */ - #email-lib-modal .email-lib-fab.fab-revealed { - transform: scale(1); - opacity: 1; - animation: emailFabPop 380ms cubic-bezier(0.34, 1.56, 0.64, 1); - } - #email-lib-modal .email-lib-fab:active { - transform: scale(0.93); - filter: brightness(0.92); - box-shadow: 0 3px 10px rgba(0,0,0,.4); - } -} -@keyframes emailFabPop { - from { transform: scale(0); opacity: 0; } - 60% { transform: scale(1.06); opacity: 1; } - to { transform: scale(1); opacity: 1; } -} -@media (prefers-reduced-motion: reduce) { - #email-lib-modal .email-lib-fab, - #email-lib-modal .email-lib-fab .email-lib-fab-label { transition-duration: 1ms; } - #email-lib-modal .email-lib-fab.fab-revealed { animation: none; } -} -/* When the modal-content has explicit height (fullscreen or user-resized), - drop the floor so the grid sizes purely from flex. */ -#email-lib-modal.email-lib-fullscreen .doclib-grid, -#email-lib-modal .modal-content[style*="height"] .doclib-grid { - min-height: 0; -} -/* Document library: same fix as the email library (#74). When fullscreened - the modal-content gets an inline `height: 100vh`, but the inner - .doclib-grid stays capped at the 400px default so the list looks like a - tiny strip floating in a giant empty modal. Mirror the email recipe: - make the modal a flex column, give the body/admin-card claim of the - remaining height, and let the grid take the rest. Scoped to the - fullscreen class so windowed-mode layout is unchanged. */ -/* Inner flex chain that lets the chats/documents grid claim the full - remaining height of the modal. Applies in BOTH fullscreen AND - right-docked states — without the docked state included, dragging a - fullscreen doclib to the right snap zone breaks the flex layout - because exitFullscreen removes .doclib-fullscreen, and the grid - falls back to its base max-height: 400px showing only ~8 items. - The CSS variable :is() selector lets both states share one rule. */ -#doclib-modal.doclib-fullscreen .doclib-modal-content, -#doclib-modal.modal-right-docked .doclib-modal-content, -#doclib-modal.modal-left-docked .doclib-modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -/* Fullscreen positioning — scoped to NOT apply when also right-docked. - Without this exclusion, dragging a fullscreen doclib to the right snap - zone keeps .doclib-fullscreen on the modal and these !important rules - override the dock's inline styles, leaving the modal stuck fullscreen - instead of becoming a side panel. */ -#doclib-modal.doclib-fullscreen:not(.modal-right-docked) .doclib-modal-content { - position: fixed !important; - top: 0 !important; - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; - right: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; -} -#doclib-modal.doclib-fullscreen .modal-header, -#doclib-modal.doclib-fullscreen .lib-tabs, -#doclib-modal.modal-right-docked .modal-header, -#doclib-modal.modal-right-docked .lib-tabs, -#doclib-modal.modal-left-docked .modal-header, -#doclib-modal.modal-left-docked .lib-tabs { - flex: 0 0 auto; -} -#doclib-modal.doclib-fullscreen .modal-body, -#doclib-modal.modal-right-docked .modal-body, -#doclib-modal.modal-left-docked .modal-body { - flex: 1 1 0; - min-height: 0; - overflow: hidden; - display: flex; - flex-direction: column; -} -#doclib-modal.doclib-fullscreen .admin-card, -#doclib-modal.modal-right-docked .admin-card, -#doclib-modal.modal-left-docked .admin-card { - flex: 1 1 0; - min-height: 0; - display: flex; - flex-direction: column; -} -#doclib-modal.doclib-fullscreen .doclib-grid:not(:has(.doclib-card-expanded)), -#doclib-modal.modal-right-docked .doclib-grid:not(:has(.doclib-card-expanded)), -#doclib-modal.modal-left-docked .doclib-grid:not(:has(.doclib-card-expanded)) { - max-height: none; - height: auto; - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* "Reply" docks the open email modal to the left as a sidebar so the doc - panel (which slides in from the right of the chat area) is visible - side-by-side. The .modal backdrop is already pointer-events:none for - email modals — only the content takes pointer events — so the chat / - doc panel underneath stays interactive. */ -.modal.email-snap-left { - align-items: stretch; - justify-content: flex-start; - left: 0 !important; - width: 100% !important; -} -.modal.email-snap-left .modal-content { - left: var(--email-doc-split-left-x, 0px) !important; - width: var(--email-doc-split-email-w, 420px) !important; - max-width: var(--email-doc-split-email-w, 420px) !important; - border-right: 1px solid var(--border); - box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; -} -@media (min-width: 769px) { - body.email-doc-split-active.doc-view #email-lib-modal, - body.email-doc-split-active.doc-view.email-front #email-lib-modal, - body.email-doc-split-active.doc-view .modal[id^="email-reader-"], - body.email-doc-split-active.doc-view.email-front .modal[id^="email-reader-"] { - z-index: 150 !important; - } - body.email-doc-split-active #email-lib-modal.email-snap-left, - body.email-doc-split-active #email-lib-modal.modal-left-docked { - left: var(--email-doc-split-left-x, 0px) !important; - width: var(--email-doc-split-email-w, 420px) !important; - overflow: hidden !important; - z-index: 155 !important; - } - body.email-doc-split-active #email-lib-modal.email-snap-left:not(.modal-dragging) .modal-content, - body.email-doc-split-active #email-lib-modal.modal-left-docked:not(.modal-dragging) .modal-content, - body.email-doc-split-active .modal[id^="email-reader-"].email-snap-left:not(.modal-dragging) .modal-content, - body.email-doc-split-active .modal[id^="email-reader-"].modal-left-docked:not(.modal-dragging) .modal-content { - position: absolute !important; - left: 0 !important; - top: 0 !important; - bottom: 0 !important; - right: auto !important; - width: var(--email-doc-split-email-w, 420px) !important; - max-width: var(--email-doc-split-email-w, 420px) !important; - height: 100vh !important; - max-height: 100vh !important; - transform: none !important; - margin: 0 !important; - } - body.email-doc-split-active.doc-view .doc-divider { - display: none !important; - } - body.email-doc-split-active.doc-view .doc-editor-pane { - position: fixed !important; - left: var(--email-doc-split-right-x, 420px) !important; - right: var(--right-dock-w, 0px) !important; - top: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - z-index: 260 !important; - margin-top: 0 !important; - transform: none !important; - border-left: none !important; - } -} - -/* Edge docking. Docked panels are fixed to the viewport edge; the workspace - panes reserve room with margins so left + right docks can be active at the - same time without skewing the entire body box. */ -body.right-dock-active { - padding-right: 0; -} -body.left-dock-active { - padding-left: 0; -} -body.left-dock-active:not(.email-doc-split-active) .chat-container { - margin-left: var(--left-dock-w, 0px); -} -body.right-dock-active .chat-container, -body.right-dock-active:not(.email-doc-split-active) .doc-editor-pane { - margin-right: var(--right-dock-w, 0px); -} -.modal.modal-right-docked { - align-items: stretch; - justify-content: flex-end; -} -.modal.modal-right-docked .modal-content { - border-left: 1px solid var(--border); - box-shadow: -4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; -} -.modal.modal-left-docked { - align-items: stretch; - justify-content: flex-start; -} -.modal.modal-left-docked .modal-content { - border-right: 1px solid var(--border); - box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; - /* Pin transitions OFF on the dock's positioning properties. The wide - sidebar collapse/expand changes --sidebar-w instantly, which means - the docked modal's `left: calc(...)` jumps by ~240px. Any CSS - transition on `left` (inherited or otherwise) would animate that - jump as a fly-across. Same defense for right-docked / fullscreen - panels in case future themes add a generic transition. */ - transition: none !important; -} -.modal.modal-right-docked .modal-content, -#email-lib-modal.email-lib-fullscreen .modal-content, -#doclib-modal.doclib-fullscreen .doclib-modal-content { - transition: none !important; -} -.modal.modal-right-docked .email-reader-header, -.modal.modal-left-docked .email-reader-header { - flex-direction: column; - /* Stretch the meta to the full header width so the absolute - action cluster's right:0 lands at the right edge (was sizing - meta to chip content width and centering it). */ - align-items: stretch; - gap: 6px; -} -/* Docked panes: 4px between From→To and 4px between To→Cc. - Meta has its own 2px gap (From→details) so an extra 2px on - details margin-top gives a 4px From→To distance, and gap:4 on - the details flex column gives 4px between To and Cc. */ -.modal.modal-right-docked .email-reader-meta-details, -.modal.modal-left-docked .email-reader-meta-details { - margin-top: 2px; - gap: 4px; -} -/* Docked: always show To/Cc — there's vertical room reserved for - the action cluster already and the chevron toggle just adds - friction. Force the hidden attribute open and hide the chevron. */ -.modal.modal-right-docked .email-reader-meta-details[hidden], -.modal.modal-left-docked .email-reader-meta-details[hidden] { - display: flex !important; -} -.modal.modal-right-docked .email-reader-meta-toggle, -.modal.modal-left-docked .email-reader-meta-toggle { - display: none; -} -.modal.modal-right-docked .email-reader-header > .email-reader-actions, -.modal.modal-left-docked .email-reader-header > .email-reader-actions { - position: static; - align-self: flex-end; - background: none; - padding-left: 0; -} -/* Docked modal: keep all meta rows as flex so From / To / Cc - labels align at the same x. Chips already overflow-scroll - inside each row when there are too many. No per-row margin-top - here — the spacing between rows comes entirely from - .email-reader-meta-details gap so From→To and To→Cc match. */ -.modal.modal-right-docked .email-reader-meta-row:not(.email-reader-meta-from), -.modal.modal-left-docked .email-reader-meta-row:not(.email-reader-meta-from) { - padding-right: 0; -} -.modal.modal-right-docked .email-reader-meta-row strong, -.modal.modal-left-docked .email-reader-meta-row strong { - min-width: 0; -} -.modal.modal-right-docked .recipient-chip, -.modal.modal-left-docked .recipient-chip { - max-width: 100%; -} -.archive-list { - margin-top: 8px; - border-top: 1px solid var(--border); - padding-top: 8px; -} -#archive-modal .memory-sort-select, -#archive-modal .memory-toolbar-btn { - height: 32px; -} -.archive-menu-btn { - position: relative; - top: 2px; -} -#archive-chips .doclib-chip { - height: 22px; - display: inline-flex; - align-items: center; - font-size: 10px; - padding: 0 8px; -} -/* Library tab panels — Chats / Archive / Research / Documents share the - same toolbar dimensions so the sort dropdown + Select button line up - identically across tabs. */ -#doclib-panel-chats .memory-sort-select, -#doclib-panel-archive .memory-sort-select, -#doclib-panel-research .memory-sort-select, -[data-doclib-panel="documents"] .memory-sort-select { - height: 24px; - font-size: 11px; - width: 86px; -} -#doclib-panel-chats .memory-toolbar-btn, -#doclib-panel-archive .memory-toolbar-btn, -#doclib-panel-research .memory-toolbar-btn, -[data-doclib-panel="documents"] .memory-toolbar-btn { - height: 24px; - font-size: 11px; - position: relative; - top: -3px; -} -/* Research's Recent (sort) + Select sat 1px lower than the other tabs — nudge up. */ -#doclib-panel-research .memory-sort-select { position: relative; top: 1.5px; } -#doclib-panel-research .memory-toolbar-btn { top: -4.5px; } -.library-sort-picker { - width: 108px; - flex: 0 0 108px; - top: 1px; -} -.library-sort-picker .library-sort-btn { - top: 0; - width: 100%; -} -.library-sort-picker .library-sort-menu { - top: calc(100% + 4px); - width: max(100%, 154px); -} -.library-sort-picker .library-sort-item[aria-selected="true"] { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); -} -#doclib-panel-chats #doclib-chats-tidy-btn, -#doclib-panel-chats #doclib-chats-select-btn, -#doclib-panel-archive #doclib-arc-select-btn { - top: 1px; -} -#doclib-panel-chats .doclib-chip-scroll-arrow { - top: calc(50% - 9px) !important; -} -[data-doclib-panel="documents"] .doclib-chip-scroll-arrow { - top: calc(50% - 9px) !important; -} -#doclib-modal .doclib-chip-scroll-arrow { - top: calc(50% - 19px) !important; -} -[data-doclib-panel="documents"] #doclib-tidy-btn, -[data-doclib-panel="documents"] #doclib-select-btn { - top: 1px; -} -#doclib-panel-research #doclib-research-tidy-btn, -#doclib-panel-research #doclib-research-select-btn { - top: -0.5px; -} -#doclib-panel-research .library-sort-picker { top: -0.5px; } -[data-doclib-panel] { padding-top: 6px !important; } -#doclib-panel-chats, #doclib-panel-archive { padding-top: 14px !important; } -#doclib-panel-chats .memory-desc, #doclib-panel-archive .memory-desc, #doclib-panel-research .memory-desc { margin-top: 7px; } -#doclib-panel-chats .memory-search-input, -#doclib-panel-archive .memory-search-input { - height: 30px !important; - min-height: 30px !important; - flex-shrink: 0; - font-size: 11px; -} -/* Unified search bar across Library tabs + Memory modal — same height, - same magnifying-glass icon at the start, consistent padding. */ -#doclib-panel-chats .memory-search-input, -#doclib-panel-archive .memory-search-input, -#doclib-panel-research .memory-search-input, -[data-doclib-panel="documents"] .memory-search-input, -#memory-modal .memory-search-input, -#tasks-modal .memory-search-input { - height: 30px; - min-height: 30px; - font-size: 11px; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: 9px center; - padding-left: 28px; -} -#doclib-panel-chats .doclib-chip { - height: 28px; -} -#doclib-panel-archive .doclib-chip { - height: 28px; -} -#doclib-panel-chats .doclib-chip, -#doclib-panel-archive .doclib-chip { - display: inline-flex; - align-items: center; - font-size: 9px; - padding: 0 8px; - border-radius: 10px; -} -.doclib-grid:has(.doclib-card-expanded) > .doclib-card:not(.doclib-card-expanded) { - display: none; -} -/* Hide everything except the grid when a card is expanded */ -.admin-card:has(.doclib-card-expanded) > *:not(.doclib-grid):not(.hwfit-cached-list) { - opacity: 0; - max-height: 0; - overflow: hidden; - pointer-events: none; - margin: 0 !important; - padding: 0 !important; - transition: opacity 0.12s ease, max-height 0.2s ease; -} -.admin-card:has(.doclib-card-expanded) > .memory-bulk-bar { - display: none; -} -.admin-card:has(.doclib-card-expanded) > .doclib-grid, -.admin-card:has(.doclib-card-expanded) > .hwfit-cached-list { - max-height: none !important; - min-height: 0 !important; - overflow: visible !important; -} -/* Firefox-mobile (no :has()) fallback: when reading an email, fully remove the - list-mode chrome (accounts row, toolbar, bulk bar, desc) — display:none so no - residual flex-gap leaves an empty strip above the reader — and zero the - admin-card gap so the grid/reader starts flush at the top. */ -#email-lib-modal.email-reading .admin-card > *:not(.doclib-grid):not(.hwfit-cached-list) { - display: none !important; -} -#email-lib-modal .admin-card:has(.doclib-card-expanded), -#email-lib-modal.email-reading .admin-card { - gap: 0 !important; - padding: 0 !important; - margin-bottom: 0 !important; - border: 0 !important; - border-radius: 0 !important; - background: transparent !important; -} -/* Override for chat-rows + research-rows: those expand inline with a - bounded preview (max-height: 60vh), so the grid must stay clipped - and scrollable. Without this scoping the overflow:visible above lets - the expanded chat preview escape the grid and overlap whatever's - underneath/beside the modal (e.g., a docked sibling panel) — what - the user calls "merging with other windows". */ -.admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - overflow: hidden auto !important; -} -/* Email modal needs the grid to STAY a constrained flex container when - expanded — overflow:visible (set above for cookbook) lets content escape - instead of letting the expanded reader fill via flex:1. We keep the same - max-height:none + min-height:0 but flip overflow back so the reader - actually fills the modal. */ -#email-lib-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid, -#email-lib-modal.email-reading .admin-card > .doclib-grid { - overflow: hidden !important; - display: flex !important; - flex: 1 1 auto !important; -} -#email-lib-modal.email-reading #email-lib-grid > .date-section-header { - display: none !important; -} -#email-lib-modal.email-reading .doclib-modal-content { - min-height: var(--email-reading-modal-min-h, auto); -} -#email-lib-modal .doclib-card.doclib-card-expanded { - flex: 1 1 auto !important; - height: 100% !important; - /* Neutral frame on the active email — the accent border + glow felt - overbearing on desktop; the size jump alone is enough signal. */ - border: 1px solid var(--border) !important; - box-shadow: 0 6px 18px rgba(0,0,0,0.12) !important; - animation: none !important; -} -/* Desktop-only, ONLY on the currently-expanded email card. Nudges the title - row down 6px / right 2px, bolds the subject, hides the timestamp from the - meta line (the reader header carries its own date), and pulls the prev/next - nav arrows in 4px. */ -@media (min-width: 769px) { - #email-lib-modal .doclib-card-expanded .email-card-titlerow, - #email-lib-modal .doclib-card-expanded .email-card-titlerow > * { - position: relative; - top: 6px; - left: 2px; - } - /* Smaller subject — the reader header below already carries weight. */ - #email-lib-modal .doclib-card-expanded .email-card-titlerow .memory-item-title { - font-weight: 700; - font-size: 12px; - } - /* A bit more breathing room around the title row so the smaller bold text - doesn't crowd the meta line below. (Timestamp kept — user changed mind.) */ - #email-lib-modal .doclib-card-expanded .email-card-titlerow { - padding: 4px 0 6px; - line-height: 1.35; - } - /* Nudge the date down/left in the meta row, and give the meta line +4px of - extra bottom space so the shifted date isn't clipped. */ - #email-lib-modal .doclib-card-expanded .email-meta-date { - position: relative; - top: 4px; - left: 0; - } - #email-lib-modal .doclib-card-expanded .memory-item-meta { - padding-bottom: 4px; - } - #email-lib-modal .doclib-card-expanded .email-card-nav-arrows { - position: relative; - left: -4px; - } -} -/* Skills modal: same mechanism as email above. The default expanded-grid - rule (overflow:visible, no flex) lets the card expand inline to ~content - height instead of filling — which is why the skill preview stopped at - partial height. Flip the grid back to a constrained flex container and - let the expanded card fill it, exactly like the email reader. */ -#memory-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid { - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - flex: 1 1 auto !important; -} -#memory-modal .doclib-card.doclib-card-expanded { - flex: 1 1 auto !important; - height: 100% !important; -} -#email-lib-modal .modal-content { - transition: width 0.22s ease-out, height 0.22s ease-out; -} -@media (min-width: 769px) { - body:not(.email-doc-split-active) #email-lib-modal:not(.email-lib-fullscreen):not(.modal-left-docked):not(.modal-right-docked) .modal-content:not([style*="height"]) { - min-height: min(560px, 85vh); - } -} - -/* Serve panel specifically: the admin-card inline style is - `overflow:hidden` (so the toolbar/header don't drift), and the list - inside has overflow:visible. On short windows that combination - clipped the cards off the bottom with no scrollbar. Make the list - itself the scroll surface so the rest of the card stays put. */ -.cookbook-group[data-backend-group="Serve"] > .admin-card { - min-height: 0; -} -.cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list, -.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list { - flex: 1 1 0; - min-height: 0; - overflow-y: auto !important; - overscroll-behavior: contain; -} -.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list .doclib-card.doclib-card-expanded { - flex: 0 0 auto !important; - overflow: visible !important; -} -/* Launch model cards use the same domino entrance as the other library lists - when a cached-model scan finishes. The class is removed after the one-shot - entrance, so filtering and sorting stay instantaneous. */ -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(1) { animation-delay: 0.04s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(2) { animation-delay: 0.08s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(3) { animation-delay: 0.12s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(4) { animation-delay: 0.16s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(5) { animation-delay: 0.20s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(6) { animation-delay: 0.24s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(7) { animation-delay: 0.28s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(8) { animation-delay: 0.32s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(9) { animation-delay: 0.36s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(10) { animation-delay: 0.40s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(11) { animation-delay: 0.44s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(12) { animation-delay: 0.48s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(n+13) { animation-delay: 0.48s; } -@media (prefers-reduced-motion: reduce) { - #cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { - animation: none; - } -} -/* Drag-and-drop visual hint for the email compose pane. Subtle accent - outline + tinted overlay so it's obvious files will attach if dropped. */ -.doc-editor-pane.email-dragover { - outline: 2px dashed var(--accent, #2563eb); - outline-offset: -8px; - background: color-mix(in srgb, var(--accent, #2563eb) 6%, transparent); -} -.doc-editor-pane.email-dragover::after { - content: 'Drop to attach'; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: var(--accent, #2563eb); - color: #fff; - padding: 8px 16px; - border-radius: 6px; - font-size: 12px; - pointer-events: none; - z-index: 1000; -} - -/* Email reader window — make the body always fill the available vertical - space regardless of how the window is positioned/resized. The user can - drag the bottom-right corner to resize; the body re-flexes automatically. */ -.email-window-modal .modal-content { - display: flex !important; - flex-direction: column !important; - resize: both; - overflow: hidden; - min-width: 320px; - min-height: 240px; - /* When user resizes/drags so the window covers a side, force the inner - body to fill all the way to the bottom. The default max-height: 85vh - (set inline by _makeDraggable's exitFullscreen) and 80vh from the - opener both cap at the *viewport* — if the browser window then changes - size (e.g. OS aero-snap), the modal stays at the old height and - truncates rows. Drop the cap whenever an explicit width OR height has - been written inline by the resize handle / drag handler. */ -} -.email-window-modal .modal-content[style*="width"], -.email-window-modal .modal-content[style*="height"] { - max-height: calc(100vh - 16px) !important; -} -.email-window-modal .email-window-body { - flex: 1 1 auto !important; - min-height: 0 !important; - /* Explicit basis of 0 makes the body grow to fill remaining space rather - than its content's intrinsic height (which was capping the thread). */ - flex-basis: 0 !important; - max-height: none !important; - overflow: auto !important; - overscroll-behavior: contain; -} - -/* Individual email reader window — fullscreen mode (drag-to-top snap or - double-click header). Same recipe as the library modal: pin to viewport - edges and let the inner body absorb the remaining height. */ -body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .modal-content { - position: fixed !important; - inset: 0 !important; - left: 0 !important; - top: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; - display: flex !important; - flex-direction: column !important; -} -body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .email-window-body { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; -} - -/* Email library modal: fullscreen mode + larger mobile sheet (mirrors the - cookbook treatment so the toolbar and bulk actions stay reachable). - Leaves whichever navigation strip is currently visible on the left - (the wide sidebar OR the icon rail — they're mutually exclusive, so - one of the two CSS vars is 0 at any given time, set by init.js). */ -body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { - position: fixed !important; - top: 0 !important; - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; - right: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; -} -/* Mobile: the /email route adds .email-lib-fullscreen, but the desktop full- - bleed rule above squares off the corners and offsets the email by the icon - rail. On phones the email should be the normal bottom-sheet — full width, - rounded top corners. Same specificity as the rule above + later in source, - so it wins on mobile. */ -@media (max-width: 768px) { - body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { - left: 0 !important; - right: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - height: 100dvh !important; - max-height: 100dvh !important; - border-radius: 14px 14px 0 0 !important; - border-top: 1px solid var(--border) !important; - } -} -/* Make the inner panes actually grow to fill the fullscreen container - instead of staying at their natural size. The body owns the remaining - height below the header; the admin-card + grid then expand into it. */ -#email-lib-modal.email-lib-fullscreen .modal-body { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; -} -#email-lib-modal.email-lib-fullscreen .modal-body > .admin-card { - flex: 1 1 auto !important; - min-height: 0 !important; -} -#email-lib-modal.email-lib-fullscreen .doclib-grid { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow: auto !important; -} -@media (max-width: 768px) { - /* Mobile email modal sizing — keep in sync with the rule earlier in the - file. 75dvh tall always so flex children (the expanded email reader) - have a real height to grow into. */ - #email-lib-modal .modal-content { - max-height: 90dvh !important; - max-height: 90vh !important; - height: 90dvh !important; - height: 90vh !important; - } - /* Inner panes grow to fill the modal-content — without flex:1 on the - body, the expanded email reader sits in a tiny box because there's - nothing pushing it to take the remaining height. overflow:hidden + - min-height:0 lets each layer pass its constraints down. */ - #email-lib-modal .modal-body, - #email-lib-modal .admin-card { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; - max-height: none !important; - } - #email-lib-modal .doclib-grid { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow-y: auto !important; - } - /* modal-content keeps its scroll OFF here — the inner flex children - (doclib-grid for the list view, email-reader-body for the expanded - reader) own the scroll surfaces. Without this, double-scroll layouts - trap touches and the reader can't claim full height. */ - #email-lib-modal .modal-content { - overflow: hidden !important; - -webkit-overflow-scrolling: touch; - overscroll-behavior: contain; - touch-action: pan-y; - } - /* Attachment row on phones: cap to ~2 rows so it never dominates the - reader. If more chips exist, the row scrolls vertically. Smaller chip - padding + max-width keeps each chip compact so two rows usually fit - everything anyway. */ - #email-lib-modal .email-reader-atts { - padding: 4px 10px !important; - gap: 3px !important; - max-height: calc(2 * 22px + 12px); /* 2 rows × chip height + padding */ - overflow-y: auto; - align-content: flex-start; - } -#email-lib-modal .email-attachment-chip { - padding: 1px 6px !important; - font-size: 10px !important; - max-width: min(240px, calc(100vw - 44px)) !important; - line-height: 18px !important; - } -} - - -/* Mobile: only ONE scroll surface inside the cookbook modal. The - modal-content is the scroller. Everything inside (cookbook-body, - cookbook-group, all the inner lists/panels) gets overflow: visible - so touch-pan never gets trapped in a nested scroller. - Without this, three levels of overflow:auto + max-height combinations - produce dead-zone areas where swipes do nothing. */ -@media (max-width: 768px) { - #cookbook-modal .modal-content { - overflow-y: auto !important; - -webkit-overflow-scrolling: touch; - overscroll-behavior: contain; - touch-action: pan-y; - } - #cookbook-modal .cookbook-body, - #cookbook-modal .cookbook-group, - #cookbook-modal .cookbook-section-body, - #cookbook-modal .hwfit-cached-list, - #cookbook-modal .doclib-grid, - #cookbook-modal .hwfit-list, - #cookbook-modal .hwfit-panel-cmd { - overflow: visible !important; - max-height: none !important; - min-height: 0 !important; - } - /* Running tmux output: cap how tall an expanded card gets (a long-lived job - can leave thousands of lines) so it doesn't balloon, but keep its own - scroll so you can read it. Outputs default collapsed on mobile now, so - you're usually looking at one expanded card at a time — no wall of nested - scrollers, and you collapse it to move past. */ - #cookbook-modal .cookbook-output-pre, - #cookbook-modal .cookbook-task .cookbook-output-pre { - max-height: 45vh !important; - min-height: 0 !important; - overflow-y: auto !important; - overflow-x: hidden !important; - overscroll-behavior: auto; /* chain to the modal at the scroll boundary */ - } - /* cookbook-body's flex:1 was needed when it owned scrolling — drop it - so the inner content drives modal-content's scroll height. */ - #cookbook-modal .cookbook-body { - flex: 0 0 auto !important; - height: auto !important; - } - /* Launch tab's cached-list normally has `flex: 1 1 0; min-height: 0` - (so it fills the modal on desktop). On mobile the parent now has - `height: auto`, which collapses `flex: 1 1 0` to ZERO PX — - models render but the list area is invisible because the flex - basis is 0 and there's no free space to grow into. Switch to - content-sized flex so the list grows with its children. */ - #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list, - #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list { - flex: 0 0 auto !important; - overflow: visible !important; - } -} -#cookbook-modal .hwfit-cached-list { - flex-shrink: 0; -} -.memory-toolbar { - transition: opacity 0.12s ease, max-height 0.2s ease; - max-height: 120px; -} -.doclib-card { - background: color-mix(in srgb, var(--fg) 3%, transparent); - border: 1px solid var(--border); - border-radius: 8px; - cursor: pointer; - transition: all 0.15s, opacity 0.3s, max-height 0.3s ease; - position: relative; -} -.doclib-card.doclib-card-deleting { - opacity: 0; - max-height: 0 !important; - overflow: hidden; - margin: 0; - padding: 0; - border-color: transparent; -} -.doclib-card.selected, -/* Universal selection highlight: any card-shaped row (documents, chats, - archive, research, skills, memories, tasks) gets the same accent outline - when its checkbox is checked. Done via :has() so the highlight follows - selection without per-renderer JS changes. The canonical checkbox class - is .memory-select-cb across every list. */ -.memory-item:has(.memory-select-cb:checked), -.doclib-card:has(.memory-select-cb:checked), -.doclib-chat-row:has(.memory-select-cb:checked) { - border-color: color-mix(in srgb, var(--red) 40%, transparent); - background: color-mix(in srgb, var(--red) 4%, transparent); - /* 2px accent outline overlaid on the 1px border — reads as a thicker - selected border without shifting layout (every card would otherwise - need a 2px transparent border to keep the same width). */ - outline: 2px solid var(--accent-primary, var(--red)); - outline-offset: -1px; -} -.doclib-card-header { - display: flex; - align-items: center; - padding: 7px 8px; - gap: 6px; - min-width: 0; -} -/* Mobile only: push the unexpanded email card's title-row text down 4px so - it sits visually centered with the surrounding card padding. Targets the - actual title-row class (.email-card-titlerow) — the earlier attempt used - .doclib-card-header which the email card builds differently. */ -@media (max-width: 768px) { - #email-lib-modal .doclib-card:not(.doclib-card-expanded) .email-card-titlerow { - margin-top: 4px; - } -} -.doclib-card-title { - font-size: 11px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; - flex: 1; -} -.doclib-card-ver { - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - color: var(--accent, var(--red)); - font-size: 9px; - flex-shrink: 0; - font-weight: 600; - letter-spacing: 0.3px; - min-width: 24px; - text-align: left; - box-sizing: border-box; -} -.doclib-card-ver-muted { - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.doclib-card-lang { - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - color: var(--accent); - font-size: 9px; - text-align: left; - flex-shrink: 0; - min-width: 52px; - box-sizing: border-box; -} -.doclib-card-session { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 60px; - width: 100px; - max-width: 100px; - font-size: 9px; - color: var(--fg-muted); - flex-shrink: 0; - text-align: left; -} -.doclib-card-time { - flex-shrink: 0; - opacity: 0.5; - font-size: 9px; - color: var(--fg-muted); - min-width: 50px; - text-align: left; -} -/* Footer — used by archive cards */ -.doclib-card-footer { - display: contents; -} -/* Preview — hidden by default, shown on expand */ -.doclib-card-preview { - display: none; - position: relative; - padding: 8px 12px; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.5; - color: var(--code-fg, var(--hl-fg, var(--fg))); - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin: 0; -} -.doclib-card-preview pre { - margin: 0; - white-space: pre-wrap; - word-break: break-all; -} -.doclib-card-expanded .doclib-card-preview pre { - flex: 1 1 0; - overflow-y: auto; - min-height: 0; -} -.doclib-card-preview code.hljs { - background: none; - padding: 0; -} -/* Expanded-only action bar — inside preview. Buttons shifted up 4px by - trimming the row's top margin so they sit closer to the preview text. */ -.doclib-card-expanded-actions { - display: none; - align-items: flex-start; - gap: 6px; - padding: 8px 0 2px; - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 4px; -} -.doclib-card-expanded-actions > .doclib-card-action-btn, -.doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - position: relative; - top: -4px; -} -.doclib-action-group { - display: flex; - flex-direction: column; - gap: 3px; -} -.doclib-action-btn-row { - display: flex; - gap: 6px; -} -.doclib-action-hint-row { - display: flex; - gap: 6px; -} -/* Match the chat/research footer buttons exactly: flat, bordered, - app font, accent on hover. */ -.doclib-card-action-btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - box-sizing: border-box; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - cursor: pointer; - /* These buttons live inside .doclib-card-preview, which forces a - monospace font. The chat/research footer buttons instead inherit the - modal font, which is the app font (Fira Code) on mobile and Inter on - desktop (.modal-content's Inter rule is inside @media min-width:769px). - Mirror that here so all three footers match in both contexts. */ - font-family: var(--font-family, 'Fira Code', monospace); - transition: border-color 0.15s, color 0.15s; -} -@media (min-width: 769px) { - .doclib-card-action-btn { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - letter-spacing: -0.015em; - } -} -.doclib-card-action-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -.doclib-card-text-btn-danger { - color: var(--color-danger, #e06c75); - border-color: var(--color-danger, #e06c75); -} -.doclib-card-text-btn-danger:hover { - color: #ff4d4d; - border-color: #ff4d4d; -} -.doclib-card-expanded-actions > .doclib-card-action-btn { - font-size: 10px; - padding: 3px 8px; -} -/* Push the Open/Clone group to the right edge of the action bar — Delete - anchors the left, primary actions sit opposite. */ -.doclib-card-expanded-actions > .doclib-action-group { - margin-left: auto; -} -.doclib-card-expanded-actions > .doclib-expanded-delete-btn, -.doclib-card-expanded-actions > .doclib-expanded-archive-btn { - left: 0 !important; -} -.doclib-expanded-mobile-more { display: none; } -.doclib-btn-hint { - font-weight: normal; - opacity: 0.35; - font-size: 8px; - min-width: 58px; - padding: 0 8px; - box-sizing: border-box; -} -@media (max-width: 768px) { - .doclib-btn-hint { display: none !important; } -} - -/* Chat row expand preview — matches the documents-tab expand style. */ -.doclib-card-chevron { - display: inline-flex; - align-items: center; - justify-content: center; - align-self: center; - flex-shrink: 0; - width: 14px; - height: 14px; - line-height: 0; - opacity: 0.3; - transform: rotate(0deg); - transition: transform 0.2s ease, opacity 0.15s; -} -.doclib-card-chevron svg { display: block; } -.doclib-card-expanded .doclib-card-chevron, -.task-card.expanded .doclib-card-chevron { - opacity: 0.8; - transform: rotate(180deg); -} -.doclib-card > .memory-item-actions { order: 1; } -.doclib-card > .doclib-card-chevron { order: 2; } -.doclib-chat-header .memory-item-actions, -.task-card .memory-item-actions { order: 2; } -.doclib-chat-header .doclib-card-chevron, -.task-card .doclib-card-chevron { order: 3; } -.hwfit-cached-list > .doclib-card > .memory-item-actions { order: 2; } -.hwfit-cached-list > .doclib-card > .doclib-card-chevron { order: 3; } -#email-lib-modal .doclib-card > .memory-item-actions { order: 1; align-self: center; } -#email-lib-modal .doclib-card > .doclib-card-chevron { display: none; } -.doclib-chat-row { flex-direction: column; align-items: stretch; } -.doclib-chat-row .doclib-card-chevron { - display: inline-flex; - opacity: 0.55; - transition: transform 0.2s ease, opacity 0.15s; - flex-shrink: 0; -} -.doclib-chat-row:not(.doclib-card-expanded) .doclib-card-chevron { - transform: rotate(0deg); -} -.doclib-chat-row.doclib-card-expanded .doclib-card-chevron { - opacity: 0.8; - transform: rotate(180deg); -} -/* Release .memory-item's base max-height: 200px when a chat row is - expanded. Without this, the row clips at 200px on desktop and the - preview's messages/actions visually spill past the row's box — - "colliding" with the next chat in the column. The mobile takeover - below already handles this via the same override; this rule is the - desktop-or-any-viewport equivalent. */ -.doclib-chat-row.doclib-card-expanded { - max-height: none; - overflow: visible; -} -/* When a chat row is expanded, take over the grid the same way documents - do: hide siblings, hide the admin-card toolbar, and let this row plus - its preview claim the full available height. .memory-item's default - max-height: 200px must be overridden or the preview will be tiny. */ -/* Mobile-only "card takeover" for an expanded chat — hides siblings, - hides the admin-card toolbar, and lets this row + its preview claim - the full available height. On desktop the expanded preview just - renders inline below the row with its messages constrained by - max-height + overflow:auto so the user gets a normal scroll. */ -@media (max-width: 820px) { - .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - flex: 1 1 auto !important; - max-height: none !important; - overflow-y: auto !important; - } - .doclib-chat-row.doclib-card-expanded { - flex: 1 1 auto !important; - max-height: none !important; - min-height: 0 !important; - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - align-items: stretch !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-header { - flex: 0 0 auto !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - padding-bottom: 4px !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { - -webkit-overflow-scrolling: touch; - max-height: none !important; - flex: 1 1 auto !important; - min-height: 0 !important; - } -} -.doclib-chat-preview { - font-size: 11px; - padding: 0 4px 6px; -} -.doclib-chat-preview .doclib-chat-open-btn { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - color: var(--fg-muted); - border: 1px solid var(--border); - background: none; - cursor: pointer; - font-family: inherit; - transition: border-color 0.15s, color 0.15s; -} -.doclib-chat-preview .task-completed-report-btn { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); -} -.doclib-chat-preview .doclib-chat-open-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -.doclib-chat-preview-messages { - margin-top: 6px; - max-height: 320px; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - padding-right: 4px; -} -/* Desktop expanded state — match the documents-tab behavior: hide - sibling chats, hide the admin-card toolbar/header, and let the - expanded row + its messages preview claim the full modal height. - Chat rows use `memory-item doclib-chat-row` (no `doclib-card` class) - so the document-tab's global rules at line 12284-12304 skip them — - these mirror those rules scoped to chat rows. The mobile media query - above (820px) keeps the same takeover behavior for phones. */ -@media (min-width: 821px) { - .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - flex: 1 1 auto !important; - max-height: none !important; - overflow: hidden auto !important; - } - .doclib-chat-row.doclib-card-expanded { - flex: 1 1 auto !important; - max-height: none !important; - min-height: 0 !important; - display: flex !important; - flex-direction: column !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { - flex: 1 1 auto !important; - min-height: 0 !important; - display: flex !important; - flex-direction: column !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow-y: auto !important; - } -} -.doclib-chat-preview-actions { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 8px; - padding: 2px 0 2px; - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 2px; - flex-shrink: 0; - /* Whole action footer nudged down 5px. */ - position: relative; - top: 5px; -} -.doclib-chat-delete-btn, -.doclib-chat-archive-btn, -.doclib-chat-restore-btn, -.doclib-chat-discuss-btn, -.doclib-chat-copy-btn { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - background: none; - cursor: pointer; - font-family: inherit; - transition: border-color 0.15s, color 0.15s; -} -.doclib-chat-archive-btn, -.doclib-chat-restore-btn, -.doclib-chat-discuss-btn, -.doclib-chat-copy-btn { - color: var(--fg-muted); - border: 1px solid var(--border); -} -.doclib-chat-restore-btn:hover { - color: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -.doclib-chat-archive-btn:hover, -.doclib-chat-discuss-btn:hover, -.doclib-chat-copy-btn:hover { - color: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -.doclib-chat-delete-btn { - color: var(--color-danger, #e06c75); - border: 1px solid var(--color-danger, #e06c75); -} -.doclib-chat-delete-btn:hover { - color: #ff4d4d; - border-color: #ff4d4d; -} -/* When a chat is archived there's no Archive button, so Delete becomes - the only left-side action and needs the auto margin to push Open right. */ -.doclib-chat-preview-actions:not(:has(.doclib-chat-archive-btn)) > .doclib-chat-delete-btn { - margin-right: auto; -} -/* Hide the "..." menu while the chat card is expanded — archive + - delete live in the preview footer instead. */ -.doclib-chat-row.doclib-card-expanded ._chat-menu { display: none; } -.doclib-chat-preview-actions.doclib-chat-preview-actions-top { - border-top: none; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 4px; - margin-bottom: 6px; - padding: 0 0 6px; - justify-content: flex-start; -} -.doclib-chat-preview-messages .doclib-chat-msg { - margin: 4px 0 10px; - padding-left: 8px; - border-left: 2px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.doclib-chat-preview-messages .doclib-chat-msg.user { - border-left-color: color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); -} -.doclib-chat-preview-messages .doclib-chat-msg-role { - font-size: 9px; - font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - opacity: 0.55; - margin-bottom: 2px; -} -.doclib-chat-preview-messages .doclib-chat-msg.user .doclib-chat-msg-role { - color: var(--accent, var(--red)); - opacity: 0.85; -} -.doclib-chat-preview-messages .doclib-chat-msg-body { - white-space: pre-wrap; - word-break: break-word; - opacity: 0.85; - line-height: 1.45; -} -/* Chat-bubble preview — mirrors the real chat layout. User bubbles hug - right with an accent tint; assistant bubbles hug left with a neutral - panel tint and a small model tag at the top. */ -.doclib-chat-bubble-row { - display: flex; - margin: 6px 0; -} -.doclib-chat-bubble-row.user { justify-content: flex-end; } -.doclib-chat-bubble-row.assistant { justify-content: flex-start; } -.doclib-chat-bubble { - max-width: 85%; - padding: 6px 10px 8px; - border-radius: 14px; - font-size: 11px; - line-height: 1.45; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.doclib-chat-bubble-row.user .doclib-chat-bubble { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); - border-bottom-right-radius: 4px; -} -.doclib-chat-bubble-row.assistant .doclib-chat-bubble { - border-bottom-left-radius: 4px; -} -.doclib-chat-msg-model { - display: inline-block; - font-size: 8px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; - opacity: 0.55; - margin-bottom: 3px; - padding: 1px 6px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg-muted, var(--fg)); -} -.doclib-chat-bubble-body { word-break: break-word; } -.doclib-chat-bubble-body p { margin: 4px 0; } -.doclib-chat-bubble-body p:first-child { margin-top: 0; } -.doclib-chat-bubble-body p:last-child { margin-bottom: 0; } -.doclib-chat-bubble-body pre { - font-size: 10px; - margin: 4px 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--bg) 60%, transparent); - border-radius: 6px; - overflow-x: auto; -} -.doclib-chat-bubble-body code { - font-size: 10px; - padding: 1px 4px; - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.doclib-chat-bubble-body pre code { background: none; padding: 0; } -.doclib-chat-bubble-body ul, -.doclib-chat-bubble-body ol { - margin: 4px 0; - padding-left: 18px; -} -.doclib-card-collapse-chevron { - display: none; - align-items: center; - justify-content: center; - width: 24px; - height: 22px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: transparent; - color: var(--color-muted, var(--fg)); - font: inherit; - opacity: 0.3; - cursor: pointer; - flex-shrink: 0; - margin: 0 0 0 4px; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.doclib-card-collapse-chevron:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); -} -.cookbook-serve-title { - display: flex; - align-items: center; - min-width: 0; -} -.cookbook-serve-title .doclib-card-collapse-chevron { - display: inline-flex; - margin-left: auto; - margin-top: 0; -} -.doclib-card-expanded .doclib-card-collapse-chevron { - display: inline-flex; -} -/* Expanded card — fills the whole grid */ -.doclib-card.doclib-card-expanded { - flex: 1; - min-height: 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); - border: 1px solid var(--border); - border-radius: 8px; - display: flex; - flex-direction: column; - overflow: hidden; - animation: doclib-expand 0.2s ease-out; -} -.doclib-card.doclib-card-expanded { - flex-direction: column !important; - align-items: stretch !important; - max-height: none !important; - gap: 4px !important; -} -.doclib-card.doclib-card-expanded > .memory-item-actions { display: none; } -/* Non-preview children of an expanded doc card carry an inline flex:1 from - the row layout. With the card now flex-column those children would steal - half the height and shrink the preview — pin them at auto height. */ -.doclib-card.doclib-card-expanded > div:not(.doclib-card-preview):not(.doclib-card-header):not(.memory-item-actions):not(.email-card-reader) { - flex: 0 0 auto !important; -} -/* The email reader IS the scroll container — keep its flex:1 so its - internal body can claim the rest of the card's height and scroll. */ -.doclib-card.doclib-card-expanded > .email-card-reader { - flex: 1 1 auto !important; - min-height: 0 !important; -} -.doclib-card.doclib-card-expanded .doclib-card-preview { - display: flex; - flex-direction: column; - flex: 1 1 auto !important; - /* The preview matches chat rows: document content scrolls, while the - footer stays in the card rather than becoming the last scroll item. */ - overflow: hidden; - min-height: 0; -} -.doclib-card.doclib-card-expanded .doclib-card-expanded-actions { - display: flex; - flex-shrink: 0; -} -/* Document cards can be expanded in the normal windowed library, where the - grid has no definite height. A zero-basis then collapses to nothing - while the footer remains visible. Keep the document text's intrinsic height - in that layout; bounded/fullscreen layouts can still shrink it and scroll. */ -#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview > pre { - flex: 1 1 auto; - min-height: 1.5em; -} - -/* Collapsible skills section headers (Your skills / Built-in). */ -.skills-section-header { - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - user-select: none; - padding: 6px 4px 4px; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.06em; - opacity: 0.6; - font-weight: 600; -} -.skills-section-header:hover { opacity: 0.9; } -.skills-section-chevron { transition: transform 0.15s ease; flex-shrink: 0; } -.skills-section-header.collapsed .skills-section-chevron { transform: rotate(-90deg); } -.skills-section-count { - margin-left: auto; - opacity: 0.6; - font-variant-numeric: tabular-nums; - font-weight: normal; -} -.skill-card-section-hidden { display: none !important; } -/* Warning banner shown when previewing/editing a built-in capability. */ -.skill-builtin-warn { - font-size: 9.5px; - line-height: 1.45; - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 35%, transparent); - border-radius: 6px; - padding: 6px 9px; - margin: 0 0 8px; - flex-shrink: 0; -} - -/* #skills-list wears both .memory-list and .doclib-grid. doclib-grid's - max-height:400px would cap the list AND clamp an expanded card — keep - the memory-list fill-the-modal behaviour instead. */ -#skills-list.doclib-grid { - max-height: none; - flex: 1; - min-height: 0; -} -#skills-list.doclib-grid:has(.doclib-card-expanded) { - overflow: hidden; /* expanded card owns its own scroll */ -} -/* When a skill is expanded, hide the sibling Add-Skill form card so the - expanded SKILL.md uses the full panel. Otherwise the two admin-cards - split the height ~50/50 and the expanded skill is stuck in its half - while the Add form idles in the other. */ -.memory-tab-panel[data-memory-panel="skills"]:has(.doclib-card-expanded) > .admin-card:nth-of-type(2) { - display: none !important; -} - -/* Skills cards reuse the doclib expand/footer machinery. The SKILL.md - preview + the edit textarea need to fill the expanded card and own - their own scroll, same as a document preview. */ -/* Collapsed skill rows used a smaller (0.9em code) title, so the click row - read slimmer than document/chat/library cards. Give the header a min-height - so the tap target matches the other library items. */ -.skill-card > .skill-card-header { - min-height: 53px; - box-sizing: border-box; - display: flex; - align-items: center; - gap: 6px; -} -/* Skill rows share Tasks' card hover colors. Draft state belongs in the - status badge, not in a second opacity layer across the whole row. */ -/* Name + description column. Name wraps to 2 lines (skill slugs are long — - use the space rather than truncating to "check-model-downl…"). */ -.skill-card-textcol { - flex: 1 1 auto; - min-width: 0; - overflow: hidden; -} -.skill-card-title-row { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.skill-card-name { - font-family: inherit; - font-weight: 500; - font-size: 12px; - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - word-break: normal; - line-height: normal; -} -.skill-card-title-row .skill-card-name { min-width: 0; } -.skill-status-published { - width: 20px; - height: 20px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 15%, transparent); - border-color: color-mix(in srgb, var(--color-success, #4ade80) 32%, transparent); - border-radius: 50%; -} -.skill-stats .skill-status-published { - width: 15px; - height: 15px; - margin-left: 3px; - vertical-align: middle; -} -.skill-stats .skill-status-published svg { - width: 9px; - height: 9px; -} -.skill-card-desc { - font-size: 10px; - opacity: 0.4; - margin-top: -1px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.skills-audit-model-field { - display: flex; - align-items: center; - gap: 8px; - margin-top: 10px; - font-size: 11px; -} -.skills-audit-model-field > span { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 68%, transparent); -} -.skills-audit-model-field select { - min-width: 0; - flex: 1; - height: 26px; - padding: 0 6px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.skill-card-tags { - display: flex; - gap: 4px; - flex-wrap: wrap; - margin-top: 4px; -} -.skill-tag-pill { - padding: 1px 5px; - font-size: 9px; -} -.skill-usage-pill { - margin-left: auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.skill-tag-filter-chip { cursor: pointer; } - -@media (max-width: 768px) { - .skill-card > .skill-card-header { - align-items: flex-start; - } - .skill-card-textcol { - min-width: 0; - } - .skill-card-tags { - max-width: 100%; - overflow: hidden; - } - .skill-card-tags .skill-tag-pill { - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -} -/* Right cluster — pills (right-aligned), stats, then the menu/chevron. */ -.skill-card-right { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; - margin-left: auto; -} -.skill-card-right .memory-cat-badge { flex-shrink: 0; } -.skill-status-bin { - background: color-mix(in srgb, var(--color-danger, #d9534f) 20%, transparent); - color: var(--color-danger, #d9534f); -} -.skill-model-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-model-student { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.skill-audit-pass { - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 16%, transparent); -} -.skill-audit-needs_work { - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 16%, transparent); -} -.skill-audit-fail { - color: var(--color-danger, #e06c75); - background: color-mix(in srgb, var(--color-danger, #e06c75) 16%, transparent); -} -.skill-audit-inconclusive, -.skill-audit-skipped, -.skill-audit-unknown { - color: color-mix(in srgb, var(--fg) 64%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-model-teacher { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 24%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-necessity-duplicate { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 22%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-trivial { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 62%, transparent); - border-color: color-mix(in srgb, var(--fg) 16%, transparent); -} -.skill-necessity-irrelevant { - background: color-mix(in srgb, var(--color-danger, #e06c75) 18%, transparent); - color: var(--color-danger, #e06c75); -} -.skills-summary-strip { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - margin: -3px 0 8px; -} -.skills-summary-strip::-webkit-scrollbar { display: none; } -.skills-summary-strip:empty { - display: none; -} -.skills-summary-chip { - display: inline-flex; - align-items: center; - gap: 5px; - height: 23px; - padding: 0 8px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 10px; - line-height: 1; - cursor: pointer; - flex: 0 0 auto; -} -.skills-summary-chip:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.skills-summary-chip.active { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - color: var(--fg); -} -.skills-summary-chip strong { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10px; - color: var(--fg); -} - -/* Shared filter/tag strip contract. These rows all sit immediately below - their screen's search or primary toolbar and stay one line at every width. */ -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -) { - align-items: center; - flex-wrap: nowrap !important; - gap: 6px; - width: 100%; - max-width: 100%; - min-width: 0; - max-height: none; - overflow-x: auto !important; - overflow-y: hidden !important; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - box-sizing: border-box; - padding: 0; - margin: -3px 0 8px; -} -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -)::-webkit-scrollbar { display: none; } -:is( - .skills-summary-strip > .skills-summary-chip, - #memory-category-filters > .skills-summary-chip, - .memory-category-filters > .memory-cat-chip, - .doclib-lang-chips > :is(.memory-cat-chip, .skills-summary-chip), - .doclib-chips > .doclib-chip, - .notes-labels-bar > .notes-label-chip, - .tasks-activity-filters > .memory-cat-chip, - .gallery-tag-chips > .gallery-chip, - .gallery-album-chips > .gallery-chip, - .gallery-ai-tags > .gallery-ai-chip, - .cal-filters > .cal-filter-item -) { - flex: 0 0 auto; - box-sizing: border-box; - height: 23px !important; - min-height: 23px; - padding: 0 8px !important; - border-radius: 6px !important; - font-size: 10px !important; - line-height: 1; - white-space: nowrap; -} -.doclib-lang-chips { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.doclib-chip-scroll-frame { - position: relative; - display: flex; - align-items: center; - flex: 1 1 100%; - min-width: 0; - max-width: 100%; -} -.doclib-chip-scroll-frame > .chip-scroll-strip { - flex: 1 1 auto; - min-width: 0; - margin-left: 0; - margin-right: 0; -} -.doclib-chip-scroll-arrow { - display: none; - position: absolute; - top: calc(50% - 1px) !important; - transform: translateY(-50%); - margin-top: 0; - z-index: 2; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - box-sizing: border-box; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 18px; - cursor: pointer; - opacity: 0.84; - transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease; -} -.doclib-chip-scroll-arrow-glyph { - display: inline-block; - position: relative; - top: -1px; - line-height: 1; -} -.doclib-chip-scroll-arrow:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); -} -@media (max-width: 768px) { - .doclib-chip-scroll-arrow { display: none !important; } -} -.doclib-chip-scroll-frame.has-overflow > .doclib-chip-scroll-arrow:not([hidden]) { - display: inline-flex; -} -.doclib-chip-scroll-arrow.left { left: 0; } -.doclib-chip-scroll-arrow.right { right: 0; } -.doclib-chip-scroll-frame.has-overflow > .chip-scroll-strip { - padding-left: 0 !important; - padding-right: 22px !important; -} -.doclib-chip-scroll-frame > .chip-scroll-strip.is-pointer-dragging { - cursor: grabbing; - user-select: none; -} -.doclib-chip-scroll-frame > .chip-scroll-strip:not(.is-pointer-dragging) { - cursor: grab; -} -/* Calendar tags are wrapped by chipScroll.js after render. Keep that wrapper - content-sized inside the calendar's column layout; the default flex growth - otherwise turns the one-line tag strip into a large empty panel. */ -#cal-body > .doclib-chip-scroll-frame:has(> .cal-filters) { - flex: 0 0 auto; - min-height: 0; -} -/* The completed-task filters are a compact toolbar, not a flexible panel. - Without this override the shared chip frame grows into the available - height of the card, which is especially visible the first time the mobile - view switches to Completed. */ -.tasks-completed-card > .doclib-chip-scroll-frame:has(> #tasks-completed-status-chips) { - flex: 0 0 auto; - min-height: 0; -} -/* The active Tasks filter row is also a compact toolbar. Keep its wrapper - content-sized so changing a filter cannot make the mobile panel grow a - large empty gap between the chips and the task cards. */ -.admin-card > .doclib-chip-scroll-frame:has(> #tasks-filter-chips) { - flex: 0 0 auto; - min-height: 0; -} -#memory-category-filters { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.cookbook-serve-tag-label { - display: inline-flex; - align-items: center; - min-width: 0; -} -#serve-search, -#hwfit-cache-select, -#serve-tags { - position: relative; - top: -2px; -} -#hwfit-cache-select { - top: -4px; -} -/* Calendar previously framed its tags as a separate card. It now follows the - same unframed placement as the other filter strips. */ -.cal-filters { - border: 0; - background: none; - box-shadow: none; -} -.skill-duplicate-keep { - background: color-mix(in srgb, var(--color-success, #4ade80) 22%, transparent); - color: var(--color-success, #4ade80); -} -.skill-duplicate-group { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - color: color-mix(in srgb, var(--fg) 62%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-duplicate-lower { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); - color: var(--color-danger, #e06c75); -} -.skill-stats { - font-size: 9px; - font-family: monospace; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.skill-turns-saved { - font-size: 9px; - opacity: 0.82; - font-variant-numeric: tabular-nums; - white-space: nowrap; - flex: 0 0 auto; -} -.skill-expanded-savings { - display: flex; - align-items: center; - min-height: 18px; - margin: 0 0 5px; -} -.skill-turns-positive { - color: color-mix(in srgb, var(--color-success, #4ade80) 82%, var(--fg)); -} -.skill-turns-zero, -.skill-turns-unknown { - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.skill-turns-negative { - color: color-mix(in srgb, var(--color-danger, #e06c75) 80%, var(--fg)); -} -.skill-conf { - display: inline-block; - min-width: 4ch; - text-align: right; - font-weight: 700; - color: var(--accent, var(--red)); -} -.skill-verified, -.skill-teachermark, -.skill-needsmark { - display: inline-flex; - align-items: center; - vertical-align: middle; - margin-right: 3px; -} -.skill-verified { color: var(--accent, #4ade80); } -.skill-teachermark { color: var(--color-warning, #f0ad4e); } -.skill-needsmark { color: var(--color-danger, #e06c75); cursor: help; } - -/* The card currently being processed by an "Audit now" run glows + pulses so - it's obvious which one the audit is on. */ -@keyframes skill-audit-pulse { - 0%, 100% { box-shadow: 0 0 6px 0 color-mix(in srgb, var(--accent, #4ade80) 55%, transparent); } - 50% { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent, #4ade80) 85%, transparent); } -} -.skill-card.skill-audit-active { - border-color: var(--accent, #4ade80) !important; - animation: skill-audit-pulse 1.3s ease-in-out infinite; -} - -/* A test is running for this skill — the app's whirlpool spinner is injected - next to the name from JS (see _setCardRunning) so the collapsed/folded card - still makes it obvious work is in progress. */ -/* Collapsed bar shows the kebab menu; expanded shows an up-chevron to - collapse. (Toggled by the .doclib-card-expanded class.) */ -.skill-kebab-btn { - display: inline-flex; - align-items: center; - justify-content: center; - background: none; - border: none; - padding: 4px; - margin: 0; - cursor: pointer; - color: var(--fg); - opacity: 0.5; - border-radius: 5px; - flex-shrink: 0; - position: relative; - top: -2px; -} -.skill-kebab-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-chevron-up { display: none; align-items: center; opacity: 0.5; flex-shrink: 0; } -/* Kebab dropdown */ -.skill-kebab-menu { - position: fixed; - /* z-index is set inline via topPortalZ() at open time (#4720); a static - value here loses once the modal bring-to-front counter climbs past it. */ - min-width: auto; - width: max-content; - padding: 4px; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 6px 20px rgba(0,0,0,0.22); - display: flex; - flex-direction: column; - gap: 1px; -} -.skill-kebab-item { - display: flex; - align-items: center; - gap: 10px; - width: 100%; - padding: 6px 8px; - background: none; - border: none; - border-radius: 8px; - cursor: pointer; - color: var(--fg); - font-size: 11px; - text-align: left; -} -.skill-kebab-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-kebab-item.danger { color: var(--color-error, #e55); } -.skill-kebab-item.danger:hover { background: color-mix(in srgb, var(--color-error, #e55) 15%, transparent); } -/* Section labels separating "Your skills" from the read-only "Built-in - capabilities" list. */ -.skills-section-label { - font-size: 10px; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.04em; - margin: 10px 2px 4px; - flex-shrink: 0; -} -.skills-section-label:first-child { margin-top: 0; } -/* When a learned skill is expanded the grid hides sibling cards; hide the - section labels too so they don't orphan above the collapsed list. */ -#skills-list:has(.doclib-card-expanded) .skills-section-label { display: none; } -/* Built-in cards are read-only — no expand/hover-pointer affordance. */ -.skill-builtin-card { cursor: default; } -.skill-builtin-card:hover { border-color: var(--border); } -.skill-card-preview .skill-md-pre { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - white-space: pre-wrap; - word-break: break-word; -} -.skill-preview-copy { - position: absolute; - top: 6px; - right: 8px; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - padding: 0; - opacity: 0; - color: var(--fg); - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - cursor: pointer; - transition: opacity .15s, color .15s, border-color .15s; -} -.skill-card-preview:hover .skill-preview-copy { opacity: 0.7; } -.skill-preview-copy:hover, -.skill-preview-copy:focus-visible { opacity: 1 !important; } -.skill-preview-copy.copied { - opacity: 1; - color: var(--color-save-green, #4caf50); - border-color: var(--color-save-green, #4caf50); -} -/* :has()-free expand layout. skills.js adds .skills-has-expanded to the - skills admin-card when a skill opens, so this works on engines that don't - support :has() (e.g. older Firefox mobile, where the expand stuck at - ~50%). Hide everything but the list so the grid fills the card; the - absolutely-positioned expanded card (mobile) then fills the grid. */ -.admin-card.skills-has-expanded > *:not(#skills-list) { - display: none !important; -} -.admin-card.skills-has-expanded > #skills-list.doclib-grid { - flex: 1 1 auto !important; - height: 100% !important; - min-height: 0 !important; - overflow: hidden !important; -} -/* Expand animation — a clean opacity fade. The mobile card snaps to a - full-screen overlay (position:absolute), so any translate/scale on it - reads as a jarring "explosion"; a pure fade just appears smoothly. (No - transform also keeps skills.js's height measurement accurate.) */ -@keyframes skill-card-expand { - from { opacity: 0; } - to { opacity: 1; } -} -.skill-card.doclib-card-expanded { - animation: skill-card-expand 0.18s ease-out; -} -/* Switching directly from one expanded card to another: no fade (it would - show the previous card collapsing through the semi-transparent new one). */ -.skill-card.doclib-card-expanded.skill-expand-instant { - animation: none !important; -} -@media (prefers-reduced-motion: reduce) { - .skill-card.doclib-card-expanded, - .skill-card.doclib-card-expanded .doclib-card-preview { - animation: none !important; - } -} -/* The preview WRAPPER inherits flex:1 (grow) from the generic doclib expand - rule — that's what stretched the card and left a void / pushed the footer - out. Size it to content (shrink + scroll only when long) so the footer - sits right under the preview and stays fully visible. */ -.skill-card.doclib-card-expanded .doclib-card-preview { - flex: 0 1 auto !important; - min-height: 0; - /* The footer lives INSIDE this wrapper. Don't let the wrapper scroll, or - the footer scrolls off with the content. Keep it clipped; the - inside is the scroller, so the footer stays pinned at the bottom. */ - overflow: hidden !important; -} -.skill-card.doclib-card-expanded .skill-md-pre { - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them - back down 4px so they're vertically centered in the skill card footer. */ -.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn, -.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - top: 0; -} -/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */ -#skills-bulk-bar .memory-toolbar-btn { - position: relative; - top: -2px; -} -/* ── Audit-all progress panel ── */ -.skills-audit-panel { - border: 1px solid var(--border); - border-radius: 8px; - padding: 8px 10px; - margin: 6px 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } -.skills-audit-title { font-size: 12px; font-weight: 600; } -.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; } -.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; } -.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; } -.skills-audit-log { - max-height: 22vh; overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10.5px; line-height: 1.5; opacity: 0.8; -} -/* ── Skill test monitor + AI eval verdict ── */ -.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; } -.skill-test-log { - flex: 1 1 auto; - min-height: 120px; - max-height: 48vh; - overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.45; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - white-space: pre-wrap; - word-break: break-word; -} -.skill-test-log > div { margin: 1px 0; } -.skill-test-meta { opacity: 0.5; } -.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; } -.skill-test-round { opacity: 0.6; margin-top: 6px !important; } -.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; } -.skill-test-out { opacity: 0.7; padding-left: 10px; } -.skill-test-say { white-space: pre-wrap; } -.skill-test-err { color: var(--color-danger, #e06c75); } -.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } -.skill-eval-badge { - font-size: 10px; font-weight: 700; letter-spacing: 0.03em; - padding: 2px 8px; border-radius: 10px; flex-shrink: 0; -} -.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); } -.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); } -.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); } -.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); } -.skill-eval-summary { font-size: 11px; opacity: 0.85; } -.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; } -.skill-eval-issues li { margin: 2px 0; } -.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } -.skill-eval-approve.suggested { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; -} -/* Already published — the button confirms the state (click to unpublish). */ -.skill-eval-approve.is-approved { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; - background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important; -} -/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title, - When to use, How, Tags) is accent-colored while the rest stays muted — real - placeholders can't be partially colored. The native placeholder is a single - space so :placeholder-shown still toggles the overlay. */ -.skill-ph-wrap { position: relative; } -.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; } -.skill-rich-ph { - position: absolute; - left: 11px; right: 11px; top: 50%; - transform: translateY(-50%); - pointer-events: none; - font-size: 12px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 40%, transparent); - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -/* Textarea: align the hint to the first line instead of vertical-centering. */ -.skill-rich-ph-top { top: 7px; transform: none; } -.skill-rich-ph .k { color: var(--accent, var(--red)); } -/* Keep the memory-input enter hint pinned to the field's right edge instead - of letting it drift after the example text. */ -#new-memory-input ~ .skill-rich-ph > svg { - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - margin-left: 0 !important; -} -/* Hide the overlay once the field has real content. */ -.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; } -.skill-md-editor { - flex: 1 1 auto; - min-height: 0; - width: 100%; - box-sizing: border-box; - resize: none; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); -} -.doclib-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -/* Unified loading row across every Library tab (Chats / Documents / Research / - Archive): one opacity, one size, with the whirlpool spinner next to it. */ -.lib-loading-row { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 28px 16px; - font-size: 12px; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.pdf-loading-state { - min-height: 120px; - margin: 0; - box-sizing: border-box; - flex-direction: column; - gap: 10px; - border: 0; - border-radius: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - box-shadow: none; - font-size: 12px; - font-weight: 600; - letter-spacing: 0.01em; -} -.pdf-loading-state .ai-spinner-whirlpool { - color: var(--accent, var(--red)); - opacity: 0.95; -} -.pdf-loading-state .ai-spinner-whirlpool canvas { - display: block; -} -.doclib-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg-muted); - font-size: 11px; - cursor: pointer; - transition: border-color 0.15s, color 0.15s; - flex-shrink: 0; - position: relative; - top: 10px; -} -/* Soft dark gradient above the button — blends the list content into the - load-more strip instead of a hard cutoff. Spans the modal width and - sits behind/above the button. */ -.doclib-load-more::before { - content: ""; - position: absolute; - left: 50%; - transform: translateX(-50%); - bottom: 100%; - margin-bottom: 4px; - width: min(600px, 92vw); - height: 34px; - pointer-events: none; - background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent); -} -.doclib-load-more:hover { - border-color: var(--red); - color: var(--red); -} -.doclib-inline-load-more { - border: 0; - border-radius: 0; - padding: 0; - background: none; - top: 0; - font-size: 9px !important; - line-height: 1.2 !important; -} - -/* Document library toolbar buttons */ -.doclib-toolbar-btn { - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - font-size: 11px; - padding: 5px 10px; - border-radius: 6px; - cursor: pointer; - transition: all 0.15s; - white-space: nowrap; - font-family: inherit; - display: inline-flex; - align-items: center; - gap: 3px; -} -.doclib-toolbar-btn:hover { - color: var(--fg); - border-color: var(--fg); -} -.doclib-toolbar-btn.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -.doclib-toolbar-btn.danger { - color: var(--color-error, #e55); -} -.doclib-toolbar-btn.danger:hover:not(:disabled) { - border-color: var(--color-error, #e55); -} -.doclib-toolbar-btn:disabled { - opacity: 0.4; - cursor: default; -} - -/* Bulk action bar */ -.doclib-bulk-bar { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--red) 5%, transparent); - font-size: 10px; - margin-bottom: 8px; -} -.doclib-bulk-bar.hidden { - display: none; -} -.doclib-bulk-check-all { - display: flex; - align-items: center; - gap: 3px; - cursor: pointer; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-size: 10px; -} -#doclib-bulk-bar .memory-toolbar-btn { - position: relative; - top: -3px; -} - -/* Custom checkboxes */ -.doclib-select-cb, -.doclib-bulk-check-all input { - -webkit-appearance: none; - appearance: none; - width: 13px; - height: 13px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - cursor: pointer; - flex-shrink: 0; - margin: 0; - position: relative; - transition: all 0.15s; -} -.doclib-select-cb:hover, -.doclib-bulk-check-all input:hover { - border-color: var(--red); -} -.doclib-select-cb:checked, -.doclib-bulk-check-all input:checked { - background: var(--red); - border-color: var(--red); -} -.doclib-select-cb:checked::after, -.doclib-bulk-check-all input:checked::after { - content: ''; - position: absolute; - left: 3px; - top: 0px; - width: 4px; - height: 8px; - border: solid var(--bg); - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); -} - - -/* + button outside scroll area (when doc is on right side) */ - -@media (max-width: 600px) { - .doclib-toolbar { - flex-direction: column; - } - .doclib-card-session, - .doclib-card-time { - display: none; - } - .doclib-card-preview { - max-height: 40vh; - } -} - -/* Collapse card tags before they compete with attachment/favorite controls. */ -.email-card-tags .email-tags-more { - color: var(--email-tags-more-color, var(--fg)); - background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent); -} -.email-tags-more-single, -[data-email-tags-count-mini] { display: none; } -.email-tags-more-single svg { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag, -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra { - display: none; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single, -.email-card-tags.email-tags-expanded .email-tags-more-single { - display: inline-flex; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini], -.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; } - -/* Keep the reader actions in drafting order. */ -.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; } -.email-reader-actions-inline > [data-act="reply"] { order: 2; } -.email-reader-actions-inline > [data-act="reply-all"] { order: 3; } -.email-reader-actions-inline > [data-act="forward"] { order: 4; } -.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed { - display: none !important; -} - -/* ── Archive browser ── */ -.archive-list { - grid-template-columns: 1fr !important; - gap: 4px !important; -} -.archive-row { - flex-direction: row !important; -} -.archive-row .doclib-card-header { - flex: 1; - padding: 8px 10px; -} -.archive-row .doclib-card-title { - flex: 1; - min-width: 0; -} -/* Archive column layout */ -.archive-header { - display: flex; - align-items: center; - padding: 4px 10px; - gap: 6px; - font-size: 9px; - font-weight: 600; - opacity: 0.35; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.archive-col-title { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: -4px; -} -.archive-col-msgs { - width: 40px; - flex-shrink: 0; - text-align: left; - margin-left: -8px; -} -.archive-col-model { - width: 90px; - flex-shrink: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.archive-col-time { - width: 55px; - flex-shrink: 0; - text-align: right; -} -.archive-col-menu { - width: 24px; - flex-shrink: 0; -} -.archive-menu-btn { - background: transparent; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - padding: 4px; - line-height: 0; - border-radius: 4px; - transition: background .15s, opacity .15s; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - margin-left: 4px; - vertical-align: middle; - height: 100%; -} -.archive-menu-btn svg { - display: block; - margin-top: -6px; -} -.archive-menu-btn:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - opacity: 1; -} - -/* ── Gallery (image library) ── */ - -.gallery-modal-content { - width: min(820px, 94vw); - max-height: 92vh; - font-size: 12px; -} -/* Keep the Gallery frame anchored while tabs or bulk-selection controls - change the content height. Centering the frame made every shorter tab move - the whole window vertically. */ -@media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal, - #cookbook-modal { - align-items: flex-start; - padding-top: 4vh; - box-sizing: border-box; - } -} -/* While the Edit tab is active the editor needs a *definite* height so - the right-side tool panel (`.ge-right-panel { overflow-y:auto }`) - scrolls INTERNALLY. Without this the modal only has `max-height`, so - `.gallery-editor { height:100% }` can't resolve — the editor grows to - its full content height, overflows the modal body, and the modal-body - scrollbar that appears can't be wheel-scrolled because the editor's - `overscroll-behavior:contain` blocks the chain. Pinning a real height - here makes the panel bounded and scrollable as designed. Scoped to - the editor view (matched via the container's inline `display:flex`) - so the Photos/Albums views keep sizing to their content. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) { - height: 92vh; -} -/* The editor tab owns the modal body's scroll context. Keep the landing - chooser and the mounted editor inside the available sheet height so a long - saved-project list cannot push the launch controls below the viewport. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body { - display: flex; - min-height: 0; - overflow: hidden; -} -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container { - min-height: 0; - overflow: hidden; -} -/* Photo-detail view sizing (issue #314). - The detail view is rendered as a `position:absolute; inset:0` overlay - *inside* `.gallery-images-container`, painted over the photo grid. Because - it's absolutely positioned it can't contribute to the container's height — - the container (and therefore the overlay's `inset:0` box) collapses to the - height of the grid sitting behind it. When the library only has a few - photos that grid is short, so the detail view is crushed: the image is - clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a - tiny, internally-scrolling strip. (With a large library the grid is tall, - which is why it looked fine in the demo video but cramped for users with - few photos.) - Fix: when the detail view is open, hide the grid-view siblings and drop the - overlay into normal flow. The container — and the window, up to its 92vh - max-height — then sizes to the detail's own content (image + metadata), so - nothing is clipped or squeezed regardless of how many photos exist. Scoped - via the detail element's inline `display:flex` so the grid / albums views - keep sizing to their own content. Works on both desktop and the mobile - full-screen sheet. */ -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) { - display: none !important; -} -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail { - position: static; -} -/* Containing block for the photo-detail overlay — keeps it inside the body - so it sits below the modal header and the tab strip instead of covering them. */ -.gallery-images-container { - position: relative; -} -.gallery-modal-content .modal-header h4 { - font-size: 1rem; -} -.gallery-stats { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-bottom: 4px; -} -.gallery-toolbar { - display: flex; - gap: 6px; - margin-bottom: 4px; - align-items: center; -} -.gallery-toolbar-break { display: none; } -/* Keep the hint anchored while independently seating the input in the row. */ -.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; } -.gallery-search { - position: relative; - top: 4px; - flex: 1; - width: 100%; - padding: 4px 8px; - padding-right: 78px; /* room for the enter hint */ - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - font: inherit; - font-size: 12px; - outline: none; -} -.gallery-search-enter-hint { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - font-weight: 600; - color: var(--accent, var(--red)); - pointer-events: none; - opacity: 0; - transition: opacity 0.12s; - white-space: nowrap; -} -/* Show the hint only once the user has typed something (placeholder gone). */ -.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; } -.gallery-search:focus { - border-color: var(--red); -} -.gallery-model-filter, -.gallery-sort { - padding: 4px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - position: relative; - top: 4px; -} -.gallery-selection-bar { - margin-bottom: 4px; - position: relative; - top: 2px; - padding-right: 18px; -} -.gallery-tag-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 4px; -} -.gallery-chip { - height: 28px; - box-sizing: border-box; - padding: 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - border-radius: 14px; - font-size: 12px; - line-height: 1; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: border-color 0.15s, background 0.15s; -} -.gallery-chip:hover { - border-color: var(--red); -} -.gallery-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Album chips row */ -.gallery-album-chips { - display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px; -} -.gallery-chip-fav { color: var(--red); } -.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); } -.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; } -/* Active-album indicator — appears when the Photos grid is filtered to one album. */ -.gallery-chip-active-album, -.gallery-active-filter-pill { - display: inline-flex; align-items: center; gap: 4px; - background: color-mix(in srgb, var(--red) 14%, transparent); - border-color: color-mix(in srgb, var(--red) 45%, transparent); - position: relative; - top: 6px; -} -.gallery-active-filter-pill { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - color: color-mix(in srgb, var(--fg) 86%, transparent); -} -.gallery-clear-all-filters { - height: 28px; - border-style: dashed; - opacity: 0.72; -} -.gallery-clear-all-filters:hover { - opacity: 1; - color: var(--red); -} -.gallery-chip-clear { - background: none; - border: none; - color: inherit; - opacity: 0.6; - cursor: pointer; - padding: 0; - margin-left: 1px; - width: 12px; - height: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 12px; - line-height: 1; - position: relative; - top: -4px; -} -.gallery-chip-clear:hover { opacity: 1; } - - - -/* Favorite button on card */ -/* Video cards: fills the same slot as , plus a small play badge - so it's clear the thumbnail represents a video. */ -.gallery-card video { - width: 100%; - aspect-ratio: 1 / 1; - object-fit: cover; - background: #000; - display: block; -} -.gallery-card-play { - position: absolute; - left: 50%; top: 50%; - transform: translate(-50%, -50%); - width: 36px; height: 36px; - display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.55); - color: #fff; - border-radius: 50%; - pointer-events: none; - z-index: 1; -} -.gallery-card-play svg { margin-left: 2px; } - -.gallery-fav-btn { - position: absolute; top: 0px; right: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; font-size: 14px; - color: rgba(255,255,255,0.6); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s; - padding: 0; -} -.gallery-fav-btn.gallery-fav-active { opacity: 1; color: var(--red); } - -.gallery-dl-btn { - position: absolute; top: 0px; left: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; - color: rgba(255,255,255,0.75); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s, background 0.15s; - padding: 0; -} -.gallery-dl-btn:hover { background: rgba(0,0,0,0.7); color: #fff; } -/* In select mode the per-thumbnail hover buttons (favorite + download) just - get in the way of picking — hide them so the card is a clean select target. - `body.gallery-selecting` is the authoritative signal (one class for the - whole grid) — the per-card .gallery-card-selectable is kept as a backup - in case the body class is missed. */ -body.gallery-selecting .gallery-fav-btn, -body.gallery-selecting .gallery-dl-btn, -.gallery-card-selectable .gallery-fav-btn, -.gallery-card-selectable .gallery-dl-btn { - display: none !important; -} - -/* AI tag chips in detail view */ -.gallery-ai-tags { display: flex; gap: 4px; flex-wrap: wrap; } -.gallery-ai-chip { - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); - border-radius: 10px; padding: 3px 8px; font-size: 10px; - color: var(--fg); opacity: 0.8; - font: inherit; font-size: 10px; - cursor: pointer; - transition: background 0.12s, opacity 0.12s, border-color 0.12s; -} -.gallery-ai-chip:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); -} -/* AI-generated tags: muted neutral with a tiny sparkle marker so the - user can tell at a glance these came from the model, not them. */ -.gallery-aitag-chip { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-aitag-chip:hover { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.gallery-aitag-mark { - font-size: 8px; - opacity: 0.55; - color: var(--accent, var(--red)); -} -/* User-applied tags keep the accent-colored chip — they're the personal/ - curated tags, so they get the strongest visual weight. */ -.gallery-user-chip { - font-weight: 600; -} -/* × to remove a user tag (appears inside the chip). */ -.gallery-tag-x { - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 5px; - width: 13px; - height: 13px; - border-radius: 50%; - font-size: 12px; - line-height: 1; - opacity: 0.55; - transition: opacity 0.12s, background 0.12s, color 0.12s; -} -.gallery-tag-x:hover { - opacity: 1; - background: color-mix(in srgb, var(--red, #ff5555) 22%, transparent); - color: var(--red, #ff5555); -} - -.gallery-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - align-content: start; - gap: 8px; - max-height: calc(92vh - 165px); - overflow-y: auto; - padding: 2px; - transition: border-color 0.15s, background 0.15s; -} -.gallery-grid.gallery-dragover { - border: 2px dashed var(--red); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.gallery-card { - position: relative; - aspect-ratio: 1; - border-radius: 6px; - overflow: hidden; - border: 1px solid var(--border); - cursor: pointer; - transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s; -} -.gallery-card.gallery-card-focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 36%, transparent); -} -/* Upload-affordance tile pinned to the top-left of the Photos grid. Matches - the Upload album tile in the Albums tab — dashed border, centered icon. */ -.gallery-card-upload { - border-style: dashed; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - opacity: 0.75; - transition: opacity 0.12s, border-color 0.15s, transform 0.15s; -} -/* The "Start AI tag" button turns into a Cancel control during a tag run. */ -.gallery-tag-cancelling { - color: var(--red) !important; - border-color: color-mix(in srgb, var(--red) 45%, var(--border)) !important; -} -/* Skeleton/shimmer placeholder tiles shown while the first page of photos loads. */ -.gallery-card-skeleton { - cursor: default; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.gallery-card-skeleton:hover { transform: none; box-shadow: none; border-color: var(--border); } -/* Chat attachment image: shimmer skeleton + centered whirlpool shown while a - sent photo uploads / its thumbnail loads (see chatRenderer buildAttachCards). */ -.attach-image-skeleton { - position: relative; - width: 160px; height: 120px; - border-radius: 6px; - display: flex; align-items: center; justify-content: center; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.attach-image-skeleton > .spinner-whirlpool { margin: 0 !important; } - -/* Corner "Aa" button on a chat photo thumbnail — opens the vision/OCR editor - so the user can correct what the vision model fed to the LLM. */ -.attach-image-preview { position: relative; } -.attach-vision-model, -.attach-image-name { - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.attach-vision-model { - font-size: 10px; - color: var(--accent, var(--red)); - opacity: 0.85; - margin-top: 3px; -} -.attach-image-name { - font-size: 10px; - opacity: 0.5; - margin-top: 1px; -} -.attach-ocr-btn { - position: absolute; top: 4px; right: 4px; - z-index: 5; /* sit above msg-action overlays so the click lands here */ - height: 22px; - background: rgba(0,0,0,0.55); color: #fff; - border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; - padding: 0 8px 0 6px; cursor: pointer; - display: inline-flex; align-items: center; justify-content: center; gap: 5px; - font-size: 11px; font-weight: 500; line-height: 1; - opacity: 0.75; transition: opacity 0.15s; -} -.attach-ocr-btn:hover { opacity: 1; } -.attach-ocr-btn svg { display: block; } -/* On mobile keep just the (larger) edit icon — the "Caption" label crowds - the corner of the small chat photo. */ -@media (max-width: 768px) { - .attach-ocr-btn .attach-ocr-label { display: none; } - .attach-ocr-btn { - width: 28px; height: 28px; - padding: 0; - } - .attach-ocr-btn svg { width: 16px; height: 16px; } -} - -/* Vision/OCR text editor modal — opened from the "Aa" button above. */ -.vision-editor-overlay { - position: fixed; inset: 0; z-index: 9999; - background: rgba(0,0,0,0.55); - display: flex; align-items: center; justify-content: center; - padding: 16px; -} -.vision-editor-panel { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 14px 16px; - width: min(560px, 92vw); - max-height: 88vh; - display: flex; flex-direction: column; gap: 8px; - box-shadow: 0 14px 40px rgba(0,0,0,0.4); -} -.vision-editor-title { - font-size: 13px; font-weight: 600; color: var(--fg); - display: flex; align-items: center; gap: 6px; -} -.vision-editor-desc { font-size: 11px; opacity: 0.6; line-height: 1.4; } -.vision-editor-text { - width: 100%; min-height: 180px; - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 8px 10px; - font-family: inherit; font-size: 12px; line-height: 1.5; - resize: vertical; outline: none; -} -.vision-editor-text:focus { border-color: var(--accent-primary, var(--red)); } -.vision-editor-hint { font-size: 10.5px; opacity: 0.55; line-height: 1.4; } -.vision-editor-actions { - display: flex; gap: 8px; justify-content: flex-end; - margin-top: 4px; -} -.vision-editor-btn { - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; - transition: border-color 0.15s, background 0.15s; - display: inline-flex; align-items: center; gap: 5px; -} -.vision-editor-btn svg { display: block; } -/* Optical nudge: the button text sits 1px higher than the SVG glyphs - alongside it. Drop the text down 1px so they line up. */ -.vision-editor-btn .vision-btn-label { position: relative; top: 1px; } -.vision-editor-btn:hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); } -.vision-editor-btn-primary { - background: var(--accent-primary, var(--red)); - color: #fff; border-color: transparent; -} -.vision-editor-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } - -/* Quick full-size lightbox shown when the user taps a chat photo thumbnail. */ -.attach-lightbox { - position: fixed; inset: 0; z-index: 9998; - background: rgba(0, 0, 0, 0.85); - display: flex; align-items: center; justify-content: center; - padding: 16px; cursor: zoom-out; - animation: attach-lightbox-fade 0.12s ease-out; -} -.attach-lightbox img { - max-width: 100%; max-height: 100%; - border-radius: 6px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); - object-fit: contain; -} -.attach-lightbox-err { - position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); - background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 10px; font-size: 12px; -} -@keyframes attach-lightbox-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -.gallery-card-upload:hover { - opacity: 1; - border-color: var(--red); - transform: translateY(-1px); -} -.gallery-card-upload-inner { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 6px; - color: var(--fg); -} -.gallery-card-upload-label { - font-size: 12px; - font-weight: 500; - opacity: 0.8; -} -.gallery-card:hover { - border-color: var(--red); - box-shadow: 0 2px 8px color-mix(in srgb, var(--red) 15%, transparent); - transform: translateY(-1px); -} -.gallery-card-overlay { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 8px; - background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 34%, rgba(0,0,0,0.22)); - color: #fff; - opacity: 0; - pointer-events: none; - transition: opacity 0.15s; -} -.gallery-card-badges { - display: flex; - gap: 4px; - flex-wrap: wrap; - align-items: flex-end; - justify-content: flex-end; - margin-top: auto; - margin-bottom: 34px; -} -.gallery-card-badge { - padding: 2px 6px; - border-radius: 999px; - background: rgba(0, 0, 0, 0.55); - border: 1px solid rgba(255, 255, 255, 0.14); - color: rgba(255, 255, 255, 0.88); - font-size: 9px; - font-weight: 600; - line-height: 1.1; -} -.gallery-card-badge-fav { - color: #ff5f6f; -} -.gallery-select-dot { - position: absolute; top: 6px; left: 6px; z-index: 2; - display: block; - width: 10px; height: 10px; border-radius: 50%; cursor: pointer; - background: color-mix(in srgb, var(--fg) 30%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 50%, transparent); - transition: background 0.15s; -} -.gallery-select-dot.selected { background: var(--red); border-color: var(--red); } -.gallery-select-dot:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -.gallery-select-btn { - padding: 9px 10px 11px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; transition: all 0.15s; - margin-top: -4px !important; -} -.gallery-select-btn:hover { opacity: 1; } -/* Match the library Select toggle's red tint (.memory-toolbar-btn.active). - Bumped contrast so the toggled state actually reads on mobile — the prior - 15% red on transparent was nearly invisible against the page background. */ -.gallery-select-btn.active { - background: color-mix(in srgb, var(--red) 28%, transparent); - color: var(--red); - border-color: var(--red); - font-weight: 600; - opacity: 1; -} -/* Toolbar action buttons sit 2px lower than the generic select-btn so - they baseline-align with the toolbar's selects/inputs instead of - floating above them. */ -.gallery-toolbar-action { - margin-top: 0 !important; - display: inline-flex; - align-items: center; - gap: 2px; -} -.gallery-bulk-bar { - display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 12px; - justify-content: flex-end; -} -.gallery-bulk-bar.hidden { display: none; } -.gallery-bulk-all { - display: inline-flex; align-items: center; gap: 4px; - font-size: 11px; opacity: 0.75; cursor: pointer; margin-right: auto; -} -.gallery-bulk-all input { accent-color: var(--accent, var(--red)); cursor: pointer; } -.gallery-bulk-delete { - padding: 3px 10px; background: transparent; color: var(--red); - border: 1px solid var(--red); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; -} -.gallery-bulk-delete:hover { background: var(--red); color: #fff; } -.gallery-bulk-cancel { - padding: 3px 10px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; -} -.gallery-bulk-cancel:hover { opacity: 1; } -.gallery-card img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} -.gallery-card-info { - position: absolute; - bottom: 0; - left: 0; - right: 0; - padding: 6px 8px; - background: linear-gradient(transparent, rgba(0,0,0,0.75)); - color: #fff; -} -.gallery-card-prompt { - font-size: 10px; - line-height: 1.3; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.gallery-card-meta { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - font-size: 9px; - opacity: 0.8; -} -.gallery-card-model { - padding: 1px 5px; - border-radius: 6px; - background: rgba(255,255,255,0.15); -} -.gallery-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -.gallery-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: none; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-load-more:hover { - border-color: var(--red); - color: var(--red); -} - -/* Gallery detail overlay */ -.gallery-detail { - position: absolute; - inset: 0; - background: var(--panel); - z-index: 10; - display: flex; - flex-direction: column; - overflow-y: auto; -} -.gallery-detail-header { - display: flex; - align-items: center; - gap: 6px; - padding: 2px 8px 4px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - /* Pin to top so a tall (portrait) photo can't cover the Back / ⋮ - menu when the detail body grows past the visible area. */ - position: sticky; - top: 0; - background: var(--panel); - /* Above the image's rotate/nav buttons (z-index:3) so they scroll cleanly - behind the header instead of colliding with it. */ - z-index: 5; - min-height: 0; -} -.gallery-detail-title { - max-width: min(360px, 40vw); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 12px; - font-weight: 600; - opacity: 0.82; - position: relative; - top: 1px; -} -.gallery-detail-back { - background: none; - border: none; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 12px; - padding: 4px 8px; - border-radius: 4px; - position: relative; - top: -2px; -} -/* Slightly larger header icons (back/edit/heart/⋮) — override the inline 14px. */ -.gallery-detail-header svg { width: 16px; height: 16px; } -.gallery-detail-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.gallery-detail-action { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - padding: 3px 8px; - border-radius: 4px; - transition: border-color 0.15s; -} -.gallery-detail-action:hover { - border-color: var(--fg); -} -.gallery-detail-action.danger { - color: var(--color-error, #e55); -} -/* Overflow menu — replaces the seven individual action buttons that used - to crowd the right side of the detail header. */ -.gallery-detail-menu-wrap { position: relative; } -.gallery-detail-menu-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 26px; - padding: 0; - position: relative; - top: -2px; -} -.gallery-detail-menu { - position: absolute; - top: calc(100% + 4px); - right: 0; - min-width: 150px; - padding: 3px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 12; - display: flex; - flex-direction: column; - gap: 0; -} -.gallery-detail-menu[hidden] { display: none; } -.gallery-detail-menu .dropdown-item-compact { - width: 100%; - background: none; - border: none; - text-align: left; - font: inherit; - /* Tighter than the global default — the photo-detail menu has 8 - items so every pixel of vertical padding adds up. */ - padding: 4px 8px; - font-size: 11px; - gap: 8px; - line-height: 1.2; -} -.gallery-detail-menu .dropdown-item-compact .dropdown-icon, -.gallery-detail-menu .dropdown-item-compact .dropdown-icon svg { - width: 12px; - height: 12px; -} -.gallery-detail-body { - display: flex; - gap: 16px; - padding: 12px; - flex: 1; - min-height: 0; - overflow: hidden; -} -.gallery-detail-image { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - min-width: 0; - min-height: 0; - height: 100%; - padding-inline: 56px; - box-sizing: border-box; - overflow: hidden; -} -/* Shrink-wraps the actual image so overlay children (heart, face boxes) - anchor to the IMAGE bounds, not the wider letterboxed wrapper. */ -.gallery-detail-img-frame { - position: relative; - display: inline-flex; - max-width: 100%; - max-height: 100%; -} -.gallery-detail-nav { - position: absolute; - top: 50%; - transform: translateY(-50%); - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-nav:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-nav-prev { left: 8px; } -.gallery-detail-nav-next { right: 8px; } - -/* Rotate buttons — same pattern as the prev/next arrows but pinned to the - top corners. Hover-revealed so they don't clutter the image. */ -.gallery-detail-rotate { - position: absolute; - top: 8px; - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-rotate:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-rotate-ccw { left: 8px; } -.gallery-detail-rotate-cw { right: 8px; } -/* Inline heart that lives next to the "Date" label in the sidebar. - Tiny outline icon by default; fills red when favorited. */ -.gallery-detail-date-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; -} -.gallery-detail-fav-inline { - background: none; - border: none; - padding: 0; - /* Nudge up 8px so the heart sits visually above the Date label - baseline rather than centred on it. */ - margin-top: -8px; - border-radius: 4px; - color: var(--fg-muted); - opacity: 0.6; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; - transition: opacity 0.15s, color 0.15s; -} -/* Shrink the SVG so the heart matches the height of the small uppercase - Date label sitting next to it. */ -.gallery-detail-fav-inline svg { - width: 12px; - height: 12px; - display: block; -} -.gallery-detail-fav-inline:hover { - opacity: 1; -} -.gallery-detail-fav-inline.active { - opacity: 1; - color: var(--red); -} -.gallery-detail-sidebar { - width: 290px; - flex-shrink: 0; - /* Vertical scroll for long metadata, but never horizontal — overflow:auto - alone would let a wide child (e.g. long album name in the select) push - the sidebar into horizontal-scroll mode. */ - overflow-x: hidden; - overflow-y: auto; - min-width: 0; -} -.gallery-detail-inspector-head { - padding-bottom: 10px; - margin-bottom: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.gallery-detail-eyebrow { - font-size: 9px; - font-weight: 700; - color: var(--accent, var(--red)); - text-transform: uppercase; - letter-spacing: 0.08em; - margin-bottom: 3px; -} -.gallery-detail-inspector-title { - font-size: 14px; - font-weight: 650; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-meta-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 6px; - margin: 4px 0 12px; -} -.gallery-detail-meta-card { - min-width: 0; - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-radius: 7px; - padding: 7px 8px; -} -.gallery-detail-meta-card span { - display: block; - font-size: 9px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 48%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 3px; -} -.gallery-detail-meta-card strong { - display: block; - min-width: 0; - font-size: 11px; - font-weight: 600; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-inline-link { - appearance: none; - background: none; - border: none; - color: var(--accent, var(--red)); - padding: 0; - font: inherit; - font-weight: inherit; - cursor: pointer; - text-align: left; -} -.gallery-detail-mini-actions { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 7px; -} -.gallery-detail-mini-btn { - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 8px; - font: inherit; - font-size: 10.5px; - cursor: pointer; - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-detail-mini-btn:hover { - border-color: color-mix(in srgb, var(--fg) 42%, var(--border)); -} -/* Constrain any child that could otherwise stretch the sidebar wider than - its declared width (selects with long option text, long URLs, etc.). */ -.gallery-detail-sidebar select, -.gallery-detail-sidebar input, -.gallery-detail-sidebar .gallery-detail-prompt { - max-width: 100%; - box-sizing: border-box; -} -.gallery-date-rel { - opacity: 0.55; - margin-left: 4px; - font-size: 11px; - font-style: italic; -} -.gallery-detail-section label { - display: block; - font-size: 10px; - font-weight: 600; - opacity: 0.6; - margin-bottom: 3px; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.gallery-detail-section div { - font-size: 12px; - word-break: break-word; -} -.gallery-detail-prompt { - white-space: pre-wrap; - line-height: 1.4; -} -.gallery-tag-input, -.gallery-detail-name-input { - width: 100%; - padding: 5px 8px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - font: inherit; - font-size: 11px; - margin-bottom: 4px; -} -.gallery-tag-input:focus, -.gallery-detail-name-input:focus { - border-color: var(--red); - outline: none; -} -/* ↵ enter hint inside the Add-a-tag field (accent), replacing the "press Enter" - placeholder text. */ -.gallery-tag-input-wrap { position: relative; } -.gallery-tag-enter-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; /* the input has 4px bottom margin; center on the box */ - color: var(--accent, var(--red)); - font-size: 13px; - line-height: 1; - pointer-events: none; - opacity: 0.8; -} -.gallery-detail-name-input { font-size: 13px; font-weight: 500; } -/* ↵ enter hint on the image name field — accent, shown while editing to signal - that Enter saves. */ -.gallery-name-wrap { position: relative; } -.gallery-name-enter { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; - color: var(--accent, var(--red)); - opacity: 0.85; - pointer-events: none; - display: none; -} -.gallery-tag-save { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 10px; - padding: 3px 10px; - border-radius: 4px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-tag-save:hover { - border-color: var(--red); - color: var(--red); -} - -@media (max-width: 600px) { - .gallery-grid { - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - } - /* Single-row toolbar on mobile: search + Sources + Recent (sort) all share - one row. The search bar shrinks so the dropdowns fit beside it. */ - .gallery-toolbar { - flex-wrap: nowrap; - gap: 4px; - } - .gallery-search-wrap { - flex: 1 1 0; - min-width: 0; - } - .gallery-search { - /* Drop the desktop right-padding reserved for the "enter to tag" hint — - hide the hint on mobile (below) so the input can use its full width. */ - padding-right: 8px; - } - .gallery-search-enter-hint { display: none; } - /* Disable the row break so the filters stay inline with search. */ - .gallery-toolbar-break { display: none; } - .gallery-model-filter, - .gallery-sort { - flex: 0 0 auto; - /* Cap each dropdown so search keeps a usable amount of width. */ - max-width: 100px; - font-size: 11px; - padding: 4px 6px; - /* Keep the same vertical alignment as the desktop toolbar. */ - position: relative; - top: 4px; - } - .gallery-select-btn { - padding: 6px 8px 8px; - font-size: 9px; - flex-shrink: 0; - /* Push Select to the far right of the single-row toolbar, regardless - of its DOM position (sits between search and the dropdowns). */ - order: 99; - margin-left: auto; - } - /* Tabs scroll horizontally rather than wrapping when narrow. */ - .gallery-tabs { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .gallery-tabs::-webkit-scrollbar { display: none; } - .gallery-tab { flex-shrink: 0; } - /* Album chips also scroll horizontally so they don't stack. */ - .gallery-album-chips { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .gallery-album-chips::-webkit-scrollbar { display: none; } - .gallery-chip { flex-shrink: 0; } - /* Tasks filter chips — same horizontal-swipe behaviour as the gallery - albums / library chips on mobile: one row, slide-to-see-more. */ - .tasks-activity-filters { - overflow-x: auto; - flex-wrap: nowrap !important; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .tasks-activity-filters::-webkit-scrollbar { display: none; } - .tasks-activity-filters > * { flex-shrink: 0; } - /* Detail view */ - .gallery-detail-body { - flex-direction: column; - overflow: visible; - } - .gallery-detail-image { - flex: 0 0 auto; - width: 100%; - height: clamp(280px, 58vh, 520px); - padding-inline: 48px; - } - .gallery-detail-sidebar { - width: 100%; - } - .gallery-detail-title { - display: none; - } - .gallery-detail-meta-grid { - grid-template-columns: 1fr; - } - /* Album grid: smaller tile minimum on mobile. */ - .gallery-albums-grid { - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 8px; - } -} - - -/* ── Scoreboard table ── */ -.scoreboard-table { - width: 100%; - border-collapse: collapse; - font-size: 0.88em; -} -.scoreboard-table th { - text-align: left; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 55%, transparent); - text-transform: uppercase; - letter-spacing: 0.5px; -} -.scoreboard-table th:not(:first-child) { - text-align: center; -} -.scoreboard-table td { - padding: 5px 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; -} -.scoreboard-table td.scoreboard-model { - text-align: left; - font-weight: 500; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.scoreboard-table td.scoreboard-pct { - font-weight: 600; - color: var(--red); -} -.scoreboard-table tbody tr:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} - -#scoreboard-overlay svg { - color: var(--accent, var(--red)); -} - -/* ── Compare search results ── */ -.compare-search-results { - display: flex; - flex-direction: column; - gap: 2px; -} -.compare-search-result { - padding: 6px 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); -} -.compare-search-result:last-child { - border-bottom: none; -} -.search-result-title { - color: var(--color-accent); - text-decoration: none; - font-weight: 500; - font-size: 0.9em; - display: block; - line-height: 1.3; -} -.search-result-title:hover { - color: var(--color-link-hover); - text-decoration: underline; -} -.search-result-snippet { - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.82em; - line-height: 1.4; - margin-top: 2px; -} -.search-result-url { - color: color-mix(in srgb, var(--fg) 35%, transparent); - font-size: 0.75em; - margin-top: 3px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - - -/* ── Cookbook Tool ── */ - -/* ── Cookbook ── */ -.cookbook-serve-preset { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - margin-bottom: 3px; - cursor: pointer; - transition: background 0.15s; -} -.cookbook-serve-preset:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cookbook-serve-preset-name { - font-size: 11px; - font-weight: 500; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-serve-preset-meta { - font-size: 9px; - font-family: 'Fira Code', monospace; - color: var(--fg-muted); - opacity: 0.5; -} -.cookbook-serve-preset-launch { - background: none; - border: none; - color: #50fa7b; - cursor: pointer; - font-size: 10px; - opacity: 0.6; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-launch:hover { opacity: 1; } -.cookbook-serve-preset-rm { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 10px; - opacity: 0.3; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-rm:hover { opacity: 0.8; color: var(--red); } -.cookbook-body { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - flex: 1; - min-height: 0; - scrollbar-width: thin; -} -#cookbook-modal .modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -#cookbook-modal .modal-header { - flex: 0 0 auto; -} -#cookbook-modal .cookbook-body { - min-height: 0; - overflow-y: auto; - overflow-x: hidden; -} -#cookbook-modal .cookbook-group { - min-height: 0; -} -#cookbook-modal .cookbook-group > .admin-card { - min-height: 0; - /* Let .cookbook-body be the SINGLE scroll surface. Nesting another - overflow:auto here trapped the wheel inside the cached-list when a - serve panel expanded — the page couldn't scroll past the panel's - bottom (Launch button got hidden). */ - overflow: visible !important; -} -#cookbook-modal .cookbook-section-body { - min-height: 0; -} -.cookbook-group { - display: flex; - flex-direction: column; - gap: 8px; - flex: 1; - min-height: 0; -} - -/* Cards — match admin-card */ -.cookbook-card { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-card-header { - display: flex; - align-items: center; - justify-content: space-between; -} -.cookbook-card-header-actions { display: flex; gap: 4px; } -.cookbook-card-title { font-size: 13px; font-weight: 600; } -.cookbook-card-desc { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} - -/* Fields grid */ -.cookbook-fields { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 6px; - margin-top: 4px; -} -.cookbook-field-label { - display: flex; - flex-direction: column; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - gap: 3px; -} - -/* Inputs — match admin-add-form input */ -.cookbook-field-input { - padding: 5px 8px; - font-size: 12px; - font-family: inherit; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - outline: none; -} -.cookbook-field-input:focus { border-color: var(--red); } -#hwfit-dl-server, #hwfit-usecase, #hwfit-server-select, #hwfit-cache-server, #serve-sort { height: 28px; width: 88px; } -/* Serve tab — match Documents sizing, but leave room for the active "Cancel" - label and center it vertically so the descenders don't clip. */ -#hwfit-cache-select { - min-width: 58px; - height: 28px; - position: relative; - top: -3px; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; -} - -/* Command preview */ -.cookbook-cmd-preview { - margin: 4px 0 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - color: var(--fg); -} - -/* Buttons — match admin-btn-sm */ -.cookbook-actions { display: flex; gap: 6px; margin-top: 2px; } -.cookbook-btn { - padding: 4px 10px; - min-width: 54px; - text-align: center; - font-size: 11px; - font-family: inherit; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - cursor: pointer; - transition: all 0.15s; -} -.cookbook-btn:hover { - background: var(--border); - border-color: var(--accent); -} -.cookbook-btn:disabled { opacity: 0.5; cursor: not-allowed; } -.cookbook-run-btn { - background: var(--accent); - color: var(--panel); - border-color: var(--accent); - font-weight: 600; -} -.cookbook-run-btn:hover { opacity: 0.85; } -.cookbook-stop-btn { - background: var(--color-error); - border-color: var(--color-error); -} - -/* Output */ -.cookbook-output-wrap { - position: relative; - margin: 0; -} -.cookbook-output-kill { - position: absolute; - top: 4px; - right: 34px; - width: 22px; - height: 22px; - padding: 0; - background: none; - border: 1px solid transparent; - border-radius: 4px; - color: var(--fg-muted); - font-size: 16px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity .15s, color .15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-output-kill:hover { color: var(--color-error, var(--warn)); border-color: var(--border); } -.cookbook-output-wrap .copy-code { - position: absolute; - top: 6px; - right: 6px; -} -.cookbook-output-wrap:hover .copy-code { opacity: 0.7; } -.cookbook-output-pre { - margin: 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - max-height: 180px; - overflow-y: auto; -} -.cookbook-output-error { color: var(--color-error); } - -/* Downloaded/cached models */ -.hwfit-cached-item { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 6px; - padding: 6px 8px; - margin: 2px 0; - border-radius: 6px; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 3%, transparent); - cursor: default; - font-size: 12px; -} -.hwfit-cached-item .hwfit-serve-panel { - flex-basis: 100%; - margin-top: 4px; -} -.hwfit-cached-item:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); -} -.hwfit-cached-header { - font-size: 10px; - opacity: 0.4; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - border: none !important; - background: none !important; - padding: 2px 8px; - cursor: default !important; -} -.hwfit-cached-header:hover { background: none !important; } -.hwfit-cached-name { - font-weight: 600; - flex-shrink: 0; -} -.hwfit-cached-repo { - flex: 1; - color: var(--fg-muted); - font-size: 11px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-cached-size, -.hwfit-cached-files { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; -} -.hwfit-cached-serve, -.hwfit-cached-delete { - flex-shrink: 0; -} -.hwfit-cached-delete { - opacity: 0.4; - font-size: 10px; -} -.hwfit-cached-delete:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} -.hwfit-cached-badge { - font-size: 9px; - padding: 1px 6px; - border-radius: 3px; - font-weight: 600; - white-space: nowrap; -} -.hwfit-cached-ready { - background: color-mix(in srgb, var(--color-success, #4caf50) 20%, transparent); - color: var(--color-success, #4caf50); -} -.hwfit-cached-dl { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 20%, transparent); - color: var(--color-warning, #f0ad4e); -} - -/* Cookbook notification dot */ -#tool-cookbook-btn { - position: relative; -} -#rail-cookbook { - position: relative; -} -.cookbook-open-loading { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - flex-shrink: 0; - margin-left: 2px; - color: var(--accent, var(--red)); - opacity: 0.9; -} -.cookbook-open-loading .ai-spinner, -.cookbook-open-loading .ai-spinner-whirlpool { - display: inline-flex !important; - width: 14px; - height: 14px; - margin: 0 !important; - gap: 0 !important; -} -.cookbook-open-loading canvas { - display: block; -} -.cookbook-section-loading-wp, -.cookbook-section-loading-wp canvas { - width: 22px !important; - height: 22px !important; - display: block !important; -} -#rail-cookbook .cookbook-open-loading { - position: absolute; - right: 2px; - top: -2px; - width: 12px; - height: 12px; - margin-left: 0; -} -/* The serve-list 'downloading' / 'download stalled' status text sits a hair - low against the rest of the meta line on mobile — nudge it up 2px. */ -@media (max-width: 768px) { - .cookbook-dl-status { - position: relative; - top: -2px; - } -} -.cookbook-notif-dot { - width: 6px; - height: 6px; - flex-shrink: 0; - border-radius: 50%; - background: var(--color-success, #4caf50); - /* Drives the breathing glow in the keyframes (matches email's dot). */ - --notif-glow: var(--color-success, #4caf50); - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -.cookbook-notif-dot.cookbook-notif-error { - background: var(--color-error, #f44); - --notif-glow: var(--color-error, #f44); - position: relative; - left: -2px; - top: -1px; -} -.rail-notify-error { color: var(--color-error, #f44) !important; } -.cookbook-tab-error-dot { - display: inline-block; width: 5px; height: 5px; border-radius: 50%; - background: var(--color-error, #f44); margin-left: 4px; vertical-align: middle; - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -@keyframes cookbook-notif-pulse { - 0%, 100% { - opacity: 1; - box-shadow: 0 0 0 0 color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 60%, transparent); - } - 50% { - opacity: 0.85; - box-shadow: 0 0 6px 2px color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 55%, transparent); - } -} -.cookbook-clear-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-clear-btn:hover { - color: var(--red) !important; - border-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 8%, transparent) !important; -} -.cookbook-bulk-bar { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - margin-bottom: 4px; - background: color-mix(in srgb, var(--red) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 6px; - font-size: 11px; -} -.cookbook-bulk-bar.hidden { display: none; } -.serve-select-cb { - width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; cursor: pointer; - background: var(--border); transition: background 0.15s; - align-self: center; - position: relative; - top: -2px; -} -#serve-bulk-bar { - position: relative; - top: -6px; -} -#serve-bulk-bar #serve-bulk-cancel { - top: -2px !important; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 28px; - height: 24px; - line-height: 1; -} -#serve-bulk-bar #serve-bulk-cancel svg { - top: 0; -} -.serve-select-cb.selected { background: var(--red); } -.serve-select-cb:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -#hwfit-cache-select.active { - background: var(--red); - color: #fff; - border-color: var(--red); -} -.cookbook-serve-dirs { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.cookbook-serve-dir-pill { - font-size: 10px; - font-family: 'Fira Code', monospace; - padding: 2px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); - color: var(--fg-muted); - letter-spacing: 0.2px; -} -/* Status pills shown inline in a Serve-tab card title (next to the model - name) when the model has a live serve / download task. Shared base - class so "running" and "downloading" sit on the same row with the - same chrome; only color varies. */ -.cookbook-serve-running-pill, -.cookbook-serve-downloading-pill { - display: inline-block; - margin-left: 6px; - padding: 1px 7px; - border-radius: 10px; - font-size: 9px; - font-weight: 500; - text-transform: lowercase; - letter-spacing: 0.3px; - vertical-align: 2px; - position: relative; - top: 1px; /* nudged down 2px from the old -1px so it sits - flush with the cap-height of the title text */ -} -.cookbook-serve-running-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); -} -.cookbook-serve-downloading-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); - opacity: 0.85; -} -.cookbook-serve-downloading-pill.is-stalled { - /* Stalled downloads stay visible but read as warning, not progress. */ - color: var(--fg-muted, #888); - background: color-mix(in srgb, var(--fg-muted, #888) 10%, transparent); - border-color: color-mix(in srgb, var(--fg-muted, #888) 30%, transparent); - opacity: 1; -} -.cookbook-serve-running-pill.is-clickable { - cursor: pointer; - transition: background 0.12s, border-color 0.12s; -} -.cookbook-serve-running-pill.is-clickable:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -.cookbook-serve-dir-edit { - font-size: 9px; - color: var(--fg-muted); - opacity: 0.4; - cursor: pointer; - text-decoration: underline; - text-underline-offset: 2px; -} -.cookbook-serve-dir-edit:hover { - opacity: 0.7; - color: var(--accent, var(--red)); -} -.cookbook-gpu-group { - display: flex; - gap: 3px; - margin-top: -4px; -} -.cookbook-gpu-btn { - width: 30px; height: 30px; - font-size: 12px; - font-family: 'Fira Code', monospace; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - color: var(--fg-muted); - cursor: pointer; - opacity: 0.4; - transition: all 0.15s; - padding: 0; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-gpu-btn:hover { - opacity: 0.7; - border-color: var(--fg-muted); -} -.cookbook-gpu-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 55%, transparent); - color: var(--fg); - font-weight: 600; -} -/* Probe annotation states (set by "Free?" button) */ -.cookbook-gpu-btn.gpu-free { - opacity: 1; - border-color: #4ade80; - color: #4ade80; -} -.cookbook-gpu-btn.gpu-busy { - opacity: 0.85; - border-color: color-mix(in srgb, var(--red) 70%, transparent); - color: var(--red); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-btn.gpu-missing { - opacity: 0.2; - text-decoration: line-through; - cursor: not-allowed; -} -/* "Clear Server" lives in the actions row next to "Probe GPUs". - Inherits .cookbook-btn sizing; this rule just adds the destructive - red tint on hover so the "this kills processes" cue stays. */ -.cookbook-gpu-clear:hover:not(:disabled) { - color: var(--red); - border-color: color-mix(in srgb, var(--red) 60%, var(--border)); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -/* GPU probe popup — per-GPU process list with kill buttons */ -.cookbook-gpu-popup { - /* Fixed positioning (relative to viewport) so we never get pulled into - a scrolling/transform stacking context from an ancestor. Z-index has - to clear the cookbook modal (260) and the rest of the high-z UI - layers (themed-confirm and various overlays sit around 9000-10000). */ - position: fixed; - z-index: 10010; - min-width: 280px; - max-width: 420px; - background: var(--panel, #1a1a1a); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 8px 24px rgba(0,0,0,0.4); - font-family: 'Fira Code', monospace; - font-size: 11px; - color: var(--fg); -} -.cookbook-gpu-popup-head { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; -} -.cookbook-gpu-popup-stats { - flex: 1; - font-weight: 400; - color: var(--fg-muted); - font-size: 10px; -} -.cookbook-gpu-popup-close { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 14px; - padding: 0 4px; -} -.cookbook-gpu-popup-close:hover { color: var(--fg); } -.cookbook-gpu-popup-body { - padding: 4px 0; - max-height: 320px; - overflow-y: auto; -} -.cookbook-gpu-popup-empty { - padding: 10px; - color: var(--fg-muted); - font-style: italic; -} -.cookbook-gpu-proc { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; -} -.cookbook-gpu-proc:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); } -.cookbook-gpu-proc-info { - flex: 1; - display: flex; - gap: 8px; - align-items: center; - min-width: 0; -} -.cookbook-gpu-proc-pid { - color: var(--fg-muted); - width: 56px; - flex-shrink: 0; -} -.cookbook-gpu-proc-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.cookbook-gpu-proc-mem { - color: var(--fg-muted); - flex-shrink: 0; -} -.cookbook-gpu-proc-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} -.cookbook-gpu-kill { - border: 1px solid color-mix(in srgb, var(--red) 60%, transparent); - background: none; - color: var(--red); - border-radius: 3px; - padding: 2px 8px; - cursor: pointer; - font-family: inherit; - font-size: 10px; -} -.cookbook-gpu-kill[data-sig="KILL"] { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.cookbook-gpu-kill:hover:not(:disabled) { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.cookbook-gpu-kill:disabled { opacity: 0.4; cursor: wait; } -.cookbook-serve-title { - display: flex; - align-items: center; - gap: 4px; - min-width: 0; -} -.cookbook-serve-title-name { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-hf-link { - font-size: 9px; - text-decoration: none; - color: var(--fg-muted); - opacity: 0.5; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - vertical-align: 1px; - letter-spacing: 0.3px; - font-weight: 600; - flex-shrink: 0; -} -.cookbook-hf-link:hover { - opacity: 0.8; - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -/* In the Trending models list the HF pill is the row's main affordance — - tint it accent so it stands out next to the model name. */ -#cookbook-hf-latest-list .cookbook-hf-link { - color: var(--accent, var(--red)); - opacity: 0.85; - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); -} - -/* Running tab sections */ -.cookbook-saved-section, -.cookbook-serve-section, -.cookbook-dl-section { - border: 1px solid var(--border); - border-radius: 6px; - margin-bottom: 6px; - overflow: hidden; -} -/* Divider between server-group sections in the Active tab so the - transition between e.g. local + remote-host server blocks reads - visually instead of running into each other. */ -.cookbook-group[data-backend-group="Running"] > [class*="cookbook-server-section-"] + [class*="cookbook-server-section-"] { - margin-top: 14px; - padding-top: 14px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.cookbook-section-header { - display: flex; - align-items: center; - gap: 6px; - padding: 7px 10px; - cursor: pointer; - user-select: none; - /* Persistent surface + border so it reads as a clickable bar instead of - blending into the panel background. */ - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - transition: background 0.15s, border-color 0.15s; -} -.cookbook-section-header:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: color-mix(in srgb, var(--fg) 22%, transparent); -} -.cookbook-section-header.has-server-color { - color: var(--fg); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 45%, var(--border)); - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 4%, var(--bg)); - box-shadow: inset 6px 0 0 var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color:hover { - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 7%, var(--bg)); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 60%, var(--border)); -} -.cookbook-section-header.has-server-color .cookbook-section-title, -.cookbook-section-header.has-server-color .cookbook-section-chevron { - color: var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color .cookbook-srv-status.ok { - background: var(--cookbook-server-accent, var(--cookbook-server-color)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 55%, transparent); -} -.cookbook-section-header .cookbook-clear-btn { - margin-left: 0; - position: relative; - top: -3px; -} -@media (max-width: 768px) { - .cookbook-section-header .cookbook-clear-btn { - top: -3px; - } - .cookbook-task-check { - top: 4px; - } -} -/* "Stop all" sits just left of "Clear finished"; it carries the auto margin so - the pair is pushed together to the right of the section title. */ -.cookbook-section-header .cookbook-stop-all-btn { - margin-left: auto; - margin-right: 6px; - position: relative; - top: -3px; -} -.cookbook-stop-all-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-stop-all-btn:hover { - color: var(--warn, #f0ad4e) !important; - border-color: var(--warn, #f0ad4e) !important; - background: color-mix(in srgb, var(--warn, #f0ad4e) 8%, transparent) !important; -} -.cookbook-section-chevron { - flex-shrink: 0; - opacity: 0.6; - transition: transform 0.15s ease, opacity 0.15s ease; -} -.cookbook-section-body { - padding: 0; -} -.cookbook-saved-toggle-header { - font-size: 10px; - font-weight: 600; - color: var(--fg-muted); - text-transform: uppercase; - letter-spacing: 0.3px; - margin-bottom: 4px; -} -.cookbook-saved-toggle:hover { color: var(--fg); } -.cookbook-saved-item { - margin: 2px 0; - border-radius: 4px; - font-size: 11px; -} -.cookbook-saved-header { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 6px; - border-radius: 4px; - transition: background 0.08s; -} -.cookbook-saved-header:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.cookbook-saved-item .hwfit-serve-panel { - margin: 4px 0 4px 6px; -} -.cookbook-saved-host { - font-size: 10px; - color: var(--fg-muted); - font-family: 'Fira Code', monospace; -} -.cookbook-saved-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-saved-launch { - font-size: 10px; - padding: 2px 8px; -} -.cookbook-saved-del { - background: none; - border: none; - color: var(--fg-muted); - opacity: 0.3; - cursor: pointer; - font-size: 11px; - padding: 0 2px; -} -.cookbook-saved-del:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} - -/* Serve config panel */ -.hwfit-serve-panel { - margin-top: 6px; - padding: 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex: 1; - display: flex; - flex-direction: column; - position: relative; -} -.cookbook-serve-slots { - display: flex; - gap: 3px; - justify-content: flex-end; - margin-bottom: 4px; -} -/* When the Save split sits inside Row 1 (next to GPUs), align it with the - input baseline (the row's grid cells stretch top-down; without this the - Save buttons sit above the GPU button group). */ -.hwfit-serve-row .cookbook-serve-slots { - align-self: end; - margin-bottom: 4px; -} -.cookbook-slot-btn { - min-width: 22px; height: 28px; - padding: 0 6px; - font-size: 10px; font-weight: 600; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - white-space: nowrap; - max-width: 80px; - overflow: hidden; - text-overflow: ellipsis; - color: var(--fg-muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.5; - transition: all 0.15s; -} -.cookbook-slot-btn:hover { opacity: 0.9; border-color: var(--fg-muted); } -/* Saved-configs split button: "Save" + dropdown-arrow joined into one control, - but each segment keeps its own style — Save uses the filled accent - "Done"-button look; the arrow stays a subtle outlined menu trigger. */ -.cookbook-saved-split { gap: 0; } -.cookbook-saved-save, -.cookbook-saved-arrow { max-width: none; } -.cookbook-saved-save { - padding: 0 10px; - gap: 4px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - font-weight: 600; - opacity: 1; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.cookbook-saved-save:hover { - background: var(--border); - border-color: var(--accent); - opacity: 1; -} -.cookbook-saved-arrow { - padding: 0 6px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - opacity: 1; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: none; -} -.cookbook-saved-menu .cookbook-saved-favorite { - border-left: 3px solid var(--red); - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-saved-fav-btn { - width: 20px; - height: 20px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: none; - color: var(--fg-muted); - opacity: 0.55; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -.cookbook-saved-fav-btn:hover, -.cookbook-saved-fav-btn.active { - color: var(--red); - opacity: 1; - background: color-mix(in srgb, var(--red) 10%, transparent); -} -.cookbook-saved-fav-badge { - flex-shrink: 0; - background: transparent; - padding: 0; -} -.cookbook-serve-favorite-model { - border-left-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-serve-fav-badge { - display: inline-flex; - align-items: center; - vertical-align: 1px; - margin-left: 5px; - margin-right: 2px; - background: transparent; - padding: 0; -} -.cookbook-slot-saved { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); } -.cookbook-slot-saved:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); } -.cookbook-slot-btn.active { opacity: 1; background: var(--accent); color: #fff; border-color: var(--accent); -} -.cookbook-slot-wrap { - position: relative; - display: inline-flex; -} -.cookbook-slot-del { - position: absolute; - top: -5px; - right: -5px; - width: 14px; - height: 14px; - padding: 0; - border: 1px solid var(--border); - border-radius: 50%; - background: var(--bg, #1a1a1a); - color: var(--fg-muted); - font-size: 12px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, color 0.15s, border-color 0.15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-slot-del:hover { - color: #f44; - border-color: #f44; -} -.cookbook-card-backend { display: none; } -/* Dependencies row — aligned tags */ -.cookbook-dep-row { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel); -} -/* Let the deps list fill the available window height instead of being - capped at the default .doclib-grid 400-px box. */ -[data-backend-group="Dependencies"] #cookbook-deps-list { - flex: 1; - max-height: none; -} - -/* Settings tab: stack the HF token + Servers cards as distinct blocks - (matches the Download tab's block-per-section layout). */ -.cookbook-settings-stack { - display: flex; - flex-direction: column; - gap: 10px; - flex: 1; - /* Scroll the whole settings panel so the Servers card can grow to hold every - server (it used to be a cramped internal scroll box that clipped them). */ - overflow-y: auto; - min-height: 0; -} -.cookbook-settings-stack.hidden { display: none; } -.cookbook-dep-row.cookbook-dep-blocked { opacity: 0.4; } -.cookbook-dep-info { flex: 1; min-width: 0; } -.cookbook-dep-section { - display: flex; - align-items: baseline; - gap: 8px; - margin: 12px 2px 4px; -} -.cookbook-dep-section-title { - font-size: 11px; - font-weight: 600; - letter-spacing: 0.03em; -} -.cookbook-dep-section-note { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-subgroup { - margin: 6px 0 8px 12px; - padding: 7px 8px 8px; - border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - border-radius: 0 6px 6px 0; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-dep-subgroup-title { - display: flex; - align-items: baseline; - gap: 7px; - margin: 0 1px 1px; - font-size: 10.5px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 78%, transparent); -} -.cookbook-dep-subgroup-title em { - font-size: 9.5px; - font-style: normal; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.cookbook-dep-tag { - font-size: 9px; - padding: 0 8px; - border-radius: 4px; - white-space: nowrap; - text-align: center; - min-width: 62px; - box-sizing: border-box; - line-height: 1; - /* Explicit height so the tags (Install / Installed) are the EXACT - same height as the sibling tags — Firefox gives buttons a taller - native box otherwise. Mirrors the server-row tag rule (height:24px). */ - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.cookbook-dep-target { - border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-cat { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -/* Rebuild tag — same look as the LLM category tag, sits to its left. */ -.cookbook-dep-rebuild { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 75%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent); - cursor: pointer; - font-family: inherit; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - position: relative; - top: -2px; -} -.cookbook-dep-reinstall { - top: -3px; -} -.cookbook-dep-rebuild:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); -} -.cookbook-dep-installed { - background: color-mix(in srgb, var(--green, #50fa7b) 18%, transparent); - color: var(--green, #50fa7b); - border: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); - /* Match Install + Installed ▾ so all three variants align in mixed rows. */ - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-na { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); - cursor: help; - /* Match other dep tag widths so N/A rows line up with Install / Installed. */ - min-width: 75.85px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-install { - background: var(--accent, var(--red)); - color: #fff; - border: none; - cursor: pointer; - font-family: inherit; - font-weight: 500; - position: relative; - top: -3px; - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - /* Strip the native button box so it's the same height as the sibling tags - (Firefox renders taller otherwise); height comes from .cookbook-dep-tag. */ - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-build-deps { - width: auto; - min-width: 87.7px; - max-width: 100%; - white-space: normal; - overflow-wrap: anywhere; - line-height: 1.15; - text-align: center; - height: auto; - min-height: 24px; -} -/* The install state is the terminal control in a dependency row. Keep it on - the far edge even when optional rebuild/category controls are present. */ -.cookbook-dep-row > .cookbook-dep-installed, -.cookbook-dep-row > .cookbook-dep-na, -.cookbook-dep-row > .cookbook-dep-install:not(.cookbook-dep-build-deps) { - margin-left: auto; -} -/* Center the "?" glyph inside the help chip. Without text-align it sits 0.5px - left of true center because of the character's natural baseline offset. */ -.hwfit-help-chip { - text-align: center; - padding-left: 0.5px; -} -.cookbook-dep-install:hover { opacity: 0.85; } -/* Installed split button: "Installed" label + separator + ▾ caret; clicking it - opens the actions menu (Update). Replaces the old ⋮ button. */ -.cookbook-dep-installed-btn { - padding: 0; - width: 87.7px; - min-width: 87.7px; - cursor: pointer; - font-family: inherit; - overflow: hidden; - position: relative; - top: -3px; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-installed-btn .cookbook-dep-caret { - display: inline-flex; - align-items: center; - align-self: stretch; - padding: 0 5px; - font-size: 14px; - line-height: 1; - position: relative; - top: 1px; - left: 4px; - opacity: 0.85; - border-left: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); -} -.hwfit-serve-row { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - margin-bottom: 6px; -} -/* Tighter min on the core-knobs row only so Dtype / TP / GPU Mem / Max - Seqs fit on one line without crushing Row 1's GPU button strip or - the Backend picker. */ -.hwfit-serve-row-core { - grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); -} -/* Save / saved-config split aligned to the right end of Row 1 and - nudged up 4px so it sits flush with the rest of the row. */ -.hwfit-serve-row .cookbook-serve-slots { - justify-self: end; - position: relative; - top: -4px; -} -/* The Settings label wraps the Save split — right-align its title text - so the word "Settings" sits above the right-aligned Save button - instead of floating off to the left of its grid cell. 8px right - padding now (16px → 8px) per follow-up tweak, brings the title back - over the actual Save button instead of overshooting it. */ -.hwfit-serve-row label:has(> .cookbook-serve-slots) { - grid-column: -2 / -1; - justify-self: end; - text-align: right; - padding-right: 0; -} -.hwfit-serve-row label:has(> .cookbook-serve-slots) > span { - display: inline-block; - position: relative; - left: -33px; -} -.hwfit-serve-topline { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - margin: 0 0 6px; -} -.hwfit-serve-topline .hwfit-serve-runtime-text { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-serve-preset-row { - display: flex; - justify-content: flex-end; - margin: 0; - flex: 0 0 auto; - position: relative; - top: -6px; -} -.hwfit-serve-row label { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; - letter-spacing: 0.3px; -} -.hwfit-serve-row-core .hwfit-context-label { - grid-column: span 2; - width: auto; - min-width: 0; - max-width: none; - justify-self: start; -} -.hwfit-context-control { - display: block; - position: relative; - align-items: center; - margin-top: 2px; - width: 100px; -} -.hwfit-context-control .hwfit-sf[data-field="ctx"] { - min-width: 0; - width: 100%; - padding-right: 40px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]) { - position: relative; - left: -66px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - position: relative; - left: -64px; -} -.hwfit-serve-row-core .hwfit-gpus-label { - position: relative; - left: -70px; -} -.hwfit-context-calc-btn { - position: absolute; - right: 3px; - top: -4px; - width: 34px; - height: 28px; - min-width: 34px; - padding: 0; - font-size: 10px; - line-height: 1; - display: inline-flex; - align-items: center; - justify-content: center; - text-align: center; - overflow: hidden; - white-space: nowrap; -} -.hwfit-context-calc-btn .spinner-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 auto !important; -} -.hwfit-context-calc-btn .ai-spinner-whirlpool { - width: 12px !important; - height: 12px !important; -} -@media (min-width: 769px) { - [data-llama-mode-toggle].mode-toggle.mode-toggle-three .mode-toggle-btn > span { - top: -6px !important; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { - font-size: 0; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { - content: 'Unif'; - font-size: 11px; - } -} -@media (max-width: 768px) { - .hwfit-serve-row-core .hwfit-context-label { - display: flex; - flex-direction: column; - align-items: flex-start; - } - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]), - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - left: 0; - } - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_env_preset"]), - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="extra_env"]) { - grid-column: 1 / -1 !important; - } - .hwfit-context-control { - display: inline-flex; - align-items: center; - gap: 4px; - width: auto; - } - .hwfit-context-control .hwfit-sf[data-field="ctx"] { - width: 100px; - padding-right: 6px; - } - .hwfit-context-calc-btn { - position: relative; - top: -2px; - left: 2px; - right: auto; - flex: 0 0 34px; - width: 34px; - min-width: 34px; - } -} -.hwfit-auto-ctx-note { - display: block; - margin-top: 3px; - font-size: 9px; - opacity: 0.6; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Ctx slider — ported from origin/main. Sits in the Scan/Download toolbar - next to the quant dropdown. Drives _ctxValue() in cookbook-hwfit.js. */ -.hwfit-ctx-control { - height: 28px; - min-width: 134px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - gap: 5px; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - font-size: 11px; /* slightly smaller than .cookbook-field-input so Context reads quieter next to Engine/Quant */ -} -.hwfit-ctx-control span { - /* Match Quant/Engine select label style: no uppercase, no letter-spacing. */ - text-transform: none; - letter-spacing: 0; - opacity: 0.9; -} -.hwfit-ctx-control > span:first-child { - font-size: 9.5px; -} -/* Editor-style slider (same look as the gallery editor sliders): thin pill - rail that fattens on interaction, circular red thumb that grows on hover. */ -.hwfit-ctx-control input[type="range"] { - width: 64px; - min-width: 64px; - height: 4px; - padding: 0; - border: 0; - -webkit-appearance: none; - appearance: none; - /* Hard-coded grey so the rail is GUARANTEED visible regardless of theme — - every theme-derived color we tried (--fg-muted, --border, accent-bg mix) - kept blending into the panel background on at least one theme. */ - background: rgba(150, 150, 150, 0.65); - border-radius: 999px; - accent-color: var(--red); - cursor: pointer; - transition: height 0.15s ease, background 0.15s ease; -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - background: var(--fg); -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - height: 10px; -} -.hwfit-ctx-control input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]::-moz-range-thumb { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]:hover::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:focus::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:active::-webkit-slider-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control input[type="range"]:hover::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:focus::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:active::-moz-range-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control output { - min-width: 28px; - text-align: right; - color: var(--fg); - font-weight: 600; -} -.hwfit-sf { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - font-family: inherit; - font-size: 12px; - padding: 0 6px; - height: 28px; - box-sizing: border-box; -} -.hwfit-sf[data-field="backend"], -.hwfit-sf[data-field="dtype"], -.hwfit-sf[data-field="tp"] { - height: 32px; - box-sizing: border-box; - width: 100%; -} -.hwfit-sf:focus { - border-color: var(--accent, var(--red)); - outline: none; -} -.hwfit-sf[type="text"] { width: 100%; } -.hwfit-sf.hwfit-sf-wide { width: 100%; } -.hwfit-serve-checks { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 6px; -} -.hwfit-sf-cb { - display: flex; - flex-direction: row; - align-items: center; - gap: 6px; - font-size: 11px; - color: var(--fg-muted); - cursor: pointer; - padding: 3px 0; -} diff --git a/static/index.html b/static/index.html index cba90c7c9..ad04daf89 100644 --- a/static/index.html +++ b/static/index.html @@ -246,15 +246,24 @@ real request is discarded and the font fetched a second time. --> - - - - - - - - - + + + + + + + + + + + + + + + + + +
+ inside is the scroller, so the footer stays pinned at the bottom. */ + overflow: hidden !important; +} +.skill-card.doclib-card-expanded .skill-md-pre { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; +} +/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them + back down 4px so they're vertically centered in the skill card footer. */ +.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn, +.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { + top: 0; +} +/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */ +#skills-bulk-bar .memory-toolbar-btn { + position: relative; + top: -2px; +} +/* ── Audit-all progress panel ── */ +.skills-audit-panel { + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px 10px; + margin: 6px 0; + background: color-mix(in srgb, var(--fg) 3%, transparent); +} +.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } +.skills-audit-title { font-size: 12px; font-weight: 600; } +.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; } +.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; } +.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; } +.skills-audit-log { + max-height: 22vh; overflow-y: auto; + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 10.5px; line-height: 1.5; opacity: 0.8; +} +/* ── Skill test monitor + AI eval verdict ── */ +.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; } +.skill-test-log { + flex: 1 1 auto; + min-height: 120px; + max-height: 48vh; + overflow-y: auto; + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 9.5px; + line-height: 1.45; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + white-space: pre-wrap; + word-break: break-word; +} +.skill-test-log > div { margin: 1px 0; } +.skill-test-meta { opacity: 0.5; } +.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; } +.skill-test-round { opacity: 0.6; margin-top: 6px !important; } +.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; } +.skill-test-out { opacity: 0.7; padding-left: 10px; } +.skill-test-say { white-space: pre-wrap; } +.skill-test-err { color: var(--color-danger, #e06c75); } +.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } +.skill-eval-badge { + font-size: 10px; font-weight: 700; letter-spacing: 0.03em; + padding: 2px 8px; border-radius: 10px; flex-shrink: 0; +} +.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); } +.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); } +.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); } +.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); } +.skill-eval-summary { font-size: 11px; opacity: 0.85; } +.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; } +.skill-eval-issues li { margin: 2px 0; } +.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } +.skill-eval-approve.suggested { + border-color: var(--color-success, #4ade80) !important; + color: var(--color-success, #4ade80) !important; +} +/* Already published — the button confirms the state (click to unpublish). */ +.skill-eval-approve.is-approved { + border-color: var(--color-success, #4ade80) !important; + color: var(--color-success, #4ade80) !important; + background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important; +} +/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title, + When to use, How, Tags) is accent-colored while the rest stays muted — real + placeholders can't be partially colored. The native placeholder is a single + space so :placeholder-shown still toggles the overlay. */ +.skill-ph-wrap { position: relative; } +.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; } +.skill-rich-ph { + position: absolute; + left: 11px; right: 11px; top: 50%; + transform: translateY(-50%); + pointer-events: none; + font-size: 12px; + line-height: 1.2; + color: color-mix(in srgb, var(--fg) 40%, transparent); + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +/* Textarea: align the hint to the first line instead of vertical-centering. */ +.skill-rich-ph-top { top: 7px; transform: none; } +.skill-rich-ph .k { color: var(--accent, var(--red)); } +/* Keep the memory-input enter hint pinned to the field's right edge instead + of letting it drift after the example text. */ +#new-memory-input ~ .skill-rich-ph > svg { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + margin-left: 0 !important; +} +/* Hide the overlay once the field has real content. */ +.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; } +.skill-md-editor { + flex: 1 1 auto; + min-height: 0; + width: 100%; + box-sizing: border-box; + resize: none; + font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; + font-size: 11.5px; + line-height: 1.5; + padding: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); +} +.doclib-empty { + text-align: center; + color: color-mix(in srgb, var(--fg) 35%, transparent); + padding: 32px 16px; + font-size: 12px; + font-style: italic; +} +/* Unified loading row across every Library tab (Chats / Documents / Research / + Archive): one opacity, one size, with the whirlpool spinner next to it. */ +.lib-loading-row { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 28px 16px; + font-size: 12px; + color: color-mix(in srgb, var(--fg) 45%, transparent); +} +.pdf-loading-state { + min-height: 120px; + margin: 0; + box-sizing: border-box; + flex-direction: column; + gap: 10px; + border: 0; + border-radius: 0; + background: transparent; + color: color-mix(in srgb, var(--fg) 68%, transparent); + box-shadow: none; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.01em; +} +.pdf-loading-state .ai-spinner-whirlpool { + color: var(--accent, var(--red)); + opacity: 0.95; +} +.pdf-loading-state .ai-spinner-whirlpool canvas { + display: block; +} +.doclib-load-more { + display: block; + margin: 10px auto 0; + padding: 6px 16px; + background: transparent; + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg-muted); + font-size: 11px; + cursor: pointer; + transition: border-color 0.15s, color 0.15s; + flex-shrink: 0; + position: relative; + top: 10px; +} +/* Soft dark gradient above the button — blends the list content into the + load-more strip instead of a hard cutoff. Spans the modal width and + sits behind/above the button. */ +.doclib-load-more::before { + content: ""; + position: absolute; + left: 50%; + transform: translateX(-50%); + bottom: 100%; + margin-bottom: 4px; + width: min(600px, 92vw); + height: 34px; + pointer-events: none; + background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent); +} +.doclib-load-more:hover { + border-color: var(--red); + color: var(--red); +} +.doclib-inline-load-more { + border: 0; + border-radius: 0; + padding: 0; + background: none; + top: 0; + font-size: 9px !important; + line-height: 1.2 !important; +} + +/* Document library toolbar buttons */ +.doclib-toolbar-btn { + background: none; + border: 1px solid var(--border); + color: var(--fg-muted); + font-size: 11px; + padding: 5px 10px; + border-radius: 6px; + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; + font-family: inherit; + display: inline-flex; + align-items: center; + gap: 3px; +} +.doclib-toolbar-btn:hover { + color: var(--fg); + border-color: var(--fg); +} +.doclib-toolbar-btn.active { + background: color-mix(in srgb, var(--red) 15%, transparent); + border-color: color-mix(in srgb, var(--red) 40%, transparent); + color: var(--red); +} +.doclib-toolbar-btn.danger { + color: var(--color-error, #e55); +} +.doclib-toolbar-btn.danger:hover:not(:disabled) { + border-color: var(--color-error, #e55); +} +.doclib-toolbar-btn:disabled { + opacity: 0.4; + cursor: default; +} + +/* Bulk action bar */ +.doclib-bulk-bar { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 8px; + border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); + border-radius: 6px; + background: color-mix(in srgb, var(--red) 5%, transparent); + font-size: 10px; + margin-bottom: 8px; +} +.doclib-bulk-bar.hidden { + display: none; +} +.doclib-bulk-check-all { + display: flex; + align-items: center; + gap: 3px; + cursor: pointer; + color: color-mix(in srgb, var(--fg) 60%, transparent); + font-size: 10px; +} +#doclib-bulk-bar .memory-toolbar-btn { + position: relative; + top: -3px; +} + +/* Custom checkboxes */ +.doclib-select-cb, +.doclib-bulk-check-all input { + -webkit-appearance: none; + appearance: none; + width: 13px; + height: 13px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + cursor: pointer; + flex-shrink: 0; + margin: 0; + position: relative; + transition: all 0.15s; +} +.doclib-select-cb:hover, +.doclib-bulk-check-all input:hover { + border-color: var(--red); +} +.doclib-select-cb:checked, +.doclib-bulk-check-all input:checked { + background: var(--red); + border-color: var(--red); +} +.doclib-select-cb:checked::after, +.doclib-bulk-check-all input:checked::after { + content: ''; + position: absolute; + left: 3px; + top: 0px; + width: 4px; + height: 8px; + border: solid var(--bg); + border-width: 0 1.5px 1.5px 0; + transform: rotate(45deg); +} + + +/* + button outside scroll area (when doc is on right side) */ + +@media (max-width: 600px) { + .doclib-toolbar { + flex-direction: column; + } + .doclib-card-session, + .doclib-card-time { + display: none; + } + .doclib-card-preview { + max-height: 40vh; + } +} + +/* Collapse card tags before they compete with attachment/favorite controls. */ +.email-card-tags .email-tags-more { + color: var(--email-tags-more-color, var(--fg)); + background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent); +} +.email-tags-more-single, +[data-email-tags-count-mini] { display: none; } +.email-tags-more-single svg { display: none; } +.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag, +.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra { + display: none; +} +.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single, +.email-card-tags.email-tags-expanded .email-tags-more-single { + display: inline-flex; +} +.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; } +.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini], +.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; } + +/* Keep the reader actions in drafting order. */ +.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; } +.email-reader-actions-inline > [data-act="reply"] { order: 2; } +.email-reader-actions-inline > [data-act="reply-all"] { order: 3; } +.email-reader-actions-inline > [data-act="forward"] { order: 4; } diff --git a/static/css/09-gallery.css b/static/css/09-gallery.css new file mode 100644 index 000000000..6554a5813 --- /dev/null +++ b/static/css/09-gallery.css @@ -0,0 +1,1382 @@ +/* 09-gallery - 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. + */ +.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed { + display: none !important; +} + +/* ── Archive browser ── */ +.archive-list { + grid-template-columns: 1fr !important; + gap: 4px !important; +} +.archive-row { + flex-direction: row !important; +} +.archive-row .doclib-card-header { + flex: 1; + padding: 8px 10px; +} +.archive-row .doclib-card-title { + flex: 1; + min-width: 0; +} +/* Archive column layout */ +.archive-header { + display: flex; + align-items: center; + padding: 4px 10px; + gap: 6px; + font-size: 9px; + font-weight: 600; + opacity: 0.35; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.archive-col-title { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-left: -4px; +} +.archive-col-msgs { + width: 40px; + flex-shrink: 0; + text-align: left; + margin-left: -8px; +} +.archive-col-model { + width: 90px; + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.archive-col-time { + width: 55px; + flex-shrink: 0; + text-align: right; +} +.archive-col-menu { + width: 24px; + flex-shrink: 0; +} +.archive-menu-btn { + background: transparent; + border: none; + color: var(--fg); + opacity: 0.3; + cursor: pointer; + padding: 4px; + line-height: 0; + border-radius: 4px; + transition: background .15s, opacity .15s; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + margin-left: 4px; + vertical-align: middle; + height: 100%; +} +.archive-menu-btn svg { + display: block; + margin-top: -6px; +} +.archive-menu-btn:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); + opacity: 1; +} + +/* ── Gallery (image library) ── */ + +.gallery-modal-content { + width: min(820px, 94vw); + max-height: 92vh; + font-size: 12px; +} +/* Keep the Gallery frame anchored while tabs or bulk-selection controls + change the content height. Centering the frame made every shorter tab move + the whole window vertically. */ +@media (min-width: 769px) { + #calendar-modal, + #gallery-modal, + #tasks-modal, + #memory-modal, + #doclib-modal, + #compare-model-overlay, + #research-overlay, + #theme-modal, + #settings-modal, + #email-lib-modal, + #cookbook-modal { + align-items: flex-start; + padding-top: 4vh; + box-sizing: border-box; + } +} +/* While the Edit tab is active the editor needs a *definite* height so + the right-side tool panel (`.ge-right-panel { overflow-y:auto }`) + scrolls INTERNALLY. Without this the modal only has `max-height`, so + `.gallery-editor { height:100% }` can't resolve — the editor grows to + its full content height, overflows the modal body, and the modal-body + scrollbar that appears can't be wheel-scrolled because the editor's + `overscroll-behavior:contain` blocks the chain. Pinning a real height + here makes the panel bounded and scrollable as designed. Scoped to + the editor view (matched via the container's inline `display:flex`) + so the Photos/Albums views keep sizing to their content. */ +.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) { + height: 92vh; +} +/* The editor tab owns the modal body's scroll context. Keep the landing + chooser and the mounted editor inside the available sheet height so a long + saved-project list cannot push the launch controls below the viewport. */ +.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body { + display: flex; + min-height: 0; + overflow: hidden; +} +.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container { + min-height: 0; + overflow: hidden; +} +/* Photo-detail view sizing (issue #314). + The detail view is rendered as a `position:absolute; inset:0` overlay + *inside* `.gallery-images-container`, painted over the photo grid. Because + it's absolutely positioned it can't contribute to the container's height — + the container (and therefore the overlay's `inset:0` box) collapses to the + height of the grid sitting behind it. When the library only has a few + photos that grid is short, so the detail view is crushed: the image is + clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a + tiny, internally-scrolling strip. (With a large library the grid is tall, + which is why it looked fine in the demo video but cramped for users with + few photos.) + Fix: when the detail view is open, hide the grid-view siblings and drop the + overlay into normal flow. The container — and the window, up to its 92vh + max-height — then sizes to the detail's own content (image + metadata), so + nothing is clipped or squeezed regardless of how many photos exist. Scoped + via the detail element's inline `display:flex` so the grid / albums views + keep sizing to their own content. Works on both desktop and the mobile + full-screen sheet. */ +#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) { + display: none !important; +} +#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail { + position: static; +} +/* Containing block for the photo-detail overlay — keeps it inside the body + so it sits below the modal header and the tab strip instead of covering them. */ +.gallery-images-container { + position: relative; +} +.gallery-modal-content .modal-header h4 { + font-size: 1rem; +} +.gallery-stats { + font-size: 10px; + color: color-mix(in srgb, var(--fg) 50%, transparent); + margin-bottom: 4px; +} +.gallery-toolbar { + display: flex; + gap: 6px; + margin-bottom: 4px; + align-items: center; +} +.gallery-toolbar-break { display: none; } +/* Keep the hint anchored while independently seating the input in the row. */ +.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; } +.gallery-search { + position: relative; + top: 4px; + flex: 1; + width: 100%; + padding: 4px 8px; + padding-right: 78px; /* room for the enter hint */ + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + font: inherit; + font-size: 12px; + outline: none; +} +.gallery-search-enter-hint { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 10px; + font-weight: 600; + color: var(--accent, var(--red)); + pointer-events: none; + opacity: 0; + transition: opacity 0.12s; + white-space: nowrap; +} +/* Show the hint only once the user has typed something (placeholder gone). */ +.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; } +.gallery-search:focus { + border-color: var(--red); +} +.gallery-model-filter, +.gallery-sort { + padding: 4px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; + position: relative; + top: 4px; +} +.gallery-selection-bar { + margin-bottom: 4px; + position: relative; + top: 2px; + padding-right: 18px; +} +.gallery-tag-chips { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin-bottom: 4px; +} +.gallery-chip { + height: 28px; + box-sizing: border-box; + padding: 0 13px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + border-radius: 14px; + font-size: 12px; + line-height: 1; + border: 1px solid var(--border); + background: none; + color: var(--fg); + cursor: pointer; + transition: border-color 0.15s, background 0.15s; +} +.gallery-chip:hover { + border-color: var(--red); +} +.gallery-chip.active { + background: color-mix(in srgb, var(--red) 15%, transparent); + border-color: color-mix(in srgb, var(--red) 40%, transparent); + color: var(--red); +} +/* Album chips row */ +.gallery-album-chips { + display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px; +} +.gallery-chip-fav { color: var(--red); } +.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); } +.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; } +/* Active-album indicator — appears when the Photos grid is filtered to one album. */ +.gallery-chip-active-album, +.gallery-active-filter-pill { + display: inline-flex; align-items: center; gap: 4px; + background: color-mix(in srgb, var(--red) 14%, transparent); + border-color: color-mix(in srgb, var(--red) 45%, transparent); + position: relative; + top: 6px; +} +.gallery-active-filter-pill { + background: color-mix(in srgb, var(--fg) 7%, transparent); + border-color: color-mix(in srgb, var(--fg) 18%, transparent); + color: color-mix(in srgb, var(--fg) 86%, transparent); +} +.gallery-clear-all-filters { + height: 28px; + border-style: dashed; + opacity: 0.72; +} +.gallery-clear-all-filters:hover { + opacity: 1; + color: var(--red); +} +.gallery-chip-clear { + background: none; + border: none; + color: inherit; + opacity: 0.6; + cursor: pointer; + padding: 0; + margin-left: 1px; + width: 12px; + height: 12px; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 12px; + line-height: 1; + position: relative; + top: -4px; +} +.gallery-chip-clear:hover { opacity: 1; } + + + +/* Favorite button on card */ +/* Video cards: fills the same slot as , plus a small play badge + so it's clear the thumbnail represents a video. */ +.gallery-card video { + width: 100%; + aspect-ratio: 1 / 1; + object-fit: cover; + background: #000; + display: block; +} +.gallery-card-play { + position: absolute; + left: 50%; top: 50%; + transform: translate(-50%, -50%); + width: 36px; height: 36px; + display: flex; align-items: center; justify-content: center; + background: rgba(0, 0, 0, 0.55); + color: #fff; + border-radius: 50%; + pointer-events: none; + z-index: 1; +} +.gallery-card-play svg { margin-left: 2px; } + +.gallery-fav-btn { + position: absolute; top: 0px; right: 4px; z-index: 2; + background: rgba(0,0,0,0.4); border: none; border-radius: 50%; + width: 26px; height: 26px; font-size: 14px; + color: rgba(255,255,255,0.6); cursor: pointer; + display: flex; align-items: center; justify-content: center; + opacity: 0; transition: opacity 0.15s; + padding: 0; +} +.gallery-fav-btn.gallery-fav-active { opacity: 1; color: var(--red); } + +.gallery-dl-btn { + position: absolute; top: 0px; left: 4px; z-index: 2; + background: rgba(0,0,0,0.4); border: none; border-radius: 50%; + width: 26px; height: 26px; + color: rgba(255,255,255,0.75); cursor: pointer; + display: flex; align-items: center; justify-content: center; + opacity: 0; transition: opacity 0.15s, background 0.15s; + padding: 0; +} +.gallery-dl-btn:hover { background: rgba(0,0,0,0.7); color: #fff; } +/* In select mode the per-thumbnail hover buttons (favorite + download) just + get in the way of picking — hide them so the card is a clean select target. + `body.gallery-selecting` is the authoritative signal (one class for the + whole grid) — the per-card .gallery-card-selectable is kept as a backup + in case the body class is missed. */ +body.gallery-selecting .gallery-fav-btn, +body.gallery-selecting .gallery-dl-btn, +.gallery-card-selectable .gallery-fav-btn, +.gallery-card-selectable .gallery-dl-btn { + display: none !important; +} + +/* AI tag chips in detail view */ +.gallery-ai-tags { display: flex; gap: 4px; flex-wrap: wrap; } +.gallery-ai-chip { + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); + border-radius: 10px; padding: 3px 8px; font-size: 10px; + color: var(--fg); opacity: 0.8; + font: inherit; font-size: 10px; + cursor: pointer; + transition: background 0.12s, opacity 0.12s, border-color 0.12s; +} +.gallery-ai-chip:hover { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); +} +/* AI-generated tags: muted neutral with a tiny sparkle marker so the + user can tell at a glance these came from the model, not them. */ +.gallery-aitag-chip { + background: color-mix(in srgb, var(--fg) 6%, transparent); + border-color: color-mix(in srgb, var(--fg) 18%, transparent); + display: inline-flex; + align-items: center; + gap: 4px; +} +.gallery-aitag-chip:hover { + background: color-mix(in srgb, var(--fg) 12%, transparent); + border-color: color-mix(in srgb, var(--fg) 35%, transparent); +} +.gallery-aitag-mark { + font-size: 8px; + opacity: 0.55; + color: var(--accent, var(--red)); +} +/* User-applied tags keep the accent-colored chip — they're the personal/ + curated tags, so they get the strongest visual weight. */ +.gallery-user-chip { + font-weight: 600; +} +/* × to remove a user tag (appears inside the chip). */ +.gallery-tag-x { + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 5px; + width: 13px; + height: 13px; + border-radius: 50%; + font-size: 12px; + line-height: 1; + opacity: 0.55; + transition: opacity 0.12s, background 0.12s, color 0.12s; +} +.gallery-tag-x:hover { + opacity: 1; + background: color-mix(in srgb, var(--red, #ff5555) 22%, transparent); + color: var(--red, #ff5555); +} + +.gallery-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + align-content: start; + gap: 8px; + max-height: calc(92vh - 165px); + overflow-y: auto; + padding: 2px; + transition: border-color 0.15s, background 0.15s; +} +.gallery-grid.gallery-dragover { + border: 2px dashed var(--red); + border-radius: 8px; + background: color-mix(in srgb, var(--red) 5%, transparent); +} +.gallery-card { + position: relative; + aspect-ratio: 1; + border-radius: 6px; + overflow: hidden; + border: 1px solid var(--border); + cursor: pointer; + transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s; +} +.gallery-card.gallery-card-focus { + border-color: var(--accent); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 36%, transparent); +} +/* Upload-affordance tile pinned to the top-left of the Photos grid. Matches + the Upload album tile in the Albums tab — dashed border, centered icon. */ +.gallery-card-upload { + border-style: dashed; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + opacity: 0.75; + transition: opacity 0.12s, border-color 0.15s, transform 0.15s; +} +/* The "Start AI tag" button turns into a Cancel control during a tag run. */ +.gallery-tag-cancelling { + color: var(--red) !important; + border-color: color-mix(in srgb, var(--red) 45%, var(--border)) !important; +} +/* Skeleton/shimmer placeholder tiles shown while the first page of photos loads. */ +.gallery-card-skeleton { + cursor: default; + background: linear-gradient(100deg, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, + color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); + background-size: 200% 100%; + animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; +} +.gallery-card-skeleton:hover { transform: none; box-shadow: none; border-color: var(--border); } +/* Chat attachment image: shimmer skeleton + centered whirlpool shown while a + sent photo uploads / its thumbnail loads (see chatRenderer buildAttachCards). */ +.attach-image-skeleton { + position: relative; + width: 160px; height: 120px; + border-radius: 6px; + display: flex; align-items: center; justify-content: center; + background: linear-gradient(100deg, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, + color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, + color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); + background-size: 200% 100%; + animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; +} +.attach-image-skeleton > .spinner-whirlpool { margin: 0 !important; } + +/* Corner "Aa" button on a chat photo thumbnail — opens the vision/OCR editor + so the user can correct what the vision model fed to the LLM. */ +.attach-image-preview { position: relative; } +.attach-vision-model, +.attach-image-name { + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.attach-vision-model { + font-size: 10px; + color: var(--accent, var(--red)); + opacity: 0.85; + margin-top: 3px; +} +.attach-image-name { + font-size: 10px; + opacity: 0.5; + margin-top: 1px; +} +.attach-ocr-btn { + position: absolute; top: 4px; right: 4px; + z-index: 5; /* sit above msg-action overlays so the click lands here */ + height: 22px; + background: rgba(0,0,0,0.55); color: #fff; + border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; + padding: 0 8px 0 6px; cursor: pointer; + display: inline-flex; align-items: center; justify-content: center; gap: 5px; + font-size: 11px; font-weight: 500; line-height: 1; + opacity: 0.75; transition: opacity 0.15s; +} +.attach-ocr-btn:hover { opacity: 1; } +.attach-ocr-btn svg { display: block; } +/* On mobile keep just the (larger) edit icon — the "Caption" label crowds + the corner of the small chat photo. */ +@media (max-width: 768px) { + .attach-ocr-btn .attach-ocr-label { display: none; } + .attach-ocr-btn { + width: 28px; height: 28px; + padding: 0; + } + .attach-ocr-btn svg { width: 16px; height: 16px; } +} + +/* Vision/OCR text editor modal — opened from the "Aa" button above. */ +.vision-editor-overlay { + position: fixed; inset: 0; z-index: 9999; + background: rgba(0,0,0,0.55); + display: flex; align-items: center; justify-content: center; + padding: 16px; +} +.vision-editor-panel { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 10px; + padding: 14px 16px; + width: min(560px, 92vw); + max-height: 88vh; + display: flex; flex-direction: column; gap: 8px; + box-shadow: 0 14px 40px rgba(0,0,0,0.4); +} +.vision-editor-title { + font-size: 13px; font-weight: 600; color: var(--fg); + display: flex; align-items: center; gap: 6px; +} +.vision-editor-desc { font-size: 11px; opacity: 0.6; line-height: 1.4; } +.vision-editor-text { + width: 100%; min-height: 180px; + background: var(--panel); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; + padding: 8px 10px; + font-family: inherit; font-size: 12px; line-height: 1.5; + resize: vertical; outline: none; +} +.vision-editor-text:focus { border-color: var(--accent-primary, var(--red)); } +.vision-editor-hint { font-size: 10.5px; opacity: 0.55; line-height: 1.4; } +.vision-editor-actions { + display: flex; gap: 8px; justify-content: flex-end; + margin-top: 4px; +} +.vision-editor-btn { + background: var(--panel); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 14px; font-size: 12px; cursor: pointer; + transition: border-color 0.15s, background 0.15s; + display: inline-flex; align-items: center; gap: 5px; +} +.vision-editor-btn svg { display: block; } +/* Optical nudge: the button text sits 1px higher than the SVG glyphs + alongside it. Drop the text down 1px so they line up. */ +.vision-editor-btn .vision-btn-label { position: relative; top: 1px; } +.vision-editor-btn:hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); } +.vision-editor-btn-primary { + background: var(--accent-primary, var(--red)); + color: #fff; border-color: transparent; +} +.vision-editor-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } + +/* Quick full-size lightbox shown when the user taps a chat photo thumbnail. */ +.attach-lightbox { + position: fixed; inset: 0; z-index: 9998; + background: rgba(0, 0, 0, 0.85); + display: flex; align-items: center; justify-content: center; + padding: 16px; cursor: zoom-out; + animation: attach-lightbox-fade 0.12s ease-out; +} +.attach-lightbox img { + max-width: 100%; max-height: 100%; + border-radius: 6px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + object-fit: contain; +} +.attach-lightbox-err { + position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); + background: var(--bg); color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 10px; font-size: 12px; +} +@keyframes attach-lightbox-fade { + from { opacity: 0; } + to { opacity: 1; } +} + +.gallery-card-upload:hover { + opacity: 1; + border-color: var(--red); + transform: translateY(-1px); +} +.gallery-card-upload-inner { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 6px; + color: var(--fg); +} +.gallery-card-upload-label { + font-size: 12px; + font-weight: 500; + opacity: 0.8; +} +.gallery-card:hover { + border-color: var(--red); + box-shadow: 0 2px 8px color-mix(in srgb, var(--red) 15%, transparent); + transform: translateY(-1px); +} +.gallery-card-overlay { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 8px; + background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 34%, rgba(0,0,0,0.22)); + color: #fff; + opacity: 0; + pointer-events: none; + transition: opacity 0.15s; +} +.gallery-card-badges { + display: flex; + gap: 4px; + flex-wrap: wrap; + align-items: flex-end; + justify-content: flex-end; + margin-top: auto; + margin-bottom: 34px; +} +.gallery-card-badge { + padding: 2px 6px; + border-radius: 999px; + background: rgba(0, 0, 0, 0.55); + border: 1px solid rgba(255, 255, 255, 0.14); + color: rgba(255, 255, 255, 0.88); + font-size: 9px; + font-weight: 600; + line-height: 1.1; +} +.gallery-card-badge-fav { + color: #ff5f6f; +} +.gallery-select-dot { + position: absolute; top: 6px; left: 6px; z-index: 2; + display: block; + width: 10px; height: 10px; border-radius: 50%; cursor: pointer; + background: color-mix(in srgb, var(--fg) 30%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 50%, transparent); + transition: background 0.15s; +} +.gallery-select-dot.selected { background: var(--red); border-color: var(--red); } +.gallery-select-dot:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } +.gallery-select-btn { + padding: 9px 10px 11px; background: transparent; color: var(--fg); + border: 1px solid var(--border); border-radius: 6px; cursor: pointer; + font-size: 11px; font-family: inherit; opacity: 0.6; transition: all 0.15s; + margin-top: -4px !important; +} +.gallery-select-btn:hover { opacity: 1; } +/* Match the library Select toggle's red tint (.memory-toolbar-btn.active). + Bumped contrast so the toggled state actually reads on mobile — the prior + 15% red on transparent was nearly invisible against the page background. */ +.gallery-select-btn.active { + background: color-mix(in srgb, var(--red) 28%, transparent); + color: var(--red); + border-color: var(--red); + font-weight: 600; + opacity: 1; +} +/* Toolbar action buttons sit 2px lower than the generic select-btn so + they baseline-align with the toolbar's selects/inputs instead of + floating above them. */ +.gallery-toolbar-action { + margin-top: 0 !important; + display: inline-flex; + align-items: center; + gap: 2px; +} +.gallery-bulk-bar { + display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 12px; + justify-content: flex-end; +} +.gallery-bulk-bar.hidden { display: none; } +.gallery-bulk-all { + display: inline-flex; align-items: center; gap: 4px; + font-size: 11px; opacity: 0.75; cursor: pointer; margin-right: auto; +} +.gallery-bulk-all input { accent-color: var(--accent, var(--red)); cursor: pointer; } +.gallery-bulk-delete { + padding: 3px 10px; background: transparent; color: var(--red); + border: 1px solid var(--red); border-radius: 4px; cursor: pointer; + font-size: 11px; font-family: inherit; +} +.gallery-bulk-delete:hover { background: var(--red); color: #fff; } +.gallery-bulk-cancel { + padding: 3px 10px; background: transparent; color: var(--fg); + border: 1px solid var(--border); border-radius: 4px; cursor: pointer; + font-size: 11px; font-family: inherit; opacity: 0.6; +} +.gallery-bulk-cancel:hover { opacity: 1; } +.gallery-card img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +.gallery-card-info { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 6px 8px; + background: linear-gradient(transparent, rgba(0,0,0,0.75)); + color: #fff; +} +.gallery-card-prompt { + font-size: 10px; + line-height: 1.3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.gallery-card-meta { + display: flex; + align-items: center; + gap: 6px; + margin-top: 2px; + font-size: 9px; + opacity: 0.8; +} +.gallery-card-model { + padding: 1px 5px; + border-radius: 6px; + background: rgba(255,255,255,0.15); +} +.gallery-empty { + text-align: center; + color: color-mix(in srgb, var(--fg) 35%, transparent); + padding: 32px 16px; + font-size: 12px; + font-style: italic; +} +.gallery-load-more { + display: block; + margin: 10px auto 0; + padding: 6px 16px; + background: none; + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 11px; + transition: border-color 0.15s, color 0.15s; +} +.gallery-load-more:hover { + border-color: var(--red); + color: var(--red); +} + +/* Gallery detail overlay */ +.gallery-detail { + position: absolute; + inset: 0; + background: var(--panel); + z-index: 10; + display: flex; + flex-direction: column; + overflow-y: auto; +} +.gallery-detail-header { + display: flex; + align-items: center; + gap: 6px; + padding: 2px 8px 4px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + /* Pin to top so a tall (portrait) photo can't cover the Back / ⋮ + menu when the detail body grows past the visible area. */ + position: sticky; + top: 0; + background: var(--panel); + /* Above the image's rotate/nav buttons (z-index:3) so they scroll cleanly + behind the header instead of colliding with it. */ + z-index: 5; + min-height: 0; +} +.gallery-detail-title { + max-width: min(360px, 40vw); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 12px; + font-weight: 600; + opacity: 0.82; + position: relative; + top: 1px; +} +.gallery-detail-back { + background: none; + border: none; + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 12px; + padding: 4px 8px; + border-radius: 4px; + position: relative; + top: -2px; +} +/* Slightly larger header icons (back/edit/heart/⋮) — override the inline 14px. */ +.gallery-detail-header svg { width: 16px; height: 16px; } +.gallery-detail-back:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.gallery-detail-action { + background: none; + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 11px; + padding: 3px 8px; + border-radius: 4px; + transition: border-color 0.15s; +} +.gallery-detail-action:hover { + border-color: var(--fg); +} +.gallery-detail-action.danger { + color: var(--color-error, #e55); +} +/* Overflow menu — replaces the seven individual action buttons that used + to crowd the right side of the detail header. */ +.gallery-detail-menu-wrap { position: relative; } +.gallery-detail-menu-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 26px; + padding: 0; + position: relative; + top: -2px; +} +.gallery-detail-menu { + position: absolute; + top: calc(100% + 4px); + right: 0; + min-width: 150px; + padding: 3px; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); + z-index: 12; + display: flex; + flex-direction: column; + gap: 0; +} +.gallery-detail-menu[hidden] { display: none; } +.gallery-detail-menu .dropdown-item-compact { + width: 100%; + background: none; + border: none; + text-align: left; + font: inherit; + /* Tighter than the global default — the photo-detail menu has 8 + items so every pixel of vertical padding adds up. */ + padding: 4px 8px; + font-size: 11px; + gap: 8px; + line-height: 1.2; +} +.gallery-detail-menu .dropdown-item-compact .dropdown-icon, +.gallery-detail-menu .dropdown-item-compact .dropdown-icon svg { + width: 12px; + height: 12px; +} +.gallery-detail-body { + display: flex; + gap: 16px; + padding: 12px; + flex: 1; + min-height: 0; + overflow: hidden; +} +.gallery-detail-image { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + min-width: 0; + min-height: 0; + height: 100%; + padding-inline: 56px; + box-sizing: border-box; + overflow: hidden; +} +/* Shrink-wraps the actual image so overlay children (heart, face boxes) + anchor to the IMAGE bounds, not the wider letterboxed wrapper. */ +.gallery-detail-img-frame { + position: relative; + display: inline-flex; + max-width: 100%; + max-height: 100%; +} +.gallery-detail-nav { + position: absolute; + top: 50%; + transform: translateY(-50%); + background: rgba(0, 0, 0, 0.45); + color: rgba(255, 255, 255, 0.85); + border: none; + border-radius: 50%; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s, background 0.15s; + z-index: 3; + padding: 0; +} +.gallery-detail-nav:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } +.gallery-detail-nav-prev { left: 8px; } +.gallery-detail-nav-next { right: 8px; } + +/* Rotate buttons — same pattern as the prev/next arrows but pinned to the + top corners. Hover-revealed so they don't clutter the image. */ +.gallery-detail-rotate { + position: absolute; + top: 8px; + background: rgba(0, 0, 0, 0.45); + color: rgba(255, 255, 255, 0.85); + border: none; + border-radius: 50%; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s, background 0.15s; + z-index: 3; + padding: 0; +} +.gallery-detail-rotate:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } +.gallery-detail-rotate-ccw { left: 8px; } +.gallery-detail-rotate-cw { right: 8px; } +/* Inline heart that lives next to the "Date" label in the sidebar. + Tiny outline icon by default; fills red when favorited. */ +.gallery-detail-date-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; +} +.gallery-detail-fav-inline { + background: none; + border: none; + padding: 0; + /* Nudge up 8px so the heart sits visually above the Date label + baseline rather than centred on it. */ + margin-top: -8px; + border-radius: 4px; + color: var(--fg-muted); + opacity: 0.6; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; + transition: opacity 0.15s, color 0.15s; +} +/* Shrink the SVG so the heart matches the height of the small uppercase + Date label sitting next to it. */ +.gallery-detail-fav-inline svg { + width: 12px; + height: 12px; + display: block; +} +.gallery-detail-fav-inline:hover { + opacity: 1; +} +.gallery-detail-fav-inline.active { + opacity: 1; + color: var(--red); +} +.gallery-detail-sidebar { + width: 290px; + flex-shrink: 0; + /* Vertical scroll for long metadata, but never horizontal — overflow:auto + alone would let a wide child (e.g. long album name in the select) push + the sidebar into horizontal-scroll mode. */ + overflow-x: hidden; + overflow-y: auto; + min-width: 0; +} +.gallery-detail-inspector-head { + padding-bottom: 10px; + margin-bottom: 10px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); +} +.gallery-detail-eyebrow { + font-size: 9px; + font-weight: 700; + color: var(--accent, var(--red)); + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 3px; +} +.gallery-detail-inspector-title { + font-size: 14px; + font-weight: 650; + line-height: 1.25; + word-break: break-word; +} +.gallery-detail-meta-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 6px; + margin: 4px 0 12px; +} +.gallery-detail-meta-card { + min-width: 0; + border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); + background: color-mix(in srgb, var(--fg) 4%, transparent); + border-radius: 7px; + padding: 7px 8px; +} +.gallery-detail-meta-card span { + display: block; + font-size: 9px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 48%, transparent); + text-transform: uppercase; + letter-spacing: 0.05em; + margin-bottom: 3px; +} +.gallery-detail-meta-card strong { + display: block; + min-width: 0; + font-size: 11px; + font-weight: 600; + line-height: 1.25; + word-break: break-word; +} +.gallery-detail-inline-link { + appearance: none; + background: none; + border: none; + color: var(--accent, var(--red)); + padding: 0; + font: inherit; + font-weight: inherit; + cursor: pointer; + text-align: left; +} +.gallery-detail-mini-actions { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 7px; +} +.gallery-detail-mini-btn { + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 4px 8px; + font: inherit; + font-size: 10.5px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 4px; +} +.gallery-detail-mini-btn:hover { + border-color: color-mix(in srgb, var(--fg) 42%, var(--border)); +} +/* Constrain any child that could otherwise stretch the sidebar wider than + its declared width (selects with long option text, long URLs, etc.). */ +.gallery-detail-sidebar select, +.gallery-detail-sidebar input, +.gallery-detail-sidebar .gallery-detail-prompt { + max-width: 100%; + box-sizing: border-box; +} +.gallery-date-rel { + opacity: 0.55; + margin-left: 4px; + font-size: 11px; + font-style: italic; +} +.gallery-detail-section label { + display: block; + font-size: 10px; + font-weight: 600; + opacity: 0.6; + margin-bottom: 3px; + text-transform: uppercase; + letter-spacing: 0.5px; +} +.gallery-detail-section div { + font-size: 12px; + word-break: break-word; +} +.gallery-detail-prompt { + white-space: pre-wrap; + line-height: 1.4; +} +.gallery-tag-input, +.gallery-detail-name-input { + width: 100%; + padding: 5px 8px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; + font: inherit; + font-size: 11px; + margin-bottom: 4px; +} +.gallery-tag-input:focus, +.gallery-detail-name-input:focus { + border-color: var(--red); + outline: none; +} +/* ↵ enter hint inside the Add-a-tag field (accent), replacing the "press Enter" + placeholder text. */ +.gallery-tag-input-wrap { position: relative; } +.gallery-tag-enter-hint { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + margin-top: -2px; /* the input has 4px bottom margin; center on the box */ + color: var(--accent, var(--red)); + font-size: 13px; + line-height: 1; + pointer-events: none; + opacity: 0.8; +} +.gallery-detail-name-input { font-size: 13px; font-weight: 500; } +/* ↵ enter hint on the image name field — accent, shown while editing to signal + that Enter saves. */ +.gallery-name-wrap { position: relative; } +.gallery-name-enter { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + margin-top: -2px; + color: var(--accent, var(--red)); + opacity: 0.85; + pointer-events: none; + display: none; +} +.gallery-tag-save { + background: none; + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font: inherit; + font-size: 10px; + padding: 3px 10px; + border-radius: 4px; + transition: border-color 0.15s, color 0.15s; +} +.gallery-tag-save:hover { + border-color: var(--red); + color: var(--red); +} + +@media (max-width: 600px) { + .gallery-grid { + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 6px; + } + /* Single-row toolbar on mobile: search + Sources + Recent (sort) all share + one row. The search bar shrinks so the dropdowns fit beside it. */ + .gallery-toolbar { + flex-wrap: nowrap; + gap: 4px; + } + .gallery-search-wrap { + flex: 1 1 0; + min-width: 0; + } + .gallery-search { + /* Drop the desktop right-padding reserved for the "enter to tag" hint — + hide the hint on mobile (below) so the input can use its full width. */ + padding-right: 8px; + } + .gallery-search-enter-hint { display: none; } + /* Disable the row break so the filters stay inline with search. */ + .gallery-toolbar-break { display: none; } + .gallery-model-filter, + .gallery-sort { + flex: 0 0 auto; + /* Cap each dropdown so search keeps a usable amount of width. */ + max-width: 100px; + font-size: 11px; + padding: 4px 6px; + /* Keep the same vertical alignment as the desktop toolbar. */ + position: relative; + top: 4px; + } + .gallery-select-btn { + padding: 6px 8px 8px; + font-size: 9px; + flex-shrink: 0; + /* Push Select to the far right of the single-row toolbar, regardless + of its DOM position (sits between search and the dropdowns). */ + order: 99; + margin-left: auto; + } + /* Tabs scroll horizontally rather than wrapping when narrow. */ + .gallery-tabs { + overflow-x: auto; + flex-wrap: nowrap; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + } + .gallery-tabs::-webkit-scrollbar { display: none; } + .gallery-tab { flex-shrink: 0; } + /* Album chips also scroll horizontally so they don't stack. */ + .gallery-album-chips { + overflow-x: auto; + flex-wrap: nowrap; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-bottom: 4px; + } + .gallery-album-chips::-webkit-scrollbar { display: none; } + .gallery-chip { flex-shrink: 0; } + /* Tasks filter chips — same horizontal-swipe behaviour as the gallery + albums / library chips on mobile: one row, slide-to-see-more. */ + .tasks-activity-filters { + overflow-x: auto; + flex-wrap: nowrap !important; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-bottom: 4px; + } + .tasks-activity-filters::-webkit-scrollbar { display: none; } + .tasks-activity-filters > * { flex-shrink: 0; } + /* Detail view */ + .gallery-detail-body { + flex-direction: column; + overflow: visible; + } + .gallery-detail-image { + flex: 0 0 auto; + width: 100%; + height: clamp(280px, 58vh, 520px); + padding-inline: 48px; + } + .gallery-detail-sidebar { + width: 100%; + } + .gallery-detail-title { + display: none; + } + .gallery-detail-meta-grid { + grid-template-columns: 1fr; + } + /* Album grid: smaller tile minimum on mobile. */ + .gallery-albums-grid { + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 8px; + } +} + + +/* ── Scoreboard table ── */ +.scoreboard-table { + width: 100%; + border-collapse: collapse; + font-size: 0.88em; +} +.scoreboard-table th { + text-align: left; + padding: 6px 10px; + border-bottom: 1px solid var(--border); + font-weight: 600; + font-size: 0.85em; + color: color-mix(in srgb, var(--fg) 55%, transparent); + text-transform: uppercase; + letter-spacing: 0.5px; +} +.scoreboard-table th:not(:first-child) { + text-align: center; +} +.scoreboard-table td { + padding: 5px 10px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); + text-align: center; +} +.scoreboard-table td.scoreboard-model { + text-align: left; + font-weight: 500; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.scoreboard-table td.scoreboard-pct { + font-weight: 600; + color: var(--red); +} diff --git a/static/css/style-part-05.css b/static/css/10-cookbook.css similarity index 55% rename from static/css/style-part-05.css rename to static/css/10-cookbook.css index 5008414f9..8acded779 100644 --- a/static/css/style-part-05.css +++ b/static/css/10-cookbook.css @@ -1,14 +1,1639 @@ -/* Fragment 6/9 of the former static/style.css. +/* 10-cookbook - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ +.scoreboard-table tbody tr:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} + +#scoreboard-overlay svg { + color: var(--accent, var(--red)); +} + +/* ── Compare search results ── */ +.compare-search-results { + display: flex; + flex-direction: column; + gap: 2px; +} +.compare-search-result { + padding: 6px 0; + border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); +} +.compare-search-result:last-child { + border-bottom: none; +} +.search-result-title { + color: var(--color-accent); + text-decoration: none; + font-weight: 500; + font-size: 0.9em; + display: block; + line-height: 1.3; +} +.search-result-title:hover { + color: var(--color-link-hover); + text-decoration: underline; +} +.search-result-snippet { + color: color-mix(in srgb, var(--fg) 70%, transparent); + font-size: 0.82em; + line-height: 1.4; + margin-top: 2px; +} +.search-result-url { + color: color-mix(in srgb, var(--fg) 35%, transparent); + font-size: 0.75em; + margin-top: 3px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + + +/* ── Cookbook Tool ── */ + +/* ── Cookbook ── */ +.cookbook-serve-preset { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + margin-bottom: 3px; + cursor: pointer; + transition: background 0.15s; +} +.cookbook-serve-preset:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } +.cookbook-serve-preset-name { + font-size: 11px; + font-weight: 500; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.cookbook-serve-preset-meta { + font-size: 9px; + font-family: 'Fira Code', monospace; + color: var(--fg-muted); + opacity: 0.5; +} +.cookbook-serve-preset-launch { + background: none; + border: none; + color: #50fa7b; + cursor: pointer; + font-size: 10px; + opacity: 0.6; + padding: 0 2px; + position: relative; + top: -4px; +} +.cookbook-serve-preset-launch:hover { opacity: 1; } +.cookbook-serve-preset-rm { + background: none; + border: none; + color: var(--fg-muted); + cursor: pointer; + font-size: 10px; + opacity: 0.3; + padding: 0 2px; + position: relative; + top: -4px; +} +.cookbook-serve-preset-rm:hover { opacity: 0.8; color: var(--red); } +.cookbook-body { + display: flex; + flex-direction: column; + gap: 10px; + overflow-y: auto; + flex: 1; + min-height: 0; + scrollbar-width: thin; +} +#cookbook-modal .modal-content { + display: flex; + flex-direction: column; + overflow: hidden; +} +#cookbook-modal .modal-header { + flex: 0 0 auto; +} +#cookbook-modal .cookbook-body { + min-height: 0; + overflow-y: auto; + overflow-x: hidden; +} +#cookbook-modal .cookbook-group { + min-height: 0; +} +#cookbook-modal .cookbook-group > .admin-card { + min-height: 0; + /* Let .cookbook-body be the SINGLE scroll surface. Nesting another + overflow:auto here trapped the wheel inside the cached-list when a + serve panel expanded — the page couldn't scroll past the panel's + bottom (Launch button got hidden). */ + overflow: visible !important; +} +#cookbook-modal .cookbook-section-body { + min-height: 0; +} +.cookbook-group { + display: flex; + flex-direction: column; + gap: 8px; + flex: 1; + min-height: 0; +} + +/* Cards — match admin-card */ +.cookbook-card { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + padding: 12px; + display: flex; + flex-direction: column; + gap: 6px; +} +.cookbook-card-header { + display: flex; + align-items: center; + justify-content: space-between; +} +.cookbook-card-header-actions { display: flex; gap: 4px; } +.cookbook-card-title { font-size: 13px; font-weight: 600; } +.cookbook-card-desc { + font-size: 11px; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} + +/* Fields grid */ +.cookbook-fields { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 6px; + margin-top: 4px; +} +.cookbook-field-label { + display: flex; + flex-direction: column; + font-size: 11px; + color: color-mix(in srgb, var(--fg) 60%, transparent); + gap: 3px; +} + +/* Inputs — match admin-add-form input */ +.cookbook-field-input { + padding: 5px 8px; + font-size: 12px; + font-family: inherit; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + outline: none; +} +.cookbook-field-input:focus { border-color: var(--red); } +#hwfit-dl-server, #hwfit-usecase, #hwfit-server-select, #hwfit-cache-server, #serve-sort { height: 28px; width: 88px; } +/* Serve tab — match Documents sizing, but leave room for the active "Cancel" + label and center it vertically so the descenders don't clip. */ +#hwfit-cache-select { + min-width: 58px; + height: 28px; + position: relative; + top: -3px; + display: inline-flex; + align-items: center; + justify-content: center; + line-height: 1; +} + +/* Command preview */ +.cookbook-cmd-preview { + margin: 4px 0 0; + padding: 8px 10px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + font-family: 'Fira Code', monospace; + font-size: 11px; + white-space: pre-wrap; + word-break: break-all; + color: var(--fg); +} + +/* Buttons — match admin-btn-sm */ +.cookbook-actions { display: flex; gap: 6px; margin-top: 2px; } +.cookbook-btn { + padding: 4px 10px; + min-width: 54px; + text-align: center; + font-size: 11px; + font-family: inherit; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); + cursor: pointer; + transition: all 0.15s; +} +.cookbook-btn:hover { + background: var(--border); + border-color: var(--accent); +} +.cookbook-btn:disabled { opacity: 0.5; cursor: not-allowed; } +.cookbook-run-btn { + background: var(--accent); + color: var(--panel); + border-color: var(--accent); + font-weight: 600; +} +.cookbook-run-btn:hover { opacity: 0.85; } +.cookbook-stop-btn { + background: var(--color-error); + border-color: var(--color-error); +} + +/* Output */ +.cookbook-output-wrap { + position: relative; + margin: 0; +} +.cookbook-output-kill { + position: absolute; + top: 4px; + right: 34px; + width: 22px; + height: 22px; + padding: 0; + background: none; + border: 1px solid transparent; + border-radius: 4px; + color: var(--fg-muted); + font-size: 16px; + line-height: 1; + cursor: pointer; + opacity: 0; + transition: opacity .15s, color .15s; + display: flex; + align-items: center; + justify-content: center; +} +.cookbook-output-kill:hover { color: var(--color-error, var(--warn)); border-color: var(--border); } +.cookbook-output-wrap .copy-code { + position: absolute; + top: 6px; + right: 6px; +} +.cookbook-output-wrap:hover .copy-code { opacity: 0.7; } +.cookbook-output-pre { + margin: 0; + padding: 8px 10px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + font-family: 'Fira Code', monospace; + font-size: 11px; + white-space: pre-wrap; + word-break: break-all; + max-height: 180px; + overflow-y: auto; +} +.cookbook-output-error { color: var(--color-error); } + +/* Downloaded/cached models */ +.hwfit-cached-item { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + padding: 6px 8px; + margin: 2px 0; + border-radius: 6px; + border: 1px solid var(--border); + background: color-mix(in srgb, var(--fg) 3%, transparent); + cursor: default; + font-size: 12px; +} +.hwfit-cached-item .hwfit-serve-panel { + flex-basis: 100%; + margin-top: 4px; +} +.hwfit-cached-item:hover { + background: color-mix(in srgb, var(--fg) 6%, transparent); +} +.hwfit-cached-header { + font-size: 10px; + opacity: 0.4; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + border: none !important; + background: none !important; + padding: 2px 8px; + cursor: default !important; +} +.hwfit-cached-header:hover { background: none !important; } +.hwfit-cached-name { + font-weight: 600; + flex-shrink: 0; +} +.hwfit-cached-repo { + flex: 1; + color: var(--fg-muted); + font-size: 11px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hwfit-cached-size, +.hwfit-cached-files { + font-size: 10px; + color: var(--fg-muted); + white-space: nowrap; +} +.hwfit-cached-serve, +.hwfit-cached-delete { + flex-shrink: 0; +} +.hwfit-cached-delete { + opacity: 0.4; + font-size: 10px; +} +.hwfit-cached-delete:hover { + opacity: 1; + color: var(--color-error, var(--warn)); +} +.hwfit-cached-badge { + font-size: 9px; + padding: 1px 6px; + border-radius: 3px; + font-weight: 600; + white-space: nowrap; +} +.hwfit-cached-ready { + background: color-mix(in srgb, var(--color-success, #4caf50) 20%, transparent); + color: var(--color-success, #4caf50); +} +.hwfit-cached-dl { + background: color-mix(in srgb, var(--color-warning, #f0ad4e) 20%, transparent); + color: var(--color-warning, #f0ad4e); +} + +/* Cookbook notification dot */ +#tool-cookbook-btn { + position: relative; +} +#rail-cookbook { + position: relative; +} +.cookbook-open-loading { + display: inline-flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + flex-shrink: 0; + margin-left: 2px; + color: var(--accent, var(--red)); + opacity: 0.9; +} +.cookbook-open-loading .ai-spinner, +.cookbook-open-loading .ai-spinner-whirlpool { + display: inline-flex !important; + width: 14px; + height: 14px; + margin: 0 !important; + gap: 0 !important; +} +.cookbook-open-loading canvas { + display: block; +} +.cookbook-section-loading-wp, +.cookbook-section-loading-wp canvas { + width: 22px !important; + height: 22px !important; + display: block !important; +} +#rail-cookbook .cookbook-open-loading { + position: absolute; + right: 2px; + top: -2px; + width: 12px; + height: 12px; + margin-left: 0; +} +/* The serve-list 'downloading' / 'download stalled' status text sits a hair + low against the rest of the meta line on mobile — nudge it up 2px. */ +@media (max-width: 768px) { + .cookbook-dl-status { + position: relative; + top: -2px; + } +} +.cookbook-notif-dot { + width: 6px; + height: 6px; + flex-shrink: 0; + border-radius: 50%; + background: var(--color-success, #4caf50); + /* Drives the breathing glow in the keyframes (matches email's dot). */ + --notif-glow: var(--color-success, #4caf50); + animation: cookbook-notif-pulse 2s ease-in-out infinite; +} +.cookbook-notif-dot.cookbook-notif-error { + background: var(--color-error, #f44); + --notif-glow: var(--color-error, #f44); + position: relative; + left: -2px; + top: -1px; +} +.rail-notify-error { color: var(--color-error, #f44) !important; } +.cookbook-tab-error-dot { + display: inline-block; width: 5px; height: 5px; border-radius: 50%; + background: var(--color-error, #f44); margin-left: 4px; vertical-align: middle; + animation: cookbook-notif-pulse 2s ease-in-out infinite; +} +@keyframes cookbook-notif-pulse { + 0%, 100% { + opacity: 1; + box-shadow: 0 0 0 0 color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 60%, transparent); + } + 50% { + opacity: 0.85; + box-shadow: 0 0 6px 2px color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 55%, transparent); + } +} +.cookbook-clear-btn { + font-size: 10px !important; + padding: 0 8px !important; + height: 22px !important; + border-radius: 6px !important; + border: 1px solid var(--border) !important; + color: color-mix(in srgb, var(--fg) 40%, transparent) !important; + opacity: 1 !important; + background: none !important; +} +.cookbook-clear-btn:hover { + color: var(--red) !important; + border-color: var(--red) !important; + background: color-mix(in srgb, var(--red) 8%, transparent) !important; +} +.cookbook-bulk-bar { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 8px; + margin-bottom: 4px; + background: color-mix(in srgb, var(--red) 6%, transparent); + border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); + border-radius: 6px; + font-size: 11px; +} +.cookbook-bulk-bar.hidden { display: none; } +.serve-select-cb { + width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; cursor: pointer; + background: var(--border); transition: background 0.15s; + align-self: center; + position: relative; + top: -2px; +} +#serve-bulk-bar { + position: relative; + top: -6px; +} +#serve-bulk-bar #serve-bulk-cancel { + top: -2px !important; + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 28px; + height: 24px; + line-height: 1; +} +#serve-bulk-bar #serve-bulk-cancel svg { + top: 0; +} +.serve-select-cb.selected { background: var(--red); } +.serve-select-cb:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } +#hwfit-cache-select.active { + background: var(--red); + color: #fff; + border-color: var(--red); +} +.cookbook-serve-dirs { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} +.cookbook-serve-dir-pill { + font-size: 10px; + font-family: 'Fira Code', monospace; + padding: 2px 8px; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); + color: var(--fg-muted); + letter-spacing: 0.2px; +} +/* Status pills shown inline in a Serve-tab card title (next to the model + name) when the model has a live serve / download task. Shared base + class so "running" and "downloading" sit on the same row with the + same chrome; only color varies. */ +.cookbook-serve-running-pill, +.cookbook-serve-downloading-pill { + display: inline-block; + margin-left: 6px; + padding: 1px 7px; + border-radius: 10px; + font-size: 9px; + font-weight: 500; + text-transform: lowercase; + letter-spacing: 0.3px; + vertical-align: 2px; + position: relative; + top: 1px; /* nudged down 2px from the old -1px so it sits + flush with the cap-height of the title text */ +} +.cookbook-serve-running-pill { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); +} +.cookbook-serve-downloading-pill { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); + opacity: 0.85; +} +.cookbook-serve-downloading-pill.is-stalled { + /* Stalled downloads stay visible but read as warning, not progress. */ + color: var(--fg-muted, #888); + background: color-mix(in srgb, var(--fg-muted, #888) 10%, transparent); + border-color: color-mix(in srgb, var(--fg-muted, #888) 30%, transparent); + opacity: 1; +} +.cookbook-serve-running-pill.is-clickable { + cursor: pointer; + transition: background 0.12s, border-color 0.12s; +} +.cookbook-serve-running-pill.is-clickable:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); +} + +.cookbook-serve-dir-edit { + font-size: 9px; + color: var(--fg-muted); + opacity: 0.4; + cursor: pointer; + text-decoration: underline; + text-underline-offset: 2px; +} +.cookbook-serve-dir-edit:hover { + opacity: 0.7; + color: var(--accent, var(--red)); +} +.cookbook-gpu-group { + display: flex; + gap: 3px; + margin-top: -4px; +} +.cookbook-gpu-btn { + width: 30px; height: 30px; + font-size: 12px; + font-family: 'Fira Code', monospace; + border: 1px solid var(--border); + border-radius: 4px; + background: none; + color: var(--fg-muted); + cursor: pointer; + opacity: 0.4; + transition: all 0.15s; + padding: 0; + display: flex; + align-items: center; + justify-content: center; +} +.cookbook-gpu-btn:hover { + opacity: 0.7; + border-color: var(--fg-muted); +} +.cookbook-gpu-btn.active { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent); + border-color: color-mix(in srgb, var(--fg) 55%, transparent); + color: var(--fg); + font-weight: 600; +} +/* Probe annotation states (set by "Free?" button) */ +.cookbook-gpu-btn.gpu-free { + opacity: 1; + border-color: #4ade80; + color: #4ade80; +} +.cookbook-gpu-btn.gpu-busy { + opacity: 0.85; + border-color: color-mix(in srgb, var(--red) 70%, transparent); + color: var(--red); + background: color-mix(in srgb, var(--red) 8%, transparent); +} +.cookbook-gpu-btn.gpu-missing { + opacity: 0.2; + text-decoration: line-through; + cursor: not-allowed; +} +/* "Clear Server" lives in the actions row next to "Probe GPUs". + Inherits .cookbook-btn sizing; this rule just adds the destructive + red tint on hover so the "this kills processes" cue stays. */ +.cookbook-gpu-clear:hover:not(:disabled) { + color: var(--red); + border-color: color-mix(in srgb, var(--red) 60%, var(--border)); + background: color-mix(in srgb, var(--red) 8%, transparent); +} +.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } +.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } +/* GPU probe popup — per-GPU process list with kill buttons */ +.cookbook-gpu-popup { + /* Fixed positioning (relative to viewport) so we never get pulled into + a scrolling/transform stacking context from an ancestor. Z-index has + to clear the cookbook modal (260) and the rest of the high-z UI + layers (themed-confirm and various overlays sit around 9000-10000). */ + position: fixed; + z-index: 10010; + min-width: 280px; + max-width: 420px; + background: var(--panel, #1a1a1a); + border: 1px solid var(--border); + border-radius: 6px; + box-shadow: 0 8px 24px rgba(0,0,0,0.4); + font-family: 'Fira Code', monospace; + font-size: 11px; + color: var(--fg); +} +.cookbook-gpu-popup-head { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + border-bottom: 1px solid var(--border); + font-weight: 600; +} +.cookbook-gpu-popup-stats { + flex: 1; + font-weight: 400; + color: var(--fg-muted); + font-size: 10px; +} +.cookbook-gpu-popup-close { + background: none; + border: none; + color: var(--fg-muted); + cursor: pointer; + font-size: 14px; + padding: 0 4px; +} +.cookbook-gpu-popup-close:hover { color: var(--fg); } +.cookbook-gpu-popup-body { + padding: 4px 0; + max-height: 320px; + overflow-y: auto; +} +.cookbook-gpu-popup-empty { + padding: 10px; + color: var(--fg-muted); + font-style: italic; +} +.cookbook-gpu-proc { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 10px; +} +.cookbook-gpu-proc:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); } +.cookbook-gpu-proc-info { + flex: 1; + display: flex; + gap: 8px; + align-items: center; + min-width: 0; +} +.cookbook-gpu-proc-pid { + color: var(--fg-muted); + width: 56px; + flex-shrink: 0; +} +.cookbook-gpu-proc-name { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.cookbook-gpu-proc-mem { + color: var(--fg-muted); + flex-shrink: 0; +} +.cookbook-gpu-proc-actions { + display: flex; + gap: 4px; + flex-shrink: 0; +} +.cookbook-gpu-kill { + border: 1px solid color-mix(in srgb, var(--red) 60%, transparent); + background: none; + color: var(--red); + border-radius: 3px; + padding: 2px 8px; + cursor: pointer; + font-family: inherit; + font-size: 10px; +} +.cookbook-gpu-kill[data-sig="KILL"] { + background: color-mix(in srgb, var(--red) 12%, transparent); +} +.cookbook-gpu-kill:hover:not(:disabled) { + background: color-mix(in srgb, var(--red) 20%, transparent); +} +.cookbook-gpu-kill:disabled { opacity: 0.4; cursor: wait; } +.cookbook-serve-title { + display: flex; + align-items: center; + gap: 4px; + min-width: 0; +} +.cookbook-serve-title-name { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.cookbook-hf-link { + font-size: 9px; + text-decoration: none; + color: var(--fg-muted); + opacity: 0.5; + padding: 1px 5px; + border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); + border-radius: 3px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + vertical-align: 1px; + letter-spacing: 0.3px; + font-weight: 600; + flex-shrink: 0; +} +.cookbook-hf-link:hover { + opacity: 0.8; + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); +} +/* In the Trending models list the HF pill is the row's main affordance — + tint it accent so it stands out next to the model name. */ +#cookbook-hf-latest-list .cookbook-hf-link { + color: var(--accent, var(--red)); + opacity: 0.85; + border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); +} + +/* Running tab sections */ +.cookbook-saved-section, +.cookbook-serve-section, +.cookbook-dl-section { + border: 1px solid var(--border); + border-radius: 6px; + margin-bottom: 6px; + overflow: hidden; +} +/* Divider between server-group sections in the Active tab so the + transition between e.g. local + remote-host server blocks reads + visually instead of running into each other. */ +.cookbook-group[data-backend-group="Running"] > [class*="cookbook-server-section-"] + [class*="cookbook-server-section-"] { + margin-top: 14px; + padding-top: 14px; + border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); +} +.cookbook-section-header { + display: flex; + align-items: center; + gap: 6px; + padding: 7px 10px; + cursor: pointer; + user-select: none; + /* Persistent surface + border so it reads as a clickable bar instead of + blending into the panel background. */ + background: color-mix(in srgb, var(--fg) 5%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + transition: background 0.15s, border-color 0.15s; +} +.cookbook-section-header:hover { + background: color-mix(in srgb, var(--fg) 9%, transparent); + border-color: color-mix(in srgb, var(--fg) 22%, transparent); +} +.cookbook-section-header.has-server-color { + color: var(--fg); + border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 45%, var(--border)); + background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 4%, var(--bg)); + box-shadow: inset 6px 0 0 var(--cookbook-server-accent, var(--cookbook-server-color)); +} +.cookbook-section-header.has-server-color:hover { + background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 7%, var(--bg)); + border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 60%, var(--border)); +} +.cookbook-section-header.has-server-color .cookbook-section-title, +.cookbook-section-header.has-server-color .cookbook-section-chevron { + color: var(--cookbook-server-accent, var(--cookbook-server-color)); +} +.cookbook-section-header.has-server-color .cookbook-srv-status.ok { + background: var(--cookbook-server-accent, var(--cookbook-server-color)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 18%, transparent), + 0 0 8px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 55%, transparent); +} +.cookbook-section-header .cookbook-clear-btn { + margin-left: 0; + position: relative; + top: -3px; +} +@media (max-width: 768px) { + .cookbook-section-header .cookbook-clear-btn { + top: -3px; + } + .cookbook-task-check { + top: 4px; + } +} +/* "Stop all" sits just left of "Clear finished"; it carries the auto margin so + the pair is pushed together to the right of the section title. */ +.cookbook-section-header .cookbook-stop-all-btn { + margin-left: auto; + margin-right: 6px; + position: relative; + top: -3px; +} +.cookbook-stop-all-btn { + font-size: 10px !important; + padding: 0 8px !important; + height: 22px !important; + border-radius: 6px !important; + border: 1px solid var(--border) !important; + color: color-mix(in srgb, var(--fg) 40%, transparent) !important; + opacity: 1 !important; + background: none !important; +} +.cookbook-stop-all-btn:hover { + color: var(--warn, #f0ad4e) !important; + border-color: var(--warn, #f0ad4e) !important; + background: color-mix(in srgb, var(--warn, #f0ad4e) 8%, transparent) !important; +} +.cookbook-section-chevron { + flex-shrink: 0; + opacity: 0.6; + transition: transform 0.15s ease, opacity 0.15s ease; +} +.cookbook-section-body { + padding: 0; +} +.cookbook-saved-toggle-header { + font-size: 10px; + font-weight: 600; + color: var(--fg-muted); + text-transform: uppercase; + letter-spacing: 0.3px; + margin-bottom: 4px; +} +.cookbook-saved-toggle:hover { color: var(--fg); } +.cookbook-saved-item { + margin: 2px 0; + border-radius: 4px; + font-size: 11px; +} +.cookbook-saved-header { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 6px; + border-radius: 4px; + transition: background 0.08s; +} +.cookbook-saved-header:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.cookbook-saved-item .hwfit-serve-panel { + margin: 4px 0 4px 6px; +} +.cookbook-saved-host { + font-size: 10px; + color: var(--fg-muted); + font-family: 'Fira Code', monospace; +} +.cookbook-saved-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.cookbook-saved-launch { + font-size: 10px; + padding: 2px 8px; +} +.cookbook-saved-del { + background: none; + border: none; + color: var(--fg-muted); + opacity: 0.3; + cursor: pointer; + font-size: 11px; + padding: 0 2px; +} +.cookbook-saved-del:hover { + opacity: 1; + color: var(--color-error, var(--warn)); +} + +/* Serve config panel */ +.hwfit-serve-panel { + margin-top: 6px; + padding: 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + flex: 1; + display: flex; + flex-direction: column; + position: relative; +} +.cookbook-serve-slots { + display: flex; + gap: 3px; + justify-content: flex-end; + margin-bottom: 4px; +} +/* When the Save split sits inside Row 1 (next to GPUs), align it with the + input baseline (the row's grid cells stretch top-down; without this the + Save buttons sit above the GPU button group). */ +.hwfit-serve-row .cookbook-serve-slots { + align-self: end; + margin-bottom: 4px; +} +.cookbook-slot-btn { + min-width: 22px; height: 28px; + padding: 0 6px; + font-size: 10px; font-weight: 600; + border: 1px solid var(--border); + border-radius: 4px; + background: none; + white-space: nowrap; + max-width: 80px; + overflow: hidden; + text-overflow: ellipsis; + color: var(--fg-muted); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + opacity: 0.5; + transition: all 0.15s; +} +.cookbook-slot-btn:hover { opacity: 0.9; border-color: var(--fg-muted); } +/* Saved-configs split button: "Save" + dropdown-arrow joined into one control, + but each segment keeps its own style — Save uses the filled accent + "Done"-button look; the arrow stays a subtle outlined menu trigger. */ +.cookbook-saved-split { gap: 0; } +.cookbook-saved-save, +.cookbook-saved-arrow { max-width: none; } +.cookbook-saved-save { + padding: 0 10px; + gap: 4px; + background: var(--bg); + color: var(--fg); + border-color: var(--border); + font-weight: 600; + opacity: 1; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.cookbook-saved-save:hover { + background: var(--border); + border-color: var(--accent); + opacity: 1; +} +.cookbook-saved-arrow { + padding: 0 6px; + background: var(--bg); + color: var(--fg); + border-color: var(--border); + opacity: 1; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: none; +} +.cookbook-saved-menu .cookbook-saved-favorite { + border-left: 3px solid var(--red); + background: color-mix(in srgb, var(--red) 4%, transparent); +} +.cookbook-saved-fav-btn { + width: 20px; + height: 20px; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + background: none; + color: var(--fg-muted); + opacity: 0.55; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +.cookbook-saved-fav-btn:hover, +.cookbook-saved-fav-btn.active { + color: var(--red); + opacity: 1; + background: color-mix(in srgb, var(--red) 10%, transparent); +} +.cookbook-saved-fav-badge { + flex-shrink: 0; + background: transparent; + padding: 0; +} +.cookbook-serve-favorite-model { + border-left-color: var(--red) !important; + background: color-mix(in srgb, var(--red) 4%, transparent); +} +.cookbook-serve-fav-badge { + display: inline-flex; + align-items: center; + vertical-align: 1px; + margin-left: 5px; + margin-right: 2px; + background: transparent; + padding: 0; +} +.cookbook-slot-saved { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); } +.cookbook-slot-saved:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); } +.cookbook-slot-btn.active { opacity: 1; background: var(--accent); color: #fff; border-color: var(--accent); +} +.cookbook-slot-wrap { + position: relative; + display: inline-flex; +} +.cookbook-slot-del { + position: absolute; + top: -5px; + right: -5px; + width: 14px; + height: 14px; + padding: 0; + border: 1px solid var(--border); + border-radius: 50%; + background: var(--bg, #1a1a1a); + color: var(--fg-muted); + font-size: 12px; + line-height: 1; + cursor: pointer; + opacity: 0; + transition: opacity 0.15s, color 0.15s, border-color 0.15s; + display: flex; + align-items: center; + justify-content: center; +} +.cookbook-slot-del:hover { + color: #f44; + border-color: #f44; +} +.cookbook-card-backend { display: none; } +/* Dependencies row — aligned tags */ +.cookbook-dep-row { + display: flex; + align-items: center; + gap: 6px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel); +} +/* Let the deps list fill the available window height instead of being + capped at the default .doclib-grid 400-px box. */ +[data-backend-group="Dependencies"] #cookbook-deps-list { + flex: 1; + max-height: none; +} + +/* Settings tab: stack the HF token + Servers cards as distinct blocks + (matches the Download tab's block-per-section layout). */ +.cookbook-settings-stack { + display: flex; + flex-direction: column; + gap: 10px; + flex: 1; + /* Scroll the whole settings panel so the Servers card can grow to hold every + server (it used to be a cramped internal scroll box that clipped them). */ + overflow-y: auto; + min-height: 0; +} +.cookbook-settings-stack.hidden { display: none; } +.cookbook-dep-row.cookbook-dep-blocked { opacity: 0.4; } +.cookbook-dep-info { flex: 1; min-width: 0; } +.cookbook-dep-section { + display: flex; + align-items: baseline; + gap: 8px; + margin: 12px 2px 4px; +} +.cookbook-dep-section-title { + font-size: 11px; + font-weight: 600; + letter-spacing: 0.03em; +} +.cookbook-dep-section-note { + font-size: 10px; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.cookbook-dep-subgroup { + margin: 6px 0 8px 12px; + padding: 7px 8px 8px; + border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); + background: color-mix(in srgb, var(--fg) 3%, transparent); + border-radius: 0 6px 6px 0; + display: flex; + flex-direction: column; + gap: 6px; +} +.cookbook-dep-subgroup-title { + display: flex; + align-items: baseline; + gap: 7px; + margin: 0 1px 1px; + font-size: 10.5px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 78%, transparent); +} +.cookbook-dep-subgroup-title em { + font-size: 9.5px; + font-style: normal; + font-weight: 500; + color: color-mix(in srgb, var(--fg) 48%, transparent); +} +.cookbook-dep-tag { + font-size: 9px; + padding: 0 8px; + border-radius: 4px; + white-space: nowrap; + text-align: center; + min-width: 62px; + box-sizing: border-box; + line-height: 1; + /* Explicit height so the tags (Install / Installed) are the EXACT + same height as the sibling tags — Firefox gives buttons a taller + native box otherwise. Mirrors the server-row tag rule (height:24px). */ + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; +} +.cookbook-dep-target { + border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.cookbook-dep-cat { + background: color-mix(in srgb, var(--fg) 10%, transparent); + color: color-mix(in srgb, var(--fg) 60%, transparent); +} +/* Rebuild tag — same look as the LLM category tag, sits to its left. */ +.cookbook-dep-rebuild { + background: color-mix(in srgb, var(--fg) 10%, transparent); + color: color-mix(in srgb, var(--fg) 75%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent); + cursor: pointer; + font-family: inherit; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + position: relative; + top: -2px; +} +.cookbook-dep-reinstall { + top: -3px; +} +.cookbook-dep-rebuild:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); +} +.cookbook-dep-installed { + background: color-mix(in srgb, var(--green, #50fa7b) 18%, transparent); + color: var(--green, #50fa7b); + border: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); + /* Match Install + Installed ▾ so all three variants align in mixed rows. */ + width: 87.7px; + min-width: 87.7px; + padding: 0 10px; + box-sizing: border-box; +} +.cookbook-dep-na { + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); + cursor: help; + /* Match other dep tag widths so N/A rows line up with Install / Installed. */ + min-width: 75.85px; + padding: 0 10px; + box-sizing: border-box; +} +.cookbook-dep-install { + background: var(--accent, var(--red)); + color: #fff; + border: none; + cursor: pointer; + font-family: inherit; + font-weight: 500; + position: relative; + top: -3px; + width: 87.7px; + min-width: 87.7px; + padding: 0 10px; + /* Strip the native button box so it's the same height as the sibling tags + (Firefox renders taller otherwise); height comes from .cookbook-dep-tag. */ + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; +} +.cookbook-dep-build-deps { + width: auto; + min-width: 87.7px; + max-width: 100%; + white-space: normal; + overflow-wrap: anywhere; + line-height: 1.15; + text-align: center; + height: auto; + min-height: 24px; +} +/* The install state is the terminal control in a dependency row. Keep it on + the far edge even when optional rebuild/category controls are present. */ +.cookbook-dep-row > .cookbook-dep-installed, +.cookbook-dep-row > .cookbook-dep-na, +.cookbook-dep-row > .cookbook-dep-install:not(.cookbook-dep-build-deps) { + margin-left: auto; +} +/* Center the "?" glyph inside the help chip. Without text-align it sits 0.5px + left of true center because of the character's natural baseline offset. */ +.hwfit-help-chip { + text-align: center; + padding-left: 0.5px; +} +.cookbook-dep-install:hover { opacity: 0.85; } +/* Installed split button: "Installed" label + separator + ▾ caret; clicking it + opens the actions menu (Update). Replaces the old ⋮ button. */ +.cookbook-dep-installed-btn { + padding: 0; + width: 87.7px; + min-width: 87.7px; + cursor: pointer; + font-family: inherit; + overflow: hidden; + position: relative; + top: -3px; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; +} +.cookbook-dep-installed-btn .cookbook-dep-caret { + display: inline-flex; + align-items: center; + align-self: stretch; + padding: 0 5px; + font-size: 14px; + line-height: 1; + position: relative; + top: 1px; + left: 4px; + opacity: 0.85; + border-left: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); +} +.hwfit-serve-row { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 6px; + margin-bottom: 6px; +} +/* Tighter min on the core-knobs row only so Dtype / TP / GPU Mem / Max + Seqs fit on one line without crushing Row 1's GPU button strip or + the Backend picker. */ +.hwfit-serve-row-core { + grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); +} +/* Save / saved-config split aligned to the right end of Row 1 and + nudged up 4px so it sits flush with the rest of the row. */ +.hwfit-serve-row .cookbook-serve-slots { + justify-self: end; + position: relative; + top: -4px; +} +/* The Settings label wraps the Save split — right-align its title text + so the word "Settings" sits above the right-aligned Save button + instead of floating off to the left of its grid cell. 8px right + padding now (16px → 8px) per follow-up tweak, brings the title back + over the actual Save button instead of overshooting it. */ +.hwfit-serve-row label:has(> .cookbook-serve-slots) { + grid-column: -2 / -1; + justify-self: end; + text-align: right; + padding-right: 0; +} +.hwfit-serve-row label:has(> .cookbook-serve-slots) > span { + display: inline-block; + position: relative; + left: -33px; +} +.hwfit-serve-topline { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; + margin: 0 0 6px; +} +.hwfit-serve-topline .hwfit-serve-runtime-text { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.hwfit-serve-preset-row { + display: flex; + justify-content: flex-end; + margin: 0; + flex: 0 0 auto; + position: relative; + top: -6px; +} +.hwfit-serve-row label { + font-size: 10px; + color: var(--fg-muted); + white-space: nowrap; + letter-spacing: 0.3px; +} +.hwfit-serve-row-core .hwfit-context-label { + grid-column: span 2; + width: auto; + min-width: 0; + max-width: none; + justify-self: start; +} +.hwfit-context-control { + display: block; + position: relative; + align-items: center; + margin-top: 2px; + width: 100px; +} +.hwfit-context-control .hwfit-sf[data-field="ctx"] { + min-width: 0; + width: 100%; + padding-right: 40px; +} +.hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]) { + position: relative; + left: -66px; +} +.hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { + position: relative; + left: -64px; +} +.hwfit-serve-row-core .hwfit-gpus-label { + position: relative; + left: -70px; +} +.hwfit-context-calc-btn { + position: absolute; + right: 3px; + top: -4px; + width: 34px; + height: 28px; + min-width: 34px; + padding: 0; + font-size: 10px; + line-height: 1; + display: inline-flex; + align-items: center; + justify-content: center; + text-align: center; + overflow: hidden; + white-space: nowrap; +} +.hwfit-context-calc-btn .spinner-whirlpool { + width: 12px !important; + height: 12px !important; + margin: 0 auto !important; +} +.hwfit-context-calc-btn .ai-spinner-whirlpool { + width: 12px !important; + height: 12px !important; +} +@media (min-width: 769px) { + [data-llama-mode-toggle].mode-toggle.mode-toggle-three .mode-toggle-btn > span { + top: -6px !important; + } + .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { + font-size: 0; + } + .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { + content: 'Unif'; + font-size: 11px; + } +} +@media (max-width: 768px) { + .hwfit-serve-row-core .hwfit-context-label { + display: flex; + flex-direction: column; + align-items: flex-start; + } + .hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]), + .hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { + left: 0; + } + details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_env_preset"]), + details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="extra_env"]) { + grid-column: 1 / -1 !important; + } + .hwfit-context-control { + display: inline-flex; + align-items: center; + gap: 4px; + width: auto; + } + .hwfit-context-control .hwfit-sf[data-field="ctx"] { + width: 100px; + padding-right: 6px; + } + .hwfit-context-calc-btn { + position: relative; + top: -2px; + left: 2px; + right: auto; + flex: 0 0 34px; + width: 34px; + min-width: 34px; + } +} +.hwfit-auto-ctx-note { + display: block; + margin-top: 3px; + font-size: 9px; + opacity: 0.6; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* Ctx slider — ported from origin/main. Sits in the Scan/Download toolbar + next to the quant dropdown. Drives _ctxValue() in cookbook-hwfit.js. */ +.hwfit-ctx-control { + height: 28px; + min-width: 134px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + gap: 5px; + padding: 0 7px; + border: 1px solid var(--border); + border-radius: 4px; + background: var(--bg); + font-size: 11px; /* slightly smaller than .cookbook-field-input so Context reads quieter next to Engine/Quant */ +} +.hwfit-ctx-control span { + /* Match Quant/Engine select label style: no uppercase, no letter-spacing. */ + text-transform: none; + letter-spacing: 0; + opacity: 0.9; +} +.hwfit-ctx-control > span:first-child { + font-size: 9.5px; +} +/* Editor-style slider (same look as the gallery editor sliders): thin pill + rail that fattens on interaction, circular red thumb that grows on hover. */ +.hwfit-ctx-control input[type="range"] { + width: 64px; + min-width: 64px; + height: 4px; + padding: 0; + border: 0; + -webkit-appearance: none; + appearance: none; + /* Hard-coded grey so the rail is GUARANTEED visible regardless of theme — + every theme-derived color we tried (--fg-muted, --border, accent-bg mix) + kept blending into the panel background on at least one theme. */ + background: rgba(150, 150, 150, 0.65); + border-radius: 999px; + accent-color: var(--red); + cursor: pointer; + transition: height 0.15s ease, background 0.15s ease; +} +.hwfit-ctx-control input[type="range"]:hover, +.hwfit-ctx-control input[type="range"]:focus, +.hwfit-ctx-control input[type="range"]:active { + background: var(--fg); +} +.hwfit-ctx-control input[type="range"]:hover, +.hwfit-ctx-control input[type="range"]:focus, +.hwfit-ctx-control input[type="range"]:active { + height: 10px; +} +.hwfit-ctx-control input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--red); + border: none; + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.hwfit-ctx-control input[type="range"]::-moz-range-thumb { + width: 12px; + height: 12px; + border-radius: 50%; + background: var(--red); + border: none; + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.hwfit-ctx-control input[type="range"]:hover::-webkit-slider-thumb, +.hwfit-ctx-control input[type="range"]:focus::-webkit-slider-thumb, +.hwfit-ctx-control input[type="range"]:active::-webkit-slider-thumb { + width: 18px; + height: 18px; +} +.hwfit-ctx-control input[type="range"]:hover::-moz-range-thumb, +.hwfit-ctx-control input[type="range"]:focus::-moz-range-thumb, +.hwfit-ctx-control input[type="range"]:active::-moz-range-thumb { + width: 18px; + height: 18px; +} +.hwfit-ctx-control output { + min-width: 28px; + text-align: right; + color: var(--fg); + font-weight: 600; +} +.hwfit-sf { + background: var(--bg); + border: 1px solid var(--border); + border-radius: 4px; + color: var(--fg); + font-family: inherit; + font-size: 12px; + padding: 0 6px; + height: 28px; + box-sizing: border-box; +} +.hwfit-sf[data-field="backend"], +.hwfit-sf[data-field="dtype"], +.hwfit-sf[data-field="tp"] { + height: 32px; + box-sizing: border-box; + width: 100%; +} +.hwfit-sf:focus { + border-color: var(--accent, var(--red)); + outline: none; +} +.hwfit-sf[type="text"] { width: 100%; } +.hwfit-sf.hwfit-sf-wide { width: 100%; } +.hwfit-serve-checks { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 6px; +} +.hwfit-sf-cb { + display: flex; + flex-direction: row; + align-items: center; + gap: 6px; + font-size: 11px; + color: var(--fg-muted); + cursor: pointer; + padding: 3px 0; +} .hwfit-sf-cb:hover { color: var(--fg); } /* Speculative method + tokens controls — render inline beside the checkbox */ .hwfit-spec-group .hwfit-spec-method, @@ -2697,3315 +4322,3 @@ font-size: 11px; } } - -/* Snapped/narrow Settings window: move the tab rail to the top. Viewport media - alone misses desktop right-half snapping, where the modal is narrow but the - browser window is not. */ -@container settings-modal (max-width: 620px) { - .settings-layout { - flex-direction: column; - } - .settings-sidebar { - width: auto; - max-height: none; - flex-direction: row; - border-right: none; - border-bottom: 1px solid var(--border); - overflow-x: auto; - overflow-y: hidden; - padding: 6px; - scrollbar-width: none; - } - .settings-sidebar::-webkit-scrollbar { - display: none; - } - .settings-sidebar-divider, - .settings-sidebar-label { - display: none; - } - .settings-nav-item { - flex: 0 0 auto; - padding: 6px 10px; - font-size: 11px; - } - .settings-panels { - padding: 12px 14px; - } -} - -/* ── Entrance Animations ── */ - -/* Welcome name — left-to-right wipe with a touch of horizontal stretch. - Fires on initial render via the .welcome-name rule; restarts on Nobody⇄ - Odysseus toggle via JS reflow trick. */ -.welcome-name { - transform-origin: left center; - animation: welcome-name-reveal 0.55s cubic-bezier(0.34, 1.32, 0.55, 1) both; -} -/* Hold the welcome-screen entrance animations until the app has finished its - initial load (fonts loaded + layout settled). Running them mid-load made the - splash jump/flicker as the page reflowed ("haywire"). Until body.welcome-ready - is set by JS, the splash sits in its final, static state (no flash, since the - non-animated state IS the resting state); adding the class then plays the - entrance once, cleanly. */ -body:not(.welcome-ready) #welcome-screen, -body:not(.welcome-ready) .welcome-name { - animation: none !important; -} -/* Hold the splash invisible (the entrance's start state) until ready, so when - the animation is released it fades/wipes in smoothly instead of flashing - from fully-visible to the animation's hidden first frame. */ -body:not(.welcome-ready) #welcome-screen { - opacity: 0; -} -@keyframes welcome-name-reveal { - from { - clip-path: inset(0 100% 0 0); - transform: scaleX(0.96); - opacity: 0.5; - } - to { - clip-path: inset(0 0 0 0); - transform: scaleX(1); - opacity: 1; - } -} - -@keyframes welcome-enter { - from { - opacity: 0; - transform: translate(-50%, -45%); - } - to { - opacity: 1; - transform: translate(-50%, -50%); - } -} - -@keyframes msg-enter { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes picker-roll-up { - from { - opacity: 0; - transform: scaleY(0.4) translateY(8px); - } - to { - opacity: 1; - transform: scaleY(1) translateY(0); - } -} -@keyframes picker-roll-down { - from { - opacity: 1; - transform: scaleY(1) translateY(0); - } - to { - opacity: 0; - transform: scaleY(0.4) translateY(8px); - } -} -@keyframes model-picker-refresh-spin { - to { transform: rotate(360deg); } -} - -@keyframes modal-enter { - from { - opacity: 0; - transform: scale(0.95) translateY(8px); - } - to { - opacity: 1; - transform: scale(1) translateY(0); - } -} -@keyframes modal-exit { - from { - opacity: 1; - transform: scale(1) translateY(0); - } - to { - opacity: 0; - transform: scale(0.97) translateY(6px); - } -} -.modal-content.modal-closing { - animation: modal-exit 0.18s ease-in both; -} - -#cookbook-modal .modal-content.cookbook-modal-entering { - animation: cookbook-modal-enter 0.28s cubic-bezier(0.22, 1.35, 0.36, 1) both; -} -/* Mobile: real bottom-sheet slide-up — the existing 12px translate is - too subtle on a phone, the modal effectively just appeared. */ -@media (max-width: 768px) { - #cookbook-modal .modal-content.cookbook-modal-entering { - animation: cookbook-modal-enter-mobile 0.32s cubic-bezier(0.22, 1, 0.36, 1) both; - } -} -#cookbook-modal .modal-content:not(.cookbook-modal-entering):not(.modal-closing) { - animation: none; -} -#cookbook-modal .modal-content.modal-closing { - animation: modal-exit 0.18s cubic-bezier(0.4, 0, 1, 1) both; -} - -/* Per-token streaming fade — new words materialize */ -.token-new { - animation: token-fade 0.4s ease-out both; -} - -@keyframes token-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -/* Disable entrance animations when loading chat history (bulk render) */ -.chat-history.no-animate .msg { - animation: none; -} - -/* Prefer reduced motion */ -@media (prefers-reduced-motion: reduce) { - .msg, - .modal-content, - #welcome-screen, - .toast, - .token-new { - animation: none !important; - transition: none !important; - } -} - -@media (min-width: 769px) { - #tool-tasks-btn #assistant-notif-dot.sidebar-notif-dot, - #tool-tasks-btn.task-failure-pending::after, - #tool-tasks-btn.task-completion-pending::after { - transform: translateX(-2px); - } -} -.tasks-modal-content { max-width: 600px; width: min(600px, 92vw); background: var(--bg); font-size: 12px; } -.task-completed-preview-row { - cursor: default; - margin-bottom: 6px; - padding: 8px 10px 10px; - max-height: none; - overflow: visible; -} -.task-completed-preview-head { - display: flex; - align-items: center; - gap: 7px; - min-height: 20px; - font-size: 11px; -} -.task-completed-chat-preview { - padding: 0 0 4px; -} -.task-completed-chat-preview .doclib-chat-preview-messages { - max-height: 220px; - margin-top: 7px; -} -.task-completed-chat-preview .doclib-chat-bubble { - max-width: 92%; -} -.task-completed-chat-preview .doclib-chat-preview-actions { - top: 2px; - margin-top: 4px; -} -.task-completed-preview-row .task-log-time { - margin-left: 8px; -} - -/* Tasks tabs reuse the .memory-tab look. The Brain window's tab bar is - full-bleed (its underline spans the whole modal width). The Tasks bar is a - direct child of .modal-content (padding:10px), so cancel that padding with - negative side margins to span edge-to-edge, then re-inset the tabs by 10px - so they line up with the rest of the modal content — matching the Brain bar. */ -.tasks-modal-content .tasks-tabs { - margin: -2px -10px 8px; - padding: 0 10px; -} - -/* Activity log — compact by default. Click the row to expand body+actions. */ -.task-log-row { - --cat-hue: 220; - border: 1px solid var(--border); - border-radius: 8px; - padding: 4px 10px; - margin-bottom: 3px; - background: color-mix(in srgb, var(--fg) 2%, transparent); - position: relative; - cursor: pointer; - transition: padding .12s ease; -} -.task-log-row:hover { - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.task-log-row.task-log-row-error { - border-color: color-mix(in srgb, var(--red, #f87171) 35%, var(--border)); - background: color-mix(in srgb, var(--red, #f87171) 4%, transparent); -} -.task-log-row.task-log-row-error:hover { - background: color-mix(in srgb, var(--red, #f87171) 7%, transparent); -} -.task-log-row.expanded { padding: 8px 10px 6px; } -.task-log-row-head { - display: flex; - align-items: center; - gap: 8px; - font-size: 11px; - margin-bottom: 0; -} -.task-log-row.expanded .task-log-row-head { margin-bottom: 4px; } -/* Collapsed: body + footer hidden. Expanded: visible. */ -.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-body, -.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-actions, -.task-log-row:not(.expanded):not(.is-skipped) .task-log-prompt { - display: none; -} -.task-log-name { - font-weight: 600; - /* Pull saturation from the per-row hue but mix with the foreground so the - title still reads in dark mode. Lightness stays adaptive. */ - color: hsl(var(--cat-hue) 60% 60%); -} -.task-log-failed-tag { - color: var(--red, #f87171); - font-size: 10px; - font-weight: 700; - line-height: 1; - white-space: nowrap; - margin-left: -2px; -} -.task-log-task-icon { - display: inline-flex; - align-items: center; - flex: 0 0 auto; - margin-right: -3px; -} -.task-log-row.task-log-row-error .task-log-name, -.task-log-row.task-log-row-error .task-log-task-icon { - color: var(--red, #f87171); -} -.task-log-task-icon svg { - top: 0 !important; - opacity: 0.46 !important; -} -.task-log-row-head .task-ai-mark { - top: 0; - margin-left: -4px; -} -.task-log-repeat { - font-size: 10px; - font-weight: 500; - line-height: 1; - color: color-mix(in srgb, var(--fg) 62%, transparent); - border-radius: 999px; - padding: 1px 0; - white-space: nowrap; -} -@media (prefers-color-scheme: light) { - .task-log-name { color: hsl(var(--cat-hue) 65% 38%); } -} -/* Per-account prefix in fan-out results — e.g. "[Default] No recent emails" - becomes a compact accent chip + plain message. Makes multi-account activity - rows readable instead of a bracket soup. */ -/* Running / queued status line in the Activity tab — whirlpool + label. */ -.task-log-running { - display: inline-flex; - align-items: center; - gap: 0; - font-size: 11px; - opacity: 0.75; -} -.task-log-running-label { font-style: normal; } - -/* New right-side placement: "Running " sits where the timestamp - normally would, on the head row's right edge. */ -.task-log-running-inline { - display: inline-flex; - align-items: center; - gap: 0; - font-size: 11px; - opacity: 0.75; -} -.task-log-force-run { - appearance: none; - box-sizing: border-box; - border: 0; - background: color-mix(in srgb, var(--fg) 7%, transparent); - box-shadow: none; - color: inherit; - opacity: .82; - margin-left: 7px; - padding: 1px 6px 1px 4px; - height: 18px; - min-height: 16px; - max-height: 18px; - border-radius: 999px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 3px; - font-family: inherit; - font-size: 10px; - line-height: 1; - cursor: pointer; - position: relative; - top: -3px; -} -.task-log-force-run svg, -.task-log-stop svg { - display: block; - flex-shrink: 0; - transform: translateY(1px); -} -.task-log-force-run:hover { - opacity: 1; - background: color-mix(in srgb, var(--green, #50fa7b) 16%, transparent); - color: var(--green, #50fa7b); -} -.task-log-stop { - border: 0; - background: transparent; - color: inherit; - opacity: .72; - padding: 0; - margin-left: 6px; - width: 12px; - height: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - position: relative; - top: -3px; -} -.task-log-stop:hover { - opacity: 1; - color: var(--red, #f87171); -} - -/* Slim single-line row for skipped (noop) runs — body/actions stripped, font - shrunk, opacity dropped. Distinguishes "task ran but had nothing to do" - from a "real" entry without flooding the feed visually. */ -.task-log-row.is-skipped { - padding: 4px 8px; - opacity: 0.45; - font-size: 11px; - background: transparent; -} -.task-log-row.is-skipped .task-log-row-head { padding: 0; } -.task-log-row.is-skipped .task-log-name { font-weight: 500; } -.task-log-row.is-skipped .task-log-skipped-reason { - flex: 1 1 auto; - margin-left: 6px; - font-style: italic; - opacity: 0.85; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.task-log-row.is-skipped:hover { opacity: 0.7; } - -.task-log-account-tag { - display: inline-block; - font-size: 10px; - font-weight: 600; - letter-spacing: 0.02em; - padding: 1px 7px; - margin-right: 4px; - border-radius: 10px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); - vertical-align: 1px; -} -.task-log-status { - width: 7px; - height: 7px; - border-radius: 50%; - flex-shrink: 0; - background: color-mix(in srgb, var(--fg) 30%, transparent); -} -.task-log-status-ok { background: #4ade80; box-shadow: 0 0 6px #4ade80, 0 0 3px #4ade80; } -.task-log-status-error { background: var(--red, #f87171); box-shadow: 0 0 6px var(--red, #f87171), 0 0 3px var(--red, #f87171); } -.task-log-status-info { background: color-mix(in srgb, var(--fg) 25%, transparent); } -.task-log-status-queued { background: #fbbf24; box-shadow: 0 0 0 2px color-mix(in srgb, #fbbf24 30%, transparent); } -.task-log-status-running { background: #60a5fa; animation: task-log-pulse 1.4s ease-in-out infinite; } -.task-log-status-skipped { background: color-mix(in srgb, var(--fg) 20%, transparent); } -.task-log-status-aborted { background: color-mix(in srgb, var(--fg) 30%, transparent); border: 1px dashed color-mix(in srgb, var(--fg) 50%, transparent); } -.task-log-row-body { - font-size: 12px; - line-height: 1.5; - color: color-mix(in srgb, var(--fg) 85%, transparent); - overflow-wrap: anywhere; - word-break: break-word; -} -.task-log-row-body p { margin: 0 0 6px 0; } -.task-log-row-body p:last-child { margin-bottom: 0; } -.task-log-row-body pre { - margin: 4px 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - border-radius: 4px; - white-space: pre-wrap; - word-break: break-word; - font-size: 11px; - max-height: 180px; - overflow: auto; -} -.task-log-row-body code { - font-size: 11px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - padding: 1px 4px; - border-radius: 3px; -} -.task-log-row-body ul, .task-log-row-body ol { - margin: 4px 0; - padding-left: 20px; -} -/* Markdown headings inside a task result — without these, ## headings - render at browser-default huge sizes and blow out the cramped row. */ -.task-log-row-body h1, -.task-log-row-body h2, -.task-log-row-body h3, -.task-log-row-body h4 { - margin: 10px 0 4px; - line-height: 1.3; - font-weight: 650; - color: var(--fg); -} -.task-log-row-body h1 { font-size: 14px; } -.task-log-row-body h2 { font-size: 13px; } -.task-log-row-body h3, -.task-log-row-body h4 { font-size: 12px; opacity: 0.9; } -.task-log-row-body h1:first-child, -.task-log-row-body h2:first-child, -.task-log-row-body h3:first-child { margin-top: 0; } -.task-log-row-body blockquote { - margin: 4px 0; - padding: 2px 0 2px 10px; - border-left: 2px solid color-mix(in srgb, var(--fg) 20%, transparent); - opacity: 0.85; -} -.task-log-row-body a { color: var(--accent, #60a5fa); text-decoration: none; } -.task-log-row-body a:hover { text-decoration: underline; } -.task-log-row-body strong { font-weight: 650; color: var(--fg); } -/* Email-summary tables — render compactly instead of default browser ugliness. */ -.task-log-row-body table { - border-collapse: collapse; - width: 100%; - margin: 6px 0; - font-size: 11px; -} -.task-log-row-body th, -.task-log-row-body td { - border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); - padding: 3px 6px; - text-align: left; - vertical-align: top; -} -.task-log-row-body th { - background: color-mix(in srgb, var(--fg) 5%, transparent); - font-weight: 600; -} -.task-log-row-actions { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-top: 4px; -} -.task-log-open-chat, -.task-log-open-report, -.task-log-copy, -.task-log-clear-cache, -.task-log-run-again, -.task-log-force-run-action { - display: inline-flex; - align-items: center; - gap: 3px; - background: none; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 5px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-family: inherit; - font-size: 10px; - padding: 1px 6px; - cursor: pointer; - transition: background .15s, color .15s, border-color .15s; - line-height: 1.4; -} -.task-log-open-chat:hover, -.task-log-open-report:hover, -.task-log-copy:hover, -.task-log-clear-cache:hover, -.task-log-run-again:hover, -.task-log-force-run-action:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--fg) 30%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.task-log-row-actions > .task-log-open-chat, -.task-log-row-actions > .task-log-copy { - margin-left: auto; -} -.task-log-clear-cache svg { - position: relative; - top: 0; -} -/* Activity filter chips — toggle-out model: ON by default (solid), - click to toggle OFF (dimmed + strikethrough) to hide that group. */ -.tasks-af-chip { - font-size: 11px; - padding: 3px 10px; - border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); - border-radius: 12px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: var(--fg); - cursor: pointer; - font-family: inherit; - transition: opacity .12s, background .12s, border-color .12s; -} -.tasks-af-chip:hover { border-color: color-mix(in srgb, var(--fg) 32%, transparent); } -.tasks-af-chip.off { - opacity: 0.4; - text-decoration: line-through; - background: transparent; -} -.tasks-af-chip.active { - border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - opacity: 1; -} -.tasks-af-chip-error { border-color: color-mix(in srgb, var(--red, #f87171) 45%, transparent); } -.tasks-af-chip-error:not(.off) { color: var(--red, #f87171); } - -/* "Open in Deep Research" is now a regular clickable chat link (a - `#research-` markdown anchor in the assistant's message), rendered as - an a.chat-link. Nudge it 4px right so it sits slightly inset. */ -a.chat-link[href^="#research-"] { - margin-left: 4px; -} -.task-log-row.is-long .task-log-row-body { - max-height: 8.5em; - overflow: hidden; - position: relative; - mask-image: linear-gradient(to bottom, #000 70%, transparent); - -webkit-mask-image: linear-gradient(to bottom, #000 70%, transparent); -} -.task-log-row.is-long.expanded .task-log-row-body { - max-height: none; - overflow: visible; - mask-image: none; - -webkit-mask-image: none; -} -.task-log-row-toggle { - margin-top: 6px; - background: none; - border: none; - font-family: inherit; - font-size: 11px; - cursor: pointer; - padding: 2px 0; - color: transparent; /* hide raw "Show more" text */ -} -.task-log-row:not(.expanded) .task-log-row-toggle::before { content: 'Show more'; } -.task-log-row.expanded .task-log-row-toggle::before { content: 'Show less'; } -.task-log-prompt { - margin-top: 6px; - font-size: 11px; -} -.task-log-prompt summary { - cursor: pointer; - opacity: 0.5; - user-select: none; -} -.task-log-prompt summary:hover { opacity: 0.8; } -.task-log-prompt pre { - margin: 4px 0 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-radius: 4px; - white-space: pre-wrap; - word-break: break-word; - font-size: 11px; - opacity: 0.75; - max-height: 200px; - overflow: auto; -} - -/* Tasks is the shared theme reference for procedural cards. Keep the tint - together so Skills cannot drift when theme colors change. Hover and - selection remain owned by the shared card state rules above. */ -.task-card, -.skill-card { - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -.task-card { - border: 1px solid var(--border); - border-radius: 8px; - padding: 7px 8px; - margin-bottom: 0; - min-height: 46px; - box-sizing: border-box; - align-items: center; -} -/* When expanded, height grows to fit the detail panel. */ -.task-card.expanded { align-items: flex-start; } -.task-card .memory-item-title { - position: relative; - top: -2px; -} -/* Always show the per-card ⋮ kebab on task cards (the default opacity:0 + - hover-reveal is unreliable on touch and easy to miss on desktop). - pointer-events / z-index belt-and-braces against any sibling overlay, - margin reset undoes the global `.modal-body button { margin-top:6px }` - that was punting the hit-target down. */ -.task-card .memory-item-actions { - opacity: 0.55; - pointer-events: auto; - position: relative; - z-index: 5; -} -.task-card:hover .memory-item-actions, -.task-card .memory-item-actions:hover { opacity: 1; } -.task-card .memory-item-actions .memory-item-btn { - margin: 0 !important; - pointer-events: auto; - position: relative; - z-index: 5; - width: 28px; - min-width: 28px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; -} -/* Make the SVG inside the kebab transparent to pointer events so the click - always lands on the BUTTON itself (some browsers / nested SVG events lose - the click when hitting the inner glyph). */ -.task-card .memory-item-actions .memory-item-btn svg { pointer-events: none; } -.task-ai-mark { - flex: 0 0 auto; - color: var(--accent, var(--red)); - opacity: 0.78; -} -/* Per-card select checkbox rides up to the title line. The "All" checkbox is - #tasks-select-all (not .memory-select-cb), so it stays put. */ -.task-card .memory-select-cb { position: relative; } -.task-card.task-card-deleting { - opacity: 0.68; - pointer-events: none; - position: relative; -} -.task-card-delete-busy { - position: absolute; - right: 11px; - top: 50%; - transform: translateY(-50%); - display: inline-flex; - align-items: center; - gap: 5px; - height: 22px; - padding: 0 8px; - border-radius: 999px; - color: var(--red); - background: color-mix(in srgb, var(--red) 12%, var(--panel, #111)); - border: 1px solid color-mix(in srgb, var(--red) 24%, transparent); - font-size: 10px; - font-weight: 600; - line-height: 1; - z-index: 7; -} -.task-card-delete-busy-spin { - width: 10px; - height: 10px; - border-radius: 999px; - border: 1.5px solid color-mix(in srgb, currentColor 28%, transparent); - border-top-color: currentColor; - animation: spin 0.8s linear infinite; -} -#tasks-pause-all-btn { position: relative; top: -2px; } -#tasks-pause-all-btn:has(input:disabled) { opacity: 0.4; } -/* Bigger ⋮ dropdown on mobile (its buttons carry inline styles → !important). */ -@media (max-width: 768px) { - .task-card .task-status-badge { - touch-action: manipulation; - -webkit-tap-highlight-color: transparent; - } - .task-card .task-state-badge svg, - .task-card .task-card-run-btn svg { - width: 10px; - height: 10px; - } -} - -.task-card-header { - display: flex; - align-items: center; - gap: 4px; - flex-wrap: wrap; -} - -.task-card-name { - font-size: 12px; - font-weight: 600; -} - -.task-card-schedule { - font-size: 10.5px; - opacity: 0.5; - margin-left: auto; -} - -.task-card-output { - font-size: 10px; - opacity: 0.45; -} - -.task-card-prompt { - font-size: 11px; - opacity: 0.55; - margin: 4px 0; - line-height: 1.4; -} - -.task-card-meta { - font-size: 10.5px; - opacity: 0.45; - margin-bottom: 6px; -} - -.task-card-actions { - display: flex; - gap: 4px; - flex-wrap: wrap; -} - -.task-btn { - font-family: inherit; - font-size: 11px; - padding: 4px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: none; - color: var(--fg); - cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s, background 0.15s; -} -.task-btn:hover { opacity: 1; } -.task-btn:disabled { opacity: 0.3; cursor: not-allowed; } - -.task-btn-primary { - background: var(--red); - color: #fff; - border-color: transparent; - opacity: 1; -} -.task-btn-primary:hover { opacity: 0.85; } - -.task-btn-danger { color: var(--red); border-color: color-mix(in srgb, var(--red) 30%, transparent); } -.task-btn-danger:hover { opacity: 1; border-color: var(--red); } - -.tasks-clock { - font-size: 10px; - opacity: 0.35; - text-align: center; - padding: 6px 0 8px; -} - -.task-preset-grid { - display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; -} -.task-preset-card { - display: flex; flex-direction: column; align-items: flex-start; gap: 2px; - padding: 12px; border: 1px solid var(--border); border-radius: 8px; - background: var(--bg); cursor: pointer; transition: all 0.15s; text-align: left; - color: var(--fg); -} -.task-preset-card:hover { - border-color: var(--accent, #cc6a3a); background: color-mix(in srgb, var(--accent, #cc6a3a) 8%, var(--bg)); -} -.task-preset-icon { font-size: 20px; margin-bottom: 2px; } -.task-preset-label { font-size: 12px; font-weight: 600; } -.task-preset-desc { font-size: 10px; opacity: 0.5; line-height: 1.3; } - -.task-form { display: flex; flex-direction: column; gap: 4px; } -.task-form-label { - font-size: 11px; - opacity: 0.55; - font-weight: 500; - display: block; - /* Consistent rhythm: 8px above each label (section gap), 4px below to the - field — same spacing for Name/Prompt/Trigger/Output/Model/Chain. */ - margin: 8px 0 4px; -} -.task-form-input { - font-family: inherit; - font-size: 11px; - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - transition: border-color 0.15s; - box-sizing: border-box; - width: 100%; - max-width: 100%; - min-width: 0; -} -.task-form-input:focus { - outline: none; - border-color: var(--red); -} -.task-form-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } - -.task-form-notif-toggle { - display: flex; - align-items: center; - gap: 8px; - margin: 9px 0 3px; - cursor: pointer; - color: var(--fg); -} -.task-form-notif-toggle input { - position: absolute; - opacity: 0; - pointer-events: none; -} -.task-form-notif-switch { - width: 30px; - height: 16px; - border-radius: 999px; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 8%, transparent); - position: relative; - flex: 0 0 auto; - transition: background 0.15s, border-color 0.15s; -} -.task-form-notif-toggle input:checked + .task-form-notif-switch { - border-color: color-mix(in srgb, var(--accent, var(--red)) 58%, transparent); - background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); -} -.task-form-notif-copy { - display: flex; - flex-direction: column; - gap: 1px; - font-size: 11px; - font-weight: 500; -} -.task-form-notif-copy span:last-child { - font-size: 10px; - font-weight: 400; - opacity: 0.52; -} - -.task-form-toggle { - display: flex; gap: 4px; -} -.task-toggle-btn { - flex: 1; padding: 4px 8px; font-size: 10px; font-family: inherit; - border: 1px solid var(--border); border-radius: 6px; background: transparent; - color: var(--fg); opacity: 0.4; cursor: pointer; transition: all 0.15s; -} -.task-toggle-btn:hover { opacity: 0.7; border-color: var(--fg); } -.task-toggle-btn.active { - opacity: 1; border-color: var(--red); color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); -} - -.task-time-picker, .task-date-picker { - display: flex; - align-items: center; - gap: 4px; -} -.task-time-select, .task-date-select { - width: auto; - min-width: 48px; - font-size: 11px; -} -.task-time-sep { opacity: 0.4; font-size: 11px; } - -.task-history-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 8px; -} - -.task-runs-list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; } -.task-run-item { - border: 1px solid var(--border); - border-radius: 6px; - padding: 8px 10px; -} -.task-run-item-header { - display: flex; - align-items: center; - gap: 4px; - font-size: 11px; -} -.task-run-time { margin-left: auto; font-size: 10px; opacity: 0.45; } -.task-run-result { - font-size: 11px; - opacity: 0.6; - margin-top: 4px; - line-height: 1.4; - white-space: pre-wrap; - word-break: break-word; -} - -/* ── Settings Form Utilities ── */ - -.settings-row { - display: flex; - align-items: center; - gap: 8px; -} -.settings-col { - display: flex; - flex-direction: column; - gap: 6px; -} -.settings-label { - font-size: 12px; - min-width: 70px; -} -.settings-select { - flex: 1; - /* Allow the select to shrink below its longest option's width inside a flex - row — without this a long model name (e.g. the Vision picker) overflowed - the card off-screen on mobile. */ - min-width: 0; - max-width: 100%; - padding: 5px 8px; - font-family: inherit; - font-size: 12px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - transition: border-color 0.15s; - appearance: none; - -webkit-appearance: none; - -moz-appearance: textfield; - outline: none; -} -input.settings-select::-webkit-outer-spin-button, -input.settings-select::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -input.settings-select::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } -.settings-select:focus { - outline: none; - border-color: var(--red); -} - -/* Deep Research uses long labels and previously fixed-width fields. Keep - every control constrained by the card at intermediate desktop widths too. */ -.admin-card:has(#set-researchSearch) .settings-row { min-width: 0; } -.admin-card:has(#set-researchSearch) .settings-row > .settings-label { flex: 0 0 112px; min-width: 0; } -#set-researchSearch, -#set-researchMaxTokens, -#set-researchRunTimeout, -.settings-row div:has(> #set-researchExtractTimeout), -.settings-row div:has(> #set-researchExtractConcurrency) { - width: auto !important; - max-width: 100% !important; - min-width: 0 !important; - flex: 1 1 0 !important; -} - -@media (max-width: 768px) { - /* Deep Research settings contain desktop-sized inline widths. On mobile, - let each control claim the card width instead of escaping the admin block. */ - .settings-row:has(#set-researchSearch), - .settings-row:has(#set-researchMaxTokens), - .settings-row:has(#set-researchExtractTimeout), - .settings-row:has(#set-researchExtractConcurrency), - .settings-row:has(#set-researchRunTimeout) { - flex-wrap: wrap; - align-items: stretch; - } - .settings-row:has(#set-researchSearch) .settings-label, - .settings-row:has(#set-researchMaxTokens) .settings-label, - .settings-row:has(#set-researchExtractTimeout) .settings-label, - .settings-row:has(#set-researchExtractConcurrency) .settings-label, - .settings-row:has(#set-researchRunTimeout) .settings-label { - flex: 1 1 auto; - } - #set-researchSearch, - #set-researchMaxTokens, - #set-researchRunTimeout, - .settings-row div:has(> #set-researchExtractTimeout), - .settings-row div:has(> #set-researchExtractConcurrency) { - width: 100% !important; - max-width: 100% !important; - flex: 1 1 100% !important; - margin-left: 0 !important; - } -} - -/* Default-chat fallback chain editor. Each row mirrors the primary - endpoint/model selectors, indented under them to read as a chain. */ -.settings-fallbacks { - display: flex; - flex-direction: column; - gap: 6px; -} -.settings-fallbacks:not(:empty) { margin-top: 2px; } -.settings-fallback-row { - display: flex; - align-items: center; - gap: 6px; - padding-left: 12px; - border-left: 2px solid color-mix(in srgb, var(--fg) 12%, transparent); - max-width: 100%; - box-sizing: border-box; -} -.settings-fallback-num { - font-size: 11px; - opacity: 0.4; - min-width: 14px; - text-align: right; -} -.settings-fallback-row .settings-select { - width: 0; - min-width: 0; - min-inline-size: 0; - box-sizing: border-box; - overflow: hidden; - text-overflow: ellipsis; -} -.settings-fallback-row .settings-select:first-of-type { - flex: 0 1 128px; - width: 128px; - max-width: 34%; -} -.settings-fallback-row .settings-select:nth-of-type(2) { - flex: 1 1 0; - width: 0; - max-width: 100%; -} -details.hwfit-serve-advanced > .hwfit-serve-row, -details.hwfit-serve-advanced > .hwfit-serve-checks, -details.hwfit-serve-advanced > .hwfit-serve-cmd-wrap, -details.hwfit-serve-advanced > .hwfit-serve-extra { - margin-top: 0; - margin-bottom: 0; -} -/* Inside Advanced, tighten the per-row grid gap and the label → - select gap so rows of single-line dropdowns don't visually sit - on top of half a row of empty space. Outside Advanced (Core, - etc.) the original ~6px breathing room stays. */ -details.hwfit-serve-advanced > .hwfit-serve-row { - gap: 4px; - row-gap: 4px; -} -details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp, -details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-llamacpp { - margin-top: -20px; - position: relative; - left: 4px; -} -details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp { - column-gap: 8px; - row-gap: 2px; -} -details.hwfit-serve-advanced > .hwfit-llama-placement-row { - margin-top: -6px !important; -} -details.hwfit-serve-advanced > .hwfit-llama-memory-row { - margin-top: -20px !important; -} -.cookbook-llama-cpu-mode details.hwfit-serve-advanced > .hwfit-llama-memory-row { - margin-top: -8px !important; -} -details.hwfit-serve-advanced > .hwfit-llama-batch-row, -details.hwfit-serve-advanced > .hwfit-llama-monitor-row, -details.hwfit-serve-advanced > .hwfit-llama-checks-row { - margin-top: -18px !important; -} -details.hwfit-serve-advanced > .hwfit-diff-settings-row { - row-gap: 2px; -} -details.hwfit-serve-advanced > .hwfit-diff-checks-row, -details.hwfit-serve-advanced > .hwfit-diff-harmonize-row { - margin-top: -10px !important; -} -details.hwfit-serve-advanced > .hwfit-diff-checks-row { - row-gap: 2px; -} -details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-vram-monitor { - gap: 4px !important; - grid-template-columns: max-content minmax(0, 1fr); - white-space: nowrap; -} -details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_block_size"] { - width: calc(100% - 6px); -} -details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_kv_cache_dtype"] { - width: 60px; - min-width: 60px; -} -details.hwfit-serve-advanced > .hwfit-serve-checks { - gap: 4px; - row-gap: 4px; -} -/* Pull the vLLM/SGLang checks row, Extra args, and the trailing - model-specific (Speculative) checks row up tight against the row - above — the previous 4px gap plus per-row baseline padding left a - ~8px gap that read as too airy in the Advanced fold. */ -details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-vllm, -details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-sglang, -details.hwfit-serve-advanced > .hwfit-serve-extra { - margin-top: -8px; -} -details.hwfit-serve-advanced > .hwfit-serve-extra { - margin-top: -18px; -} -details.hwfit-serve-advanced > .hwfit-serve-row:last-of-type, -details.hwfit-serve-advanced > .hwfit-serve-checks:last-of-type { - margin-bottom: 0; -} - -.settings-fallback-remove { - flex-shrink: 0; - width: 32px; - height: 32px; - display: inline-flex; - align-items: center; - justify-content: center; - border: 1px solid var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 55%, transparent); - cursor: pointer; - transition: border-color 0.12s, color 0.12s, background 0.12s; -} -.settings-fallback-remove:hover { - border-color: var(--red); - color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); -} -.settings-fallback-add { - align-self: flex-start; - margin-top: 2px; - font-size: 11px; - padding: 3px 9px; - border: 1px dashed var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 65%, transparent); - cursor: pointer; - transition: border-color 0.12s, color 0.12s; -} -.settings-fallback-add:hover { - border-color: var(--red); - color: var(--red); -} -.settings-input { - flex: 1; - padding: 5px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-family: inherit; - font-size: 12px; - transition: border-color 0.15s; -} -.settings-input:focus { - outline: none; - border-color: var(--red); -} -/* ── Contacts manager (Settings → Integrations → CardDAV) ── */ -.contacts-add-row { - display: flex; - gap: 6px; - margin-bottom: 8px; - align-items: center; -} -.contacts-list { - display: flex; - flex-direction: column; - gap: 2px; - max-height: 360px; - overflow-y: auto; -} -.contact-row { - padding: 6px 4px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 35%, transparent); -} -.contact-row:last-child { border-bottom: none; } -.contact-row-edit { margin-top: 4px; } - -/* ── Sort Dropdown ── */ - -.sort-dropdown { - position: absolute; - right: 0; - top: 100%; - z-index: 1000; - min-width: 120px !important; - width: max-content; - padding: 4px !important; - margin-top: 4px; - background: var(--panel) !important; - border: 1px solid var(--border) !important; - border-radius: 8px !important; - box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; - backdrop-filter: none !important; -} -.sort-dropdown-item { - cursor: pointer; - padding: 6px 8px 6px 6px !important; - font-size: 11px !important; - border-radius: 6px !important; - border-bottom: none !important; - white-space: nowrap; - transition: background 0.1s; -} -.sort-dropdown-item:hover { - background: color-mix(in srgb, var(--accent) 10%, transparent) !important; -} -.sort-dropdown-item.ai-sort-option { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); -} -.sort-dropdown-item.ai-sort-option .auto-sort-icon { - color: var(--accent, var(--red)); - display: inline-flex; - align-items: center; -} -#auto-sort-sessions-btn { - transform: translateX(-4px); -} -.sort-dropdown-item.ai-sort-option:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent) !important; -} -.sort-dropdown-sep { - border-top: 1px solid var(--border); - margin-top: 2px; - padding-top: 6px; -} - -/* Mobile: bigger taps for the chats sort/funnel menu and the select-mode - bulk-action bar. Also drop the "Rearrange" item — it's a touch-finicky - feature better left to desktop. */ -@media (max-width: 768px) { - /* Nudge the funnel/sort button right on mobile so it doesn't hug the - chevron next to it. */ - #session-sort-btn { transform: translateX(13px); } - #session-sort-dropdown.sort-dropdown { - min-width: 200px !important; - padding: 6px !important; - } - #session-sort-dropdown .sort-dropdown-item { - padding: 12px 14px !important; - font-size: 14px !important; - } - #session-rearrange-toggle { display: none !important; } - - /* Select-mode bar — make Archive / Delete / Cancel buttons + the - Select-all toggle finger-sized. */ - .session-bulk-bar { - padding: 8px 10px; - font-size: 14px; - gap: 10px; - } - .session-bulk-btn { - padding: 10px; - min-width: 44px; - min-height: 44px; - } - .session-bulk-btn svg { width: 20px; height: 20px; } - #session-select-all-dot { font-size: 22px !important; padding: 6px; } - #session-select-all-label { font-size: 14px !important; padding: 4px; } -} - - -/* ── Admin: Built-in tools ── */ -.admin-tool-category { - border: 1px solid var(--border); - border-radius: 6px; - margin-bottom: 6px; - overflow: hidden; - transition: border-color 0.15s; -} -.admin-tool-category:hover { - border-color: color-mix(in srgb, var(--fg) 20%, var(--border)); -} -.admin-tool-cat-header { - font-size: 12px; - font-weight: 500; - padding: 8px 10px; - transition: background 0.15s; -} -.admin-tool-cat-header:hover { - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.admin-tool-cat-body { - border-top: 1px solid var(--border); - padding: 4px 0; -} -.admin-tool-cat-body.hidden { - display: none; -} -.admin-tool-row { - display: flex; - align-items: center; - gap: 8px; - padding: 5px 10px; - padding-right: 28px; /* align toggles with header toggle (chevron 12px + gap 6px + padding) */ - transition: background 0.08s; -} -.admin-tool-row:hover { - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.admin-tool-info { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 1px; -} -.admin-tool-name { - font-size: 11px; - font-weight: 500; -} -.admin-tool-desc { - font-size: 10px; - opacity: 0.4; -} -.admin-tool-ctx { - font-size: 9px; - opacity: 0.3; - flex-shrink: 0; - min-width: 35px; - text-align: right; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; -} - -/* Cookbook serve param hints */ -.hwfit-hint { - display: inline-flex; - align-items: center; - justify-content: center; - width: 12px; - height: 12px; - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - font-size: 8px; - font-weight: 600; - opacity: 0.35; - cursor: help; - margin-left: 2px; - vertical-align: middle; - transition: opacity 0.1s; -} -.hwfit-hint:hover { - opacity: 0.8; - background: color-mix(in srgb, var(--accent) 15%, transparent); -} -.hwfit-dl-dot { - color: var(--green); - font-size: 8px; - margin-left: 4px; - opacity: 0.7; -} -.hwfit-parser-tag { - font-size: 9px; - opacity: 0.4; - background: color-mix(in srgb, var(--fg) 8%, transparent); - padding: 1px 5px; - border-radius: 3px; - margin-left: 4px; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; -} - -/* Cookbook output copy — show only on hover */ -.cookbook-output-wrap .cookbook-output-copy { - opacity: 0; - transition: opacity 0.1s; -} - -/* Cached model menu button */ -.hwfit-cached-menu-btn { - background: none; - border: none; - color: var(--fg); - font-size: 16px; - cursor: pointer; - opacity: 0; - padding: 2px 4px; - border-radius: 4px; - transition: opacity 0.1s; - flex-shrink: 0; - position: relative; - top: -2px; -} -.memory-item:hover .hwfit-cached-menu-btn, -.hwfit-cached-item:hover .hwfit-cached-menu-btn { opacity: 0.4; } -.hwfit-cached-menu-btn:hover { opacity: 1 !important; } -.hwfit-cached-menu-btn svg { pointer-events: none; } -@media (max-width: 768px) { - #cookbook-modal .hwfit-cached-menu-btn { - opacity: 0.72 !important; - width: 32px; - height: 32px; - min-width: 32px; - padding: 0; - justify-content: center; - top: -4px; - } - #cookbook-modal .hwfit-cached-menu-btn:active { - opacity: 1 !important; - background: color-mix(in srgb, var(--fg) 9%, transparent); - } -} - -/* Quick run button */ -.cookbook-run-btn { - background: var(--accent, var(--red)) !important; - color: var(--panel) !important; - border-color: var(--accent, var(--red)) !important; - font-weight: 600; -} -.cookbook-run-btn:hover { - opacity: 0.9; -} -#hwfit-cache-scan { - position: relative; - top: 1px; - width: 26px; - height: 24px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -#hwfit-cache-scan.spinning svg { - animation: modelPickerRefreshSpin 0.75s linear infinite; -} -#serve-search { - height: 28px; - min-height: 28px; - font-family: inherit; - font-size: 11px; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: 9px center; - padding-left: 28px; -} -.cookbook-sort-select-wrap { - position: relative; - display: inline-flex; - flex-shrink: 0; - align-items: center; -} -.cookbook-sort-select-wrap > svg { - position: absolute; - left: 9px; - z-index: 1; - pointer-events: none; - opacity: 0.65; - transform: translateY(4px); -} - -/* Cookbook model directory tags */ -.cookbook-modeldir-tag { - font-size: 11px; - padding: 3px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - display: inline-flex; - align-items: center; - gap: 4px; - font-family: 'Berkeley Mono', 'SF Mono', monospace; -} -.cookbook-modeldir-default { - opacity: 0.5; -} -.cookbook-server-bottom-actions { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 4px; - margin-top: 5px; -} -.cookbook-server-bottom-actions > button { - height: 22px; - box-sizing: border-box; - display: inline-flex; - align-items: center; -} -/* Default-server radio-check in a Settings server title (same look as the - model-dir download target). Active = accent-tinted. */ -.cookbook-srv-default { - cursor: pointer; - opacity: 0.35; - display: inline-flex; - align-items: center; - gap: 3px; - line-height: 1; - transition: opacity 0.12s, color 0.12s; -} -.cookbook-srv-default:hover { opacity: 0.8; } -.cookbook-srv-default.active { opacity: 1; color: var(--accent, var(--red)); } -.cookbook-srv-default-label { font-size: 10px; font-weight: 600; letter-spacing: 0.02em; } -.cookbook-srv-default-icon { - display: inline-flex; - align-items: center; - line-height: 0; -} -.cookbook-srv-default-icon svg { - width: 13px; - height: 13px; -} -.cookbook-srv-default-icon, -.cookbook-srv-default-label { - position: relative; - top: 2px; -} -.email-settings-default, -.email-settings-default .cookbook-srv-default-icon, -.email-settings-default .cookbook-srv-default-label { - position: relative; - top: -2px; -} -/* Download-target toggle inside a model-dir tag */ -.cookbook-modeldir-dl { - cursor: pointer; - opacity: 0.35; - display: inline-flex; - align-items: center; - line-height: 0; - transition: opacity 0.12s, color 0.12s; -} -.cookbook-modeldir-dl:hover { opacity: 0.8; } -.cookbook-modeldir-dl.active { opacity: 1; color: var(--accent, var(--red)); } -/* The tag currently flagged as the download target — clearly highlighted so - it's obvious where downloads land. */ -.cookbook-modeldir-target { - opacity: 1; - color: var(--accent, var(--red)); - font-weight: 600; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); -} -.cookbook-modeldir-rm { - cursor: pointer; - opacity: 0.4; - font-size: 10px; -} -.cookbook-modeldir-rm:hover { - opacity: 1; -} -.cookbook-modeldir-add { - background: none; - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - font-size: 10px; - font-weight: 500; - height: 19.5px; - box-sizing: border-box; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 2px; - position: relative; - top: -3px; - padding: 0 9px; - transition: border-color 0.12s, background 0.12s; -} -.cookbook-modeldir-add:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); } - -/* Cookbook serve optimizations */ -.hwfit-serve-opts { - display: flex; - align-items: center; - gap: 8px; - margin: 4px 0; -} -.hwfit-apply-opts { - font-size: 11px !important; - min-width: auto !important; - white-space: nowrap; -} -.hwfit-opts-desc { - font-size: 9px; - opacity: 0.4; - font-style: italic; -} - -/* Library modal tabs */ -.lib-tabs, -.admin-tabs { - display: flex; - gap: 0; - margin-bottom: 8px; - border-bottom: 1px solid var(--border); -} -.lib-tab, -.admin-tab { - background: none; - border: none; - border-bottom: 2px solid transparent; - color: var(--fg-muted); - font-size: 12px; - font-family: inherit; - padding: 6px 14px; - cursor: pointer; - transition: color 0.1s, border-color 0.1s; -} -.lib-tab:hover, -.admin-tab:hover { color: var(--fg); } -.lib-tab.active, -.admin-tab.active { - color: var(--accent, var(--red)); - border-bottom-color: var(--accent, var(--red)); -} - -/* Cookbook tab count badge */ -.cookbook-tab-count { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 16px; - height: 16px; - padding: 0 4px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); - color: var(--accent, var(--red)); - font-size: 9px; - font-weight: 700; - margin-left: 4px; - line-height: 1; -} - -/* ── Generate Visual Report button (deep research) ── */ -.view-report-btn { - display: inline-flex; - align-items: center; - gap: 6px; - margin-top: 12px; - padding: 8px 16px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 10%, transparent); - color: var(--fg); - font-family: inherit; - font-size: 0.88em; - font-weight: 500; - transition: background 0.15s ease; - cursor: pointer; -} -.view-report-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--red) 18%, transparent); -} -.view-report-btn svg { - flex-shrink: 0; -} -.report-btn-wrap { - margin-top: 12px; - display: flex; - flex-wrap: wrap; - gap: 8px; -} -.view-report-btn.chat-about-btn { - border-color: color-mix(in srgb, var(--fg) 22%, transparent); - background: color-mix(in srgb, var(--fg) 6%, transparent); -} -.view-report-btn.chat-about-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--fg) 12%, transparent); -} -.view-report-btn:disabled { - opacity: 0.6; - cursor: progress; -} -.report-spinner { - display: flex; - align-items: center; - gap: 6px; - font-size: 0.88em; - color: var(--fg); - opacity: 0.7; -} -.report-spinner-text { - font-weight: 500; -} -.report-spinner-dots::after { - content: ''; - animation: report-dots 1.4s steps(4, end) infinite; -} -@keyframes report-dots { - 0% { content: ''; } - 25% { content: '.'; } - 50% { content: '..'; } - 75% { content: '...'; } - 100% { content: ''; } -} - -/* ── Continue research hint ────────────────────────── */ -.continue-research-wrap { - margin-top: 8px; -} -.continue-research-hint { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 0.78em; - color: var(--fg-dim, var(--fg)); - opacity: 0.5; - padding: 4px 0; -} -.continue-research-hint svg { - flex-shrink: 0; - opacity: 0.6; -} - -/* ── Research reconnect & timer ────────────────────── */ -.msg.research-reconnect { - border-left: 3px solid var(--red); - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.research-timer { - font-size: 0.8em; - opacity: 0.6; - margin-top: 4px; - font-family: monospace; - color: var(--fg-dim, var(--fg)); -} - -/* ── Research synapse visualization ─────────────────────────────── - Live SVG graph of an in-flight deep-research run. The query is the - central node; sub-questions branch off per round; sources are leaf - nodes that pop in as they're captured. Pulses indicate activity. */ -.research-synapse { - margin: 6px 0 4px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 24%, var(--border)); - border-radius: 7px; - background: - linear-gradient(color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), - linear-gradient(90deg, color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), - radial-gradient(ellipse at 50% 48%, color-mix(in srgb, var(--accent, var(--red)) 13%, transparent) 0%, transparent 62%), - color-mix(in srgb, var(--panel) 68%, var(--bg)); - background-size: 18px 18px, 18px 18px, auto, auto; - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--fg) 5%, transparent); - overflow: hidden; -} -.research-synapse .rs-stage { - height: 400px; - position: relative; -} -.research-synapse .rs-meta-header { - position: absolute; - z-index: 4; - top: 0; - left: 0; - right: 0; - border-top: 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - padding: 5px 8px 6px; - background: color-mix(in srgb, var(--panel) 76%, transparent); -} -.research-synapse-ascii-filter > .rs-stage > svg { - display: none; -} -.research-synapse-ascii-filter .rs-ascii-overlay { - position: absolute; - z-index: 3; - inset: 28px 10px 10px; - margin: 0; - overflow: hidden; - pointer-events: none; - color: color-mix(in srgb, var(--accent, var(--red)) 76%, var(--fg)); - font: 600 10px/1.35 ui-monospace, SFMono-Regular, Consolas, monospace; - white-space: pre; - text-align: center; - opacity: 1; - text-shadow: 0 0 7px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); - mix-blend-mode: screen; -} -.research-synapse-compact .rs-meta { padding: 5px 8px 6px; font-size: 10px; } -.research-synapse-compact .rs-label-sub { font-size: 8px; } -.research-synapse .rs-stage-head { - position: absolute; - z-index: 2; - top: 7px; - left: 9px; - right: 9px; - display: flex; - align-items: center; - gap: 5px; - color: var(--fg-dim, var(--fg)); - font: 600 8px/1 ui-monospace, "JetBrains Mono", monospace; - text-transform: uppercase; - pointer-events: none; -} -.research-synapse .rs-live-dot { - width: 5px; - height: 5px; - border-radius: 50%; - background: var(--accent, var(--red)); - box-shadow: 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 75%, transparent); - animation: rs-live 1.8s ease-in-out infinite; -} -.research-synapse .rs-phase-chip { - margin-left: auto; - padding: 3px 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, var(--border)); - border-radius: 3px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, var(--bg)); - color: var(--accent, var(--red)); -} -.research-synapse svg { - display: block; - width: 100%; - height: 100%; -} -.research-synapse .rs-edge { - stroke: color-mix(in srgb, var(--accent, var(--red)) 32%, var(--border)); - stroke-width: 1.35; - fill: none; - opacity: 0.72; -} -.research-synapse .rs-node { - fill: var(--bg); - stroke: var(--accent, var(--red)); - stroke-width: 1.5; - transition: all 0.3s ease; - transform-box: fill-box; - transform-origin: center; -} -.research-synapse .rs-source-index { - fill: var(--fg); - font: 650 4.5px/1 ui-monospace, "JetBrains Mono", monospace; - pointer-events: none; - opacity: 0.88; -} -.research-synapse .rs-node-expanded { animation: rs-hub-expand 0.75s ease-out; } -.research-synapse .rs-node-new { - animation: rs-pop 0.6s ease-out; -} -.research-synapse .rs-pulse { - fill: var(--accent, var(--red)); - opacity: 0; - animation: rs-pulse 2.6s ease-out infinite; - transform-box: fill-box; - transform-origin: center; -} -.research-synapse .rs-label { - fill: var(--fg); - font-size: 10px; - font-family: ui-monospace, "JetBrains Mono", monospace; - pointer-events: none; - opacity: 0.85; -} -.research-synapse .rs-label-sub { - font-size: 9px; - opacity: 0.7; -} -.research-synapse .rs-meta { - display: grid; - grid-template-columns: minmax(0, 1fr) auto auto auto; - align-items: center; - gap: 10px; - padding: 6px 10px 8px; - font-family: ui-monospace, "JetBrains Mono", monospace; - font-size: 11px; - color: var(--fg-dim, var(--fg)); - opacity: 0.92; - border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - background: color-mix(in srgb, var(--panel) 76%, transparent); -} -.research-synapse .rs-meta .rs-status { - display: none; - color: var(--accent, var(--red)); - font-weight: 600; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.research-synapse .rs-meta b { - color: var(--fg); - font-weight: 600; -} -.research-synapse.rs-complete .rs-meta .rs-status { color: var(--color-success, #4caf50); } -.research-synapse.rs-error .rs-meta .rs-status { color: var(--red); } -.research-synapse.rs-phase-reading .rs-phase-chip, -.research-synapse.rs-phase-analyzing .rs-phase-chip { color: var(--gold, #d4a72c); } -.research-synapse.rs-phase-writing .rs-phase-chip { color: var(--color-success, #4caf50); } - -@media (prefers-reduced-motion: reduce) { - .research-synapse .rs-live-dot, - .research-synapse .rs-pulse, - .research-synapse .rs-node-new, - .research-synapse .rs-node-expanded, - .research-synapse .rs-edge-firing { animation: none !important; } -} - -/* ── Raw findings items (inside sources-style box) ── */ -.finding-item { - display: flex; - flex-direction: column; - gap: 4px; -} -.finding-item .source-link { - border-radius: 6px 6px 0 0; -} -.finding-summary { - padding: 6px 8px 8px; - font-size: 0.82em; - line-height: 1.5; - color: var(--fg-dim, var(--fg)); - opacity: 0.85; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); - margin-bottom: 4px; - white-space: pre-wrap; - word-break: break-word; -} -.finding-item:last-child .finding-summary { - border-bottom: none; - margin-bottom: 0; -} - -/* ═══════════════════════════════════════════════════════════ - Gallery Editor - ═══════════════════════════════════════════════════════════ */ - -/* Tab bar */ -.gallery-tabs { - display: flex; - gap: 0; - border-bottom: 1px solid var(--border); - padding: 0 16px; - background: var(--panel); -} -.gallery-tab { - padding: 8px 18px; - background: none; - border: none; - border-bottom: 2px solid transparent; - color: var(--fg); - opacity: 0.6; - cursor: pointer; - font-size: 13px; - font-weight: 500; - transition: opacity 0.15s, border-color 0.15s; -} -.gallery-tab:hover { opacity: 0.85; } -.gallery-tab.active { - opacity: 1; - border-bottom-color: var(--red); -} - -/* Narrow modal tab strips should stay on one row. Resized docked windows can - be much narrower than the viewport, so this cannot live only in mobile media - queries. */ -.cookbook-tabs, -.memory-tabs, -.admin-tabs, -.lib-tabs, -.gallery-tabs, -.preset-tabs { - flex-wrap: nowrap !important; - overflow-x: auto !important; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - overscroll-behavior-x: contain; - scrollbar-width: none; -} -.cookbook-tabs::-webkit-scrollbar, -.memory-tabs::-webkit-scrollbar, -.admin-tabs::-webkit-scrollbar, -.lib-tabs::-webkit-scrollbar, -.gallery-tabs::-webkit-scrollbar, -.preset-tabs::-webkit-scrollbar { - display: none; -} -.cookbook-tabs > *, -.memory-tabs > *, -.admin-tabs > *, -.lib-tabs > *, -.gallery-tabs > *, -.preset-tabs > * { - flex: 0 0 auto; -} -.cookbook-tab, -.memory-tab, -.admin-tab, -.lib-tab, -.gallery-tab, -.preset-tab { - display: inline-flex; - align-items: center; - justify-content: center; - white-space: nowrap; - line-height: 1; -} -.gallery-tab { - gap: 6px; -} - -@container (max-width: 360px) { - .cookbook-tab:has(svg), - .memory-tab:has(svg), - .admin-tab:has(svg), - .lib-tab:has(svg), - .gallery-tab:has(svg), - .preset-tab:has(svg) { - width: 34px; - min-width: 34px; - padding-left: 0; - padding-right: 0; - font-size: 0; - } - - .cookbook-tab:has(svg) svg, - .memory-tab:has(svg) svg, - .admin-tab:has(svg) svg, - .lib-tab:has(svg) svg, - .gallery-tab:has(svg) svg, - .preset-tab:has(svg) svg { - width: 14px; - height: 14px; - margin-right: 0 !important; - vertical-align: middle !important; - } - - .memory-tab:has(svg) .memory-count, - .gallery-tab:has(svg) .gallery-tab-label, - .gallery-tab:has(svg) .gallery-tab-close, - .cookbook-tab:has(svg) .cookbook-tab-count, - .preset-tab:has(svg) .preset-count { - display: none !important; - } -} -/* Icon + label layout inside each tab. */ -.gallery-tab { - display: inline-flex; - align-items: center; - gap: 6px; -} -.gallery-tab-icon { - display: inline-flex; - align-items: center; - opacity: 0.85; -} -.gallery-tab.active .gallery-tab-icon { opacity: 1; } -/* Close × on the Edit tab — appears on hover. */ -.gallery-tab-close { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - margin-left: 2px; - border-radius: 4px; - opacity: 0; - transition: opacity 0.12s, background 0.12s; - /* Use a slightly larger glyph at a larger line-height so the × sits - visually centered — the U+00D7 character has uneven em-box metrics. */ - font-size: 16px; - line-height: 16px; - font-weight: 500; - cursor: pointer; - padding-bottom: 2px; - box-sizing: border-box; -} -.gallery-tab:hover .gallery-tab-close, -.gallery-tab.active .gallery-tab-close { opacity: 0.65; } -/* Hide the close × entirely on the Edit tab when there's no edit open. */ -.gallery-tab[data-tab="editor"]:not(.has-edit) .gallery-tab-close { display: none !important; } -.gallery-tab-close:hover { opacity: 1 !important; background: color-mix(in srgb, var(--red) 25%, transparent); color: var(--red); } -/* Inline rename input shown when the Edit tab is double-clicked. */ -.gallery-tab-rename-input { - background: var(--bg); - border: 1px solid var(--red); - border-radius: 3px; - color: var(--fg); - font: inherit; - font-size: 13px; - padding: 1px 4px; - width: 140px; - outline: none; -} - -/* Albums tab — grid of album cards with cover thumbnails. */ -.gallery-albums-container { - padding: 12px 4px; - max-height: calc(92vh - 120px); - overflow-y: auto; - border: 2px dashed transparent; - border-radius: 8px; - transition: border-color 0.15s, background 0.15s; -} -.gallery-settings-container { - padding: 8px 4px; - max-height: calc(92vh - 120px); - overflow-y: auto; -} -.gallery-albums-container.gallery-dragover { - border-color: var(--red); - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.gallery-albums-empty { - display: flex; flex-direction: column; align-items: center; gap: 12px; - padding: 48px 16px; - opacity: 0.7; - font-size: 13px; -} -.gallery-albums-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: 12px; -} -.gallery-album-card { - position: relative; - display: flex; - flex-direction: column; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - overflow: hidden; - cursor: pointer; - transition: transform 0.12s, border-color 0.12s, box-shadow 0.12s; -} -.gallery-album-card:hover { - transform: translateY(-2px); - border-color: var(--red); - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 12%, transparent); -} -.gallery-album-menu-btn { - position: absolute; - top: 6px; right: 6px; - width: 24px; height: 24px; - display: flex; align-items: center; justify-content: center; - background: color-mix(in srgb, var(--bg) 70%, transparent); - color: var(--fg); - border: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - border-radius: 6px; - cursor: pointer; - opacity: 0; - transition: opacity 0.12s, background 0.12s; - z-index: 2; - backdrop-filter: blur(4px); - /* Plain "…" text — the previous SVG was rendering as a flat black - block on some themes when its currentColor didn't contrast. */ - font-size: 16px; - line-height: 1; - font-weight: 600; - padding: 0 0 4px; -} -.gallery-album-card:hover .gallery-album-menu-btn, -.gallery-album-menu-btn:focus-visible { opacity: 1; } -.gallery-album-menu-btn:hover { background: var(--bg); } -.gallery-album-menu-pop { - position: absolute; - top: 34px; right: 6px; - min-width: 140px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); - display: flex; flex-direction: column; - padding: 4px; - z-index: 3; -} -.gallery-album-menu-pop[hidden] { display: none; } -.gallery-album-cover { - aspect-ratio: 4 / 3; - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - display: flex; align-items: center; justify-content: center; - overflow: hidden; -} -.gallery-album-placeholder { - opacity: 0.4; - font-size: 40px; - display: flex; align-items: center; justify-content: center; - width: 100%; height: 100%; -} -.gallery-album-info { - padding: 8px 10px; - display: flex; flex-direction: column; gap: 2px; -} -.gallery-album-name { - font-size: 13px; - font-weight: 500; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -.gallery-album-count { - font-size: 11px; - opacity: 0.55; -} -.gallery-album-card-add { - border-style: dashed; - opacity: 0.7; -} -.gallery-album-card-add:hover { opacity: 1; } -.gallery-album-action-tile { - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); -} - -/* Edit-tab empty state — shown before any image is loaded. */ -.gallery-editor-landing { - display: flex; flex-direction: column; align-items: center; justify-content: center; - gap: 10px; - padding: 28px 16px; - flex: 1; - text-align: center; - color: var(--fg); -} -.gallery-editor-landing h3 { - margin: 0; - font-size: 16px; - font-weight: 600; -} -.gallery-editor-heading { - display: inline-flex; - align-items: center; - gap: 7px; -} -.gallery-editor-heading > svg { opacity: 0.6; flex: 0 0 auto; } -/* "vision model" link in the AI-tagging description → opens Settings. */ -.ge-vision-link { - color: var(--accent, var(--red)); - text-decoration: underline; - text-underline-offset: 2px; - cursor: pointer; -} -.ge-vision-link:hover { opacity: 0.8; } -.ge-alpha-tag { - font-size: 9px; - font-weight: 700; - letter-spacing: 0.06em; - text-transform: uppercase; - vertical-align: middle; - padding: 1px 5px; - border-radius: 4px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - position: relative; - top: -1px; -} -.gallery-editor-landing-actions { - display: flex; gap: 10px; - margin-top: 8px; -} -/* Bigger primary action buttons specifically inside the editor landing — - the gallery-wide .gallery-select-btn is a compact toolbar style which is - too small for the empty-state hero. */ -.gallery-editor-landing-actions .gallery-select-btn { - padding: 7px 22px 17px; - font-size: 13px; - opacity: 0.85; -} -/* Visible template picker: every canvas ratio is inspectable without opening - a native menu, and its SVG outline previews the resulting canvas shape. */ -.gallery-editor-template-picker { - width: min(760px, 96%); - margin-top: 10px; -} -.gallery-editor-template-picker h4 { - margin: 0 0 7px; - font-size: 11px; - opacity: 0.55; - text-transform: uppercase; - letter-spacing: 0.06em; -} -.gallery-editor-template-options { - display: grid; - grid-template-columns: repeat(4, minmax(105px, 1fr)); - gap: 6px; -} -.gallery-editor-template-option { - height: 72px; - min-width: 0; - padding: 6px 7px; - display: grid; - grid-template-rows: 28px auto auto; - justify-items: center; - align-items: center; - background: var(--panel); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 5px; - font: inherit; - font-size: 11px; - cursor: pointer; - overflow: hidden; -} -.gallery-editor-template-option svg { color: var(--accent, var(--red)); } -.gallery-editor-template-option:hover, -.gallery-editor-template-option:focus-visible { - outline: none; - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 7%, var(--panel)); -} -.gallery-editor-template-option span { - width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-weight: 600; -} -.gallery-editor-template-option small { - width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 9px; - opacity: 0.52; -} -@media (max-width: 620px) { - .gallery-editor-landing { - justify-content: flex-start; - gap: 7px; - padding: 18px 12px; - overflow-y: auto; - } - .gallery-editor-landing-actions { - width: 100%; - gap: 6px; - justify-content: center; - } - .gallery-editor-landing-actions .gallery-select-btn { - flex: 1 1 0; - min-width: 0; - max-width: 180px; - padding: 6px 8px 10px; - font-size: 11px; - } - .gallery-editor-template-picker { - width: 100%; - margin-top: 4px; - } - .gallery-editor-template-options { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - /* The common square, widescreen, portrait, and Instagram presets are - enough to start on a phone. Keep the print and 4K presets on desktop. */ - .gallery-editor-template-option:nth-child(n + 5) { - display: none; - } -} -/* Saved-drafts grid on the editor landing. Surfaces every persisted - in-progress project so the user can resume without re-opening the - original photo. Each card: thumbnail + name + last-saved hint + ×. */ -.gallery-editor-drafts { - width: min(720px, 92%); - margin: 28px auto 0; /* centered */ - padding-top: 18px; - border-top: 1px solid var(--border); - display: flex; - flex-direction: column; - gap: 10px; -} -.gallery-editor-drafts { - position: relative; -} -.gallery-editor-drafts-loading { - position: absolute; - inset: 30px 0 0 0; - z-index: 4; - display: flex; - align-items: center; - justify-content: center; - background: var(--panel); - border-radius: 6px; - min-height: 80px; -} -.gallery-editor-drafts-header { - display: flex; - align-items: center; - justify-content: center; /* center the title / search / select */ - gap: 8px; - margin-bottom: 8px; -} -.gallery-editor-drafts-title { - margin: 0; - font-size: 11px; - text-transform: uppercase; - letter-spacing: 0.06em; - opacity: 0.55; - font-weight: 600; - text-align: left; - flex-shrink: 0; -} -.gallery-editor-drafts-search { - flex: 1 1 auto; - min-width: 0; - max-width: none; - height: 26px; /* same thickness as the Select button */ - box-sizing: border-box; - padding: 0 8px; - font-size: 12px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; -} -.gallery-editor-drafts-search:focus { outline: none; border-color: var(--red); } -/* The shared .gallery-select-btn has a -4px top margin + tall asymmetric padding - meant for the main gallery toolbar; in this header it makes the button a - different height / offset from the search box. Normalize so it lines up. */ -.gallery-editor-drafts-header .gallery-select-btn { - margin-top: 0 !important; - height: 26px; /* match the search bar */ - box-sizing: border-box; - padding: 0 10px; - font-size: 12px; - line-height: 1; - flex-shrink: 0; -} - -.cookbook-official-filter { - display: inline-flex; - align-items: center; - gap: 4px; - flex: 0 1 auto; - min-width: 0; - max-width: 100%; - color: var(--fg); - font-size: 10px; - opacity: 0.72; - white-space: nowrap; - position: relative; - top: 1px; -} -.cookbook-official-filter > span:first-child { - position: relative; - top: 1px; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-size: 9px; -} -.cookbook-official-filter .toggle { - width: 26px; - height: 14px; -} -.cookbook-official-filter .toggle .slider:before { - width: 10px; - height: 10px; - left: 2px; - bottom: 2px; -} -.cookbook-official-filter .toggle input:checked + .slider:before { - transform: translateX(12px); -} -#gallery-editor-drafts-select { margin-left: auto; flex-shrink: 0; } -#gallery-editor-drafts-select.active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -/* Graceful exit when a project card is deleted — fade + shrink before re-render. */ -.gallery-editor-draft-card.gallery-draft-removing { - opacity: 0; - transform: scale(0.92); - transition: opacity 0.24s ease, transform 0.24s ease; - pointer-events: none; -} -/* Draft select dot uses the standard small .gallery-select-dot look (same as the - photo grid) — no oversized white-ring checkbox. */ -.gallery-editor-drafts-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); - gap: 10px; -} -.gallery-drafts-just-opened > .gallery-editor-draft-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.gallery-drafts-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.gallery-drafts-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.gallery-drafts-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.gallery-drafts-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.gallery-drafts-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.gallery-drafts-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.gallery-drafts-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.gallery-drafts-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.gallery-drafts-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.gallery-drafts-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.gallery-drafts-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.gallery-drafts-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.gallery-drafts-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.gallery-drafts-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.gallery-drafts-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.gallery-drafts-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.gallery-drafts-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.gallery-drafts-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.gallery-drafts-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.gallery-drafts-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.gallery-drafts-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } -.gallery-drafts-view-toggle { - display: inline-flex; - position: relative; - top: 0; - height: 24px; - flex: 0 0 auto; - overflow: hidden; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); -} -.gallery-drafts-view-toggle button { - width: 25px; - height: 22px; - margin: 0; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - border: 0; - border-right: 1px solid var(--border); - background: transparent; - color: color-mix(in srgb, var(--fg) 55%, transparent); - cursor: pointer; - line-height: 0; -} -.gallery-drafts-view-toggle button:last-child { border-right: 0; } -.gallery-drafts-view-toggle button:hover { color: var(--fg); } -.gallery-drafts-view-toggle button.active { - height: 22px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-thumb { - width: 44px; - height: 44px; - aspect-ratio: auto; -} -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete { - position: static; - opacity: 0.55; - background: transparent; - color: var(--fg); -} -.gallery-editor-draft-delete-trash { display: none; } -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete-trash { - display: block; - width: 18px; - height: 18px; - position: relative; - top: -2px; -} -.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete:hover { - opacity: 1; - color: var(--color-error, #f44); - background: color-mix(in srgb, var(--color-error, #f44) 10%, transparent); -} -.gallery-editor-draft-card { - position: relative; - display: flex; - flex-direction: column; - gap: 4px; - padding: 6px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 70%, transparent); - cursor: pointer; - transition: border-color 0.15s, background 0.15s, transform 0.15s; - text-align: left; -} -.gallery-editor-draft-card:hover { - border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--panel) 90%, transparent); -} -.gallery-editor-draft-thumb { - width: 100%; - aspect-ratio: 1; - object-fit: cover; - border-radius: 4px; - background: var(--bg); - display: block; -} -.gallery-editor-draft-thumb-empty { - background: repeating-linear-gradient( - 45deg, - color-mix(in srgb, var(--fg) 4%, transparent), - color-mix(in srgb, var(--fg) 4%, transparent) 6px, - transparent 6px, - transparent 12px - ); -} -.gallery-editor-draft-info { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; -} -.gallery-editor-draft-name { - font-size: 12px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.gallery-editor-draft-meta { - font-size: 10px; - opacity: 0.55; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.gallery-editor-draft-delete { - position: absolute; - top: -2px; - right: 8px; - width: 22px; - height: 22px; - border-radius: 50%; - border: none; - background: rgba(0, 0, 0, 0.55); - color: #fff; - font-size: 14px; - line-height: 1; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.12s, background 0.12s; -} -.gallery-editor-draft-delete { - padding-bottom: 2px; -} -.gallery-editor-draft-delete:hover { background: var(--red); } -/* On very narrow phones, give project search its own row instead of - compressing the title and view controls into an overflowing header. */ -@media (max-width: 360px) { - .gallery-editor-drafts-header { - flex-wrap: wrap; - justify-content: flex-start; - gap: 6px; - } - .gallery-editor-drafts-title { - flex: 1 1 auto; - } - .gallery-editor-drafts-search { - order: 10; - flex: 1 1 100%; - width: 100%; - max-width: none; - } - #gallery-editor-drafts-select { - margin-left: 0; - } -} -/* Editor layout */ -.gallery-editor { - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; - height: 100%; - overflow: hidden; - /* Prevent the editor's own scroll/touch from chaining out to the - gallery modal or the page body — critical on mobile so touching - the canvas doesn't slide the whole page. */ - overscroll-behavior: contain; - touch-action: pan-y; -} -/* Pin the topbar to the top of the editor so its action buttons remain - visible if the gallery modal body itself ends up scrolling. */ -.ge-topbar { position: sticky; top: 0; z-index: 5; } - -/* Top bar */ -.ge-topbar { - display: flex; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: thin; - align-items: center; - justify-content: space-between; - padding: 6px 10px; - background: var(--panel); - border-bottom: 1px solid var(--border); - flex-shrink: 0; - gap: 8px; -} -.ge-topbar-left, .ge-topbar-right { - display: flex; - flex: 0 0 auto; - align-items: center; - gap: 4px; -} -.ge-topbar-right > * { - flex-shrink: 0; -} -.ge-topbar .dropdown[popover] { - position: fixed; - margin: 0; - bottom: auto; - max-height: calc(100vh - 80px); - overflow-y: auto; -} -.ge-draft-status { - flex: 0 1 auto; - min-width: 0; - max-width: 92px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 52%, transparent); - font-size: 10px; - line-height: 1; - transition: color 0.15s, opacity 0.15s; -} -.ge-draft-status[data-state="pending"], -.ge-draft-status[data-state="saving"] { color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); } -.ge-draft-status[data-state="saved"] { color: color-mix(in srgb, var(--color-success, #4ade80) 78%, var(--fg)); } -.ge-draft-status[data-state="error"] { color: var(--color-error, #ef4444); } -/* "ALPHA" badge — flags the editor as in-development. */ -.ge-alpha-badge { - flex-shrink: 0; - font-size: 9px; - font-weight: 700; - letter-spacing: 0.1em; - line-height: 1; - padding: 3px 6px; - margin-right: 4px; - border-radius: 4px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - text-transform: uppercase; - user-select: none; - cursor: default; -} -@media (max-width: 768px) { - /* Keep the toolbar tight on mobile — badge stays but shrinks. */ - .ge-alpha-badge { font-size: 8px; padding: 2px 5px; margin-right: 2px; } -} -.ge-diffusion-status { - font-size: 11px; - cursor: pointer; - padding: 2px 8px; - border-radius: 4px; - transition: all 0.15s; - white-space: nowrap; -} -.ge-diffusion-status.online { - color: var(--color-success, #4ade80); -} -.ge-diffusion-status.offline { - color: var(--color-error, #ef4444); - opacity: 0.7; -} -.ge-diffusion-status.offline:hover { - opacity: 1; - background: color-mix(in srgb, var(--color-error) 10%, transparent); -} -.ge-topbar-fill { - display: inline-flex; - align-items: center; - gap: 4px; - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -.ge-topbar-fill:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: var(--accent, var(--red)); -} -/* Mask-color swatch in the topbar — small label + circular swatch so - the user can pick a contrasting overlay colour from anywhere. */ -.ge-topbar-mask-color-wrap { - display: inline-flex; - align-items: center; - gap: 4px; - cursor: pointer; -} -.ge-topbar-mask-color-label { - font-size: 10px; - opacity: 0.6; - text-transform: uppercase; - letter-spacing: 0.06em; -} -.ge-topbar-mask-color { - width: 18px; - height: 18px; - flex: 0 0 18px; -} -.ge-topbar-mask-color.cp-swatch-input { - width: 18px; - height: 18px; - flex: 0 0 18px; - border-radius: 50%; - padding: 0; -} -.ge-topbar-sep { - width: 1px; - height: 16px; - background: var(--border); - margin: 0 4px; -} - -/* Editor body (toolbar + canvas + panel) */ -.ge-editor-body { - display: flex; - flex: 1; - min-height: 0; - overflow: hidden; - position: relative; -} -.gallery-editor-container { - flex: 1; - min-height: 0; - display: none; -} - -.ge-toolbar { - display: flex; - flex-direction: column; - gap: 2px; - /* Asymmetric horizontal padding shifts the whole column 4 px left — - buttons, hover backgrounds, active highlight pills, and section - separators all move together. */ - padding: 8px 8px 8px 0; - background: var(--panel); - border-right: 1px solid var(--border); - width: 64px; - flex-shrink: 0; - min-height: 0; - overflow-x: hidden; - overflow-y: auto; - scrollbar-width: none; -} -.ge-toolbar::-webkit-scrollbar { display: none; } -.ge-tool-sep { - font-size: 8px; - text-transform: uppercase; - letter-spacing: 0.05em; - color: var(--color-muted); - text-align: center; - padding: 8px 0 4px; - border-top: 1px solid var(--border); - margin-top: 4px; - opacity: 0.6; - flex-shrink: 0; -} -/* All tool buttons share a fixed height so the column reads as a clean - grid. Long labels (e.g. "Remove BG") truncate with ellipsis instead of - wrapping to two lines and making one button taller than the others. */ -.ge-tool-btn { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 2px; - padding: 0 2px; - height: 42px; - flex-shrink: 0; - border: none; - background: none; - color: var(--fg); - opacity: 0.6; - cursor: pointer; - border-radius: 6px; - transition: background 0.15s, opacity 0.15s; -} -.ge-tool-btn { position: relative; } -/* Hover background is now a pseudo so we can shift it 2 px left - independently from the button's own bounds (which contain the icon - and label at their already-shifted positions). */ -.ge-tool-btn:hover { opacity: 0.85; } -.ge-tool-btn.active { - opacity: 1; - color: var(--red); - background: none; - /* Padding stays the same as inactive buttons so the icon stays - horizontally aligned with its neighbors. The taller highlight is - drawn by ::before so it can extend up/down without pushing the - button or shifting the icon. */ -} -/* Tool button contents stay above the active highlight pseudo. Also - shifted an additional 2 px left so the icon + label sit further inside - the highlight pill (which still anchors to the button bounds). */ -.ge-tool-btn > * { position: relative; z-index: 1; left: -2px; } -.ge-tool-icon { font-size: 18px; line-height: 1; } -.ge-tool-label { font-size: 10px; line-height: 1.25; } -/* AI badge — same ✦ glyph the Enhance tool uses, pinned to the top- - left of any tool button marked `ai: true`. Same color as the icon - itself (inherits foreground) so it reads as part of the tool, not - a flag. */ -.ge-tool-ai { - position: absolute !important; - top: 1px; - z-index: 2; - color: inherit; - opacity: 0.7; - pointer-events: none; - font-size: 11px; - line-height: 1; - font-weight: 700; - left: 3px !important; -} -.ge-tool-btn.is-ai:hover .ge-tool-ai { opacity: 0.95; } -.ge-tool-btn.is-ai.active .ge-tool-ai { opacity: 1; } -/* Inline ✦ marker used on AI action buttons (Generate, Remove, etc.) - so they read as AI-backed at a glance — same glyph as .ge-tool-ai - in the toolbar, just sitting inline next to the label. */ -.ge-btn-ai-mark { - display: inline-block; - font-size: 11px; - line-height: 1; - font-weight: 700; - opacity: 0.75; - margin-right: 1px; -} - -/* Per-tool clear-selection badge — shows a tiny X in the top-right of - the Lasso / Wand button when each holds a selection. Click clears - the selection without switching tools (handler stops propagation). */ -.ge-tool-clear { - position: absolute !important; - top: 3px; - right: 3px; - width: 14px; - height: 14px; - display: none; - align-items: center; - justify-content: center; - border-radius: 50%; - color: #fff; - background: var(--red); - cursor: pointer; - opacity: 0.95; - z-index: 2; - box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 70%, transparent), - 0 1px 4px color-mix(in srgb, var(--red) 50%, transparent); - transition: opacity 0.12s, transform 0.12s, box-shadow 0.12s; - left: auto !important; -} -.ge-tool-clear:hover { - opacity: 1; - transform: scale(1.15); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 80%, transparent), - 0 2px 6px color-mix(in srgb, var(--red) 60%, transparent); -} - -/* Aspect-ratio placeholder shown while a draft is loading. */ -.ge-canvas-placeholder { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border: 1px dashed color-mix(in srgb, var(--fg) 20%, transparent); - border-radius: 4px; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 1; -} -/* Canvas area (center) */ -.ge-canvas-area { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - overflow: auto; - background: var(--bg); - position: relative; - min-width: 0; - /* Stop touch interactions inside the editor from dragging the whole - page (or the gallery modal). Touches on the canvas are owned by the - editor's drawing logic — no native pan/zoom. */ - overscroll-behavior: contain; -} -.ge-ruler { - position: absolute; - z-index: 16; - touch-action: none; - user-select: none; -} -.ge-ruler-horizontal { - top: 0; - left: 18px; - right: 0; - width: calc(100% - 18px); - height: 18px; - cursor: row-resize; -} -.ge-ruler-vertical { - top: 18px; - bottom: 0; - left: 0; - width: 18px; - height: calc(100% - 18px); - cursor: col-resize; -} -.ge-ruler-corner { - position: absolute; - top: 0; - left: 0; - z-index: 17; - width: 18px; - height: 18px; - background: var(--panel); - border-right: 1px solid var(--border); - border-bottom: 1px solid var(--border); - box-sizing: border-box; -} -.ge-wand-loading { - position: absolute; - inset: 0; - z-index: 25; - display: flex; - align-items: center; - justify-content: center; - background: color-mix(in srgb, var(--bg) 30%, transparent); - pointer-events: none; -} -.ge-ai-command { - position: absolute; - left: 50%; - bottom: 14px; - z-index: 18; - width: min(430px, calc(100% - 28px)); - transform: translateX(-50%); - padding: 7px; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 94%, transparent); - box-shadow: 0 8px 22px color-mix(in srgb, #000 26%, transparent); - backdrop-filter: blur(8px); - transition: bottom 0.16s ease, opacity 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; -} -.ge-ai-command-toggle { - width: 100%; - height: 28px; - display: none; - align-items: center; - justify-content: center; - gap: 5px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--accent, var(--red)); - font: inherit; - font-size: 11px; - font-weight: 600; - cursor: pointer; -} -.ge-ai-command-toggle-caret { - opacity: 0.7; - flex-shrink: 0; -} -.ge-ai-command-head { - display: flex; - align-items: center; - min-height: 24px; - padding: 0 2px 5px; - color: var(--fg); -} -.ge-ai-command-title { - margin-left: 5px; - font-size: 11px; - font-weight: 650; -} -.ge-ai-command-scope { - margin-left: 7px; - padding-left: 7px; - border-left: 1px solid var(--border); - color: var(--fg-muted); - font-size: 9px; - line-height: 1; - text-transform: uppercase; -} -.ge-ai-command-collapsed { - bottom: -16px; - width: auto; - min-width: 102px; - padding: 2px 9px; - opacity: 0.32; - border-color: color-mix(in srgb, var(--accent, var(--red)) 18%, var(--border)); - box-shadow: 0 3px 10px color-mix(in srgb, #000 16%, transparent); -} -.ge-canvas-area:hover .ge-ai-command-collapsed, -.ge-ai-command-collapsed:hover, -.ge-ai-command-collapsed:focus-within { - bottom: 10px; - opacity: 0.94; - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 24%, transparent); -} -.ge-ai-command-form { - display: flex; - align-items: center; - gap: 6px; -} -.ge-ai-command-input { - flex: 1 1 auto; - min-width: 0; - height: 32px; - box-sizing: border-box; - padding: 0 9px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--bg) 76%, #000 24%); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.ge-ai-command-input:focus { - outline: none; - border-color: var(--accent, var(--red)); -} -.ge-ai-command-clear { - position: relative; - top: -2px; - width: 26px; - height: 32px; - flex: 0 0 26px; - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: -38px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--fg-muted); - cursor: pointer; - padding: 0; - z-index: 1; -} -.ge-ai-command-clear:hover { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.ge-ai-command-run { - position: relative; - top: -2px; - background: var(--send-btn-bg, var(--red)); - color: #fff; - border: none; - border-radius: 8px; - min-width: 32px; - width: 32px; - height: 32px !important; - padding: 0; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - transition: background 0.25s, opacity 0.12s; - overflow: hidden; -} -.ge-ai-command-run:hover { - background: var(--send-btn-hover, color-mix(in srgb, var(--red) 80%, white)); -} -.ge-ai-command-busy .ge-ai-command-run { - opacity: 0.58; - cursor: wait; -} -.ge-ai-command-busy .ge-ai-command-run svg { - display: none; -} -.ge-ai-command-close { - width: 28px; - height: 32px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border: 0; - border-radius: 8px; - background: transparent; - color: var(--fg); - opacity: 0.52; - cursor: pointer; - padding: 0; - margin-left: auto; -} -.ge-ai-command-close:hover { - opacity: 0.9; - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.ge-ai-command-suggestions { - position: absolute; - left: 7px; - right: 7px; - bottom: calc(100% - 2px); - z-index: 2; - display: flex; - flex-direction: column; - gap: 2px; - max-height: 236px; - overflow: auto; - padding: 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 97%, #000 3%); - box-shadow: 0 10px 28px color-mix(in srgb, #000 28%, transparent); -} -.ge-ai-command-suggestion { - width: 100%; - display: grid; - grid-template-columns: 38px minmax(82px, auto) minmax(0, 1fr); - align-items: center; - gap: 8px; - min-height: 28px; - padding: 4px 7px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--fg); - text-align: left; - cursor: pointer; - font: inherit; -} -.ge-ai-command-suggestion:hover, -.ge-ai-command-suggestion.active { - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); -} -.ge-ai-command-suggestion-kind { - min-width: 0; - overflow: hidden; - color: var(--accent, var(--red)); - font-size: 8px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; -} -.ge-ai-command-suggestion-main { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 11px; - font-weight: 650; -} -.ge-ai-command-suggestion-hint { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--fg-muted); - font-size: 10px; -} diff --git a/static/css/11-tasks.css b/static/css/11-tasks.css new file mode 100644 index 000000000..1ff602fa3 --- /dev/null +++ b/static/css/11-tasks.css @@ -0,0 +1,1972 @@ +/* 11-tasks - 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. + */ +/* Snapped/narrow Settings window: move the tab rail to the top. Viewport media + alone misses desktop right-half snapping, where the modal is narrow but the + browser window is not. */ +@container settings-modal (max-width: 620px) { + .settings-layout { + flex-direction: column; + } + .settings-sidebar { + width: auto; + max-height: none; + flex-direction: row; + border-right: none; + border-bottom: 1px solid var(--border); + overflow-x: auto; + overflow-y: hidden; + padding: 6px; + scrollbar-width: none; + } + .settings-sidebar::-webkit-scrollbar { + display: none; + } + .settings-sidebar-divider, + .settings-sidebar-label { + display: none; + } + .settings-nav-item { + flex: 0 0 auto; + padding: 6px 10px; + font-size: 11px; + } + .settings-panels { + padding: 12px 14px; + } +} + +/* ── Entrance Animations ── */ + +/* Welcome name — left-to-right wipe with a touch of horizontal stretch. + Fires on initial render via the .welcome-name rule; restarts on Nobody⇄ + Odysseus toggle via JS reflow trick. */ +.welcome-name { + transform-origin: left center; + animation: welcome-name-reveal 0.55s cubic-bezier(0.34, 1.32, 0.55, 1) both; +} +/* Hold the welcome-screen entrance animations until the app has finished its + initial load (fonts loaded + layout settled). Running them mid-load made the + splash jump/flicker as the page reflowed ("haywire"). Until body.welcome-ready + is set by JS, the splash sits in its final, static state (no flash, since the + non-animated state IS the resting state); adding the class then plays the + entrance once, cleanly. */ +body:not(.welcome-ready) #welcome-screen, +body:not(.welcome-ready) .welcome-name { + animation: none !important; +} +/* Hold the splash invisible (the entrance's start state) until ready, so when + the animation is released it fades/wipes in smoothly instead of flashing + from fully-visible to the animation's hidden first frame. */ +body:not(.welcome-ready) #welcome-screen { + opacity: 0; +} +@keyframes welcome-name-reveal { + from { + clip-path: inset(0 100% 0 0); + transform: scaleX(0.96); + opacity: 0.5; + } + to { + clip-path: inset(0 0 0 0); + transform: scaleX(1); + opacity: 1; + } +} + +@keyframes welcome-enter { + from { + opacity: 0; + transform: translate(-50%, -45%); + } + to { + opacity: 1; + transform: translate(-50%, -50%); + } +} + +@keyframes msg-enter { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes picker-roll-up { + from { + opacity: 0; + transform: scaleY(0.4) translateY(8px); + } + to { + opacity: 1; + transform: scaleY(1) translateY(0); + } +} +@keyframes picker-roll-down { + from { + opacity: 1; + transform: scaleY(1) translateY(0); + } + to { + opacity: 0; + transform: scaleY(0.4) translateY(8px); + } +} +@keyframes model-picker-refresh-spin { + to { transform: rotate(360deg); } +} + +@keyframes modal-enter { + from { + opacity: 0; + transform: scale(0.95) translateY(8px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} +@keyframes modal-exit { + from { + opacity: 1; + transform: scale(1) translateY(0); + } + to { + opacity: 0; + transform: scale(0.97) translateY(6px); + } +} +.modal-content.modal-closing { + animation: modal-exit 0.18s ease-in both; +} + +#cookbook-modal .modal-content.cookbook-modal-entering { + animation: cookbook-modal-enter 0.28s cubic-bezier(0.22, 1.35, 0.36, 1) both; +} +/* Mobile: real bottom-sheet slide-up — the existing 12px translate is + too subtle on a phone, the modal effectively just appeared. */ +@media (max-width: 768px) { + #cookbook-modal .modal-content.cookbook-modal-entering { + animation: cookbook-modal-enter-mobile 0.32s cubic-bezier(0.22, 1, 0.36, 1) both; + } +} +#cookbook-modal .modal-content:not(.cookbook-modal-entering):not(.modal-closing) { + animation: none; +} +#cookbook-modal .modal-content.modal-closing { + animation: modal-exit 0.18s cubic-bezier(0.4, 0, 1, 1) both; +} + +/* Per-token streaming fade — new words materialize */ +.token-new { + animation: token-fade 0.4s ease-out both; +} + +@keyframes token-fade { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Disable entrance animations when loading chat history (bulk render) */ +.chat-history.no-animate .msg { + animation: none; +} + +/* Prefer reduced motion */ +@media (prefers-reduced-motion: reduce) { + .msg, + .modal-content, + #welcome-screen, + .toast, + .token-new { + animation: none !important; + transition: none !important; + } +} + +@media (min-width: 769px) { + #tool-tasks-btn #assistant-notif-dot.sidebar-notif-dot, + #tool-tasks-btn.task-failure-pending::after, + #tool-tasks-btn.task-completion-pending::after { + transform: translateX(-2px); + } +} +.tasks-modal-content { max-width: 600px; width: min(600px, 92vw); background: var(--bg); font-size: 12px; } +.task-completed-preview-row { + cursor: default; + margin-bottom: 6px; + padding: 8px 10px 10px; + max-height: none; + overflow: visible; +} +.task-completed-preview-head { + display: flex; + align-items: center; + gap: 7px; + min-height: 20px; + font-size: 11px; +} +.task-completed-chat-preview { + padding: 0 0 4px; +} +.task-completed-chat-preview .doclib-chat-preview-messages { + max-height: 220px; + margin-top: 7px; +} +.task-completed-chat-preview .doclib-chat-bubble { + max-width: 92%; +} +.task-completed-chat-preview .doclib-chat-preview-actions { + top: 2px; + margin-top: 4px; +} +.task-completed-preview-row .task-log-time { + margin-left: 8px; +} + +/* Tasks tabs reuse the .memory-tab look. The Brain window's tab bar is + full-bleed (its underline spans the whole modal width). The Tasks bar is a + direct child of .modal-content (padding:10px), so cancel that padding with + negative side margins to span edge-to-edge, then re-inset the tabs by 10px + so they line up with the rest of the modal content — matching the Brain bar. */ +.tasks-modal-content .tasks-tabs { + margin: -2px -10px 8px; + padding: 0 10px; +} + +/* Activity log — compact by default. Click the row to expand body+actions. */ +.task-log-row { + --cat-hue: 220; + border: 1px solid var(--border); + border-radius: 8px; + padding: 4px 10px; + margin-bottom: 3px; + background: color-mix(in srgb, var(--fg) 2%, transparent); + position: relative; + cursor: pointer; + transition: padding .12s ease; +} +.task-log-row:hover { + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.task-log-row.task-log-row-error { + border-color: color-mix(in srgb, var(--red, #f87171) 35%, var(--border)); + background: color-mix(in srgb, var(--red, #f87171) 4%, transparent); +} +.task-log-row.task-log-row-error:hover { + background: color-mix(in srgb, var(--red, #f87171) 7%, transparent); +} +.task-log-row.expanded { padding: 8px 10px 6px; } +.task-log-row-head { + display: flex; + align-items: center; + gap: 8px; + font-size: 11px; + margin-bottom: 0; +} +.task-log-row.expanded .task-log-row-head { margin-bottom: 4px; } +/* Collapsed: body + footer hidden. Expanded: visible. */ +.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-body, +.task-log-row:not(.expanded):not(.is-skipped) .task-log-row-actions, +.task-log-row:not(.expanded):not(.is-skipped) .task-log-prompt { + display: none; +} +.task-log-name { + font-weight: 600; + /* Pull saturation from the per-row hue but mix with the foreground so the + title still reads in dark mode. Lightness stays adaptive. */ + color: hsl(var(--cat-hue) 60% 60%); +} +.task-log-failed-tag { + color: var(--red, #f87171); + font-size: 10px; + font-weight: 700; + line-height: 1; + white-space: nowrap; + margin-left: -2px; +} +.task-log-task-icon { + display: inline-flex; + align-items: center; + flex: 0 0 auto; + margin-right: -3px; +} +.task-log-row.task-log-row-error .task-log-name, +.task-log-row.task-log-row-error .task-log-task-icon { + color: var(--red, #f87171); +} +.task-log-task-icon svg { + top: 0 !important; + opacity: 0.46 !important; +} +.task-log-row-head .task-ai-mark { + top: 0; + margin-left: -4px; +} +.task-log-repeat { + font-size: 10px; + font-weight: 500; + line-height: 1; + color: color-mix(in srgb, var(--fg) 62%, transparent); + border-radius: 999px; + padding: 1px 0; + white-space: nowrap; +} +@media (prefers-color-scheme: light) { + .task-log-name { color: hsl(var(--cat-hue) 65% 38%); } +} +/* Per-account prefix in fan-out results — e.g. "[Default] No recent emails" + becomes a compact accent chip + plain message. Makes multi-account activity + rows readable instead of a bracket soup. */ +/* Running / queued status line in the Activity tab — whirlpool + label. */ +.task-log-running { + display: inline-flex; + align-items: center; + gap: 0; + font-size: 11px; + opacity: 0.75; +} +.task-log-running-label { font-style: normal; } + +/* New right-side placement: "Running " sits where the timestamp + normally would, on the head row's right edge. */ +.task-log-running-inline { + display: inline-flex; + align-items: center; + gap: 0; + font-size: 11px; + opacity: 0.75; +} +.task-log-force-run { + appearance: none; + box-sizing: border-box; + border: 0; + background: color-mix(in srgb, var(--fg) 7%, transparent); + box-shadow: none; + color: inherit; + opacity: .82; + margin-left: 7px; + padding: 1px 6px 1px 4px; + height: 18px; + min-height: 16px; + max-height: 18px; + border-radius: 999px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 3px; + font-family: inherit; + font-size: 10px; + line-height: 1; + cursor: pointer; + position: relative; + top: -3px; +} +.task-log-force-run svg, +.task-log-stop svg { + display: block; + flex-shrink: 0; + transform: translateY(1px); +} +.task-log-force-run:hover { + opacity: 1; + background: color-mix(in srgb, var(--green, #50fa7b) 16%, transparent); + color: var(--green, #50fa7b); +} +.task-log-stop { + border: 0; + background: transparent; + color: inherit; + opacity: .72; + padding: 0; + margin-left: 6px; + width: 12px; + height: 12px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + position: relative; + top: -3px; +} +.task-log-stop:hover { + opacity: 1; + color: var(--red, #f87171); +} + +/* Slim single-line row for skipped (noop) runs — body/actions stripped, font + shrunk, opacity dropped. Distinguishes "task ran but had nothing to do" + from a "real" entry without flooding the feed visually. */ +.task-log-row.is-skipped { + padding: 4px 8px; + opacity: 0.45; + font-size: 11px; + background: transparent; +} +.task-log-row.is-skipped .task-log-row-head { padding: 0; } +.task-log-row.is-skipped .task-log-name { font-weight: 500; } +.task-log-row.is-skipped .task-log-skipped-reason { + flex: 1 1 auto; + margin-left: 6px; + font-style: italic; + opacity: 0.85; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.task-log-row.is-skipped:hover { opacity: 0.7; } + +.task-log-account-tag { + display: inline-block; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.02em; + padding: 1px 7px; + margin-right: 4px; + border-radius: 10px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); + vertical-align: 1px; +} +.task-log-status { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; + background: color-mix(in srgb, var(--fg) 30%, transparent); +} +.task-log-status-ok { background: #4ade80; box-shadow: 0 0 6px #4ade80, 0 0 3px #4ade80; } +.task-log-status-error { background: var(--red, #f87171); box-shadow: 0 0 6px var(--red, #f87171), 0 0 3px var(--red, #f87171); } +.task-log-status-info { background: color-mix(in srgb, var(--fg) 25%, transparent); } +.task-log-status-queued { background: #fbbf24; box-shadow: 0 0 0 2px color-mix(in srgb, #fbbf24 30%, transparent); } +.task-log-status-running { background: #60a5fa; animation: task-log-pulse 1.4s ease-in-out infinite; } +.task-log-status-skipped { background: color-mix(in srgb, var(--fg) 20%, transparent); } +.task-log-status-aborted { background: color-mix(in srgb, var(--fg) 30%, transparent); border: 1px dashed color-mix(in srgb, var(--fg) 50%, transparent); } +.task-log-row-body { + font-size: 12px; + line-height: 1.5; + color: color-mix(in srgb, var(--fg) 85%, transparent); + overflow-wrap: anywhere; + word-break: break-word; +} +.task-log-row-body p { margin: 0 0 6px 0; } +.task-log-row-body p:last-child { margin-bottom: 0; } +.task-log-row-body pre { + margin: 4px 0; + padding: 6px 8px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + border-radius: 4px; + white-space: pre-wrap; + word-break: break-word; + font-size: 11px; + max-height: 180px; + overflow: auto; +} +.task-log-row-body code { + font-size: 11px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + padding: 1px 4px; + border-radius: 3px; +} +.task-log-row-body ul, .task-log-row-body ol { + margin: 4px 0; + padding-left: 20px; +} +/* Markdown headings inside a task result — without these, ## headings + render at browser-default huge sizes and blow out the cramped row. */ +.task-log-row-body h1, +.task-log-row-body h2, +.task-log-row-body h3, +.task-log-row-body h4 { + margin: 10px 0 4px; + line-height: 1.3; + font-weight: 650; + color: var(--fg); +} +.task-log-row-body h1 { font-size: 14px; } +.task-log-row-body h2 { font-size: 13px; } +.task-log-row-body h3, +.task-log-row-body h4 { font-size: 12px; opacity: 0.9; } +.task-log-row-body h1:first-child, +.task-log-row-body h2:first-child, +.task-log-row-body h3:first-child { margin-top: 0; } +.task-log-row-body blockquote { + margin: 4px 0; + padding: 2px 0 2px 10px; + border-left: 2px solid color-mix(in srgb, var(--fg) 20%, transparent); + opacity: 0.85; +} +.task-log-row-body a { color: var(--accent, #60a5fa); text-decoration: none; } +.task-log-row-body a:hover { text-decoration: underline; } +.task-log-row-body strong { font-weight: 650; color: var(--fg); } +/* Email-summary tables — render compactly instead of default browser ugliness. */ +.task-log-row-body table { + border-collapse: collapse; + width: 100%; + margin: 6px 0; + font-size: 11px; +} +.task-log-row-body th, +.task-log-row-body td { + border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); + padding: 3px 6px; + text-align: left; + vertical-align: top; +} +.task-log-row-body th { + background: color-mix(in srgb, var(--fg) 5%, transparent); + font-weight: 600; +} +.task-log-row-actions { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-top: 4px; +} +.task-log-open-chat, +.task-log-open-report, +.task-log-copy, +.task-log-clear-cache, +.task-log-run-again, +.task-log-force-run-action { + display: inline-flex; + align-items: center; + gap: 3px; + background: none; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + border-radius: 5px; + color: color-mix(in srgb, var(--fg) 60%, transparent); + font-family: inherit; + font-size: 10px; + padding: 1px 6px; + cursor: pointer; + transition: background .15s, color .15s, border-color .15s; + line-height: 1.4; +} +.task-log-open-chat:hover, +.task-log-open-report:hover, +.task-log-copy:hover, +.task-log-clear-cache:hover, +.task-log-run-again:hover, +.task-log-force-run-action:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--fg) 30%, transparent); + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.task-log-row-actions > .task-log-open-chat, +.task-log-row-actions > .task-log-copy { + margin-left: auto; +} +.task-log-clear-cache svg { + position: relative; + top: 0; +} +/* Activity filter chips — toggle-out model: ON by default (solid), + click to toggle OFF (dimmed + strikethrough) to hide that group. */ +.tasks-af-chip { + font-size: 11px; + padding: 3px 10px; + border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); + border-radius: 12px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + color: var(--fg); + cursor: pointer; + font-family: inherit; + transition: opacity .12s, background .12s, border-color .12s; +} +.tasks-af-chip:hover { border-color: color-mix(in srgb, var(--fg) 32%, transparent); } +.tasks-af-chip.off { + opacity: 0.4; + text-decoration: line-through; + background: transparent; +} +.tasks-af-chip.active { + border-color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + opacity: 1; +} +.tasks-af-chip-error { border-color: color-mix(in srgb, var(--red, #f87171) 45%, transparent); } +.tasks-af-chip-error:not(.off) { color: var(--red, #f87171); } + +/* "Open in Deep Research" is now a regular clickable chat link (a + `#research-` markdown anchor in the assistant's message), rendered as + an a.chat-link. Nudge it 4px right so it sits slightly inset. */ +a.chat-link[href^="#research-"] { + margin-left: 4px; +} +.task-log-row.is-long .task-log-row-body { + max-height: 8.5em; + overflow: hidden; + position: relative; + mask-image: linear-gradient(to bottom, #000 70%, transparent); + -webkit-mask-image: linear-gradient(to bottom, #000 70%, transparent); +} +.task-log-row.is-long.expanded .task-log-row-body { + max-height: none; + overflow: visible; + mask-image: none; + -webkit-mask-image: none; +} +.task-log-row-toggle { + margin-top: 6px; + background: none; + border: none; + font-family: inherit; + font-size: 11px; + cursor: pointer; + padding: 2px 0; + color: transparent; /* hide raw "Show more" text */ +} +.task-log-row:not(.expanded) .task-log-row-toggle::before { content: 'Show more'; } +.task-log-row.expanded .task-log-row-toggle::before { content: 'Show less'; } +.task-log-prompt { + margin-top: 6px; + font-size: 11px; +} +.task-log-prompt summary { + cursor: pointer; + opacity: 0.5; + user-select: none; +} +.task-log-prompt summary:hover { opacity: 0.8; } +.task-log-prompt pre { + margin: 4px 0 0; + padding: 6px 8px; + background: color-mix(in srgb, var(--fg) 4%, transparent); + border-radius: 4px; + white-space: pre-wrap; + word-break: break-word; + font-size: 11px; + opacity: 0.75; + max-height: 200px; + overflow: auto; +} + +/* Tasks is the shared theme reference for procedural cards. Keep the tint + together so Skills cannot drift when theme colors change. Hover and + selection remain owned by the shared card state rules above. */ +.task-card, +.skill-card { + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +.task-card { + border: 1px solid var(--border); + border-radius: 8px; + padding: 7px 8px; + margin-bottom: 0; + min-height: 46px; + box-sizing: border-box; + align-items: center; +} +/* When expanded, height grows to fit the detail panel. */ +.task-card.expanded { align-items: flex-start; } +.task-card .memory-item-title { + position: relative; + top: -2px; +} +/* Always show the per-card ⋮ kebab on task cards (the default opacity:0 + + hover-reveal is unreliable on touch and easy to miss on desktop). + pointer-events / z-index belt-and-braces against any sibling overlay, + margin reset undoes the global `.modal-body button { margin-top:6px }` + that was punting the hit-target down. */ +.task-card .memory-item-actions { + opacity: 0.55; + pointer-events: auto; + position: relative; + z-index: 5; +} +.task-card:hover .memory-item-actions, +.task-card .memory-item-actions:hover { opacity: 1; } +.task-card .memory-item-actions .memory-item-btn { + margin: 0 !important; + pointer-events: auto; + position: relative; + z-index: 5; + width: 28px; + min-width: 28px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; +} +/* Make the SVG inside the kebab transparent to pointer events so the click + always lands on the BUTTON itself (some browsers / nested SVG events lose + the click when hitting the inner glyph). */ +.task-card .memory-item-actions .memory-item-btn svg { pointer-events: none; } +.task-ai-mark { + flex: 0 0 auto; + color: var(--accent, var(--red)); + opacity: 0.78; +} +/* Per-card select checkbox rides up to the title line. The "All" checkbox is + #tasks-select-all (not .memory-select-cb), so it stays put. */ +.task-card .memory-select-cb { position: relative; } +.task-card.task-card-deleting { + opacity: 0.68; + pointer-events: none; + position: relative; +} +.task-card-delete-busy { + position: absolute; + right: 11px; + top: 50%; + transform: translateY(-50%); + display: inline-flex; + align-items: center; + gap: 5px; + height: 22px; + padding: 0 8px; + border-radius: 999px; + color: var(--red); + background: color-mix(in srgb, var(--red) 12%, var(--panel, #111)); + border: 1px solid color-mix(in srgb, var(--red) 24%, transparent); + font-size: 10px; + font-weight: 600; + line-height: 1; + z-index: 7; +} +.task-card-delete-busy-spin { + width: 10px; + height: 10px; + border-radius: 999px; + border: 1.5px solid color-mix(in srgb, currentColor 28%, transparent); + border-top-color: currentColor; + animation: spin 0.8s linear infinite; +} +#tasks-pause-all-btn { position: relative; top: -2px; } +#tasks-pause-all-btn:has(input:disabled) { opacity: 0.4; } +/* Bigger ⋮ dropdown on mobile (its buttons carry inline styles → !important). */ +@media (max-width: 768px) { + .task-card .task-status-badge { + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; + } + .task-card .task-state-badge svg, + .task-card .task-card-run-btn svg { + width: 10px; + height: 10px; + } +} + +.task-card-header { + display: flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.task-card-name { + font-size: 12px; + font-weight: 600; +} + +.task-card-schedule { + font-size: 10.5px; + opacity: 0.5; + margin-left: auto; +} + +.task-card-output { + font-size: 10px; + opacity: 0.45; +} + +.task-card-prompt { + font-size: 11px; + opacity: 0.55; + margin: 4px 0; + line-height: 1.4; +} + +.task-card-meta { + font-size: 10.5px; + opacity: 0.45; + margin-bottom: 6px; +} + +.task-card-actions { + display: flex; + gap: 4px; + flex-wrap: wrap; +} + +.task-btn { + font-family: inherit; + font-size: 11px; + padding: 4px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: none; + color: var(--fg); + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s, background 0.15s; +} +.task-btn:hover { opacity: 1; } +.task-btn:disabled { opacity: 0.3; cursor: not-allowed; } + +.task-btn-primary { + background: var(--red); + color: #fff; + border-color: transparent; + opacity: 1; +} +.task-btn-primary:hover { opacity: 0.85; } + +.task-btn-danger { color: var(--red); border-color: color-mix(in srgb, var(--red) 30%, transparent); } +.task-btn-danger:hover { opacity: 1; border-color: var(--red); } + +.tasks-clock { + font-size: 10px; + opacity: 0.35; + text-align: center; + padding: 6px 0 8px; +} + +.task-preset-grid { + display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; +} +.task-preset-card { + display: flex; flex-direction: column; align-items: flex-start; gap: 2px; + padding: 12px; border: 1px solid var(--border); border-radius: 8px; + background: var(--bg); cursor: pointer; transition: all 0.15s; text-align: left; + color: var(--fg); +} +.task-preset-card:hover { + border-color: var(--accent, #cc6a3a); background: color-mix(in srgb, var(--accent, #cc6a3a) 8%, var(--bg)); +} +.task-preset-icon { font-size: 20px; margin-bottom: 2px; } +.task-preset-label { font-size: 12px; font-weight: 600; } +.task-preset-desc { font-size: 10px; opacity: 0.5; line-height: 1.3; } + +.task-form { display: flex; flex-direction: column; gap: 4px; } +.task-form-label { + font-size: 11px; + opacity: 0.55; + font-weight: 500; + display: block; + /* Consistent rhythm: 8px above each label (section gap), 4px below to the + field — same spacing for Name/Prompt/Trigger/Output/Model/Chain. */ + margin: 8px 0 4px; +} +.task-form-input { + font-family: inherit; + font-size: 11px; + padding: 6px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + transition: border-color 0.15s; + box-sizing: border-box; + width: 100%; + max-width: 100%; + min-width: 0; +} +.task-form-input:focus { + outline: none; + border-color: var(--red); +} +.task-form-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } + +.task-form-notif-toggle { + display: flex; + align-items: center; + gap: 8px; + margin: 9px 0 3px; + cursor: pointer; + color: var(--fg); +} +.task-form-notif-toggle input { + position: absolute; + opacity: 0; + pointer-events: none; +} +.task-form-notif-switch { + width: 30px; + height: 16px; + border-radius: 999px; + border: 1px solid var(--border); + background: color-mix(in srgb, var(--fg) 8%, transparent); + position: relative; + flex: 0 0 auto; + transition: background 0.15s, border-color 0.15s; +} +.task-form-notif-toggle input:checked + .task-form-notif-switch { + border-color: color-mix(in srgb, var(--accent, var(--red)) 58%, transparent); + background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); +} +.task-form-notif-copy { + display: flex; + flex-direction: column; + gap: 1px; + font-size: 11px; + font-weight: 500; +} +.task-form-notif-copy span:last-child { + font-size: 10px; + font-weight: 400; + opacity: 0.52; +} + +.task-form-toggle { + display: flex; gap: 4px; +} +.task-toggle-btn { + flex: 1; padding: 4px 8px; font-size: 10px; font-family: inherit; + border: 1px solid var(--border); border-radius: 6px; background: transparent; + color: var(--fg); opacity: 0.4; cursor: pointer; transition: all 0.15s; +} +.task-toggle-btn:hover { opacity: 0.7; border-color: var(--fg); } +.task-toggle-btn.active { + opacity: 1; border-color: var(--red); color: var(--red); + background: color-mix(in srgb, var(--red) 10%, transparent); +} + +.task-time-picker, .task-date-picker { + display: flex; + align-items: center; + gap: 4px; +} +.task-time-select, .task-date-select { + width: auto; + min-width: 48px; + font-size: 11px; +} +.task-time-sep { opacity: 0.4; font-size: 11px; } + +.task-history-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; +} + +.task-runs-list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; } +.task-run-item { + border: 1px solid var(--border); + border-radius: 6px; + padding: 8px 10px; +} +.task-run-item-header { + display: flex; + align-items: center; + gap: 4px; + font-size: 11px; +} +.task-run-time { margin-left: auto; font-size: 10px; opacity: 0.45; } +.task-run-result { + font-size: 11px; + opacity: 0.6; + margin-top: 4px; + line-height: 1.4; + white-space: pre-wrap; + word-break: break-word; +} + +/* ── Settings Form Utilities ── */ + +.settings-row { + display: flex; + align-items: center; + gap: 8px; +} +.settings-col { + display: flex; + flex-direction: column; + gap: 6px; +} +.settings-label { + font-size: 12px; + min-width: 70px; +} +.settings-select { + flex: 1; + /* Allow the select to shrink below its longest option's width inside a flex + row — without this a long model name (e.g. the Vision picker) overflowed + the card off-screen on mobile. */ + min-width: 0; + max-width: 100%; + padding: 5px 8px; + font-family: inherit; + font-size: 12px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + transition: border-color 0.15s; + appearance: none; + -webkit-appearance: none; + -moz-appearance: textfield; + outline: none; +} +input.settings-select::-webkit-outer-spin-button, +input.settings-select::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +input.settings-select::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } +.settings-select:focus { + outline: none; + border-color: var(--red); +} + +/* Deep Research uses long labels and previously fixed-width fields. Keep + every control constrained by the card at intermediate desktop widths too. */ +.admin-card:has(#set-researchSearch) .settings-row { min-width: 0; } +.admin-card:has(#set-researchSearch) .settings-row > .settings-label { flex: 0 0 112px; min-width: 0; } +#set-researchSearch, +#set-researchMaxTokens, +#set-researchRunTimeout, +.settings-row div:has(> #set-researchExtractTimeout), +.settings-row div:has(> #set-researchExtractConcurrency) { + width: auto !important; + max-width: 100% !important; + min-width: 0 !important; + flex: 1 1 0 !important; +} + +@media (max-width: 768px) { + /* Deep Research settings contain desktop-sized inline widths. On mobile, + let each control claim the card width instead of escaping the admin block. */ + .settings-row:has(#set-researchSearch), + .settings-row:has(#set-researchMaxTokens), + .settings-row:has(#set-researchExtractTimeout), + .settings-row:has(#set-researchExtractConcurrency), + .settings-row:has(#set-researchRunTimeout) { + flex-wrap: wrap; + align-items: stretch; + } + .settings-row:has(#set-researchSearch) .settings-label, + .settings-row:has(#set-researchMaxTokens) .settings-label, + .settings-row:has(#set-researchExtractTimeout) .settings-label, + .settings-row:has(#set-researchExtractConcurrency) .settings-label, + .settings-row:has(#set-researchRunTimeout) .settings-label { + flex: 1 1 auto; + } + #set-researchSearch, + #set-researchMaxTokens, + #set-researchRunTimeout, + .settings-row div:has(> #set-researchExtractTimeout), + .settings-row div:has(> #set-researchExtractConcurrency) { + width: 100% !important; + max-width: 100% !important; + flex: 1 1 100% !important; + margin-left: 0 !important; + } +} + +/* Default-chat fallback chain editor. Each row mirrors the primary + endpoint/model selectors, indented under them to read as a chain. */ +.settings-fallbacks { + display: flex; + flex-direction: column; + gap: 6px; +} +.settings-fallbacks:not(:empty) { margin-top: 2px; } +.settings-fallback-row { + display: flex; + align-items: center; + gap: 6px; + padding-left: 12px; + border-left: 2px solid color-mix(in srgb, var(--fg) 12%, transparent); + max-width: 100%; + box-sizing: border-box; +} +.settings-fallback-num { + font-size: 11px; + opacity: 0.4; + min-width: 14px; + text-align: right; +} +.settings-fallback-row .settings-select { + width: 0; + min-width: 0; + min-inline-size: 0; + box-sizing: border-box; + overflow: hidden; + text-overflow: ellipsis; +} +.settings-fallback-row .settings-select:first-of-type { + flex: 0 1 128px; + width: 128px; + max-width: 34%; +} +.settings-fallback-row .settings-select:nth-of-type(2) { + flex: 1 1 0; + width: 0; + max-width: 100%; +} +details.hwfit-serve-advanced > .hwfit-serve-row, +details.hwfit-serve-advanced > .hwfit-serve-checks, +details.hwfit-serve-advanced > .hwfit-serve-cmd-wrap, +details.hwfit-serve-advanced > .hwfit-serve-extra { + margin-top: 0; + margin-bottom: 0; +} +/* Inside Advanced, tighten the per-row grid gap and the label → + select gap so rows of single-line dropdowns don't visually sit + on top of half a row of empty space. Outside Advanced (Core, + etc.) the original ~6px breathing room stays. */ +details.hwfit-serve-advanced > .hwfit-serve-row { + gap: 4px; + row-gap: 4px; +} +details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp, +details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-llamacpp { + margin-top: -20px; + position: relative; + left: 4px; +} +details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-backend-llamacpp { + column-gap: 8px; + row-gap: 2px; +} +details.hwfit-serve-advanced > .hwfit-llama-placement-row { + margin-top: -6px !important; +} +details.hwfit-serve-advanced > .hwfit-llama-memory-row { + margin-top: -20px !important; +} +.cookbook-llama-cpu-mode details.hwfit-serve-advanced > .hwfit-llama-memory-row { + margin-top: -8px !important; +} +details.hwfit-serve-advanced > .hwfit-llama-batch-row, +details.hwfit-serve-advanced > .hwfit-llama-monitor-row, +details.hwfit-serve-advanced > .hwfit-llama-checks-row { + margin-top: -18px !important; +} +details.hwfit-serve-advanced > .hwfit-diff-settings-row { + row-gap: 2px; +} +details.hwfit-serve-advanced > .hwfit-diff-checks-row, +details.hwfit-serve-advanced > .hwfit-diff-harmonize-row { + margin-top: -10px !important; +} +details.hwfit-serve-advanced > .hwfit-diff-checks-row { + row-gap: 2px; +} +details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-vram-monitor { + gap: 4px !important; + grid-template-columns: max-content minmax(0, 1fr); + white-space: nowrap; +} +details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_block_size"] { + width: calc(100% - 6px); +} +details.hwfit-serve-advanced .hwfit-sf[data-field="vllm_kv_cache_dtype"] { + width: 60px; + min-width: 60px; +} +details.hwfit-serve-advanced > .hwfit-serve-checks { + gap: 4px; + row-gap: 4px; +} +/* Pull the vLLM/SGLang checks row, Extra args, and the trailing + model-specific (Speculative) checks row up tight against the row + above — the previous 4px gap plus per-row baseline padding left a + ~8px gap that read as too airy in the Advanced fold. */ +details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-vllm, +details.hwfit-serve-advanced > .hwfit-serve-checks.hwfit-backend-sglang, +details.hwfit-serve-advanced > .hwfit-serve-extra { + margin-top: -8px; +} +details.hwfit-serve-advanced > .hwfit-serve-extra { + margin-top: -18px; +} +details.hwfit-serve-advanced > .hwfit-serve-row:last-of-type, +details.hwfit-serve-advanced > .hwfit-serve-checks:last-of-type { + margin-bottom: 0; +} + +.settings-fallback-remove { + flex-shrink: 0; + width: 32px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 55%, transparent); + cursor: pointer; + transition: border-color 0.12s, color 0.12s, background 0.12s; +} +.settings-fallback-remove:hover { + border-color: var(--red); + color: var(--red); + background: color-mix(in srgb, var(--red) 10%, transparent); +} +.settings-fallback-add { + align-self: flex-start; + margin-top: 2px; + font-size: 11px; + padding: 3px 9px; + border: 1px dashed var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 65%, transparent); + cursor: pointer; + transition: border-color 0.12s, color 0.12s; +} +.settings-fallback-add:hover { + border-color: var(--red); + color: var(--red); +} +.settings-input { + flex: 1; + padding: 5px 8px; + background: var(--bg); + border: 1px solid var(--border); + border-radius: 6px; + color: var(--fg); + font-family: inherit; + font-size: 12px; + transition: border-color 0.15s; +} +.settings-input:focus { + outline: none; + border-color: var(--red); +} +/* ── Contacts manager (Settings → Integrations → CardDAV) ── */ +.contacts-add-row { + display: flex; + gap: 6px; + margin-bottom: 8px; + align-items: center; +} +.contacts-list { + display: flex; + flex-direction: column; + gap: 2px; + max-height: 360px; + overflow-y: auto; +} +.contact-row { + padding: 6px 4px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 35%, transparent); +} +.contact-row:last-child { border-bottom: none; } +.contact-row-edit { margin-top: 4px; } + +/* ── Sort Dropdown ── */ + +.sort-dropdown { + position: absolute; + right: 0; + top: 100%; + z-index: 1000; + min-width: 120px !important; + width: max-content; + padding: 4px !important; + margin-top: 4px; + background: var(--panel) !important; + border: 1px solid var(--border) !important; + border-radius: 8px !important; + box-shadow: 0 4px 16px rgba(0,0,0,0.4) !important; + backdrop-filter: none !important; +} +.sort-dropdown-item { + cursor: pointer; + padding: 6px 8px 6px 6px !important; + font-size: 11px !important; + border-radius: 6px !important; + border-bottom: none !important; + white-space: nowrap; + transition: background 0.1s; +} +.sort-dropdown-item:hover { + background: color-mix(in srgb, var(--accent) 10%, transparent) !important; +} +.sort-dropdown-item.ai-sort-option { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); +} +.sort-dropdown-item.ai-sort-option .auto-sort-icon { + color: var(--accent, var(--red)); + display: inline-flex; + align-items: center; +} +#auto-sort-sessions-btn { + transform: translateX(-4px); +} +.sort-dropdown-item.ai-sort-option:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent) !important; +} +.sort-dropdown-sep { + border-top: 1px solid var(--border); + margin-top: 2px; + padding-top: 6px; +} + +/* Mobile: bigger taps for the chats sort/funnel menu and the select-mode + bulk-action bar. Also drop the "Rearrange" item — it's a touch-finicky + feature better left to desktop. */ +@media (max-width: 768px) { + /* Nudge the funnel/sort button right on mobile so it doesn't hug the + chevron next to it. */ + #session-sort-btn { transform: translateX(13px); } + #session-sort-dropdown.sort-dropdown { + min-width: 200px !important; + padding: 6px !important; + } + #session-sort-dropdown .sort-dropdown-item { + padding: 12px 14px !important; + font-size: 14px !important; + } + #session-rearrange-toggle { display: none !important; } + + /* Select-mode bar — make Archive / Delete / Cancel buttons + the + Select-all toggle finger-sized. */ + .session-bulk-bar { + padding: 8px 10px; + font-size: 14px; + gap: 10px; + } + .session-bulk-btn { + padding: 10px; + min-width: 44px; + min-height: 44px; + } + .session-bulk-btn svg { width: 20px; height: 20px; } + #session-select-all-dot { font-size: 22px !important; padding: 6px; } + #session-select-all-label { font-size: 14px !important; padding: 4px; } +} + + +/* ── Admin: Built-in tools ── */ +.admin-tool-category { + border: 1px solid var(--border); + border-radius: 6px; + margin-bottom: 6px; + overflow: hidden; + transition: border-color 0.15s; +} +.admin-tool-category:hover { + border-color: color-mix(in srgb, var(--fg) 20%, var(--border)); +} +.admin-tool-cat-header { + font-size: 12px; + font-weight: 500; + padding: 8px 10px; + transition: background 0.15s; +} +.admin-tool-cat-header:hover { + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.admin-tool-cat-body { + border-top: 1px solid var(--border); + padding: 4px 0; +} +.admin-tool-cat-body.hidden { + display: none; +} +.admin-tool-row { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 10px; + padding-right: 28px; /* align toggles with header toggle (chevron 12px + gap 6px + padding) */ + transition: background 0.08s; +} +.admin-tool-row:hover { + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.admin-tool-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 1px; +} +.admin-tool-name { + font-size: 11px; + font-weight: 500; +} +.admin-tool-desc { + font-size: 10px; + opacity: 0.4; +} +.admin-tool-ctx { + font-size: 9px; + opacity: 0.3; + flex-shrink: 0; + min-width: 35px; + text-align: right; + font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; +} + +/* Cookbook serve param hints */ +.hwfit-hint { + display: inline-flex; + align-items: center; + justify-content: center; + width: 12px; + height: 12px; + border-radius: 50%; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + font-size: 8px; + font-weight: 600; + opacity: 0.35; + cursor: help; + margin-left: 2px; + vertical-align: middle; + transition: opacity 0.1s; +} +.hwfit-hint:hover { + opacity: 0.8; + background: color-mix(in srgb, var(--accent) 15%, transparent); +} +.hwfit-dl-dot { + color: var(--green); + font-size: 8px; + margin-left: 4px; + opacity: 0.7; +} +.hwfit-parser-tag { + font-size: 9px; + opacity: 0.4; + background: color-mix(in srgb, var(--fg) 8%, transparent); + padding: 1px 5px; + border-radius: 3px; + margin-left: 4px; + font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; +} + +/* Cookbook output copy — show only on hover */ +.cookbook-output-wrap .cookbook-output-copy { + opacity: 0; + transition: opacity 0.1s; +} + +/* Cached model menu button */ +.hwfit-cached-menu-btn { + background: none; + border: none; + color: var(--fg); + font-size: 16px; + cursor: pointer; + opacity: 0; + padding: 2px 4px; + border-radius: 4px; + transition: opacity 0.1s; + flex-shrink: 0; + position: relative; + top: -2px; +} +.memory-item:hover .hwfit-cached-menu-btn, +.hwfit-cached-item:hover .hwfit-cached-menu-btn { opacity: 0.4; } +.hwfit-cached-menu-btn:hover { opacity: 1 !important; } +.hwfit-cached-menu-btn svg { pointer-events: none; } +@media (max-width: 768px) { + #cookbook-modal .hwfit-cached-menu-btn { + opacity: 0.72 !important; + width: 32px; + height: 32px; + min-width: 32px; + padding: 0; + justify-content: center; + top: -4px; + } + #cookbook-modal .hwfit-cached-menu-btn:active { + opacity: 1 !important; + background: color-mix(in srgb, var(--fg) 9%, transparent); + } +} + +/* Quick run button */ +.cookbook-run-btn { + background: var(--accent, var(--red)) !important; + color: var(--panel) !important; + border-color: var(--accent, var(--red)) !important; + font-weight: 600; +} +.cookbook-run-btn:hover { + opacity: 0.9; +} +#hwfit-cache-scan { + position: relative; + top: 1px; + width: 26px; + height: 24px; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +#hwfit-cache-scan.spinning svg { + animation: modelPickerRefreshSpin 0.75s linear infinite; +} +#serve-search { + height: 28px; + min-height: 28px; + font-family: inherit; + font-size: 11px; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: 9px center; + padding-left: 28px; +} +.cookbook-sort-select-wrap { + position: relative; + display: inline-flex; + flex-shrink: 0; + align-items: center; +} +.cookbook-sort-select-wrap > svg { + position: absolute; + left: 9px; + z-index: 1; + pointer-events: none; + opacity: 0.65; + transform: translateY(4px); +} + +/* Cookbook model directory tags */ +.cookbook-modeldir-tag { + font-size: 11px; + padding: 3px 8px; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + display: inline-flex; + align-items: center; + gap: 4px; + font-family: 'Berkeley Mono', 'SF Mono', monospace; +} +.cookbook-modeldir-default { + opacity: 0.5; +} +.cookbook-server-bottom-actions { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 4px; + margin-top: 5px; +} +.cookbook-server-bottom-actions > button { + height: 22px; + box-sizing: border-box; + display: inline-flex; + align-items: center; +} +/* Default-server radio-check in a Settings server title (same look as the + model-dir download target). Active = accent-tinted. */ +.cookbook-srv-default { + cursor: pointer; + opacity: 0.35; + display: inline-flex; + align-items: center; + gap: 3px; + line-height: 1; + transition: opacity 0.12s, color 0.12s; +} +.cookbook-srv-default:hover { opacity: 0.8; } +.cookbook-srv-default.active { opacity: 1; color: var(--accent, var(--red)); } +.cookbook-srv-default-label { font-size: 10px; font-weight: 600; letter-spacing: 0.02em; } +.cookbook-srv-default-icon { + display: inline-flex; + align-items: center; + line-height: 0; +} +.cookbook-srv-default-icon svg { + width: 13px; + height: 13px; +} +.cookbook-srv-default-icon, +.cookbook-srv-default-label { + position: relative; + top: 2px; +} +.email-settings-default, +.email-settings-default .cookbook-srv-default-icon, +.email-settings-default .cookbook-srv-default-label { + position: relative; + top: -2px; +} +/* Download-target toggle inside a model-dir tag */ +.cookbook-modeldir-dl { + cursor: pointer; + opacity: 0.35; + display: inline-flex; + align-items: center; + line-height: 0; + transition: opacity 0.12s, color 0.12s; +} +.cookbook-modeldir-dl:hover { opacity: 0.8; } +.cookbook-modeldir-dl.active { opacity: 1; color: var(--accent, var(--red)); } +/* The tag currently flagged as the download target — clearly highlighted so + it's obvious where downloads land. */ +.cookbook-modeldir-target { + opacity: 1; + color: var(--accent, var(--red)); + font-weight: 600; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); +} +.cookbook-modeldir-rm { + cursor: pointer; + opacity: 0.4; + font-size: 10px; +} +.cookbook-modeldir-rm:hover { + opacity: 1; +} +.cookbook-modeldir-add { + background: none; + border: 1px solid var(--border); + border-radius: 4px; + color: var(--fg); + font-size: 10px; + font-weight: 500; + height: 19.5px; + box-sizing: border-box; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 2px; + position: relative; + top: -3px; + padding: 0 9px; + transition: border-color 0.12s, background 0.12s; +} +.cookbook-modeldir-add:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); } + +/* Cookbook serve optimizations */ +.hwfit-serve-opts { + display: flex; + align-items: center; + gap: 8px; + margin: 4px 0; +} +.hwfit-apply-opts { + font-size: 11px !important; + min-width: auto !important; + white-space: nowrap; +} +.hwfit-opts-desc { + font-size: 9px; + opacity: 0.4; + font-style: italic; +} + +/* Library modal tabs */ +.lib-tabs, +.admin-tabs { + display: flex; + gap: 0; + margin-bottom: 8px; + border-bottom: 1px solid var(--border); +} +.lib-tab, +.admin-tab { + background: none; + border: none; + border-bottom: 2px solid transparent; + color: var(--fg-muted); + font-size: 12px; + font-family: inherit; + padding: 6px 14px; + cursor: pointer; + transition: color 0.1s, border-color 0.1s; +} +.lib-tab:hover, +.admin-tab:hover { color: var(--fg); } +.lib-tab.active, +.admin-tab.active { + color: var(--accent, var(--red)); + border-bottom-color: var(--accent, var(--red)); +} + +/* Cookbook tab count badge */ +.cookbook-tab-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 16px; + height: 16px; + padding: 0 4px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent, var(--red)) 20%, transparent); + color: var(--accent, var(--red)); + font-size: 9px; + font-weight: 700; + margin-left: 4px; + line-height: 1; +} + +/* ── Generate Visual Report button (deep research) ── */ +.view-report-btn { + display: inline-flex; + align-items: center; + gap: 6px; + margin-top: 12px; + padding: 8px 16px; + border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--red) 10%, transparent); + color: var(--fg); + font-family: inherit; + font-size: 0.88em; + font-weight: 500; + transition: background 0.15s ease; + cursor: pointer; +} +.view-report-btn:hover:not(:disabled) { + background: color-mix(in srgb, var(--red) 18%, transparent); +} +.view-report-btn svg { + flex-shrink: 0; +} +.report-btn-wrap { + margin-top: 12px; + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.view-report-btn.chat-about-btn { + border-color: color-mix(in srgb, var(--fg) 22%, transparent); + background: color-mix(in srgb, var(--fg) 6%, transparent); +} +.view-report-btn.chat-about-btn:hover:not(:disabled) { + background: color-mix(in srgb, var(--fg) 12%, transparent); +} +.view-report-btn:disabled { + opacity: 0.6; + cursor: progress; +} +.report-spinner { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.88em; + color: var(--fg); + opacity: 0.7; +} +.report-spinner-text { + font-weight: 500; +} +.report-spinner-dots::after { + content: ''; + animation: report-dots 1.4s steps(4, end) infinite; +} +@keyframes report-dots { + 0% { content: ''; } + 25% { content: '.'; } + 50% { content: '..'; } + 75% { content: '...'; } + 100% { content: ''; } +} + +/* ── Continue research hint ────────────────────────── */ +.continue-research-wrap { + margin-top: 8px; +} +.continue-research-hint { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 0.78em; + color: var(--fg-dim, var(--fg)); + opacity: 0.5; + padding: 4px 0; +} +.continue-research-hint svg { + flex-shrink: 0; + opacity: 0.6; +} + +/* ── Research reconnect & timer ────────────────────── */ +.msg.research-reconnect { + border-left: 3px solid var(--red); + background: color-mix(in srgb, var(--red) 5%, transparent); +} +.research-timer { + font-size: 0.8em; + opacity: 0.6; + margin-top: 4px; + font-family: monospace; + color: var(--fg-dim, var(--fg)); +} + +/* ── Research synapse visualization ─────────────────────────────── + Live SVG graph of an in-flight deep-research run. The query is the + central node; sub-questions branch off per round; sources are leaf + nodes that pop in as they're captured. Pulses indicate activity. */ +.research-synapse { + margin: 6px 0 4px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 24%, var(--border)); + border-radius: 7px; + background: + linear-gradient(color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), + linear-gradient(90deg, color-mix(in srgb, var(--fg) 3%, transparent) 1px, transparent 1px), + radial-gradient(ellipse at 50% 48%, color-mix(in srgb, var(--accent, var(--red)) 13%, transparent) 0%, transparent 62%), + color-mix(in srgb, var(--panel) 68%, var(--bg)); + background-size: 18px 18px, 18px 18px, auto, auto; + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--fg) 5%, transparent); + overflow: hidden; +} +.research-synapse .rs-stage { + height: 400px; + position: relative; +} +.research-synapse .rs-meta-header { + position: absolute; + z-index: 4; + top: 0; + left: 0; + right: 0; + border-top: 0; + border-bottom: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + padding: 5px 8px 6px; + background: color-mix(in srgb, var(--panel) 76%, transparent); +} +.research-synapse-ascii-filter > .rs-stage > svg { + display: none; +} +.research-synapse-ascii-filter .rs-ascii-overlay { + position: absolute; + z-index: 3; + inset: 28px 10px 10px; + margin: 0; + overflow: hidden; + pointer-events: none; + color: color-mix(in srgb, var(--accent, var(--red)) 76%, var(--fg)); + font: 600 10px/1.35 ui-monospace, SFMono-Regular, Consolas, monospace; + white-space: pre; + text-align: center; + opacity: 1; + text-shadow: 0 0 7px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); + mix-blend-mode: screen; +} +.research-synapse-compact .rs-meta { padding: 5px 8px 6px; font-size: 10px; } +.research-synapse-compact .rs-label-sub { font-size: 8px; } +.research-synapse .rs-stage-head { + position: absolute; + z-index: 2; + top: 7px; + left: 9px; + right: 9px; + display: flex; + align-items: center; + gap: 5px; + color: var(--fg-dim, var(--fg)); + font: 600 8px/1 ui-monospace, "JetBrains Mono", monospace; + text-transform: uppercase; + pointer-events: none; +} +.research-synapse .rs-live-dot { + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--accent, var(--red)); + box-shadow: 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 75%, transparent); + animation: rs-live 1.8s ease-in-out infinite; +} +.research-synapse .rs-phase-chip { + margin-left: auto; + padding: 3px 5px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, var(--border)); + border-radius: 3px; + background: color-mix(in srgb, var(--accent, var(--red)) 8%, var(--bg)); + color: var(--accent, var(--red)); +} +.research-synapse svg { + display: block; + width: 100%; + height: 100%; +} +.research-synapse .rs-edge { + stroke: color-mix(in srgb, var(--accent, var(--red)) 32%, var(--border)); + stroke-width: 1.35; + fill: none; + opacity: 0.72; +} +.research-synapse .rs-node { + fill: var(--bg); + stroke: var(--accent, var(--red)); + stroke-width: 1.5; + transition: all 0.3s ease; + transform-box: fill-box; + transform-origin: center; +} +.research-synapse .rs-source-index { + fill: var(--fg); + font: 650 4.5px/1 ui-monospace, "JetBrains Mono", monospace; + pointer-events: none; + opacity: 0.88; +} +.research-synapse .rs-node-expanded { animation: rs-hub-expand 0.75s ease-out; } +.research-synapse .rs-node-new { + animation: rs-pop 0.6s ease-out; +} +.research-synapse .rs-pulse { + fill: var(--accent, var(--red)); + opacity: 0; + animation: rs-pulse 2.6s ease-out infinite; + transform-box: fill-box; + transform-origin: center; +} +.research-synapse .rs-label { + fill: var(--fg); + font-size: 10px; + font-family: ui-monospace, "JetBrains Mono", monospace; + pointer-events: none; + opacity: 0.85; +} +.research-synapse .rs-label-sub { + font-size: 9px; + opacity: 0.7; +} +.research-synapse .rs-meta { + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto auto; + align-items: center; + gap: 10px; + padding: 6px 10px 8px; + font-family: ui-monospace, "JetBrains Mono", monospace; + font-size: 11px; + color: var(--fg-dim, var(--fg)); + opacity: 0.92; + border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + background: color-mix(in srgb, var(--panel) 76%, transparent); +} +.research-synapse .rs-meta .rs-status { + display: none; + color: var(--accent, var(--red)); + font-weight: 600; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.research-synapse .rs-meta b { + color: var(--fg); + font-weight: 600; +} +.research-synapse.rs-complete .rs-meta .rs-status { color: var(--color-success, #4caf50); } +.research-synapse.rs-error .rs-meta .rs-status { color: var(--red); } diff --git a/static/css/12-gallery.css b/static/css/12-gallery.css new file mode 100644 index 000000000..16f47e863 --- /dev/null +++ b/static/css/12-gallery.css @@ -0,0 +1,839 @@ +/* 12-gallery - 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. + */ +.research-synapse.rs-phase-reading .rs-phase-chip, +.research-synapse.rs-phase-analyzing .rs-phase-chip { color: var(--gold, #d4a72c); } +.research-synapse.rs-phase-writing .rs-phase-chip { color: var(--color-success, #4caf50); } + +@media (prefers-reduced-motion: reduce) { + .research-synapse .rs-live-dot, + .research-synapse .rs-pulse, + .research-synapse .rs-node-new, + .research-synapse .rs-node-expanded, + .research-synapse .rs-edge-firing { animation: none !important; } +} + +/* ── Raw findings items (inside sources-style box) ── */ +.finding-item { + display: flex; + flex-direction: column; + gap: 4px; +} +.finding-item .source-link { + border-radius: 6px 6px 0 0; +} +.finding-summary { + padding: 6px 8px 8px; + font-size: 0.82em; + line-height: 1.5; + color: var(--fg-dim, var(--fg)); + opacity: 0.85; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); + margin-bottom: 4px; + white-space: pre-wrap; + word-break: break-word; +} +.finding-item:last-child .finding-summary { + border-bottom: none; + margin-bottom: 0; +} + +/* ═══════════════════════════════════════════════════════════ + Gallery Editor + ═══════════════════════════════════════════════════════════ */ + +/* Tab bar */ +.gallery-tabs { + display: flex; + gap: 0; + border-bottom: 1px solid var(--border); + padding: 0 16px; + background: var(--panel); +} +.gallery-tab { + padding: 8px 18px; + background: none; + border: none; + border-bottom: 2px solid transparent; + color: var(--fg); + opacity: 0.6; + cursor: pointer; + font-size: 13px; + font-weight: 500; + transition: opacity 0.15s, border-color 0.15s; +} +.gallery-tab:hover { opacity: 0.85; } +.gallery-tab.active { + opacity: 1; + border-bottom-color: var(--red); +} + +/* Narrow modal tab strips should stay on one row. Resized docked windows can + be much narrower than the viewport, so this cannot live only in mobile media + queries. */ +.cookbook-tabs, +.memory-tabs, +.admin-tabs, +.lib-tabs, +.gallery-tabs, +.preset-tabs { + flex-wrap: nowrap !important; + overflow-x: auto !important; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + overscroll-behavior-x: contain; + scrollbar-width: none; +} +.cookbook-tabs::-webkit-scrollbar, +.memory-tabs::-webkit-scrollbar, +.admin-tabs::-webkit-scrollbar, +.lib-tabs::-webkit-scrollbar, +.gallery-tabs::-webkit-scrollbar, +.preset-tabs::-webkit-scrollbar { + display: none; +} +.cookbook-tabs > *, +.memory-tabs > *, +.admin-tabs > *, +.lib-tabs > *, +.gallery-tabs > *, +.preset-tabs > * { + flex: 0 0 auto; +} +.cookbook-tab, +.memory-tab, +.admin-tab, +.lib-tab, +.gallery-tab, +.preset-tab { + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; + line-height: 1; +} +.gallery-tab { + gap: 6px; +} + +@container (max-width: 360px) { + .cookbook-tab:has(svg), + .memory-tab:has(svg), + .admin-tab:has(svg), + .lib-tab:has(svg), + .gallery-tab:has(svg), + .preset-tab:has(svg) { + width: 34px; + min-width: 34px; + padding-left: 0; + padding-right: 0; + font-size: 0; + } + + .cookbook-tab:has(svg) svg, + .memory-tab:has(svg) svg, + .admin-tab:has(svg) svg, + .lib-tab:has(svg) svg, + .gallery-tab:has(svg) svg, + .preset-tab:has(svg) svg { + width: 14px; + height: 14px; + margin-right: 0 !important; + vertical-align: middle !important; + } + + .memory-tab:has(svg) .memory-count, + .gallery-tab:has(svg) .gallery-tab-label, + .gallery-tab:has(svg) .gallery-tab-close, + .cookbook-tab:has(svg) .cookbook-tab-count, + .preset-tab:has(svg) .preset-count { + display: none !important; + } +} +/* Icon + label layout inside each tab. */ +.gallery-tab { + display: inline-flex; + align-items: center; + gap: 6px; +} +.gallery-tab-icon { + display: inline-flex; + align-items: center; + opacity: 0.85; +} +.gallery-tab.active .gallery-tab-icon { opacity: 1; } +/* Close × on the Edit tab — appears on hover. */ +.gallery-tab-close { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + margin-left: 2px; + border-radius: 4px; + opacity: 0; + transition: opacity 0.12s, background 0.12s; + /* Use a slightly larger glyph at a larger line-height so the × sits + visually centered — the U+00D7 character has uneven em-box metrics. */ + font-size: 16px; + line-height: 16px; + font-weight: 500; + cursor: pointer; + padding-bottom: 2px; + box-sizing: border-box; +} +.gallery-tab:hover .gallery-tab-close, +.gallery-tab.active .gallery-tab-close { opacity: 0.65; } +/* Hide the close × entirely on the Edit tab when there's no edit open. */ +.gallery-tab[data-tab="editor"]:not(.has-edit) .gallery-tab-close { display: none !important; } +.gallery-tab-close:hover { opacity: 1 !important; background: color-mix(in srgb, var(--red) 25%, transparent); color: var(--red); } +/* Inline rename input shown when the Edit tab is double-clicked. */ +.gallery-tab-rename-input { + background: var(--bg); + border: 1px solid var(--red); + border-radius: 3px; + color: var(--fg); + font: inherit; + font-size: 13px; + padding: 1px 4px; + width: 140px; + outline: none; +} + +/* Albums tab — grid of album cards with cover thumbnails. */ +.gallery-albums-container { + padding: 12px 4px; + max-height: calc(92vh - 120px); + overflow-y: auto; + border: 2px dashed transparent; + border-radius: 8px; + transition: border-color 0.15s, background 0.15s; +} +.gallery-settings-container { + padding: 8px 4px; + max-height: calc(92vh - 120px); + overflow-y: auto; +} +.gallery-albums-container.gallery-dragover { + border-color: var(--red); + background: color-mix(in srgb, var(--red) 5%, transparent); +} +.gallery-albums-empty { + display: flex; flex-direction: column; align-items: center; gap: 12px; + padding: 48px 16px; + opacity: 0.7; + font-size: 13px; +} +.gallery-albums-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 12px; +} +.gallery-album-card { + position: relative; + display: flex; + flex-direction: column; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + overflow: hidden; + cursor: pointer; + transition: transform 0.12s, border-color 0.12s, box-shadow 0.12s; +} +.gallery-album-card:hover { + transform: translateY(-2px); + border-color: var(--red); + box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 12%, transparent); +} +.gallery-album-menu-btn { + position: absolute; + top: 6px; right: 6px; + width: 24px; height: 24px; + display: flex; align-items: center; justify-content: center; + background: color-mix(in srgb, var(--bg) 70%, transparent); + color: var(--fg); + border: 1px solid color-mix(in srgb, var(--border) 60%, transparent); + border-radius: 6px; + cursor: pointer; + opacity: 0; + transition: opacity 0.12s, background 0.12s; + z-index: 2; + backdrop-filter: blur(4px); + /* Plain "…" text — the previous SVG was rendering as a flat black + block on some themes when its currentColor didn't contrast. */ + font-size: 16px; + line-height: 1; + font-weight: 600; + padding: 0 0 4px; +} +.gallery-album-card:hover .gallery-album-menu-btn, +.gallery-album-menu-btn:focus-visible { opacity: 1; } +.gallery-album-menu-btn:hover { background: var(--bg); } +.gallery-album-menu-pop { + position: absolute; + top: 34px; right: 6px; + min-width: 140px; + background: var(--panel); + border: 1px solid var(--border); + border-radius: 8px; + box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); + display: flex; flex-direction: column; + padding: 4px; + z-index: 3; +} +.gallery-album-menu-pop[hidden] { display: none; } +.gallery-album-cover { + aspect-ratio: 4 / 3; + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + display: flex; align-items: center; justify-content: center; + overflow: hidden; +} +.gallery-album-placeholder { + opacity: 0.4; + font-size: 40px; + display: flex; align-items: center; justify-content: center; + width: 100%; height: 100%; +} +.gallery-album-info { + padding: 8px 10px; + display: flex; flex-direction: column; gap: 2px; +} +.gallery-album-name { + font-size: 13px; + font-weight: 500; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; +} +.gallery-album-count { + font-size: 11px; + opacity: 0.55; +} +.gallery-album-card-add { + border-style: dashed; + opacity: 0.7; +} +.gallery-album-card-add:hover { opacity: 1; } +.gallery-album-action-tile { + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); +} + +/* Edit-tab empty state — shown before any image is loaded. */ +.gallery-editor-landing { + display: flex; flex-direction: column; align-items: center; justify-content: center; + gap: 10px; + padding: 28px 16px; + flex: 1; + text-align: center; + color: var(--fg); +} +.gallery-editor-landing h3 { + margin: 0; + font-size: 16px; + font-weight: 600; +} +.gallery-editor-heading { + display: inline-flex; + align-items: center; + gap: 7px; +} +.gallery-editor-heading > svg { opacity: 0.6; flex: 0 0 auto; } +/* "vision model" link in the AI-tagging description → opens Settings. */ +.ge-vision-link { + color: var(--accent, var(--red)); + text-decoration: underline; + text-underline-offset: 2px; + cursor: pointer; +} +.ge-vision-link:hover { opacity: 0.8; } +.ge-alpha-tag { + font-size: 9px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + vertical-align: middle; + padding: 1px 5px; + border-radius: 4px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + position: relative; + top: -1px; +} +.gallery-editor-landing-actions { + display: flex; gap: 10px; + margin-top: 8px; +} +/* Bigger primary action buttons specifically inside the editor landing — + the gallery-wide .gallery-select-btn is a compact toolbar style which is + too small for the empty-state hero. */ +.gallery-editor-landing-actions .gallery-select-btn { + padding: 7px 22px 17px; + font-size: 13px; + opacity: 0.85; +} +/* Visible template picker: every canvas ratio is inspectable without opening + a native menu, and its SVG outline previews the resulting canvas shape. */ +.gallery-editor-template-picker { + width: min(760px, 96%); + margin-top: 10px; +} +.gallery-editor-template-picker h4 { + margin: 0 0 7px; + font-size: 11px; + opacity: 0.55; + text-transform: uppercase; + letter-spacing: 0.06em; +} +.gallery-editor-template-options { + display: grid; + grid-template-columns: repeat(4, minmax(105px, 1fr)); + gap: 6px; +} +.gallery-editor-template-option { + height: 72px; + min-width: 0; + padding: 6px 7px; + display: grid; + grid-template-rows: 28px auto auto; + justify-items: center; + align-items: center; + background: var(--panel); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 5px; + font: inherit; + font-size: 11px; + cursor: pointer; + overflow: hidden; +} +.gallery-editor-template-option svg { color: var(--accent, var(--red)); } +.gallery-editor-template-option:hover, +.gallery-editor-template-option:focus-visible { + outline: none; + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 7%, var(--panel)); +} +.gallery-editor-template-option span { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 600; +} +.gallery-editor-template-option small { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 9px; + opacity: 0.52; +} +@media (max-width: 620px) { + .gallery-editor-landing { + justify-content: flex-start; + gap: 7px; + padding: 18px 12px; + overflow-y: auto; + } + .gallery-editor-landing-actions { + width: 100%; + gap: 6px; + justify-content: center; + } + .gallery-editor-landing-actions .gallery-select-btn { + flex: 1 1 0; + min-width: 0; + max-width: 180px; + padding: 6px 8px 10px; + font-size: 11px; + } + .gallery-editor-template-picker { + width: 100%; + margin-top: 4px; + } + .gallery-editor-template-options { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + /* The common square, widescreen, portrait, and Instagram presets are + enough to start on a phone. Keep the print and 4K presets on desktop. */ + .gallery-editor-template-option:nth-child(n + 5) { + display: none; + } +} +/* Saved-drafts grid on the editor landing. Surfaces every persisted + in-progress project so the user can resume without re-opening the + original photo. Each card: thumbnail + name + last-saved hint + ×. */ +.gallery-editor-drafts { + width: min(720px, 92%); + margin: 28px auto 0; /* centered */ + padding-top: 18px; + border-top: 1px solid var(--border); + display: flex; + flex-direction: column; + gap: 10px; +} +.gallery-editor-drafts { + position: relative; +} +.gallery-editor-drafts-loading { + position: absolute; + inset: 30px 0 0 0; + z-index: 4; + display: flex; + align-items: center; + justify-content: center; + background: var(--panel); + border-radius: 6px; + min-height: 80px; +} +.gallery-editor-drafts-header { + display: flex; + align-items: center; + justify-content: center; /* center the title / search / select */ + gap: 8px; + margin-bottom: 8px; +} +.gallery-editor-drafts-title { + margin: 0; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.06em; + opacity: 0.55; + font-weight: 600; + text-align: left; + flex-shrink: 0; +} +.gallery-editor-drafts-search { + flex: 1 1 auto; + min-width: 0; + max-width: none; + height: 26px; /* same thickness as the Select button */ + box-sizing: border-box; + padding: 0 8px; + font-size: 12px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; +} +.gallery-editor-drafts-search:focus { outline: none; border-color: var(--red); } +/* The shared .gallery-select-btn has a -4px top margin + tall asymmetric padding + meant for the main gallery toolbar; in this header it makes the button a + different height / offset from the search box. Normalize so it lines up. */ +.gallery-editor-drafts-header .gallery-select-btn { + margin-top: 0 !important; + height: 26px; /* match the search bar */ + box-sizing: border-box; + padding: 0 10px; + font-size: 12px; + line-height: 1; + flex-shrink: 0; +} + +.cookbook-official-filter { + display: inline-flex; + align-items: center; + gap: 4px; + flex: 0 1 auto; + min-width: 0; + max-width: 100%; + color: var(--fg); + font-size: 10px; + opacity: 0.72; + white-space: nowrap; + position: relative; + top: 1px; +} +.cookbook-official-filter > span:first-child { + position: relative; + top: 1px; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: 9px; +} +.cookbook-official-filter .toggle { + width: 26px; + height: 14px; +} +.cookbook-official-filter .toggle .slider:before { + width: 10px; + height: 10px; + left: 2px; + bottom: 2px; +} +.cookbook-official-filter .toggle input:checked + .slider:before { + transform: translateX(12px); +} +#gallery-editor-drafts-select { margin-left: auto; flex-shrink: 0; } +#gallery-editor-drafts-select.active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: var(--accent, var(--red)); + color: var(--accent, var(--red)); +} +/* Graceful exit when a project card is deleted — fade + shrink before re-render. */ +.gallery-editor-draft-card.gallery-draft-removing { + opacity: 0; + transform: scale(0.92); + transition: opacity 0.24s ease, transform 0.24s ease; + pointer-events: none; +} +/* Draft select dot uses the standard small .gallery-select-dot look (same as the + photo grid) — no oversized white-ring checkbox. */ +.gallery-editor-drafts-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); + gap: 10px; +} +.gallery-drafts-just-opened > .gallery-editor-draft-card { + animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; +} +.gallery-drafts-just-opened > :nth-child(1) { animation-delay: 0.02s; } +.gallery-drafts-just-opened > :nth-child(2) { animation-delay: 0.04s; } +.gallery-drafts-just-opened > :nth-child(3) { animation-delay: 0.06s; } +.gallery-drafts-just-opened > :nth-child(4) { animation-delay: 0.08s; } +.gallery-drafts-just-opened > :nth-child(5) { animation-delay: 0.10s; } +.gallery-drafts-just-opened > :nth-child(6) { animation-delay: 0.12s; } +.gallery-drafts-just-opened > :nth-child(7) { animation-delay: 0.14s; } +.gallery-drafts-just-opened > :nth-child(8) { animation-delay: 0.16s; } +.gallery-drafts-just-opened > :nth-child(9) { animation-delay: 0.18s; } +.gallery-drafts-just-opened > :nth-child(10) { animation-delay: 0.20s; } +.gallery-drafts-just-opened > :nth-child(11) { animation-delay: 0.22s; } +.gallery-drafts-just-opened > :nth-child(12) { animation-delay: 0.24s; } +.gallery-drafts-just-opened > :nth-child(13) { animation-delay: 0.26s; } +.gallery-drafts-just-opened > :nth-child(14) { animation-delay: 0.28s; } +.gallery-drafts-just-opened > :nth-child(15) { animation-delay: 0.30s; } +.gallery-drafts-just-opened > :nth-child(16) { animation-delay: 0.32s; } +.gallery-drafts-just-opened > :nth-child(17) { animation-delay: 0.34s; } +.gallery-drafts-just-opened > :nth-child(18) { animation-delay: 0.36s; } +.gallery-drafts-just-opened > :nth-child(19) { animation-delay: 0.38s; } +.gallery-drafts-just-opened > :nth-child(20) { animation-delay: 0.40s; } +.gallery-drafts-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } +.gallery-drafts-view-toggle { + display: inline-flex; + position: relative; + top: 0; + height: 24px; + flex: 0 0 auto; + overflow: hidden; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); +} +.gallery-drafts-view-toggle button { + width: 25px; + height: 22px; + margin: 0; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + border: 0; + border-right: 1px solid var(--border); + background: transparent; + color: color-mix(in srgb, var(--fg) 55%, transparent); + cursor: pointer; + line-height: 0; +} +.gallery-drafts-view-toggle button:last-child { border-right: 0; } +.gallery-drafts-view-toggle button:hover { color: var(--fg); } +.gallery-drafts-view-toggle button.active { + height: 22px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-thumb { + width: 44px; + height: 44px; + aspect-ratio: auto; +} +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete { + position: static; + opacity: 0.55; + background: transparent; + color: var(--fg); +} +.gallery-editor-draft-delete-trash { display: none; } +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete-trash { + display: block; + width: 18px; + height: 18px; + position: relative; + top: -2px; +} +.gallery-editor-drafts-grid.list-view .gallery-editor-draft-delete:hover { + opacity: 1; + color: var(--color-error, #f44); + background: color-mix(in srgb, var(--color-error, #f44) 10%, transparent); +} +.gallery-editor-draft-card { + position: relative; + display: flex; + flex-direction: column; + gap: 4px; + padding: 6px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 70%, transparent); + cursor: pointer; + transition: border-color 0.15s, background 0.15s, transform 0.15s; + text-align: left; +} +.gallery-editor-draft-card:hover { + border-color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--panel) 90%, transparent); +} +.gallery-editor-draft-thumb { + width: 100%; + aspect-ratio: 1; + object-fit: cover; + border-radius: 4px; + background: var(--bg); + display: block; +} +.gallery-editor-draft-thumb-empty { + background: repeating-linear-gradient( + 45deg, + color-mix(in srgb, var(--fg) 4%, transparent), + color-mix(in srgb, var(--fg) 4%, transparent) 6px, + transparent 6px, + transparent 12px + ); +} +.gallery-editor-draft-info { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} +.gallery-editor-draft-name { + font-size: 12px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.gallery-editor-draft-meta { + font-size: 10px; + opacity: 0.55; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.gallery-editor-draft-delete { + position: absolute; + top: -2px; + right: 8px; + width: 22px; + height: 22px; + border-radius: 50%; + border: none; + background: rgba(0, 0, 0, 0.55); + color: #fff; + font-size: 14px; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + transition: opacity 0.12s, background 0.12s; +} +.gallery-editor-draft-delete { + padding-bottom: 2px; +} +.gallery-editor-draft-delete:hover { background: var(--red); } +/* On very narrow phones, give project search its own row instead of + compressing the title and view controls into an overflowing header. */ +@media (max-width: 360px) { + .gallery-editor-drafts-header { + flex-wrap: wrap; + justify-content: flex-start; + gap: 6px; + } + .gallery-editor-drafts-title { + flex: 1 1 auto; + } + .gallery-editor-drafts-search { + order: 10; + flex: 1 1 100%; + width: 100%; + max-width: none; + } + #gallery-editor-drafts-select { + margin-left: 0; + } +} +/* Editor layout */ +.gallery-editor { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + height: 100%; + overflow: hidden; + /* Prevent the editor's own scroll/touch from chaining out to the + gallery modal or the page body — critical on mobile so touching + the canvas doesn't slide the whole page. */ + overscroll-behavior: contain; + touch-action: pan-y; +} +/* Pin the topbar to the top of the editor so its action buttons remain + visible if the gallery modal body itself ends up scrolling. */ +.ge-topbar { position: sticky; top: 0; z-index: 5; } + +/* Top bar */ +.ge-topbar { + display: flex; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: thin; + align-items: center; + justify-content: space-between; + padding: 6px 10px; + background: var(--panel); + border-bottom: 1px solid var(--border); + flex-shrink: 0; + gap: 8px; +} +.ge-topbar-left, .ge-topbar-right { + display: flex; + flex: 0 0 auto; + align-items: center; + gap: 4px; +} +.ge-topbar-right > * { + flex-shrink: 0; +} +.ge-topbar .dropdown[popover] { + position: fixed; + margin: 0; + bottom: auto; + max-height: calc(100vh - 80px); + overflow-y: auto; +} +.ge-draft-status { + flex: 0 1 auto; + min-width: 0; + max-width: 92px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: color-mix(in srgb, var(--fg) 52%, transparent); + font-size: 10px; + line-height: 1; + transition: color 0.15s, opacity 0.15s; +} diff --git a/static/css/style-part-06.css b/static/css/13-image-editor.css similarity index 71% rename from static/css/style-part-06.css rename to static/css/13-image-editor.css index a781aeda8..d759ed7d3 100644 --- a/static/css/style-part-06.css +++ b/static/css/13-image-editor.css @@ -1,14 +1,551 @@ -/* Fragment 7/9 of the former static/style.css. +/* 13-image-editor - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ +.ge-draft-status[data-state="pending"], +.ge-draft-status[data-state="saving"] { color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); } +.ge-draft-status[data-state="saved"] { color: color-mix(in srgb, var(--color-success, #4ade80) 78%, var(--fg)); } +.ge-draft-status[data-state="error"] { color: var(--color-error, #ef4444); } +/* "ALPHA" badge — flags the editor as in-development. */ +.ge-alpha-badge { + flex-shrink: 0; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.1em; + line-height: 1; + padding: 3px 6px; + margin-right: 4px; + border-radius: 4px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + text-transform: uppercase; + user-select: none; + cursor: default; +} +@media (max-width: 768px) { + /* Keep the toolbar tight on mobile — badge stays but shrinks. */ + .ge-alpha-badge { font-size: 8px; padding: 2px 5px; margin-right: 2px; } +} +.ge-diffusion-status { + font-size: 11px; + cursor: pointer; + padding: 2px 8px; + border-radius: 4px; + transition: all 0.15s; + white-space: nowrap; +} +.ge-diffusion-status.online { + color: var(--color-success, #4ade80); +} +.ge-diffusion-status.offline { + color: var(--color-error, #ef4444); + opacity: 0.7; +} +.ge-diffusion-status.offline:hover { + opacity: 1; + background: color-mix(in srgb, var(--color-error) 10%, transparent); +} +.ge-topbar-fill { + display: inline-flex; + align-items: center; + gap: 4px; + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +.ge-topbar-fill:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); + border-color: var(--accent, var(--red)); +} +/* Mask-color swatch in the topbar — small label + circular swatch so + the user can pick a contrasting overlay colour from anywhere. */ +.ge-topbar-mask-color-wrap { + display: inline-flex; + align-items: center; + gap: 4px; + cursor: pointer; +} +.ge-topbar-mask-color-label { + font-size: 10px; + opacity: 0.6; + text-transform: uppercase; + letter-spacing: 0.06em; +} +.ge-topbar-mask-color { + width: 18px; + height: 18px; + flex: 0 0 18px; +} +.ge-topbar-mask-color.cp-swatch-input { + width: 18px; + height: 18px; + flex: 0 0 18px; + border-radius: 50%; + padding: 0; +} +.ge-topbar-sep { + width: 1px; + height: 16px; + background: var(--border); + margin: 0 4px; +} + +/* Editor body (toolbar + canvas + panel) */ +.ge-editor-body { + display: flex; + flex: 1; + min-height: 0; + overflow: hidden; + position: relative; +} +.gallery-editor-container { + flex: 1; + min-height: 0; + display: none; +} + +.ge-toolbar { + display: flex; + flex-direction: column; + gap: 2px; + /* Asymmetric horizontal padding shifts the whole column 4 px left — + buttons, hover backgrounds, active highlight pills, and section + separators all move together. */ + padding: 8px 8px 8px 0; + background: var(--panel); + border-right: 1px solid var(--border); + width: 64px; + flex-shrink: 0; + min-height: 0; + overflow-x: hidden; + overflow-y: auto; + scrollbar-width: none; +} +.ge-toolbar::-webkit-scrollbar { display: none; } +.ge-tool-sep { + font-size: 8px; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-muted); + text-align: center; + padding: 8px 0 4px; + border-top: 1px solid var(--border); + margin-top: 4px; + opacity: 0.6; + flex-shrink: 0; +} +/* All tool buttons share a fixed height so the column reads as a clean + grid. Long labels (e.g. "Remove BG") truncate with ellipsis instead of + wrapping to two lines and making one button taller than the others. */ +.ge-tool-btn { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + padding: 0 2px; + height: 42px; + flex-shrink: 0; + border: none; + background: none; + color: var(--fg); + opacity: 0.6; + cursor: pointer; + border-radius: 6px; + transition: background 0.15s, opacity 0.15s; +} +.ge-tool-btn { position: relative; } +/* Hover background is now a pseudo so we can shift it 2 px left + independently from the button's own bounds (which contain the icon + and label at their already-shifted positions). */ +.ge-tool-btn:hover { opacity: 0.85; } +.ge-tool-btn.active { + opacity: 1; + color: var(--red); + background: none; + /* Padding stays the same as inactive buttons so the icon stays + horizontally aligned with its neighbors. The taller highlight is + drawn by ::before so it can extend up/down without pushing the + button or shifting the icon. */ +} +/* Tool button contents stay above the active highlight pseudo. Also + shifted an additional 2 px left so the icon + label sit further inside + the highlight pill (which still anchors to the button bounds). */ +.ge-tool-btn > * { position: relative; z-index: 1; left: -2px; } +.ge-tool-icon { font-size: 18px; line-height: 1; } +.ge-tool-label { font-size: 10px; line-height: 1.25; } +/* AI badge — same ✦ glyph the Enhance tool uses, pinned to the top- + left of any tool button marked `ai: true`. Same color as the icon + itself (inherits foreground) so it reads as part of the tool, not + a flag. */ +.ge-tool-ai { + position: absolute !important; + top: 1px; + z-index: 2; + color: inherit; + opacity: 0.7; + pointer-events: none; + font-size: 11px; + line-height: 1; + font-weight: 700; + left: 3px !important; +} +.ge-tool-btn.is-ai:hover .ge-tool-ai { opacity: 0.95; } +.ge-tool-btn.is-ai.active .ge-tool-ai { opacity: 1; } +/* Inline ✦ marker used on AI action buttons (Generate, Remove, etc.) + so they read as AI-backed at a glance — same glyph as .ge-tool-ai + in the toolbar, just sitting inline next to the label. */ +.ge-btn-ai-mark { + display: inline-block; + font-size: 11px; + line-height: 1; + font-weight: 700; + opacity: 0.75; + margin-right: 1px; +} + +/* Per-tool clear-selection badge — shows a tiny X in the top-right of + the Lasso / Wand button when each holds a selection. Click clears + the selection without switching tools (handler stops propagation). */ +.ge-tool-clear { + position: absolute !important; + top: 3px; + right: 3px; + width: 14px; + height: 14px; + display: none; + align-items: center; + justify-content: center; + border-radius: 50%; + color: #fff; + background: var(--red); + cursor: pointer; + opacity: 0.95; + z-index: 2; + box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 70%, transparent), + 0 1px 4px color-mix(in srgb, var(--red) 50%, transparent); + transition: opacity 0.12s, transform 0.12s, box-shadow 0.12s; + left: auto !important; +} +.ge-tool-clear:hover { + opacity: 1; + transform: scale(1.15); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 80%, transparent), + 0 2px 6px color-mix(in srgb, var(--red) 60%, transparent); +} + +/* Aspect-ratio placeholder shown while a draft is loading. */ +.ge-canvas-placeholder { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border: 1px dashed color-mix(in srgb, var(--fg) 20%, transparent); + border-radius: 4px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 1; +} +/* Canvas area (center) */ +.ge-canvas-area { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + overflow: auto; + background: var(--bg); + position: relative; + min-width: 0; + /* Stop touch interactions inside the editor from dragging the whole + page (or the gallery modal). Touches on the canvas are owned by the + editor's drawing logic — no native pan/zoom. */ + overscroll-behavior: contain; +} +.ge-ruler { + position: absolute; + z-index: 16; + touch-action: none; + user-select: none; +} +.ge-ruler-horizontal { + top: 0; + left: 18px; + right: 0; + width: calc(100% - 18px); + height: 18px; + cursor: row-resize; +} +.ge-ruler-vertical { + top: 18px; + bottom: 0; + left: 0; + width: 18px; + height: calc(100% - 18px); + cursor: col-resize; +} +.ge-ruler-corner { + position: absolute; + top: 0; + left: 0; + z-index: 17; + width: 18px; + height: 18px; + background: var(--panel); + border-right: 1px solid var(--border); + border-bottom: 1px solid var(--border); + box-sizing: border-box; +} +.ge-wand-loading { + position: absolute; + inset: 0; + z-index: 25; + display: flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--bg) 30%, transparent); + pointer-events: none; +} +.ge-ai-command { + position: absolute; + left: 50%; + bottom: 14px; + z-index: 18; + width: min(430px, calc(100% - 28px)); + transform: translateX(-50%); + padding: 7px; + box-sizing: border-box; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--panel, var(--bg)) 94%, transparent); + box-shadow: 0 8px 22px color-mix(in srgb, #000 26%, transparent); + backdrop-filter: blur(8px); + transition: bottom 0.16s ease, opacity 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease; +} +.ge-ai-command-toggle { + width: 100%; + height: 28px; + display: none; + align-items: center; + justify-content: center; + gap: 5px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--accent, var(--red)); + font: inherit; + font-size: 11px; + font-weight: 600; + cursor: pointer; +} +.ge-ai-command-toggle-caret { + opacity: 0.7; + flex-shrink: 0; +} +.ge-ai-command-head { + display: flex; + align-items: center; + min-height: 24px; + padding: 0 2px 5px; + color: var(--fg); +} +.ge-ai-command-title { + margin-left: 5px; + font-size: 11px; + font-weight: 650; +} +.ge-ai-command-scope { + margin-left: 7px; + padding-left: 7px; + border-left: 1px solid var(--border); + color: var(--fg-muted); + font-size: 9px; + line-height: 1; + text-transform: uppercase; +} +.ge-ai-command-collapsed { + bottom: -16px; + width: auto; + min-width: 102px; + padding: 2px 9px; + opacity: 0.32; + border-color: color-mix(in srgb, var(--accent, var(--red)) 18%, var(--border)); + box-shadow: 0 3px 10px color-mix(in srgb, #000 16%, transparent); +} +.ge-canvas-area:hover .ge-ai-command-collapsed, +.ge-ai-command-collapsed:hover, +.ge-ai-command-collapsed:focus-within { + bottom: 10px; + opacity: 0.94; + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 24%, transparent); +} +.ge-ai-command-form { + display: flex; + align-items: center; + gap: 6px; +} +.ge-ai-command-input { + flex: 1 1 auto; + min-width: 0; + height: 32px; + box-sizing: border-box; + padding: 0 9px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--bg) 76%, #000 24%); + color: var(--fg); + font: inherit; + font-size: 12px; +} +.ge-ai-command-input:focus { + outline: none; + border-color: var(--accent, var(--red)); +} +.ge-ai-command-clear { + position: relative; + top: -2px; + width: 26px; + height: 32px; + flex: 0 0 26px; + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: -38px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--fg-muted); + cursor: pointer; + padding: 0; + z-index: 1; +} +.ge-ai-command-clear:hover { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.ge-ai-command-run { + position: relative; + top: -2px; + background: var(--send-btn-bg, var(--red)); + color: #fff; + border: none; + border-radius: 8px; + min-width: 32px; + width: 32px; + height: 32px !important; + padding: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + transition: background 0.25s, opacity 0.12s; + overflow: hidden; +} +.ge-ai-command-run:hover { + background: var(--send-btn-hover, color-mix(in srgb, var(--red) 80%, white)); +} +.ge-ai-command-busy .ge-ai-command-run { + opacity: 0.58; + cursor: wait; +} +.ge-ai-command-busy .ge-ai-command-run svg { + display: none; +} +.ge-ai-command-close { + width: 28px; + height: 32px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border: 0; + border-radius: 8px; + background: transparent; + color: var(--fg); + opacity: 0.52; + cursor: pointer; + padding: 0; + margin-left: auto; +} +.ge-ai-command-close:hover { + opacity: 0.9; + background: color-mix(in srgb, var(--fg) 10%, transparent); +} +.ge-ai-command-suggestions { + position: absolute; + left: 7px; + right: 7px; + bottom: calc(100% - 2px); + z-index: 2; + display: flex; + flex-direction: column; + gap: 2px; + max-height: 236px; + overflow: auto; + padding: 5px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--panel, var(--bg)) 97%, #000 3%); + box-shadow: 0 10px 28px color-mix(in srgb, #000 28%, transparent); +} +.ge-ai-command-suggestion { + width: 100%; + display: grid; + grid-template-columns: 38px minmax(82px, auto) minmax(0, 1fr); + align-items: center; + gap: 8px; + min-height: 28px; + padding: 4px 7px; + border: 0; + border-radius: 6px; + background: transparent; + color: var(--fg); + text-align: left; + cursor: pointer; + font: inherit; +} +.ge-ai-command-suggestion:hover, +.ge-ai-command-suggestion.active { + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); +} +.ge-ai-command-suggestion-kind { + min-width: 0; + overflow: hidden; + color: var(--accent, var(--red)); + font-size: 8px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; +} +.ge-ai-command-suggestion-main { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 11px; + font-weight: 650; +} +.ge-ai-command-suggestion-hint { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--fg-muted); + font-size: 10px; +} .ge-ai-command-status { margin-top: 4px; font-size: 10px; @@ -4284,1728 +4821,3 @@ button .spinner-whirlpool { #group-model-picker .memory-item:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); } -#group-model-picker input[type="checkbox"] { - accent-color: var(--accent, var(--red)); -} -#group-model-picker input[type="radio"] { - accent-color: var(--accent, var(--red)); -} -#group-model-picker .btn-primary { - background: var(--accent, var(--red)); - color: #fff; - border: none; - border-radius: 6px; - cursor: pointer; - font-weight: 500; -} -#group-model-picker .btn-primary:disabled { - opacity: 0.4; - cursor: not-allowed; -} -#group-model-picker .btn-primary:hover:not(:disabled) { - filter: brightness(1.1); -} - -/* ── Email document type ── */ -.doc-email-header { - display: flex; - flex-direction: column; - gap: 6px; - padding: 10px 12px; - border-bottom: 1px solid var(--border); - background: var(--bg); - flex-shrink: 0; -} -.email-field { - display: flex; - align-items: center; - gap: 8px; -} -.email-field label { - font-size: 11px; - font-weight: 600; - color: var(--fg); - opacity: 0.5; - min-width: 50px; - text-align: right; -} -.email-field input { - flex: 1; - background: transparent; - border: 1px solid var(--border); - border-radius: 4px; - padding: 5px 8px; - font-size: 13px; - font-family: inherit; - color: var(--fg); - outline: none; -} -.email-field input:focus { - border-color: var(--accent, #4a9eff); -} -.email-actions { - display: flex; - gap: 8px; - justify-content: flex-end; - margin-top: 2px; -} -.email-draft-btn { - background: transparent; - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - padding: 6px 14px; - font-size: 12px; - cursor: pointer; - font-family: inherit; -} -.email-draft-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } -.email-draft-btn:disabled { opacity: 0.5; cursor: not-allowed; } - -/* ── Email inbox list ── */ -.email-body { border-top: 1px solid var(--border); } -.email-folder-bar { padding: 4px 8px; border-bottom: 1px solid var(--border); } -.email-folder-select { - background: transparent; border: 1px solid var(--border); border-radius: 4px; - color: var(--fg); font-size: 11px; padding: 2px 6px; cursor: pointer; width: 100%; height: 26px; -} -.email-item { - display: flex; align-items: flex-start; gap: 8px; padding: 8px 12px; - cursor: pointer; border-bottom: 1px solid var(--border); position: relative; transition: background 0.1s; -} -.email-item:hover { background: var(--hover-bg, rgba(255,255,255,0.03)); } -.email-item-spam:hover { opacity: 0.75; } -.email-tag-spam { - display: inline-flex; align-items: center; gap: 4px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); - color: var(--accent, var(--red)); - border-radius: 10px; padding: 1px 6px; font-size: 9px; font-weight: 600; - text-transform: lowercase; letter-spacing: 0.3px; -} -.email-spam-unflag { - background: none; border: none; color: inherit; cursor: pointer; - padding: 0 2px; font-size: 10px; line-height: 1; opacity: 0.6; -} -.email-spam-unflag:hover { opacity: 1; color: var(--fg); } -/* Unread state is now a subtle icon, not full styling */ -.email-done-check { - display: inline-flex; align-items: center; justify-content: center; - width: 16px; height: 16px; border-radius: 50%; background: transparent; - border: 1.5px solid var(--border); color: transparent; - flex-shrink: 0; cursor: pointer; transition: all 0.15s; - margin-right: 4px; -} -.email-done-check:hover { border-color: var(--accent, #4a9eff); } -.email-done-check.active { background: var(--accent, #4a9eff); border-color: var(--accent, #4a9eff); color: #fff; } -.email-done-check.active svg { display: block; } -.email-done-check svg { display: none; } - - - - -.email-delete-busy > :not(.email-delete-overlay) { - opacity: 0.42; - transition: opacity 0.12s ease; -} - -.email-delete-overlay { - position: absolute; - inset: 0; - z-index: 8; - display: flex; - align-items: center; - justify-content: center; - border-radius: inherit; - background: color-mix(in srgb, var(--panel, var(--bg)) 82%, transparent); - pointer-events: none; -} - -.email-delete-overlay .spinner-whirlpool, -.email-delete-overlay .ai-spinner-whirlpool { - margin: 0 !important; -} - -.doclib-card.email-card-removing { - pointer-events: none !important; - max-height: 0 !important; - padding-top: 0 !important; - padding-bottom: 0 !important; - margin-top: 0 !important; - margin-bottom: 0 !important; - border-color: transparent !important; - opacity: 0; - transform: translateX(-10px) scale(0.985); - transition: - opacity 0.18s ease, - transform 0.22s ease, - max-height 0.23s ease, - padding 0.23s ease, - margin 0.23s ease, - border-color 0.18s ease; -} - -@media (prefers-reduced-motion: reduce) { - .doclib-card.email-card-removing { - transition: opacity 0.08s ease; - transform: none; - } -} - -/* Library card done check — always visible, subtle icon next to title */ -.email-card-done { - display: inline-flex; align-items: center; justify-content: center; - flex-shrink: 0; cursor: pointer; - transition: opacity 0.15s, color 0.15s; - opacity: 0.15; color: var(--fg); -} -.email-card-titlerow .memory-item-title { - flex: 1 1 auto; - min-width: 0; -} -.email-card-titlerow { position: relative; min-width: 0; } -.email-card-status { - display: inline-flex; - align-items: center; - justify-content: flex-end; - gap: 7px; - flex: 0 0 auto; - min-width: max-content; - margin-left: auto; - white-space: nowrap; -} -.email-card-status > * { - flex: 0 0 auto; -} -.email-card-status + .email-card-nav-arrows { - margin-left: 6px; -} -.email-card-status .email-card-nav-arrows { - margin-left: 2px; -} -.email-card-attachment { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 13px; - width: 13px; - height: 18px; -} -/* Hover preview: bright accent when unchecked so the user sees a check coming. - Once active, keep the exact same color on hover so the done state does not - visually flip while the pointer is still over it. */ -.email-card-done:not(.active):hover { - opacity: 0.75 !important; - color: var(--accent-primary, var(--red)); -} -.email-card-done.active { opacity: 0.95; color: var(--accent-primary, var(--red)); } -.email-card-done.active:hover { - opacity: 0.95 !important; - color: var(--accent-primary, var(--red)) !important; -} -.email-card-favorite { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 18px; - width: 18px; - height: 18px; - margin: 0; - padding: 0; - border: 0; - background: transparent; - color: var(--fg); - cursor: pointer; - position: relative; - top: -3px; - opacity: 0.18; - transition: opacity 0.15s, color 0.15s, transform 0.15s; -} -.email-card-favorite:hover, -.email-card-favorite:focus-visible { - opacity: 0.8; - color: var(--accent-primary, var(--red)); -} -.email-card-favorite.active { - opacity: 0.9; - color: var(--accent-primary, var(--red)); -} -@keyframes check-pop { - 0% { transform: scale(1); } - 40% { transform: scale(1.7) rotate(-10deg); } - 70% { transform: scale(1.1) rotate(4deg); } - 100% { transform: scale(1) rotate(0); } -} -@keyframes check-unpop { - 0% { transform: scale(1); opacity: 0.95; } - 35% { transform: scale(0.45) rotate(8deg); opacity: 0.05; } - 65% { transform: scale(0.9); opacity: 0.2; } - 100% { transform: scale(1); } -} - -/* Expanded email preview in library — override .memory-item row layout */ -.doclib-card.email-card-expanded.memory-item { - cursor: default !important; - background: var(--bg) !important; - border: 1px solid var(--border) !important; - display: flex !important; - flex-direction: column !important; - align-items: stretch !important; - /* Fill the available grid height instead of the old hardcoded 70vh — - the modal already caps height (90dvh on mobile, auto on desktop), so - the card just needs to flex into whatever remains. */ - flex: 1 1 auto !important; - height: 100% !important; - min-height: 0 !important; - max-height: none !important; - padding: 0 !important; - overflow: hidden !important; - gap: 0 !important; -} -.doclib-card.email-card-expanded.memory-item > div:first-child { - /* The summary content (subject, date) - acts as the title bar. Left padding - trimmed 14→6px to shift the title 8px left total. */ - padding: 2px 4px 10px !important; /* top trimmed to lift the header text up */ - margin-top: -14px !important; /* pull the whole bar up into the freed space */ - border-bottom: 1px solid var(--border); - flex: 0 0 auto !important; - width: 100%; -} -/* Keep the per-card '...' actions menu visible while the email card is - expanded — we used to hide it and surface a duplicate menu button in - the expanded header, but that was redundant. The bottom menu now - carries the actions in both states. */ -/* When expanded inline, show the subject a touch larger than the 12px card - title (close to the new-tab window header) without being oversized. */ -.doclib-card.email-card-expanded .memory-item-title { - font-size: 14px; - font-weight: 600; -} -/* The sender name is redundant once expanded (the From: row shows it just - below), so drop it and keep only the date — which then sits left-aligned - directly under the subject. Nudge the date 1px right to line up exactly with - the subject text, and up closer to it. */ -.doclib-card.email-card-expanded .email-meta-sender, -.doclib-card.email-card-expanded .email-meta-sep { - display: none; -} -.doclib-card.email-card-expanded .memory-item-meta { - margin-top: -9px !important; -} -.doclib-card.email-card-expanded .email-meta-date { - margin-left: 3px; -} -.email-card-reader { - display: flex; - flex-direction: column; - flex: 1; - min-height: 0; - font-size: 12px; -} -.email-card-reader-loading { - background: color-mix(in srgb, var(--bg) 86%, #000 14%); - border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); - min-height: 240px; -} -.email-card-reader-error { - background: color-mix(in srgb, var(--bg) 86%, #000 14%); - border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); -} -.email-reader-load-error { - display: flex; - min-height: 180px; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 9px; - padding: 24px 16px; - text-align: center; - color: var(--fg); -} -.email-reader-load-error-title { - font-size: 13px; - font-weight: 700; -} -.email-reader-load-error-msg { - max-width: min(460px, 92%); - color: color-mix(in srgb, var(--fg) 62%, transparent); - font-size: 11px; - line-height: 1.35; - overflow-wrap: anywhere; -} -.email-skeleton-line, -.email-skeleton-dot { - display: block; - border-radius: 999px; - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--fg) 12%, transparent) 0%, - color-mix(in srgb, var(--fg) 22%, transparent) 45%, - color-mix(in srgb, var(--fg) 12%, transparent) 90% - ); - background-size: 220% 100%; - animation: email-skeleton-shimmer 1.35s ease-in-out infinite; -} -.email-skeleton-dot { - width: 9px; - height: 9px; - flex: 0 0 auto; -} -.email-list-skeleton { - position: absolute; - inset: 0; - padding: 10px 7px; - display: flex; - flex-direction: column; - gap: 8px; - pointer-events: none; -} -.email-skeleton-row { - display: flex; - align-items: center; - gap: 10px; - min-height: 48px; - padding: 8px 10px; - border: 1px solid color-mix(in srgb, var(--border) 76%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 70%, transparent); -} -.email-skeleton-lines { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 7px; -} -.email-reader-skeleton { - display: flex; - flex-direction: column; - min-height: 100%; - flex: 1; -} -.email-reader-skeleton-header { - min-height: 60px; - padding: 12px 14px; - display: flex; - align-items: flex-start; - gap: 8px; - border-bottom: 1px solid var(--border); - background: color-mix(in srgb, var(--panel) 72%, transparent); -} -.email-reader-skeleton-header .chip { width: 132px; height: 18px; } -.email-reader-skeleton-header .chip.short { width: 78px; } -.email-reader-skeleton-header .action { - width: 38px; - height: 28px; - margin-left: auto; - border-radius: 6px; -} -.email-reader-skeleton-header .action + .action { margin-left: 0; } -.email-reader-skeleton-atts { - display: flex; - gap: 7px; - padding: 8px 14px; - border-bottom: 1px solid var(--border); -} -.email-reader-skeleton-atts .attachment { width: 126px; height: 20px; border-radius: 6px; } -.email-reader-skeleton-atts .attachment.short { width: 88px; } -.email-reader-skeleton-body { - display: flex; - flex-direction: column; - gap: 11px; - padding: 16px 14px; - min-height: 340px; - flex: 1; -} -.email-reader-skeleton-body .body { width: 88%; height: 9px; border-radius: 6px; } -.email-reader-skeleton-body .body.wide { width: 100%; } -.email-reader-skeleton-body .body.medium { width: 94%; } -.email-reader-skeleton-body .body.short { width: 62%; } -.email-reader-skeleton-body .body.gap { - margin-top: 12px; - width: 78%; -} -.email-search-progress-row { - display: flex; - align-items: center; - justify-content: center; - gap: 7px; - min-height: 30px; - padding: 6px 10px; - font-size: 11px; - opacity: 0.65; -} -.email-search-progress-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--accent-primary, var(--red)); - box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); - animation: email-search-progress-pulse 1.2s ease-in-out infinite; -} -@media (prefers-reduced-motion: reduce) { - .email-skeleton-line, - .email-skeleton-dot, - .email-inline-image-skeleton, - .email-inline-image-placeholder.is-loading .email-inline-image-icon, - .email-inline-image-frame.is-loading, - .email-search-progress-dot { - animation: none; - } -} -/* Per-email unread dot in the expanded reader's title row. Background color - stays inline (per-sender hue from _senderColor), but the dot gets a soft - breathing glow + a 4px vertical nudge so it reads as centered against - the row instead of riding the baseline. */ -.email-card-unread-dot { - position: relative; - top: 0; - animation: email-card-unread-breathe 2.2s ease-in-out infinite; -} -.email-reader-header { - display: flex; - flex-direction: row; - /* Top-align so when the user expands To/Cc, the action cluster - stays on the From line above instead of drifting to the middle. */ - align-items: flex-start; - gap: 12px; - /* Taller header so the 44px-tall absolute-positioned action - cluster inside the From row has vertical breathing room. */ - padding: 8px 14px; - min-height: 60px; - border-bottom: 1px solid var(--border); - background: var(--bg); - flex-shrink: 0; - /* Containing block for the absolute action cluster. */ - position: relative; - /* Container so the action-cluster wrap breakpoints below react to - the reader's own width instead of the page viewport. */ - container-type: inline-size; - container-name: emailreader; -} -.email-reader-header > .email-reader-actions { - margin-left: auto; - flex-shrink: 0; -} -/* Meta block stacks the From / To / Cc rows; each row keeps label - tight to its chip(s) on the same line. The recipient-chips inside - scroll horizontally so long lists slide under the floating action - cluster on the right. */ -.email-reader-meta { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; - /* Positioning context for the absolute action cluster. */ - position: relative; -} -.email-reader-meta .recipient-chips { - display: inline-flex; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - gap: 4px; - max-width: 100%; - scrollbar-width: none; - -ms-overflow-style: none; -} -.email-reader-meta .recipient-chips::-webkit-scrollbar { display: none; } -.email-reader-meta-row { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.email-reader-meta-row.email-reader-meta-from { - position: relative; - /* Never wrap "From:" off the chip onto its own line — the chip - span has overflow-x scroll built in, so it shrinks/scrolls - instead of jumping to a new row. */ - flex-wrap: nowrap; - min-width: 0; - /* Reserve space on the right for the 1-row cluster in wide mode. */ - padding-right: 320px; -} -.email-reader-meta > .email-reader-meta-details { - min-width: 0; - padding-right: 320px; -} -.email-reader-meta > .email-reader-actions-inline { - position: absolute; - top: 1px; - right: 0; - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: flex-start; - justify-content: flex-end; - gap: 4px; - z-index: 2; - /* Sit visually above the From baseline like the lifted overlay. */ - margin-top: -9px; -} -/* Recipient chip span keeps at least ~4 characters visible (60px) - before the overflow-scroll kicks in, so the sender / first - recipient name is always partly readable. */ -.email-reader-meta .recipient-chips { - min-width: 60px; -} -/* Medium reader widths: cap the cluster at 3 icons per row so it - never wraps past 2 rows. min-width forces the cluster to reserve - enough room for 3 icons in a row — without it, grid col 2 could - shrink below the cap and the cluster fanned out into 4+ short - rows. */ -@container emailreader (max-width: 450px) { - .email-reader-meta > .email-reader-actions-inline { - /* 3 icons (48px each) + 2 gaps (4px each) = 152px exact. Adding - 6px slack so subpixel rounding can't tip a row from 3 to 2 - icons and fan the cluster into a 3rd row. */ - min-width: 158px; - max-width: 158px; - /* Solid background so the email body content behind the cluster - doesn't peek through the gaps between icons or the slack on - the right edge when the cluster's wrapped to 2 rows. */ - background: var(--bg); - } - .email-reader-meta > .email-reader-actions-inline .memory-toolbar-btn.reader-icon-btn { - width: 48px; - min-width: 48px; - max-width: 48px; - height: 40px; - padding-left: 0; - padding-right: 0; - gap: 2px; - } - .email-reader-meta > .email-reader-actions-inline .reader-btn-label { - display: block; - max-width: 44px; - overflow: hidden; - text-overflow: ellipsis; - font-size: 8px; - line-height: 1; - } - /* Tighten the right-side padding now that the cluster is 158px - wide (instead of the ~308 wide 1-row layout). */ - .email-reader-meta-row.email-reader-meta-from, - .email-reader-meta > .email-reader-meta-details { - padding-right: 170px; - } - /* Cluster wrapped to 2 rows (44px + 4 gap + 44px = 92px) sits - absolute above the From baseline. Set min-height on the meta - (a descendant of the container, more reliable than targeting - the container itself) so the header grows enough to contain - the cluster + 4px of breathing room between the cluster's - bottom and the header bottom. */ - .email-reader-meta { - min-height: 96px; - } -} -/* Very narrow reader: once the chip span has shrunk to its ~4 - character min, the cluster snaps to absolute overlay with a soft - shadow so it visually floats above the truncated chip. */ -@container emailreader (max-width: 380px) { - /* Pane too narrow even for 158px of right-side reservation — kill - the padding so chips use the full width, and let the cluster - overlay via z-index + shadow. */ - .email-reader-meta-row.email-reader-meta-from, - .email-reader-meta > .email-reader-meta-details { - padding-right: 0; - } - .email-reader-meta > .email-reader-actions-inline { - /* Keep the same -7px lift as the medium-width state so the - cluster doesn't slide ~4px DOWN when it transitions into - overlay mode. */ - /* margin-top stays inherited at -7px from the base rule. */ - /* 158px content + 22px padding-left so the inner row still fits - 3 icons cleanly. */ - min-width: 180px; - max-width: 180px; - background: linear-gradient(to right, transparent 0, var(--bg) 18px); - padding-left: 22px; - /* Soft shadow on the left edge so the overlap reads as the - cluster "floating over" the chip. */ - box-shadow: -6px 0 12px -6px rgba(0, 0, 0, 0.25); - } -} -/* Mobile: drop the box-shadow AND the left-edge gradient fade — - either can read as a shadow under the icons on phone screens. - Also lift the cluster 1px more. !important needed because the - @container query overlay rule has the same selector specificity. */ -@media (max-width: 768px) { - .memory-toolbar-btn[data-mode="ai-reply-fast"] { - height: 30px !important; - min-height: 30px !important; - } - .email-reader-meta > .email-reader-actions-inline { - box-shadow: none !important; - background: none !important; - padding-left: 0 !important; - margin-top: -10px; - } -} -/* Docked panes used to flow the cluster under the meta; now they - share the same absolute-top-right + wrap behaviour as undocked. - No docked-specific overrides — the container queries below pick - the right cluster size and overlay state from the reader width. */ -/* Gmail-style chevron — collapsed view shows only From; the chevron - reveals the To/Cc details inline below. */ -.email-reader-meta-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - margin-left: -3px; - padding: 0; - border: none; - background: transparent; - color: inherit; - opacity: 0.55; - cursor: pointer; - border-radius: 4px; - flex-shrink: 0; -} -.email-reader-meta-toggle:hover { opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent); } -.email-reader-meta-toggle svg { transition: transform 0.15s ease; } -.email-reader-meta-toggle.open svg { transform: rotate(180deg); } -.email-reader-meta-toggle.email-reader-cc-toggle { - width: auto; - min-width: 18px; - gap: 3px; - margin-left: 1px; - padding: 0 5px; - color: var(--accent, var(--red)); - font-family: inherit; - font-size: 9px; - font-weight: 600; - line-height: 1; -} -.email-reader-meta-details { - display: flex; - flex-direction: column; - gap: 6px; -} -/* When expanded, To/Cc details live in the header flow. A floating popover - can overlap the folded attachment strip directly beneath the header. */ -.email-reader-meta > .email-reader-meta-details:not([hidden]) { - position: relative; - top: auto; - left: auto; - right: auto; - margin-top: 4px; - background: transparent; - border: 0; - border-radius: 0; - padding: 0; - box-shadow: none; - z-index: 1; -} -/* Reserve a fixed label width so From: / To: / Cc: all align and - their chips start at the same x. */ -.email-reader-meta-row strong { opacity: 0.5; font-weight: 600; flex-shrink: 0; min-width: 36px; } -.email-reader-meta-row > span { - white-space: nowrap; - min-width: 0; -} -/* Recipient chips */ -.recipient-chips { - display: inline-flex !important; flex-wrap: wrap; gap: 4px; - white-space: normal !important; overflow: visible !important; - text-overflow: clip !important; -} -.recipient-chip { - display: inline-flex; align-items: center; - gap: 5px; - padding: 1px 8px; font-size: 10px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--border); - border-radius: 10px; - color: var(--fg); - white-space: nowrap; - cursor: pointer; - transition: background 0.15s, border-color 0.15s, max-width 0.2s; - max-width: 220px; - overflow: hidden; - text-overflow: ellipsis; -} -.recipient-chip-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} -.recipient-chip-copy { - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - top: -2px; - width: 14px; - height: 14px; - padding: 0; - border: none; - background: none; - color: inherit; - opacity: 0.55; - cursor: pointer; - flex: 0 0 auto; -} -.recipient-chip-copy:hover, -.recipient-chip-copy.copied { - opacity: 1; - color: var(--accent-primary, var(--red)); -} -.recipient-chip-copy[hidden] { - display: none !important; -} -.recipient-chip:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); -} -.recipient-chip.expanded { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); - border-color: var(--accent-primary, var(--red)); - max-width: 500px; -} -.recipient-chip.popover-open { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); - border-color: var(--accent-primary, var(--red)); -} -.recipient-chip-popover { - position: fixed; - z-index: 12050; - display: flex; - align-items: center; - gap: 8px; - max-width: min(420px, calc(100vw - 20px)); - padding: 8px 10px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--bg) 96%, var(--fg) 4%); - color: var(--fg); - box-shadow: 0 14px 34px rgba(0, 0, 0, 0.42); - font-size: 12px; - line-height: 1.35; - pointer-events: auto; -} -.recipient-chip-popover-main { - min-width: 0; -} -.recipient-chip-popover-name { - font-weight: 650; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.recipient-chip-popover-detail { - overflow-wrap: anywhere; - opacity: 0.86; -} -.recipient-chip-popover-copy { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 26px; - height: 26px; - padding: 0; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: inherit; - cursor: pointer; -} -.recipient-chip-popover-copy:hover, -.recipient-chip-popover-copy.copied { - color: var(--accent-primary, var(--red)); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, transparent); -} -@container docpane (max-width: 460px) { - .email-reader-header { - flex-direction: column; - gap: 6px; - } - .email-reader-header > .email-reader-actions { - /* On narrow widths fall back to in-flow so it doesn't overlap - the meta entirely. */ - position: static; - align-self: flex-end; - background: none; - padding-left: 0; - } - .email-reader-meta-row:not(.email-reader-meta-from) { - padding-right: 0; - } - .email-reader-meta-row strong { - min-width: 0; - } - .recipient-chip { - max-width: 100%; - } -} -/* Mobile: long recipient lists (To/Cc with many addresses) shouldn't wrap to - N rows and push the body down. Keep them on one row, horizontally scrollable, - no scrollbar chrome. */ -@media (max-width: 768px) { - .recipient-chips { - flex-wrap: nowrap !important; - overflow-x: auto !important; - overflow-y: hidden !important; - scrollbar-width: none; - -webkit-overflow-scrolling: touch; - } - .recipient-chips::-webkit-scrollbar { display: none; } - .recipient-chip { flex-shrink: 0; } -} -.email-reader-actions { - display: flex; flex-direction: column; gap: 4px; align-items: flex-end; - flex-shrink: 0; - margin-top: -4px; -} -/* Two stacked rows: Reply/Reply-all/Forward on the From line, with - Summary/AI-reply/More on a row below. */ -.email-reader-actions-row { - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: center; - justify-content: flex-end; - gap: 4px; -} -.email-reader-atts { - display: flex; flex-wrap: wrap; gap: 6px; - padding: 8px 14px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} -.email-reader-body { - font-size: 12px; - line-height: 1.55; - white-space: pre-wrap; - word-wrap: break-word; - overflow-wrap: anywhere; - flex: 1; - overflow-y: auto; - padding: 14px; - min-height: 0; - background: var(--panel); - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--border) 65%, transparent); - /* Use the OS colored emoji font for incoming mail content */ - font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"; - font-variant-emoji: emoji; -} -.email-reader-body.html-body img { max-width: 100%; height: auto; } -.email-reader-body.html-body [style*="max-width:600px"], -.email-reader-body.html-body [style*="max-width: 600px"], -.email-reader-body.html-body [style*="width:600px"], -.email-reader-body.html-body [style*="width: 600px"], -.email-reader-body.html-body table[width="600"] { - max-width: none !important; - width: 100% !important; - margin-left: 0 !important; - margin-right: 0 !important; -} -.email-reader-body.html-body [align="center"] { - margin-left: 0 !important; - margin-right: 0 !important; -} -.email-reader-body .email-inline-image-placeholder { - display: inline-flex; - align-items: center; - gap: 9px; - max-width: min(100%, 360px); - min-height: 58px; - margin: 6px 0; - padding: 8px 10px; - border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 72%, transparent); - color: var(--fg); - vertical-align: middle; -} -.email-inline-image-skeleton { - width: 58px; - height: 42px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 6px; - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--fg) 7%, transparent) 0%, - color-mix(in srgb, var(--fg) 13%, transparent) 45%, - color-mix(in srgb, var(--fg) 7%, transparent) 90% - ); - background-size: 220% 100%; - animation: email-skeleton-shimmer 1.35s ease-in-out infinite; -} -.email-inline-image-icon { - opacity: 0.52; - display: inline-flex; -} -.email-inline-image-placeholder.is-loading .email-inline-image-icon svg { - display: none; -} -.email-inline-image-info { - display: inline-flex; - flex-direction: column; - min-width: 0; - line-height: 1.25; -} -.email-inline-image-title { - font-size: 11px; - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-inline-image-sub { - font-size: 10px; - opacity: 0.58; -} -.email-inline-image-actions { - display: inline-flex; - align-items: center; - gap: 5px; - margin-left: auto; - flex: 0 0 auto; -} -.email-inline-image-btn { - display: inline-flex; - align-items: center; - justify-content: center; - transform: translateY(-2px); - height: 24px; - line-height: 1; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: inherit; - font: inherit; - font-size: 10px; - text-decoration: none; - cursor: pointer; -} -.email-inline-image-btn:hover { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); - color: var(--accent-primary, var(--red)); -} -.email-inline-image-download-btn { - width: 24px; - min-width: 24px; - padding: 0; -} -.email-inline-image-loaded { - max-width: 100%; - height: auto; - border-radius: 6px; -} -.email-inline-image-frame { - display: inline-flex; - max-width: 100%; - min-width: min(360px, 100%); - min-height: 90px; - margin: 6px 0; - align-items: center; - justify-content: center; - border-radius: 8px; - background: linear-gradient( - 90deg, - color-mix(in srgb, var(--fg) 7%, transparent) 0%, - color-mix(in srgb, var(--fg) 13%, transparent) 45%, - color-mix(in srgb, var(--fg) 7%, transparent) 90% - ); - background-size: 220% 100%; -} -.email-inline-image-frame .email-inline-image-loaded { - opacity: 0; - transition: opacity 0.15s ease; -} -.email-inline-image-load-all { - display: flex; - align-items: center; - gap: 8px; - width: 100%; - max-width: 100%; - margin: -4px 0 6px; - padding: 7px 9px; - border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--panel) 72%, transparent); - font-size: 11px; -} -.email-inline-image-load-all span { - opacity: 0.68; - margin-right: auto; -} -.email-inline-image-load-all button { - transform: translateY(-4px); - height: 24px; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: inherit; - font: inherit; - font-size: 10px; - cursor: pointer; -} -.email-inline-image-load-all button:hover { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); - color: var(--accent-primary, var(--red)); -} -@media (max-width: 520px) { - .email-reader-body .email-inline-image-placeholder { - display: flex; - width: 100%; - } - .email-inline-image-skeleton { - width: 48px; - } - .email-inline-image-actions { - flex-direction: column; - align-items: stretch; - } -} -.email-reader-body.html-body table { max-width: 100%; border-collapse: collapse; } -/* Gmail Drive chips arrive with inline width/height/border/padding from - Gmail's composer (border:1px solid #ddd, white-ish bg assumed). On a - dark theme that reads as a near-invisible white box. Override with a - theme-aware chip surface so the filename + icon stay visible. */ -.email-reader-body .gmail_chip, -.email-reader-body .gmail_drive_chip { - width: auto !important; - max-width: 100% !important; - height: auto !important; - max-height: none !important; - display: inline-flex !important; - align-items: center !important; - gap: 6px; - padding: 6px 10px !important; - margin: 6px 0 !important; - border: 1px solid var(--border) !important; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.email-reader-body .gmail_chip:hover, -.email-reader-body .gmail_drive_chip:hover { - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)) !important; - background: color-mix(in srgb, var(--accent, var(--red)) 6%, transparent); -} -.email-reader-body .gmail_chip a, -.email-reader-body .gmail_drive_chip a { - color: var(--accent, var(--red)) !important; - display: inline-flex !important; - align-items: center; - gap: 6px; - max-width: 100% !important; -} -.email-reader-body .gmail_chip a > span, -.email-reader-body .gmail_drive_chip a > span { - color: var(--fg) !important; - font-size: 12px; -} -.email-reader-body .gmail_chip img, -.email-reader-body .gmail_drive_chip img { - width: 16px !important; - height: 16px !important; - padding-right: 0 !important; - filter: none; -} - -/* Force any phone/sms/date/address auto-link to inherit the body color so - numbers don't render as bright-blue browser-default text inside email - content. format-detection in turns off detection on iOS, but some - mail clients ship pre-wrapped tel: links — those still need taming. */ -.email-reader-body a[href^="tel:"], -.email-reader-body a[href^="sms:"], -.email-reader-body a[x-apple-data-detectors], -.email-bubble-body a[href^="tel:"], -.email-bubble-body a[href^="sms:"], -.email-bubble-body a[x-apple-data-detectors], -.email-thread-turn-body a[href^="tel:"], -.email-thread-turn-body a[href^="sms:"], -.email-thread-turn-body a[x-apple-data-detectors] { - color: inherit !important; - text-decoration: none !important; - pointer-events: auto; -} -.email-reader-body a, -.email-bubble-body a, -.email-thread-turn-body a { - color: var(--accent-primary, var(--red)); - text-decoration: underline; - overflow-wrap: anywhere; - word-break: normal; - cursor: pointer; -} -.email-reader-body a:hover, -.email-bubble-body a:hover, -.email-thread-turn-body a:hover { opacity: 0.8; } -/* Summary block — same band chrome as Attachments / Signature / Earlier- - thread (no accent tint, no rounded corners, no leading star icon). The - only difference is the label, so all collapsible sections in the email - reader look uniform. */ -.email-summary-panel { - margin: 0 0 12px 0; - padding: 10px 12px; - background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); - border-radius: 6px; -} -.email-summary-header { - display: flex; align-items: center; gap: 5px; - font-size: 10px; font-weight: 600; text-transform: uppercase; - color: var(--accent-primary, var(--red)); opacity: 0.85; - margin-bottom: 6px; letter-spacing: 0.4px; -} -.email-summary-content { - font-size: 12px; line-height: 1.5; color: var(--fg); white-space: pre-wrap; -} -/* Click-to-fold: tap the summary header to collapse/expand. The chevron - flips when collapsed, the content hides + the panel's bottom margin - tightens so a folded summary doesn't take up vertical space. */ -.email-summary-toggle { cursor: pointer; user-select: none; } -.email-summary-panel.collapsed { padding-bottom: 6px; margin-bottom: 6px; } -.email-translation-loading { - display: flex; - align-items: center; - justify-content: center; - min-height: 26px; -} -.email-translation-busy { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 7px; -} -.email-translation-spinner { - display: inline-flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; -} -.email-translation-loading-text { - opacity: 0.68; - font-size: 12px; -} -.email-translation-panel .email-translation-icon { - color: var(--accent-primary, var(--red)); -} - -/* Foldable attachments — same fold-on-click UX as the summary panel. */ -.email-reader-atts-wrap { - display: flex; flex-direction: column; - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} -.email-reader-atts-header { - display: flex; align-items: center; gap: 5px; - padding: 6px 14px; - font-size: 10px; font-weight: 600; text-transform: uppercase; - color: var(--fg); opacity: 0.7; - cursor: pointer; user-select: none; - letter-spacing: 0.4px; -} -.email-attachments-download-all { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - height: 22px; - padding: 0 8px; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); - color: var(--accent-primary, var(--red)); - font: inherit; - font-size: 10px; - font-weight: 700; - text-transform: none; - letter-spacing: 0; - cursor: pointer; - flex: 0 0 auto; -} -.email-attachments-download-all:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 60%, transparent); -} -.email-attachments-download-all .spinner-whirlpool, -.email-attachments-download-all .ai-spinner-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 !important; -} -.email-reader-atts-wrap > .email-reader-atts { - border-bottom: none !important; -} -.email-reader-atts-hidden-note { - padding: 0 14px 6px; - font-size: 10px; - color: var(--fg-muted); - opacity: 0.65; -} - -/* Quote fold = neutral full-width band (matches attachments header). */ -.email-quote-fold { - margin: 0 -14px; - padding: 0; - border: 0; - border-top: 1px solid var(--border) !important; - border-radius: 0; - outline: 0 !important; - box-shadow: none !important; - /* Neutralise any rich-mail HTML that injects a yellow / colored bg - or border onto blockquote ancestors. */ - background: transparent !important; -} -/* Same neutralisation for any element nested inside the fold (the inner - blockquote, paragraphs, divs from the original message). Yellow outlines - in Outlook-style mails almost always come from inline border-color or - box-shadow on these. */ -.email-quote-fold *, -.email-quote-fold *::before, -.email-quote-fold *::after { - outline-color: transparent !important; - box-shadow: none !important; -} -.email-quote-fold blockquote, -.email-quote-fold blockquote[type="cite"], -.email-quote-fold table, -.email-quote-fold div { - border-color: var(--border) !important; - /* Override the .msg blockquote rule that sets a colored left border via - var(--hl-function) — that's where the "yellow" outline was coming from. */ - border-left-color: var(--border) !important; - border-radius: 0 !important; - background: transparent !important; -} -/* JS marks the last .email-quote-fold in each email body with this class, - so it's the only one that gets rounded bottom corners. Avoids :has() - browser-support quirks. */ -.email-quote-fold.last-fold { - border-radius: 0 0 8px 8px; - overflow: hidden; -} -/* ── Threaded reply turns (recursive parser) ── */ -.email-thread-turn-body { - padding: 8px 14px 4px; - font-size: 12px; - line-height: 1.45; -} -/* Nested turns indent slightly + get their own card outline so the - reply hierarchy is obvious at a glance. */ -.email-thread-turn-body .email-thread-turn { - margin: 8px 0 4px; - margin-left: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -/* The original blockquote's left stripe is redundant when each turn is - already a card — drop it inside thread bodies. */ -.email-thread-turn-body blockquote { - border-left: none !important; - margin: 0 !important; - padding: 0 !important; - background: transparent !important; -} -/* Flatten the inner blockquote: no rounded corners, no yellow tint - inherited from rich-mail HTML. Subtle left border + neutral bg. */ -.email-quote-fold blockquote { - margin: 0; - padding: 8px 12px; - border: 0; - border-left: 2px solid var(--border); - border-radius: 0 !important; - background: transparent !important; - color: inherit; -} -/* "From … · Date …" chip appended to the summary line */ -.email-fold-summary-meta { - margin-left: 6px; - font-size: 10px; - font-weight: 500; - text-transform: none; - letter-spacing: 0; - opacity: 0.55; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -/* Sender name is the primary affordance — slightly larger and not all-caps so - "From: Sam" feels like a click target on the person, not a section label. */ -.email-fold-summary-name { - font-size: 11px; - font-weight: 600; - text-transform: none; - letter-spacing: 0.1px; - color: var(--fg); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.email-quote-fold .email-fold-summary { - display: flex; align-items: center; gap: 5px; - padding: 6px 14px; - cursor: pointer; list-style: none; - font-size: 10px; font-weight: 600; text-transform: uppercase; - letter-spacing: 0.4px; - /* Force neutral page colors regardless of any rich-mail HTML wrapping us */ - color: var(--fg) !important; - background: transparent !important; - background-color: transparent !important; - text-shadow: none !important; - font-style: normal !important; - opacity: 0.7; - user-select: none; - border-radius: 0; -} -.email-quote-fold .email-fold-summary > * { - /* Prevent inline-color attrs on summary children (icon SVGs, meta span) - from picking up a forced color from ancestor email HTML. */ - color: inherit !important; - background: transparent !important; -} -.email-quote-fold .email-fold-summary { list-style: none; list-style-type: none; } -.email-quote-fold[open] > *:not(summary) { - padding: 8px 14px 12px 14px; - border-top: 1px solid var(--border); -} - -/* Signature fold = clean full-width band like the attachments header. No - accent overlay, no rounded corners — neutral chrome so it doesn't look - like the AI Summary panel. */ -.email-sig-fold { - margin: 8px 0 0; - padding: 0; - border: 0; - border-top: 1px dashed color-mix(in srgb, var(--fg) 18%, transparent); - background: transparent; - border-radius: 0; -} -.email-sig-fold .email-fold-summary { - display: flex; align-items: center; gap: 5px; - padding: 4px 0; - cursor: pointer; - list-style: none; list-style-type: none; - font-size: 9.5px; font-weight: 600; text-transform: uppercase; - letter-spacing: 0.4px; - color: var(--fg); opacity: 0.55; - user-select: none; -} -.email-sig-fold[open] > *:not(summary) { - padding: 6px 0 0; - border-top: 0; - font-size: 11px; line-height: 1.45; - color: color-mix(in srgb, var(--fg) 75%, transparent); - white-space: pre-wrap; -} - -/* ── Chat-bubble layout for email threads ── - Each parsed turn becomes a bubble. The active account's outgoing - replies align right (mine); everyone else aligns left (theirs). - Bubbles read top→bottom oldest→newest, like a chat. */ -.email-bubbles { - display: flex; - flex-direction: column; - gap: 10px; - padding: 4px 0 8px; - margin: 0 -2px; -} -.email-bubble-row { - display: flex; - align-items: flex-end; - gap: 8px; - width: 100%; - min-width: 0; -} -.email-bubble { - flex: 0 1 auto; - max-width: 90%; - min-width: 0; - padding: 8px 12px; - border: 1px solid var(--border); - background: var(--panel); - font-size: 12.5px; - line-height: 1.5; - word-wrap: break-word; - overflow-wrap: anywhere; - overflow: hidden; -} -.email-bubble-mine .email-bubble { - background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 10%, var(--bg)); - border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 35%, var(--border)); - border-radius: 14px 14px 4px 14px; -} -.email-bubble-theirs .email-bubble { - background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 5%, var(--panel)); - border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 25%, var(--border)); - border-radius: 14px 14px 14px 4px; -} -.email-bubble-head { - display: flex; - align-items: baseline; - gap: 8px; - font-size: 10.5px; - margin-bottom: 4px; - opacity: 0.7; - min-width: 0; -} -.email-bubble-author { - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 50ch; -} -.email-bubble-date { - opacity: 0.7; - font-weight: 400; - white-space: nowrap; -} -.email-bubble-body { - white-space: normal; - /* Reader-controlled typography — override sender's inline styles AND - attribute-based sizing (, , , h1..h6). Weight - and italics still pass through for emphasis. */ - font-family: inherit; - font-size: var(--email-body-size, 13.5px); - line-height: 1.5; -} -/* Heading tags carry browser-default font-size multipliers that bypass - `font-size: inherit` when an inline style was set; pin them too. */ -.email-bubble-body h1, -.email-bubble-body h2, -.email-bubble-body h3, -.email-bubble-body h4, -.email-bubble-body h5, -.email-bubble-body h6, -.email-bubble-body big, -.email-bubble-body small, -.email-bubble-body font { - font-size: inherit !important; - line-height: inherit !important; -} -/* Sender HTML often wraps content in a fixed-width - or . Override so content fills the reader's available - width — otherwise resizing the email window doesn't reflow anything. */ -.email-bubble-body, -.email-bubble-body * { - max-width: 100% !important; -} -.email-bubble-body div[style*="width"], -.email-bubble-body table[width], -.email-bubble-body table { - width: 100% !important; - box-sizing: border-box !important; -} -.email-bubble-body blockquote { - border-left: 2px solid var(--border) !important; - margin: 4px 0 !important; - padding: 4px 10px !important; - background: transparent !important; - border-radius: 0 !important; - color: inherit; -} -.email-bubble-avatar { - flex-shrink: 0; - width: 26px; - height: 26px; - border-radius: 50%; - /* Per-sender color set inline (`style="background:hsl(...)"`); this - fallback only kicks in when JS hasn't assigned one. */ - background: color-mix(in srgb, var(--fg) 14%, transparent); - color: #fff; - text-shadow: 0 1px 0 rgba(0, 0, 0, 0.22); - display: flex; - align-items: center; - justify-content: center; - font-size: 9.5px; - font-weight: 600; - letter-spacing: 0.3px; - user-select: none; -} -/* Sig folds inside a bubble: tone down — no full-bleed band, since the - bubble already provides the chrome. */ -.email-bubble-body .email-sig-fold { - margin: 8px 0 0; - border-top: 1px dashed var(--border); -} -.email-bubble-body .email-sig-fold[open] > *:not(summary), -.email-bubble-body .email-quote-fold[open] > *:not(summary) { - padding: 6px 0 0; - border-top: 0; -} -@media (max-width: 600px) { - .email-bubble { max-width: 96%; } - .email-bubble-avatar { display: none; } -} - -/* ── Schedule Send modal ── */ -.schedule-send-body { - display: flex; - flex-direction: column; - gap: 6px; - padding: 16px 18px 12px; -} -.schedule-send-label { - font-size: 11px; - font-weight: 500; - letter-spacing: 0.3px; - text-transform: uppercase; - color: var(--fg); - opacity: 0.55; - margin-top: 6px; -} -.schedule-send-label:first-child { margin-top: 0; } -.schedule-send-presets { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 6px; - margin-bottom: 4px; -} -.schedule-send-presets .memory-toolbar-btn { - padding: 5px 10px 11px; - font-size: 12px; - justify-content: center; - line-height: 1; -} -.schedule-send-confirm svg { - width: 12px; - height: 12px; - vertical-align: -1px; - margin-right: 6px; -} -.schedule-send-datetime { - font-size: 13px; - padding: 8px 10px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - outline: none; - font-family: inherit; - width: 100%; - box-sizing: border-box; -} -.schedule-send-datetime:focus { - border-color: var(--hl-function); -} -.schedule-send-footer { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 8px; - padding: 10px 18px 14px; - border-top: 1px solid var(--border); - margin-top: 4px; -} -.schedule-send-confirm { - background: var(--accent-primary, var(--red)) !important; - color: #fff !important; - border-color: var(--accent-primary, var(--red)) !important; -} -.schedule-send-confirm:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 85%, white) !important; -} - -/* Senders embed inline colors in their HTML that clash with the app's - theme (black text in dark mode, white backgrounds, yellow highlights, - etc.). Force theme colors on every descendant of the rendered body so - emails inherit the user's chosen palette regardless of what the - sender's mail client emits. */ -.email-reader-body *, -.email-bubble-body * { - color: var(--fg) !important; - -webkit-text-fill-color: currentColor !important; - background-color: transparent !important; - background-image: none !important; -} -.email-reader-body a, -.email-bubble-body a, -.email-thread-turn-body a { - color: var(--hl-function) !important; -} -.email-reader-body a[href^="tel:"], -.email-reader-body a[href^="sms:"], -.email-reader-body a[href^="x-apple-data-detectors:"], -.email-reader-body a[x-apple-data-detectors], -.email-bubble-body a[href^="tel:"], -.email-bubble-body a[href^="sms:"], -.email-bubble-body a[href^="x-apple-data-detectors:"], -.email-bubble-body a[x-apple-data-detectors], -.email-thread-turn-body a[href^="tel:"], -.email-thread-turn-body a[href^="sms:"], -.email-thread-turn-body a[href^="x-apple-data-detectors:"], -.email-thread-turn-body a[x-apple-data-detectors] { - color: inherit !important; - -webkit-text-fill-color: currentColor !important; - text-decoration: none !important; - background-color: transparent !important; - background-image: none !important; - -webkit-background-clip: border-box !important; - background-clip: border-box !important; -} - -/* Prefer local/system emoji fonts; avoid remote font providers. */ -.doc-editor-textarea, .doc-editor-highlight, #doc-editor-code, -.doc-md-preview, .doc-csv-preview { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', monospace; - font-variant-emoji: text; -} -/* Preview panes claim the editor's space so the action footer stays pinned - at the bottom of the pane even when the rendered content is short - (shorter table, single-paragraph markdown, etc). */ -.doc-csv-preview, -.doc-md-preview { - flex: 1 1 auto; - min-height: 0; - overflow: auto; -} - -/* Email documents override: use colored system emoji in compose. */ -.doc-editor-textarea.email-mode, -#doc-editor-code.email-mode, -#doc-editor-code.email-mode .doc-editor-highlight, -#doc-editor-code.email-mode .doc-editor-textarea { - font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla" !important; - font-variant-emoji: emoji; -} - -/* Single-layer rendering for ALL documents (not just email): the highlight - overlay ( with hljs spans) and the transparent textarea use two - different rendering paths internally — the textarea uses the browser's - native form line-breaker, the overlay uses CSS line-breaking — and they - can never be guaranteed to wrap byte-identical no matter how many CSS - properties are pinned. Pick one source of truth: make the textarea its - own visible text, hide the overlay. Trade-off: no syntax highlighting in - the live editor (rendered markdown preview / chat output still has it). - The caret is now glued to the typed text by definition since both are - rendered by the same element. */ -.doc-editor-highlight, -#doc-editor-code.email-mode .doc-editor-highlight { - display: none !important; -} -/* Find bar is open: float the highlight overlay ON TOP of the - textarea with transparent background + transparent text, so only - the solid spans are visible. pointer-events - off so clicks still go through to the textarea below. */ -body.doc-find-active .doc-editor-highlight, -body.doc-find-active #doc-editor-code.email-mode .doc-editor-highlight { - display: block !important; - z-index: 5 !important; - background: transparent !important; - pointer-events: none; -} -body.doc-find-active .doc-editor-highlight, -body.doc-find-active .doc-editor-highlight * { - color: transparent !important; - background: transparent !important; -} diff --git a/static/css/14-email.css b/static/css/14-email.css new file mode 100644 index 000000000..56d1df6df --- /dev/null +++ b/static/css/14-email.css @@ -0,0 +1,4729 @@ +/* 14-email - 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. + */ +#group-model-picker input[type="checkbox"] { + accent-color: var(--accent, var(--red)); +} +#group-model-picker input[type="radio"] { + accent-color: var(--accent, var(--red)); +} +#group-model-picker .btn-primary { + background: var(--accent, var(--red)); + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 500; +} +#group-model-picker .btn-primary:disabled { + opacity: 0.4; + cursor: not-allowed; +} +#group-model-picker .btn-primary:hover:not(:disabled) { + filter: brightness(1.1); +} + +/* ── Email document type ── */ +.doc-email-header { + display: flex; + flex-direction: column; + gap: 6px; + padding: 10px 12px; + border-bottom: 1px solid var(--border); + background: var(--bg); + flex-shrink: 0; +} +.email-field { + display: flex; + align-items: center; + gap: 8px; +} +.email-field label { + font-size: 11px; + font-weight: 600; + color: var(--fg); + opacity: 0.5; + min-width: 50px; + text-align: right; +} +.email-field input { + flex: 1; + background: transparent; + border: 1px solid var(--border); + border-radius: 4px; + padding: 5px 8px; + font-size: 13px; + font-family: inherit; + color: var(--fg); + outline: none; +} +.email-field input:focus { + border-color: var(--accent, #4a9eff); +} +.email-actions { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 2px; +} +.email-draft-btn { + background: transparent; + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 14px; + font-size: 12px; + cursor: pointer; + font-family: inherit; +} +.email-draft-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } +.email-draft-btn:disabled { opacity: 0.5; cursor: not-allowed; } + +/* ── Email inbox list ── */ +.email-body { border-top: 1px solid var(--border); } +.email-folder-bar { padding: 4px 8px; border-bottom: 1px solid var(--border); } +.email-folder-select { + background: transparent; border: 1px solid var(--border); border-radius: 4px; + color: var(--fg); font-size: 11px; padding: 2px 6px; cursor: pointer; width: 100%; height: 26px; +} +.email-item { + display: flex; align-items: flex-start; gap: 8px; padding: 8px 12px; + cursor: pointer; border-bottom: 1px solid var(--border); position: relative; transition: background 0.1s; +} +.email-item:hover { background: var(--hover-bg, rgba(255,255,255,0.03)); } +.email-item-spam:hover { opacity: 0.75; } +.email-tag-spam { + display: inline-flex; align-items: center; gap: 4px; + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); + color: var(--accent, var(--red)); + border-radius: 10px; padding: 1px 6px; font-size: 9px; font-weight: 600; + text-transform: lowercase; letter-spacing: 0.3px; +} +.email-spam-unflag { + background: none; border: none; color: inherit; cursor: pointer; + padding: 0 2px; font-size: 10px; line-height: 1; opacity: 0.6; +} +.email-spam-unflag:hover { opacity: 1; color: var(--fg); } +/* Unread state is now a subtle icon, not full styling */ +.email-done-check { + display: inline-flex; align-items: center; justify-content: center; + width: 16px; height: 16px; border-radius: 50%; background: transparent; + border: 1.5px solid var(--border); color: transparent; + flex-shrink: 0; cursor: pointer; transition: all 0.15s; + margin-right: 4px; +} +.email-done-check:hover { border-color: var(--accent, #4a9eff); } +.email-done-check.active { background: var(--accent, #4a9eff); border-color: var(--accent, #4a9eff); color: #fff; } +.email-done-check.active svg { display: block; } +.email-done-check svg { display: none; } + + + + +.email-delete-busy > :not(.email-delete-overlay) { + opacity: 0.42; + transition: opacity 0.12s ease; +} + +.email-delete-overlay { + position: absolute; + inset: 0; + z-index: 8; + display: flex; + align-items: center; + justify-content: center; + border-radius: inherit; + background: color-mix(in srgb, var(--panel, var(--bg)) 82%, transparent); + pointer-events: none; +} + +.email-delete-overlay .spinner-whirlpool, +.email-delete-overlay .ai-spinner-whirlpool { + margin: 0 !important; +} + +.doclib-card.email-card-removing { + pointer-events: none !important; + max-height: 0 !important; + padding-top: 0 !important; + padding-bottom: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + border-color: transparent !important; + opacity: 0; + transform: translateX(-10px) scale(0.985); + transition: + opacity 0.18s ease, + transform 0.22s ease, + max-height 0.23s ease, + padding 0.23s ease, + margin 0.23s ease, + border-color 0.18s ease; +} + +@media (prefers-reduced-motion: reduce) { + .doclib-card.email-card-removing { + transition: opacity 0.08s ease; + transform: none; + } +} + +/* Library card done check — always visible, subtle icon next to title */ +.email-card-done { + display: inline-flex; align-items: center; justify-content: center; + flex-shrink: 0; cursor: pointer; + transition: opacity 0.15s, color 0.15s; + opacity: 0.15; color: var(--fg); +} +.email-card-titlerow .memory-item-title { + flex: 1 1 auto; + min-width: 0; +} +.email-card-titlerow { position: relative; min-width: 0; } +.email-card-status { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 7px; + flex: 0 0 auto; + min-width: max-content; + margin-left: auto; + white-space: nowrap; +} +.email-card-status > * { + flex: 0 0 auto; +} +.email-card-status + .email-card-nav-arrows { + margin-left: 6px; +} +.email-card-status .email-card-nav-arrows { + margin-left: 2px; +} +.email-card-attachment { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 13px; + width: 13px; + height: 18px; +} +/* Hover preview: bright accent when unchecked so the user sees a check coming. + Once active, keep the exact same color on hover so the done state does not + visually flip while the pointer is still over it. */ +.email-card-done:not(.active):hover { + opacity: 0.75 !important; + color: var(--accent-primary, var(--red)); +} +.email-card-done.active { opacity: 0.95; color: var(--accent-primary, var(--red)); } +.email-card-done.active:hover { + opacity: 0.95 !important; + color: var(--accent-primary, var(--red)) !important; +} +.email-card-favorite { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 18px; + width: 18px; + height: 18px; + margin: 0; + padding: 0; + border: 0; + background: transparent; + color: var(--fg); + cursor: pointer; + position: relative; + top: -3px; + opacity: 0.18; + transition: opacity 0.15s, color 0.15s, transform 0.15s; +} +.email-card-favorite:hover, +.email-card-favorite:focus-visible { + opacity: 0.8; + color: var(--accent-primary, var(--red)); +} +.email-card-favorite.active { + opacity: 0.9; + color: var(--accent-primary, var(--red)); +} +@keyframes check-pop { + 0% { transform: scale(1); } + 40% { transform: scale(1.7) rotate(-10deg); } + 70% { transform: scale(1.1) rotate(4deg); } + 100% { transform: scale(1) rotate(0); } +} +@keyframes check-unpop { + 0% { transform: scale(1); opacity: 0.95; } + 35% { transform: scale(0.45) rotate(8deg); opacity: 0.05; } + 65% { transform: scale(0.9); opacity: 0.2; } + 100% { transform: scale(1); } +} + +/* Expanded email preview in library — override .memory-item row layout */ +.doclib-card.email-card-expanded.memory-item { + cursor: default !important; + background: var(--bg) !important; + border: 1px solid var(--border) !important; + display: flex !important; + flex-direction: column !important; + align-items: stretch !important; + /* Fill the available grid height instead of the old hardcoded 70vh — + the modal already caps height (90dvh on mobile, auto on desktop), so + the card just needs to flex into whatever remains. */ + flex: 1 1 auto !important; + height: 100% !important; + min-height: 0 !important; + max-height: none !important; + padding: 0 !important; + overflow: hidden !important; + gap: 0 !important; +} +.doclib-card.email-card-expanded.memory-item > div:first-child { + /* The summary content (subject, date) - acts as the title bar. Left padding + trimmed 14→6px to shift the title 8px left total. */ + padding: 2px 4px 10px !important; /* top trimmed to lift the header text up */ + margin-top: -14px !important; /* pull the whole bar up into the freed space */ + border-bottom: 1px solid var(--border); + flex: 0 0 auto !important; + width: 100%; +} +/* Keep the per-card '...' actions menu visible while the email card is + expanded — we used to hide it and surface a duplicate menu button in + the expanded header, but that was redundant. The bottom menu now + carries the actions in both states. */ +/* When expanded inline, show the subject a touch larger than the 12px card + title (close to the new-tab window header) without being oversized. */ +.doclib-card.email-card-expanded .memory-item-title { + font-size: 14px; + font-weight: 600; +} +/* The sender name is redundant once expanded (the From: row shows it just + below), so drop it and keep only the date — which then sits left-aligned + directly under the subject. Nudge the date 1px right to line up exactly with + the subject text, and up closer to it. */ +.doclib-card.email-card-expanded .email-meta-sender, +.doclib-card.email-card-expanded .email-meta-sep { + display: none; +} +.doclib-card.email-card-expanded .memory-item-meta { + margin-top: -9px !important; +} +.doclib-card.email-card-expanded .email-meta-date { + margin-left: 3px; +} +.email-card-reader { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + font-size: 12px; +} +.email-card-reader-loading { + background: color-mix(in srgb, var(--bg) 86%, #000 14%); + border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); + min-height: 240px; +} +.email-card-reader-error { + background: color-mix(in srgb, var(--bg) 86%, #000 14%); + border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); +} +.email-reader-load-error { + display: flex; + min-height: 180px; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 9px; + padding: 24px 16px; + text-align: center; + color: var(--fg); +} +.email-reader-load-error-title { + font-size: 13px; + font-weight: 700; +} +.email-reader-load-error-msg { + max-width: min(460px, 92%); + color: color-mix(in srgb, var(--fg) 62%, transparent); + font-size: 11px; + line-height: 1.35; + overflow-wrap: anywhere; +} +.email-skeleton-line, +.email-skeleton-dot { + display: block; + border-radius: 999px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 12%, transparent) 0%, + color-mix(in srgb, var(--fg) 22%, transparent) 45%, + color-mix(in srgb, var(--fg) 12%, transparent) 90% + ); + background-size: 220% 100%; + animation: email-skeleton-shimmer 1.35s ease-in-out infinite; +} +.email-skeleton-dot { + width: 9px; + height: 9px; + flex: 0 0 auto; +} +.email-list-skeleton { + position: absolute; + inset: 0; + padding: 10px 7px; + display: flex; + flex-direction: column; + gap: 8px; + pointer-events: none; +} +.email-skeleton-row { + display: flex; + align-items: center; + gap: 10px; + min-height: 48px; + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--border) 76%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 70%, transparent); +} +.email-skeleton-lines { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 7px; +} +.email-reader-skeleton { + display: flex; + flex-direction: column; + min-height: 100%; + flex: 1; +} +.email-reader-skeleton-header { + min-height: 60px; + padding: 12px 14px; + display: flex; + align-items: flex-start; + gap: 8px; + border-bottom: 1px solid var(--border); + background: color-mix(in srgb, var(--panel) 72%, transparent); +} +.email-reader-skeleton-header .chip { width: 132px; height: 18px; } +.email-reader-skeleton-header .chip.short { width: 78px; } +.email-reader-skeleton-header .action { + width: 38px; + height: 28px; + margin-left: auto; + border-radius: 6px; +} +.email-reader-skeleton-header .action + .action { margin-left: 0; } +.email-reader-skeleton-atts { + display: flex; + gap: 7px; + padding: 8px 14px; + border-bottom: 1px solid var(--border); +} +.email-reader-skeleton-atts .attachment { width: 126px; height: 20px; border-radius: 6px; } +.email-reader-skeleton-atts .attachment.short { width: 88px; } +.email-reader-skeleton-body { + display: flex; + flex-direction: column; + gap: 11px; + padding: 16px 14px; + min-height: 340px; + flex: 1; +} +.email-reader-skeleton-body .body { width: 88%; height: 9px; border-radius: 6px; } +.email-reader-skeleton-body .body.wide { width: 100%; } +.email-reader-skeleton-body .body.medium { width: 94%; } +.email-reader-skeleton-body .body.short { width: 62%; } +.email-reader-skeleton-body .body.gap { + margin-top: 12px; + width: 78%; +} +.email-search-progress-row { + display: flex; + align-items: center; + justify-content: center; + gap: 7px; + min-height: 30px; + padding: 6px 10px; + font-size: 11px; + opacity: 0.65; +} +.email-search-progress-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent-primary, var(--red)); + box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); + animation: email-search-progress-pulse 1.2s ease-in-out infinite; +} +@media (prefers-reduced-motion: reduce) { + .email-skeleton-line, + .email-skeleton-dot, + .email-inline-image-skeleton, + .email-inline-image-placeholder.is-loading .email-inline-image-icon, + .email-inline-image-frame.is-loading, + .email-search-progress-dot { + animation: none; + } +} +/* Per-email unread dot in the expanded reader's title row. Background color + stays inline (per-sender hue from _senderColor), but the dot gets a soft + breathing glow + a 4px vertical nudge so it reads as centered against + the row instead of riding the baseline. */ +.email-card-unread-dot { + position: relative; + top: 0; + animation: email-card-unread-breathe 2.2s ease-in-out infinite; +} +.email-reader-header { + display: flex; + flex-direction: row; + /* Top-align so when the user expands To/Cc, the action cluster + stays on the From line above instead of drifting to the middle. */ + align-items: flex-start; + gap: 12px; + /* Taller header so the 44px-tall absolute-positioned action + cluster inside the From row has vertical breathing room. */ + padding: 8px 14px; + min-height: 60px; + border-bottom: 1px solid var(--border); + background: var(--bg); + flex-shrink: 0; + /* Containing block for the absolute action cluster. */ + position: relative; + /* Container so the action-cluster wrap breakpoints below react to + the reader's own width instead of the page viewport. */ + container-type: inline-size; + container-name: emailreader; +} +.email-reader-header > .email-reader-actions { + margin-left: auto; + flex-shrink: 0; +} +/* Meta block stacks the From / To / Cc rows; each row keeps label + tight to its chip(s) on the same line. The recipient-chips inside + scroll horizontally so long lists slide under the floating action + cluster on the right. */ +.email-reader-meta { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + /* Positioning context for the absolute action cluster. */ + position: relative; +} +.email-reader-meta .recipient-chips { + display: inline-flex; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + gap: 4px; + max-width: 100%; + scrollbar-width: none; + -ms-overflow-style: none; +} +.email-reader-meta .recipient-chips::-webkit-scrollbar { display: none; } +.email-reader-meta-row { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.email-reader-meta-row.email-reader-meta-from { + position: relative; + /* Never wrap "From:" off the chip onto its own line — the chip + span has overflow-x scroll built in, so it shrinks/scrolls + instead of jumping to a new row. */ + flex-wrap: nowrap; + min-width: 0; + /* Reserve space on the right for the 1-row cluster in wide mode. */ + padding-right: 320px; +} +.email-reader-meta > .email-reader-meta-details { + min-width: 0; + padding-right: 320px; +} +.email-reader-meta > .email-reader-actions-inline { + position: absolute; + top: 1px; + right: 0; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + justify-content: flex-end; + gap: 4px; + z-index: 2; + /* Sit visually above the From baseline like the lifted overlay. */ + margin-top: -9px; +} +/* Recipient chip span keeps at least ~4 characters visible (60px) + before the overflow-scroll kicks in, so the sender / first + recipient name is always partly readable. */ +.email-reader-meta .recipient-chips { + min-width: 60px; +} +/* Medium reader widths: cap the cluster at 3 icons per row so it + never wraps past 2 rows. min-width forces the cluster to reserve + enough room for 3 icons in a row — without it, grid col 2 could + shrink below the cap and the cluster fanned out into 4+ short + rows. */ +@container emailreader (max-width: 450px) { + .email-reader-meta > .email-reader-actions-inline { + /* 3 icons (48px each) + 2 gaps (4px each) = 152px exact. Adding + 6px slack so subpixel rounding can't tip a row from 3 to 2 + icons and fan the cluster into a 3rd row. */ + min-width: 158px; + max-width: 158px; + /* Solid background so the email body content behind the cluster + doesn't peek through the gaps between icons or the slack on + the right edge when the cluster's wrapped to 2 rows. */ + background: var(--bg); + } + .email-reader-meta > .email-reader-actions-inline .memory-toolbar-btn.reader-icon-btn { + width: 48px; + min-width: 48px; + max-width: 48px; + height: 40px; + padding-left: 0; + padding-right: 0; + gap: 2px; + } + .email-reader-meta > .email-reader-actions-inline .reader-btn-label { + display: block; + max-width: 44px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 8px; + line-height: 1; + } + /* Tighten the right-side padding now that the cluster is 158px + wide (instead of the ~308 wide 1-row layout). */ + .email-reader-meta-row.email-reader-meta-from, + .email-reader-meta > .email-reader-meta-details { + padding-right: 170px; + } + /* Cluster wrapped to 2 rows (44px + 4 gap + 44px = 92px) sits + absolute above the From baseline. Set min-height on the meta + (a descendant of the container, more reliable than targeting + the container itself) so the header grows enough to contain + the cluster + 4px of breathing room between the cluster's + bottom and the header bottom. */ + .email-reader-meta { + min-height: 96px; + } +} +/* Very narrow reader: once the chip span has shrunk to its ~4 + character min, the cluster snaps to absolute overlay with a soft + shadow so it visually floats above the truncated chip. */ +@container emailreader (max-width: 380px) { + /* Pane too narrow even for 158px of right-side reservation — kill + the padding so chips use the full width, and let the cluster + overlay via z-index + shadow. */ + .email-reader-meta-row.email-reader-meta-from, + .email-reader-meta > .email-reader-meta-details { + padding-right: 0; + } + .email-reader-meta > .email-reader-actions-inline { + /* Keep the same -7px lift as the medium-width state so the + cluster doesn't slide ~4px DOWN when it transitions into + overlay mode. */ + /* margin-top stays inherited at -7px from the base rule. */ + /* 158px content + 22px padding-left so the inner row still fits + 3 icons cleanly. */ + min-width: 180px; + max-width: 180px; + background: linear-gradient(to right, transparent 0, var(--bg) 18px); + padding-left: 22px; + /* Soft shadow on the left edge so the overlap reads as the + cluster "floating over" the chip. */ + box-shadow: -6px 0 12px -6px rgba(0, 0, 0, 0.25); + } +} +/* Mobile: drop the box-shadow AND the left-edge gradient fade — + either can read as a shadow under the icons on phone screens. + Also lift the cluster 1px more. !important needed because the + @container query overlay rule has the same selector specificity. */ +@media (max-width: 768px) { + .memory-toolbar-btn[data-mode="ai-reply-fast"] { + height: 30px !important; + min-height: 30px !important; + } + .email-reader-meta > .email-reader-actions-inline { + box-shadow: none !important; + background: none !important; + padding-left: 0 !important; + margin-top: -10px; + } +} +/* Docked panes used to flow the cluster under the meta; now they + share the same absolute-top-right + wrap behaviour as undocked. + No docked-specific overrides — the container queries below pick + the right cluster size and overlay state from the reader width. */ +/* Gmail-style chevron — collapsed view shows only From; the chevron + reveals the To/Cc details inline below. */ +.email-reader-meta-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + margin-left: -3px; + padding: 0; + border: none; + background: transparent; + color: inherit; + opacity: 0.55; + cursor: pointer; + border-radius: 4px; + flex-shrink: 0; +} +.email-reader-meta-toggle:hover { opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent); } +.email-reader-meta-toggle svg { transition: transform 0.15s ease; } +.email-reader-meta-toggle.open svg { transform: rotate(180deg); } +.email-reader-meta-toggle.email-reader-cc-toggle { + width: auto; + min-width: 18px; + gap: 3px; + margin-left: 1px; + padding: 0 5px; + color: var(--accent, var(--red)); + font-family: inherit; + font-size: 9px; + font-weight: 600; + line-height: 1; +} +.email-reader-meta-details { + display: flex; + flex-direction: column; + gap: 6px; +} +/* When expanded, To/Cc details live in the header flow. A floating popover + can overlap the folded attachment strip directly beneath the header. */ +.email-reader-meta > .email-reader-meta-details:not([hidden]) { + position: relative; + top: auto; + left: auto; + right: auto; + margin-top: 4px; + background: transparent; + border: 0; + border-radius: 0; + padding: 0; + box-shadow: none; + z-index: 1; +} +/* Reserve a fixed label width so From: / To: / Cc: all align and + their chips start at the same x. */ +.email-reader-meta-row strong { opacity: 0.5; font-weight: 600; flex-shrink: 0; min-width: 36px; } +.email-reader-meta-row > span { + white-space: nowrap; + min-width: 0; +} +/* Recipient chips */ +.recipient-chips { + display: inline-flex !important; flex-wrap: wrap; gap: 4px; + white-space: normal !important; overflow: visible !important; + text-overflow: clip !important; +} +.recipient-chip { + display: inline-flex; align-items: center; + gap: 5px; + padding: 1px 8px; font-size: 10px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid var(--border); + border-radius: 10px; + color: var(--fg); + white-space: nowrap; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, max-width 0.2s; + max-width: 220px; + overflow: hidden; + text-overflow: ellipsis; +} +.recipient-chip-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} +.recipient-chip-copy { + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + top: -2px; + width: 14px; + height: 14px; + padding: 0; + border: none; + background: none; + color: inherit; + opacity: 0.55; + cursor: pointer; + flex: 0 0 auto; +} +.recipient-chip-copy:hover, +.recipient-chip-copy.copied { + opacity: 1; + color: var(--accent-primary, var(--red)); +} +.recipient-chip-copy[hidden] { + display: none !important; +} +.recipient-chip:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); +} +.recipient-chip.expanded { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); + border-color: var(--accent-primary, var(--red)); + max-width: 500px; +} +.recipient-chip.popover-open { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); + border-color: var(--accent-primary, var(--red)); +} +.recipient-chip-popover { + position: fixed; + z-index: 12050; + display: flex; + align-items: center; + gap: 8px; + max-width: min(420px, calc(100vw - 20px)); + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--bg) 96%, var(--fg) 4%); + color: var(--fg); + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.42); + font-size: 12px; + line-height: 1.35; + pointer-events: auto; +} +.recipient-chip-popover-main { + min-width: 0; +} +.recipient-chip-popover-name { + font-weight: 650; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.recipient-chip-popover-detail { + overflow-wrap: anywhere; + opacity: 0.86; +} +.recipient-chip-popover-copy { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 26px; + height: 26px; + padding: 0; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + color: inherit; + cursor: pointer; +} +.recipient-chip-popover-copy:hover, +.recipient-chip-popover-copy.copied { + color: var(--accent-primary, var(--red)); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, transparent); +} +@container docpane (max-width: 460px) { + .email-reader-header { + flex-direction: column; + gap: 6px; + } + .email-reader-header > .email-reader-actions { + /* On narrow widths fall back to in-flow so it doesn't overlap + the meta entirely. */ + position: static; + align-self: flex-end; + background: none; + padding-left: 0; + } + .email-reader-meta-row:not(.email-reader-meta-from) { + padding-right: 0; + } + .email-reader-meta-row strong { + min-width: 0; + } + .recipient-chip { + max-width: 100%; + } +} +/* Mobile: long recipient lists (To/Cc with many addresses) shouldn't wrap to + N rows and push the body down. Keep them on one row, horizontally scrollable, + no scrollbar chrome. */ +@media (max-width: 768px) { + .recipient-chips { + flex-wrap: nowrap !important; + overflow-x: auto !important; + overflow-y: hidden !important; + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + } + .recipient-chips::-webkit-scrollbar { display: none; } + .recipient-chip { flex-shrink: 0; } +} +.email-reader-actions { + display: flex; flex-direction: column; gap: 4px; align-items: flex-end; + flex-shrink: 0; + margin-top: -4px; +} +/* Two stacked rows: Reply/Reply-all/Forward on the From line, with + Summary/AI-reply/More on a row below. */ +.email-reader-actions-row { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: flex-end; + gap: 4px; +} +.email-reader-atts { + display: flex; flex-wrap: wrap; gap: 6px; + padding: 8px 14px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.email-reader-body { + font-size: 12px; + line-height: 1.55; + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: anywhere; + flex: 1; + overflow-y: auto; + padding: 14px; + min-height: 0; + background: var(--panel); + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--border) 65%, transparent); + /* Use the OS colored emoji font for incoming mail content */ + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"; + font-variant-emoji: emoji; +} +.email-reader-body.html-body img { max-width: 100%; height: auto; } +.email-reader-body.html-body [style*="max-width:600px"], +.email-reader-body.html-body [style*="max-width: 600px"], +.email-reader-body.html-body [style*="width:600px"], +.email-reader-body.html-body [style*="width: 600px"], +.email-reader-body.html-body table[width="600"] { + max-width: none !important; + width: 100% !important; + margin-left: 0 !important; + margin-right: 0 !important; +} +.email-reader-body.html-body [align="center"] { + margin-left: 0 !important; + margin-right: 0 !important; +} +.email-reader-body .email-inline-image-placeholder { + display: inline-flex; + align-items: center; + gap: 9px; + max-width: min(100%, 360px); + min-height: 58px; + margin: 6px 0; + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 72%, transparent); + color: var(--fg); + vertical-align: middle; +} +.email-inline-image-skeleton { + width: 58px; + height: 42px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 6px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 7%, transparent) 0%, + color-mix(in srgb, var(--fg) 13%, transparent) 45%, + color-mix(in srgb, var(--fg) 7%, transparent) 90% + ); + background-size: 220% 100%; + animation: email-skeleton-shimmer 1.35s ease-in-out infinite; +} +.email-inline-image-icon { + opacity: 0.52; + display: inline-flex; +} +.email-inline-image-placeholder.is-loading .email-inline-image-icon svg { + display: none; +} +.email-inline-image-info { + display: inline-flex; + flex-direction: column; + min-width: 0; + line-height: 1.25; +} +.email-inline-image-title { + font-size: 11px; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-inline-image-sub { + font-size: 10px; + opacity: 0.58; +} +.email-inline-image-actions { + display: inline-flex; + align-items: center; + gap: 5px; + margin-left: auto; + flex: 0 0 auto; +} +.email-inline-image-btn { + display: inline-flex; + align-items: center; + justify-content: center; + transform: translateY(-2px); + height: 24px; + line-height: 1; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: inherit; + font: inherit; + font-size: 10px; + text-decoration: none; + cursor: pointer; +} +.email-inline-image-btn:hover { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); + color: var(--accent-primary, var(--red)); +} +.email-inline-image-download-btn { + width: 24px; + min-width: 24px; + padding: 0; +} +.email-inline-image-loaded { + max-width: 100%; + height: auto; + border-radius: 6px; +} +.email-inline-image-frame { + display: inline-flex; + max-width: 100%; + min-width: min(360px, 100%); + min-height: 90px; + margin: 6px 0; + align-items: center; + justify-content: center; + border-radius: 8px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 7%, transparent) 0%, + color-mix(in srgb, var(--fg) 13%, transparent) 45%, + color-mix(in srgb, var(--fg) 7%, transparent) 90% + ); + background-size: 220% 100%; +} +.email-inline-image-frame .email-inline-image-loaded { + opacity: 0; + transition: opacity 0.15s ease; +} +.email-inline-image-load-all { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + max-width: 100%; + margin: -4px 0 6px; + padding: 7px 9px; + border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 72%, transparent); + font-size: 11px; +} +.email-inline-image-load-all span { + opacity: 0.68; + margin-right: auto; +} +.email-inline-image-load-all button { + transform: translateY(-4px); + height: 24px; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: inherit; + font: inherit; + font-size: 10px; + cursor: pointer; +} +.email-inline-image-load-all button:hover { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); + color: var(--accent-primary, var(--red)); +} +@media (max-width: 520px) { + .email-reader-body .email-inline-image-placeholder { + display: flex; + width: 100%; + } + .email-inline-image-skeleton { + width: 48px; + } + .email-inline-image-actions { + flex-direction: column; + align-items: stretch; + } +} +.email-reader-body.html-body table { max-width: 100%; border-collapse: collapse; } +/* Gmail Drive chips arrive with inline width/height/border/padding from + Gmail's composer (border:1px solid #ddd, white-ish bg assumed). On a + dark theme that reads as a near-invisible white box. Override with a + theme-aware chip surface so the filename + icon stay visible. */ +.email-reader-body .gmail_chip, +.email-reader-body .gmail_drive_chip { + width: auto !important; + max-width: 100% !important; + height: auto !important; + max-height: none !important; + display: inline-flex !important; + align-items: center !important; + gap: 6px; + padding: 6px 10px !important; + margin: 6px 0 !important; + border: 1px solid var(--border) !important; + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.email-reader-body .gmail_chip:hover, +.email-reader-body .gmail_drive_chip:hover { + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)) !important; + background: color-mix(in srgb, var(--accent, var(--red)) 6%, transparent); +} +.email-reader-body .gmail_chip a, +.email-reader-body .gmail_drive_chip a { + color: var(--accent, var(--red)) !important; + display: inline-flex !important; + align-items: center; + gap: 6px; + max-width: 100% !important; +} +.email-reader-body .gmail_chip a > span, +.email-reader-body .gmail_drive_chip a > span { + color: var(--fg) !important; + font-size: 12px; +} +.email-reader-body .gmail_chip img, +.email-reader-body .gmail_drive_chip img { + width: 16px !important; + height: 16px !important; + padding-right: 0 !important; + filter: none; +} + +/* Force any phone/sms/date/address auto-link to inherit the body color so + numbers don't render as bright-blue browser-default text inside email + content. format-detection in turns off detection on iOS, but some + mail clients ship pre-wrapped tel: links — those still need taming. */ +.email-reader-body a[href^="tel:"], +.email-reader-body a[href^="sms:"], +.email-reader-body a[x-apple-data-detectors], +.email-bubble-body a[href^="tel:"], +.email-bubble-body a[href^="sms:"], +.email-bubble-body a[x-apple-data-detectors], +.email-thread-turn-body a[href^="tel:"], +.email-thread-turn-body a[href^="sms:"], +.email-thread-turn-body a[x-apple-data-detectors] { + color: inherit !important; + text-decoration: none !important; + pointer-events: auto; +} +.email-reader-body a, +.email-bubble-body a, +.email-thread-turn-body a { + color: var(--accent-primary, var(--red)); + text-decoration: underline; + overflow-wrap: anywhere; + word-break: normal; + cursor: pointer; +} +.email-reader-body a:hover, +.email-bubble-body a:hover, +.email-thread-turn-body a:hover { opacity: 0.8; } +/* Summary block — same band chrome as Attachments / Signature / Earlier- + thread (no accent tint, no rounded corners, no leading star icon). The + only difference is the label, so all collapsible sections in the email + reader look uniform. */ +.email-summary-panel { + margin: 0 0 12px 0; + padding: 10px 12px; + background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); + border-radius: 6px; +} +.email-summary-header { + display: flex; align-items: center; gap: 5px; + font-size: 10px; font-weight: 600; text-transform: uppercase; + color: var(--accent-primary, var(--red)); opacity: 0.85; + margin-bottom: 6px; letter-spacing: 0.4px; +} +.email-summary-content { + font-size: 12px; line-height: 1.5; color: var(--fg); white-space: pre-wrap; +} +/* Click-to-fold: tap the summary header to collapse/expand. The chevron + flips when collapsed, the content hides + the panel's bottom margin + tightens so a folded summary doesn't take up vertical space. */ +.email-summary-toggle { cursor: pointer; user-select: none; } +.email-summary-panel.collapsed { padding-bottom: 6px; margin-bottom: 6px; } +.email-translation-loading { + display: flex; + align-items: center; + justify-content: center; + min-height: 26px; +} +.email-translation-busy { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; +} +.email-translation-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; +} +.email-translation-loading-text { + opacity: 0.68; + font-size: 12px; +} +.email-translation-panel .email-translation-icon { + color: var(--accent-primary, var(--red)); +} + +/* Foldable attachments — same fold-on-click UX as the summary panel. */ +.email-reader-atts-wrap { + display: flex; flex-direction: column; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.email-reader-atts-header { + display: flex; align-items: center; gap: 5px; + padding: 6px 14px; + font-size: 10px; font-weight: 600; text-transform: uppercase; + color: var(--fg); opacity: 0.7; + cursor: pointer; user-select: none; + letter-spacing: 0.4px; +} +.email-attachments-download-all { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + height: 22px; + padding: 0 8px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); + color: var(--accent-primary, var(--red)); + font: inherit; + font-size: 10px; + font-weight: 700; + text-transform: none; + letter-spacing: 0; + cursor: pointer; + flex: 0 0 auto; +} +.email-attachments-download-all:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 60%, transparent); +} +.email-attachments-download-all .spinner-whirlpool, +.email-attachments-download-all .ai-spinner-whirlpool { + width: 12px !important; + height: 12px !important; + margin: 0 !important; +} +.email-reader-atts-wrap > .email-reader-atts { + border-bottom: none !important; +} +.email-reader-atts-hidden-note { + padding: 0 14px 6px; + font-size: 10px; + color: var(--fg-muted); + opacity: 0.65; +} + +/* Quote fold = neutral full-width band (matches attachments header). */ +.email-quote-fold { + margin: 0 -14px; + padding: 0; + border: 0; + border-top: 1px solid var(--border) !important; + border-radius: 0; + outline: 0 !important; + box-shadow: none !important; + /* Neutralise any rich-mail HTML that injects a yellow / colored bg + or border onto blockquote ancestors. */ + background: transparent !important; +} +/* Same neutralisation for any element nested inside the fold (the inner + blockquote, paragraphs, divs from the original message). Yellow outlines + in Outlook-style mails almost always come from inline border-color or + box-shadow on these. */ +.email-quote-fold *, +.email-quote-fold *::before, +.email-quote-fold *::after { + outline-color: transparent !important; + box-shadow: none !important; +} +.email-quote-fold blockquote, +.email-quote-fold blockquote[type="cite"], +.email-quote-fold table, +.email-quote-fold div { + border-color: var(--border) !important; + /* Override the .msg blockquote rule that sets a colored left border via + var(--hl-function) — that's where the "yellow" outline was coming from. */ + border-left-color: var(--border) !important; + border-radius: 0 !important; + background: transparent !important; +} +/* JS marks the last .email-quote-fold in each email body with this class, + so it's the only one that gets rounded bottom corners. Avoids :has() + browser-support quirks. */ +.email-quote-fold.last-fold { + border-radius: 0 0 8px 8px; + overflow: hidden; +} +/* ── Threaded reply turns (recursive parser) ── */ +.email-thread-turn-body { + padding: 8px 14px 4px; + font-size: 12px; + line-height: 1.45; +} +/* Nested turns indent slightly + get their own card outline so the + reply hierarchy is obvious at a glance. */ +.email-thread-turn-body .email-thread-turn { + margin: 8px 0 4px; + margin-left: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +/* The original blockquote's left stripe is redundant when each turn is + already a card — drop it inside thread bodies. */ +.email-thread-turn-body blockquote { + border-left: none !important; + margin: 0 !important; + padding: 0 !important; + background: transparent !important; +} +/* Flatten the inner blockquote: no rounded corners, no yellow tint + inherited from rich-mail HTML. Subtle left border + neutral bg. */ +.email-quote-fold blockquote { + margin: 0; + padding: 8px 12px; + border: 0; + border-left: 2px solid var(--border); + border-radius: 0 !important; + background: transparent !important; + color: inherit; +} +/* "From … · Date …" chip appended to the summary line */ +.email-fold-summary-meta { + margin-left: 6px; + font-size: 10px; + font-weight: 500; + text-transform: none; + letter-spacing: 0; + opacity: 0.55; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +/* Sender name is the primary affordance — slightly larger and not all-caps so + "From: Sam" feels like a click target on the person, not a section label. */ +.email-fold-summary-name { + font-size: 11px; + font-weight: 600; + text-transform: none; + letter-spacing: 0.1px; + color: var(--fg); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.email-quote-fold .email-fold-summary { + display: flex; align-items: center; gap: 5px; + padding: 6px 14px; + cursor: pointer; list-style: none; + font-size: 10px; font-weight: 600; text-transform: uppercase; + letter-spacing: 0.4px; + /* Force neutral page colors regardless of any rich-mail HTML wrapping us */ + color: var(--fg) !important; + background: transparent !important; + background-color: transparent !important; + text-shadow: none !important; + font-style: normal !important; + opacity: 0.7; + user-select: none; + border-radius: 0; +} +.email-quote-fold .email-fold-summary > * { + /* Prevent inline-color attrs on summary children (icon SVGs, meta span) + from picking up a forced color from ancestor email HTML. */ + color: inherit !important; + background: transparent !important; +} +.email-quote-fold .email-fold-summary { list-style: none; list-style-type: none; } +.email-quote-fold[open] > *:not(summary) { + padding: 8px 14px 12px 14px; + border-top: 1px solid var(--border); +} + +/* Signature fold = clean full-width band like the attachments header. No + accent overlay, no rounded corners — neutral chrome so it doesn't look + like the AI Summary panel. */ +.email-sig-fold { + margin: 8px 0 0; + padding: 0; + border: 0; + border-top: 1px dashed color-mix(in srgb, var(--fg) 18%, transparent); + background: transparent; + border-radius: 0; +} +.email-sig-fold .email-fold-summary { + display: flex; align-items: center; gap: 5px; + padding: 4px 0; + cursor: pointer; + list-style: none; list-style-type: none; + font-size: 9.5px; font-weight: 600; text-transform: uppercase; + letter-spacing: 0.4px; + color: var(--fg); opacity: 0.55; + user-select: none; +} +.email-sig-fold[open] > *:not(summary) { + padding: 6px 0 0; + border-top: 0; + font-size: 11px; line-height: 1.45; + color: color-mix(in srgb, var(--fg) 75%, transparent); + white-space: pre-wrap; +} + +/* ── Chat-bubble layout for email threads ── + Each parsed turn becomes a bubble. The active account's outgoing + replies align right (mine); everyone else aligns left (theirs). + Bubbles read top→bottom oldest→newest, like a chat. */ +.email-bubbles { + display: flex; + flex-direction: column; + gap: 10px; + padding: 4px 0 8px; + margin: 0 -2px; +} +.email-bubble-row { + display: flex; + align-items: flex-end; + gap: 8px; + width: 100%; + min-width: 0; +} +.email-bubble { + flex: 0 1 auto; + max-width: 90%; + min-width: 0; + padding: 8px 12px; + border: 1px solid var(--border); + background: var(--panel); + font-size: 12.5px; + line-height: 1.5; + word-wrap: break-word; + overflow-wrap: anywhere; + overflow: hidden; +} +.email-bubble-mine .email-bubble { + background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 10%, var(--bg)); + border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 35%, var(--border)); + border-radius: 14px 14px 4px 14px; +} +.email-bubble-theirs .email-bubble { + background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 5%, var(--panel)); + border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 25%, var(--border)); + border-radius: 14px 14px 14px 4px; +} +.email-bubble-head { + display: flex; + align-items: baseline; + gap: 8px; + font-size: 10.5px; + margin-bottom: 4px; + opacity: 0.7; + min-width: 0; +} +.email-bubble-author { + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 50ch; +} +.email-bubble-date { + opacity: 0.7; + font-weight: 400; + white-space: nowrap; +} +.email-bubble-body { + white-space: normal; + /* Reader-controlled typography — override sender's inline styles AND + attribute-based sizing (, , , h1..h6). Weight + and italics still pass through for emphasis. */ + font-family: inherit; + font-size: var(--email-body-size, 13.5px); + line-height: 1.5; +} +/* Heading tags carry browser-default font-size multipliers that bypass + `font-size: inherit` when an inline style was set; pin them too. */ +.email-bubble-body h1, +.email-bubble-body h2, +.email-bubble-body h3, +.email-bubble-body h4, +.email-bubble-body h5, +.email-bubble-body h6, +.email-bubble-body big, +.email-bubble-body small, +.email-bubble-body font { + font-size: inherit !important; + line-height: inherit !important; +} +/* Sender HTML often wraps content in a fixed-width + or . Override so content fills the reader's available + width — otherwise resizing the email window doesn't reflow anything. */ +.email-bubble-body, +.email-bubble-body * { + max-width: 100% !important; +} +.email-bubble-body div[style*="width"], +.email-bubble-body table[width], +.email-bubble-body table { + width: 100% !important; + box-sizing: border-box !important; +} +.email-bubble-body blockquote { + border-left: 2px solid var(--border) !important; + margin: 4px 0 !important; + padding: 4px 10px !important; + background: transparent !important; + border-radius: 0 !important; + color: inherit; +} +.email-bubble-avatar { + flex-shrink: 0; + width: 26px; + height: 26px; + border-radius: 50%; + /* Per-sender color set inline (`style="background:hsl(...)"`); this + fallback only kicks in when JS hasn't assigned one. */ + background: color-mix(in srgb, var(--fg) 14%, transparent); + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.22); + display: flex; + align-items: center; + justify-content: center; + font-size: 9.5px; + font-weight: 600; + letter-spacing: 0.3px; + user-select: none; +} +/* Sig folds inside a bubble: tone down — no full-bleed band, since the + bubble already provides the chrome. */ +.email-bubble-body .email-sig-fold { + margin: 8px 0 0; + border-top: 1px dashed var(--border); +} +.email-bubble-body .email-sig-fold[open] > *:not(summary), +.email-bubble-body .email-quote-fold[open] > *:not(summary) { + padding: 6px 0 0; + border-top: 0; +} +@media (max-width: 600px) { + .email-bubble { max-width: 96%; } + .email-bubble-avatar { display: none; } +} + +/* ── Schedule Send modal ── */ +.schedule-send-body { + display: flex; + flex-direction: column; + gap: 6px; + padding: 16px 18px 12px; +} +.schedule-send-label { + font-size: 11px; + font-weight: 500; + letter-spacing: 0.3px; + text-transform: uppercase; + color: var(--fg); + opacity: 0.55; + margin-top: 6px; +} +.schedule-send-label:first-child { margin-top: 0; } +.schedule-send-presets { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; + margin-bottom: 4px; +} +.schedule-send-presets .memory-toolbar-btn { + padding: 5px 10px 11px; + font-size: 12px; + justify-content: center; + line-height: 1; +} +.schedule-send-confirm svg { + width: 12px; + height: 12px; + vertical-align: -1px; + margin-right: 6px; +} +.schedule-send-datetime { + font-size: 13px; + padding: 8px 10px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + outline: none; + font-family: inherit; + width: 100%; + box-sizing: border-box; +} +.schedule-send-datetime:focus { + border-color: var(--hl-function); +} +.schedule-send-footer { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 8px; + padding: 10px 18px 14px; + border-top: 1px solid var(--border); + margin-top: 4px; +} +.schedule-send-confirm { + background: var(--accent-primary, var(--red)) !important; + color: #fff !important; + border-color: var(--accent-primary, var(--red)) !important; +} +.schedule-send-confirm:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 85%, white) !important; +} + +/* Senders embed inline colors in their HTML that clash with the app's + theme (black text in dark mode, white backgrounds, yellow highlights, + etc.). Force theme colors on every descendant of the rendered body so + emails inherit the user's chosen palette regardless of what the + sender's mail client emits. */ +.email-reader-body *, +.email-bubble-body * { + color: var(--fg) !important; + -webkit-text-fill-color: currentColor !important; + background-color: transparent !important; + background-image: none !important; +} +.email-reader-body a, +.email-bubble-body a, +.email-thread-turn-body a { + color: var(--hl-function) !important; +} +.email-reader-body a[href^="tel:"], +.email-reader-body a[href^="sms:"], +.email-reader-body a[href^="x-apple-data-detectors:"], +.email-reader-body a[x-apple-data-detectors], +.email-bubble-body a[href^="tel:"], +.email-bubble-body a[href^="sms:"], +.email-bubble-body a[href^="x-apple-data-detectors:"], +.email-bubble-body a[x-apple-data-detectors], +.email-thread-turn-body a[href^="tel:"], +.email-thread-turn-body a[href^="sms:"], +.email-thread-turn-body a[href^="x-apple-data-detectors:"], +.email-thread-turn-body a[x-apple-data-detectors] { + color: inherit !important; + -webkit-text-fill-color: currentColor !important; + text-decoration: none !important; + background-color: transparent !important; + background-image: none !important; + -webkit-background-clip: border-box !important; + background-clip: border-box !important; +} + +/* Prefer local/system emoji fonts; avoid remote font providers. */ +.doc-editor-textarea, .doc-editor-highlight, #doc-editor-code, +.doc-md-preview, .doc-csv-preview { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', monospace; + font-variant-emoji: text; +} +/* Preview panes claim the editor's space so the action footer stays pinned + at the bottom of the pane even when the rendered content is short + (shorter table, single-paragraph markdown, etc). */ +.doc-csv-preview, +.doc-md-preview { + flex: 1 1 auto; + min-height: 0; + overflow: auto; +} + +/* Email documents override: use colored system emoji in compose. */ +.doc-editor-textarea.email-mode, +#doc-editor-code.email-mode, +#doc-editor-code.email-mode .doc-editor-highlight, +#doc-editor-code.email-mode .doc-editor-textarea { + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla" !important; + font-variant-emoji: emoji; +} + +/* Single-layer rendering for ALL documents (not just email): the highlight + overlay ( with hljs spans) and the transparent textarea use two + different rendering paths internally — the textarea uses the browser's + native form line-breaker, the overlay uses CSS line-breaking — and they + can never be guaranteed to wrap byte-identical no matter how many CSS + properties are pinned. Pick one source of truth: make the textarea its + own visible text, hide the overlay. Trade-off: no syntax highlighting in + the live editor (rendered markdown preview / chat output still has it). + The caret is now glued to the typed text by definition since both are + rendered by the same element. */ +.doc-editor-highlight, +#doc-editor-code.email-mode .doc-editor-highlight { + display: none !important; +} +/* Find bar is open: float the highlight overlay ON TOP of the + textarea with transparent background + transparent text, so only + the solid spans are visible. pointer-events + off so clicks still go through to the textarea below. */ +body.doc-find-active .doc-editor-highlight, +body.doc-find-active #doc-editor-code.email-mode .doc-editor-highlight { + display: block !important; + z-index: 5 !important; + background: transparent !important; + pointer-events: none; +} +body.doc-find-active .doc-editor-highlight, +body.doc-find-active .doc-editor-highlight * { + color: transparent !important; + background: transparent !important; +} +.doc-editor-textarea, +#doc-editor-code.email-mode .doc-editor-textarea { + color: var(--fg) !important; + caret-color: var(--fg) !important; + z-index: 1; +} + +/* Emoji picker (monochrome icon picker) */ +.emoji-picker-btn { + background: none; border: none; color: var(--fg); opacity: 0.35; + padding: 4px 7px; cursor: pointer; min-height: 28px; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.1s; +} +.emoji-picker-btn:hover { opacity: 1; } +.emoji-picker { + width: min(300px, 92vw); max-height: min(300px, 55vh); + background: var(--panel); border: 1px solid var(--border); border-radius: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.3); + display: flex; flex-direction: column; overflow: hidden; +} +.emoji-picker-search { + padding: 8px 12px; background: var(--panel); border: none; + border-bottom: 1px solid var(--border); color: var(--fg); + font-size: 12px; font-family: inherit; outline: none; +} +.emoji-picker-groups { overflow-y: auto; padding: 4px 0; flex: 1; } +.emoji-picker-group { padding: 0 6px 4px 6px; } +.emoji-picker-group-name { + font-size: 10px; font-weight: 600; opacity: 0.5; + text-transform: uppercase; letter-spacing: 0.5px; + padding: 4px 4px 2px 4px; +} +.emoji-picker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; } +.emoji-picker-item { + background: none; border: none; cursor: pointer; + padding: 4px; border-radius: 4px; + display: flex; align-items: center; justify-content: center; + color: var(--fg); transition: background 0.1s; +} +.emoji-picker-item:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } +.emoji-picker-item svg { width: 16px; height: 16px; } + +.email-avatar { + width: 28px; height: 28px; border-radius: 50%; background: var(--accent, #4a9eff); + color: #fff; display: flex; align-items: center; justify-content: center; + font-size: 12px; font-weight: 600; flex-shrink: 0; margin-top: 1px; +} +.email-item-content { flex: 1; min-width: 0; overflow: hidden; } +.email-item-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } +.email-sender { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.email-filter-chip { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; + margin: 4px 8px 6px; + padding: 4px 8px; + border-radius: 12px; + font-size: 11px; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + color: var(--fg); +} +.email-filter-chip-clear { + background: none; + border: none; + color: inherit; + cursor: pointer; + font-size: 14px; + line-height: 1; + padding: 0 2px; + opacity: 0.6; +} +.email-date { font-size: 10px; opacity: 0.5; white-space: nowrap; flex-shrink: 0; } +.email-subject { font-size: 11px; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } +.email-sent-chip, +.email-folder-chip { + display: inline-flex; + align-items: center; + height: 14px; + padding: 0 5px; + margin-right: 5px; + border-radius: 999px; + font-size: 8px; + line-height: 1; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + vertical-align: 1px; +} +.email-folder-chip { + position: relative; + top: 4px; + color: color-mix(in srgb, var(--fg) 75%, transparent); + background: rgba(127, 127, 127, 0.13); + border-color: rgba(127, 127, 127, 0.24); +} +.email-meta-date-group { + display: inline-flex; + align-items: center; + gap: 5px; + white-space: nowrap; +} +.email-meta-date-group .email-folder-chip { + margin-right: 0; +} +.email-card-expanded .email-sent-chip, +.email-card-reader .email-sent-chip { + position: relative; + top: 4px; +} +.email-tags { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 3px; margin-left: 6px; vertical-align: middle; position: relative; top: -2px; } +.email-card-tags { + flex: 0 0 auto; + min-width: 0; + max-width: 48%; + position: relative; + top: -4px; +} +.email-card-expanded .email-tags, +.email-card-reader .email-tags { top: -4px; } +.doclib-card-expanded .email-card-tags { top: -4px; } +.email-card-expanded .email-card-titlerow .email-tag, +.email-card-expanded .email-card-titlerow .email-tags-more { + transform: translateY(-2px); +} +@media (max-width: 768px) { + .email-card-expanded .email-card-titlerow .email-tag, + .email-card-expanded .email-card-titlerow .email-tags-more { + transform: translateY(2px); + } +} +.email-card-tags.email-tags-expanded { + position: absolute; + z-index: 4; + right: calc(var(--email-status-width, 44px) + 6px); + top: -5px; + max-width: min(58%, 260px); + padding: 4px 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent); +} +.email-tags-more { + appearance: none; + border: 0; + box-sizing: border-box; + border-radius: 999px; + background: rgba(127, 127, 127, 0.16); + color: var(--fg); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 1px; + height: 13px !important; + min-height: 0 !important; + min-width: 23px; + padding: 1px 4px; + font-family: inherit; + font-size: 9px; + line-height: 1; + margin: 0; + opacity: 0.68; + position: relative; + top: 0; + vertical-align: middle; +} +.email-tags-more svg { display: block; flex: 0 0 auto; } +.email-tags-expanded .email-tags-more svg { transform: rotate(180deg); } +.email-tags-toggle-inline { + position: absolute; + right: 94px; + top: 50%; + transform: translateY(calc(-50% - 5px)); + height: 20px !important; + min-width: 34px; + padding: 2px 6px !important; + font-size: 10px !important; + line-height: 1 !important; + opacity: 0.62; + border-radius: 999px !important; +} +.email-tags-toggle-inline.active { + opacity: 1; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 36%, transparent); +} +.email-tags-toggle-inline.email-tags-empty { + color: var(--warning, #d6b35a); + background: color-mix(in srgb, var(--warning, #d6b35a) 12%, transparent); + border-color: color-mix(in srgb, var(--warning, #d6b35a) 34%, transparent); +} +.email-tag { + display: inline-block; + font-size: 9px; + line-height: 1; + padding: 2px 5px; + border-radius: 8px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.3px; + background: rgba(127, 127, 127, 0.18); + color: var(--fg); +} +.email-tag-clickable { + appearance: none; + border: 0; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + font-family: inherit; + font-size: 9px; + font-weight: 500; + height: 13px !important; + min-height: 0 !important; + line-height: 1; + margin: 0; + padding: 2px 5px !important; + text-transform: uppercase; + vertical-align: middle; + flex: 0 0 auto; +} +.email-tag-work { background: rgba(96, 165, 250, 0.22); color: #60a5fa; } +.email-tag-personal { background: rgba(74, 222, 128, 0.22); color: #4ade80; } +.email-tag-finance { background: rgba(250, 204, 21, 0.22); color: #facc15; } +.email-tag-bills { background: rgba(251, 146, 60, 0.22); color: #fb923c; } +.email-tag-receipt { background: rgba(251, 146, 60, 0.22); color: #fb923c; } +.email-tag-travel { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } +.email-tag-newsletter { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } +.email-tag-promo, +.email-tag-marketing { background: rgba(244, 114, 182, 0.22); color: #f472b6; } +.email-tag-notification { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } +.email-tag-security { background: rgba(248, 113, 113, 0.22); color: #f87171; } +.email-tag-urgent { background: color-mix(in srgb, var(--color-error, #e06c75) 25%, transparent); color: var(--color-error, #e06c75); font-weight: 600; } +.email-tag-reply-soon { background: rgba(240, 173, 78, 0.22); color: #f0ad4e; } +.email-tag-social { background: rgba(56, 189, 248, 0.22); color: #38bdf8; } +.email-tag-shopping { background: rgba(236, 72, 153, 0.22); color: #ec4899; } +.email-tag-calendar { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } +.email-menu-wrap { position: relative; flex-shrink: 0; } +.email-menu-btn { + background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; + padding: 4px; transition: opacity 0.15s; + position: relative; top: 0px; +} +.email-dropdown { + position: absolute; right: 0; top: 100%; z-index: 9999; + min-width: 120px; background: var(--bg); border: 1px solid var(--border); + border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); padding: 4px 0; +} +.email-loading { padding: 16px; text-align: center; font-size: 12px; opacity: 0.5; } +.email-load-more { padding: 8px; text-align: center; } +.email-load-more-btn { + background: transparent; border: 1px solid var(--border); border-radius: 4px; + color: var(--fg); padding: 4px 12px; font-size: 11px; cursor: pointer; +} +.email-load-more-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } +.email-spinner { + display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); + border-top-color: #fff; border-radius: 50%; animation: email-spin 0.6s linear infinite; + vertical-align: middle; margin-right: 4px; +} + +/* Email section doesn't collapse — hide the auto-injected chevron */ +#email-section .section-collapse-btn { display: none !important; } + +#email-section:hover #email-compose-btn { + opacity: 0.7 !important; +} +#email-compose-btn:hover { + opacity: 1 !important; + background: none !important; +} +#email-compose-btn svg { + width: 11px; + height: 11px; + transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); +} +#email-compose-btn .list-item-plus-label { + top: calc(50% - 0.5px); + color: var(--fg); + font-size: 10px; +} +#email-compose-btn:hover svg { + transform: rotate(180deg) scale(1.15); +} + +/* Library row "+" — identical to the email compose "+": hidden until the row + is hovered, no accent box, and the same rotate-in animation. */ +#library-new-doc-btn, +#notes-new-note-btn { + opacity: 0.4 !important; + transition: opacity 0.15s ease; +} +#tool-library-btn:hover #library-new-doc-btn, +#tool-notes-btn:hover #notes-new-note-btn { + opacity: 0.7 !important; +} +#library-new-doc-btn:hover, +#notes-new-note-btn:hover { + opacity: 1 !important; + background: none !important; +} +#library-new-doc-btn svg, +#notes-new-note-btn svg { + transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); +} +#library-new-doc-btn .list-item-plus-label, +#notes-new-note-btn .list-item-plus-label { + top: calc(50% + 1px); +} +#library-new-doc-btn:hover svg, +#notes-new-note-btn:hover svg { + transform: rotate(180deg) scale(1.15); +} + + +/* MD toolbar attach button — matches other toolbar buttons */ +.md-toolbar-attach-btn { + background: none; border: none; color: var(--fg); opacity: 0.35; + padding: 4px 7px; cursor: pointer; min-height: 28px; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.1s; +} +.md-toolbar-attach-btn:hover { opacity: 1; } + +/* Email reader icon buttons — vertical icon + label stack. */ +.memory-toolbar-btn.reader-icon-btn { + width: auto; + height: 44px; + /* padding: 4px 2px; */ + position: relative; + top: 1px; + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + flex: 0 0 auto; +} +.memory-toolbar-btn.reader-icon-btn svg { width: 18px; height: 18px; } +.memory-toolbar-btn.reader-icon-btn[data-act="reply"] svg { + color: inherit; + stroke: currentColor; +} +.memory-toolbar-btn.reader-icon-btn.active { + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + color: var(--accent, var(--red)); +} +.memory-toolbar-btn.reader-icon-btn[data-act="more"] { + color: inherit; +} +.memory-toolbar-btn.reader-icon-btn[data-act="more"] svg { fill: currentColor; } +.memory-toolbar-btn.reader-icon-btn.reader-more-active { + background: color-mix(in srgb, var(--fg) 12%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); + color: inherit; +} +/* Cookbook Serve dropdown triggers (the cached-model kebab + the saved-config + arrow) — accent-colored to signal they open a menu, with a filled tint when + their dropdown is open. Matches the email reader More button pattern. */ +.hwfit-cached-menu-btn, +.cookbook-saved-arrow { + color: var(--accent, var(--red)); + /* Base .memory-item-btn is flex with align-items but no justify-content, + so the kebab SVG sat left-aligned inside the button instead of being + horizontally centered. Pin it center. */ + justify-content: center; +} +.hwfit-cached-menu-btn.cookbook-menu-active, +.cookbook-saved-arrow.cookbook-menu-active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); + color: var(--accent, var(--red)); +} + +/* Card action menus share one hit target and baseline. Keep this late in the + cascade because modal buttons and Cookbook Serve add their own offsets. */ +.memory-item-actions > .memory-item-btn, +.memory-item-actions .hwfit-cached-menu-btn { + box-sizing: border-box; + width: 28px; + min-width: 28px; + height: 22px; + min-height: 22px; + margin: 0 !important; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-muted); + font-size: 11px; + display: inline-flex; + align-items: center; + align-self: center; + justify-content: center; + position: relative; + top: 0 !important; + transform: none !important; + line-height: 1; + transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s; +} +.memory-item-actions .hwfit-cached-menu-btn:hover { + opacity: 1 !important; + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); + color: var(--fg); +} +.memory-item-actions .hwfit-cached-menu-btn { + transform: translateY(2px) !important; +} + +@media (min-width: 769px) { + .task-card .memory-item-actions { + top: 6px; + } + .task-card .memory-item-actions > .memory-item-btn { + transform: none !important; + } +} + +/* All kebab menus use the same muted tone as the expand/collapse chevron. */ +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +) { + color: var(--color-muted, var(--fg)) !important; +} +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +) svg { + color: inherit; + fill: currentColor; +} +/* Card kebabs are lightweight menu affordances, not bordered buttons. The + old base hover rule gave only some library cards a boxed outline while + chats, tasks, and cookbook stayed borderless. */ +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +):hover { + background: none !important; + border-color: transparent !important; + color: var(--fg) !important; + opacity: 1; +} +/* Skills used a separate, larger kebab with a different hover treatment. + Match every other card action trigger. */ +.skill-kebab-btn { + box-sizing: border-box; + width: 28px; + min-width: 28px; + height: 22px; + min-height: 22px; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-muted) !important; + opacity: 0.55; + top: -2px; +} +@media (max-width: 768px) { + .memory-item-actions > .memory-item-btn, + .memory-item-actions .hwfit-cached-menu-btn { + width: 40px; + min-width: 40px; + height: 40px; + min-height: 40px; + } + /* Card menus all need the same comfortable tap target. These components + previously escaped the shared action-button sizing through more-specific + desktop selectors. */ + .memory-menu-btn, + .skill-kebab-btn, + .task-card .memory-item-actions .memory-item-btn { + width: 40px !important; + min-width: 40px !important; + height: 40px !important; + min-height: 40px !important; + } + .memory-menu-btn { opacity: 0.7; } + /* Document cards seat their actions on a slightly higher title baseline + than chats/research cards. Bring only that kebab down 4px. */ + #doclib-modal .doclib-card[data-doc-id] > .doclib-document-header .memory-item-actions .memory-item-btn { + transform: translateY(-2px) !important; + } +} +/* Mobile: center the kebab/menu dropdowns in the viewport so they don't + slide left/right as the window width changes (they were positioned via + inline `right: ...px` relative to the kebab button). Stable position is + easier to land on by touch. The inline `top:` set by the menu code is + preserved; we only override the horizontal axis. */ +@media (max-width: 768px) { + .email-card-dropdown, + .cookbook-saved-menu, + .cookbook-dep-menu { + left: 50% !important; + right: auto !important; + transform: translateX(-50%); + } +} +/* Mobile-only Cancel item appended to dropdowns (kebab/More menus). On + desktop outside-click closes them cleanly; on touch that's fiddly, so + give an explicit Cancel row. Visually separated from the action items + above by a top divider + slight muted color. */ +.dropdown-cancel-mobile { display: none; } +@media (max-width: 768px) { + .dropdown-cancel-mobile { + display: flex; + border-top: 1px solid var(--border); + margin-top: 4px; + padding-top: 8px; + opacity: 0.85; + } +} +/* Launch-command Copy button morphs into Cancel once Launch has been clicked. */ +.hwfit-serve-copy.hwfit-serve-cancel { + border-color: var(--color-danger, #e06c75) !important; + color: var(--color-danger, #e06c75) !important; +} +.hwfit-serve-copy.hwfit-serve-cancel:hover { + background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); +} + +/* One visual contract for card kebab menus. These menus are built by several + modules, and native button defaults previously made identical rows render + with different fonts, heights, and hover movement. */ +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) { + box-sizing: border-box; + padding: 4px !important; + color: var(--fg); + background: var(--panel, var(--bg)) !important; + border: 1px solid var(--border) !important; + border-radius: 8px !important; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important; + font-family: inherit !important; + font-size: 11px !important; + line-height: 1.2; +} +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) > .dropdown-item-compact { + box-sizing: border-box; + width: 100%; + min-height: 28px; + padding: 6px 8px; + gap: 10px; + border: 0; + border-radius: 6px; + color: var(--fg); + background: transparent; + font: inherit !important; + line-height: 1.2; + text-align: left; + white-space: nowrap; +} +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) > .dropdown-item-compact:hover:not(:disabled) { + color: var(--fg); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + transform: none; +} +@media (max-width: 768px) { + :is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu + ) > .dropdown-item-compact { + min-height: 44px; + padding: 12px !important; + gap: 10px !important; + font-size: 14px !important; + } +} +/* Email modal title unread badge — small accent pill next to "Email". */ +.email-lib-unread-badge { + display: inline-block; + font-size: 0.55em; + font-weight: 700; + background: var(--accent-primary, var(--red)); + color: var(--bg, #1a1a1a); + border-radius: 9px; + padding: 1px 7px; + margin-left: 8px; + vertical-align: 2px; + line-height: 1.3; + letter-spacing: 0.02em; + cursor: pointer; +} +.email-lib-unread-badge.email-lib-away-badge { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); + padding: 0 7px; +} +.email-lib-auto-reply-badge { + display: inline-block; + margin-left: 7px; + padding: 1px 7px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border)); + border-radius: 9px; + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + color: var(--accent, var(--red)); + font-size: 0.55em; + font-weight: 700; + line-height: 1.3; + letter-spacing: 0.01em; + vertical-align: 2px; + cursor: pointer; +} +.email-lib-auto-reply-status-dot { + width: 6px; + height: 6px; + margin-left: 6px; + border-radius: 50%; + background: var(--accent, var(--red)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), + 0 0 8px color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); + vertical-align: 2px; +} +.modal-header h4 > .email-lib-auto-reply-badge { + font-size: 0.65em; + white-space: nowrap; + vertical-align: 1px; +} +#email-lib-modal.email-lib-unread-active .doclib-modal-content { + box-shadow: + 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent), + 0 0 18px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), + var(--shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.35)); +} +#email-lib-modal .email-lib-header-actions .minimize-btn { + position: relative; + left: 6px; +} +/* Per-card prev/next nav — only visible on the currently expanded card, + sits at the right of the title row next to the done check. */ +.email-card-nav-arrows { + display: none; + margin-left: auto; + gap: 2px; + flex-shrink: 0; + align-items: center; + position: relative; + top: -3px; + left: 19px; +} +.doclib-card.email-card-expanded .email-card-nav-arrows { + display: inline-flex; +} +.email-card-header-menu { + display: none; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 28px; + height: 28px; + padding: 0; + border: none; + border-radius: 6px; + background: none; + color: var(--fg); + opacity: 0.65; + cursor: pointer; + position: relative; + top: -3px; + transition: opacity 0.12s, background 0.12s; +} +.doclib-card.email-card-expanded .email-card-header-menu { + display: inline-flex; +} +.email-card-header-menu:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.email-card-nav-btn { + background: none; + border: none; + color: var(--fg); + opacity: 0.65; + cursor: pointer; + padding: 6px 8px; + border-radius: 6px; + display: inline-flex; + align-items: center; + justify-content: center; + transition: opacity 0.12s, background 0.12s; +} +.email-card-nav-btn:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} + +/* ── Email document editor ── */ +.doc-email-header { + display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; + border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; +} +.doc-email-fields { + display: flex; + flex-direction: column; + gap: 6px; + min-height: 0; +} +.doc-email-collapse-btn { + width: 100%; + min-height: 24px; + display: none; + align-items: center; + gap: 7px; + padding: 2px 4px 3px; + border: none; + background: transparent; + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; + opacity: 0.72; + text-align: left; +} +.doc-email-collapse-btn:hover { opacity: 1; color: var(--accent, var(--red)); } +.doc-email-collapse-btn svg { + flex-shrink: 0; + opacity: 0.65; + transition: transform 0.14s ease; +} +.doc-email-collapse-summary { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + opacity: 0.74; +} +.doc-email-header.doc-email-header-collapsed .doc-email-collapse-btn svg { + transform: rotate(180deg); +} +.email-field { display: flex; align-items: center; gap: 8px; position: relative; } +.email-field label { font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.5; min-width: 50px; text-align: right; flex-shrink: 0; } +.email-field input { + flex: 1; width: 100%; background: transparent; border: 1px solid var(--border); border-radius: 4px; + padding: 5px 8px; font-size: 13px; font-family: inherit; color: var(--fg); outline: none; + min-width: 0; +} +.email-field input:focus { border-color: var(--accent, #4a9eff); } +@container docpane (max-width: 460px) { + .doc-email-header .email-field { + display: grid; + grid-template-columns: 1fr; + gap: 3px; + align-items: stretch; + } + .doc-email-header .email-field label { + min-width: 0; + text-align: left; + } + .doc-email-header .email-field input { + width: 100%; + } +} +/* Cc toggle and attach button are absolute so they don't steal width + from the To input. Anchored to the input's vertical center + 4px + nudge so the label sits where the user expects it. */ +.email-field .email-cc-toggle { + position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); + z-index: 2; +} +/* Accent-tinted prefix label (To / Cc / Bcc / Subject) sitting at + the left edge of each input. Replaces both the old tag + and the all-placeholder accent. */ +.email-field .email-field-prefix { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + font-size: 11px; + font-weight: 600; + color: var(--accent-primary, var(--red)); + pointer-events: none; + z-index: 1; + line-height: 1; +} +.email-field input { padding-left: 44px; } +.email-field #doc-email-subject { padding-left: 64px; } +/* X close button inside Cc / Bcc fields. */ +.email-field .email-cc-close { + position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); + background: none; border: none; color: var(--fg); + opacity: 0.4; cursor: pointer; + width: 22px; height: 22px; + display: inline-flex; align-items: center; justify-content: center; + border-radius: 4px; + padding: 0; + z-index: 2; +} +.email-field .email-cc-close:hover { + opacity: 1; + color: var(--accent, var(--red)); + background: color-mix(in srgb, currentColor 10%, transparent); +} +.email-field input { padding-right: 60px; } +.email-field #doc-email-cc, .email-field #doc-email-bcc { padding-right: 32px; } +.email-field #doc-email-subject { padding-right: 8px; } + +.doc-email-actions { + display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; + border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; + align-items: center; + position: relative; + z-index: 30; + pointer-events: auto; +} +#doc-actions-footer .email-send-split { + margin-left: auto; +} +.doc-save-state-icon { display: none; flex: 0 0 auto; } +.doc-save-button[data-save-state="dirty"] { color: var(--accent, var(--red)); } +.doc-save-button[data-save-state="saving"] { color: var(--fg-muted, var(--fg)); } +.doc-save-button[data-save-state="saved"] { color: var(--fg); } +.doc-save-button[data-save-state="error"] { color: var(--color-danger, #e06c75); } +.doc-stats-wrap { + position: relative; + margin-left: 0; + flex: 0 0 auto; +} +.doc-stats-btn { + height: 28px; + border: 0; + border-radius: 4px; + padding: 0 7px; + background: transparent; + color: var(--fg-muted, var(--fg)); + font: inherit; + font-size: 10px; + cursor: pointer; + white-space: nowrap; +} +.doc-stats-btn:hover, +.doc-stats-btn[aria-expanded="true"] { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 7%, transparent); +} +.doc-stats-btn.doc-stats-selected { + color: var(--accent, var(--red)); + font-weight: 600; +} +.doc-stats-popover { + position: fixed; + right: auto; + bottom: auto; + width: 190px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); + color: var(--fg); + font-size: 11px; +} +.doc-stats-scope { + padding-bottom: 6px; + margin-bottom: 5px; + border-bottom: 1px solid var(--border); + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + text-transform: uppercase; +} +.doc-stats-row { + min-height: 22px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; +} +/* WYSIWYG email body — what the recipient sees, edited in place. */ +.doc-email-richbody { + flex: 1; + min-height: 0; + overflow-y: auto; + padding: 12px 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.6; + color: var(--fg); + background: var(--bg); + outline: none; + white-space: pre-wrap; + text-align: left; + word-wrap: break-word; +} +.doc-email-richbody.richtext-mode { + background: var(--bg); +} +.doc-rich-empty-import { + position: absolute; + top: 90px; + right: 18px; + bottom: auto; + left: auto; + z-index: 2; + display: flex; + pointer-events: none; +} +.doc-rich-empty-import .doc-rich-empty-import-btn { + position: static; + float: none; + margin: 0; + opacity: 1; + pointer-events: auto; + transform: none; +} +.doc-email-richbody.richtext-mode ul.rich-checklist { + list-style: none; + padding-left: 1.8em; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li { + position: relative; + min-height: 1.6em; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li::before { + content: ""; + position: absolute; + left: -1.55em; + top: 0.3em; + width: 0.95em; + height: 0.95em; + box-sizing: border-box; + border: 1.5px solid color-mix(in srgb, var(--fg) 48%, transparent); + border-radius: 3px; + background: var(--sidebar-bg, var(--panel)); + cursor: pointer; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::before { + border-color: var(--accent, var(--red)); + background: var(--accent, var(--red)); +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::after { + content: ""; + position: absolute; + left: -1.27em; + top: 0.44em; + width: 0.28em; + height: 0.5em; + border: solid #fff; + border-width: 0 1.5px 1.5px 0; + transform: rotate(45deg); + pointer-events: none; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"] { + color: color-mix(in srgb, var(--fg) 62%, transparent); + text-decoration: line-through; +} +.doc-email-richbody.richtext-mode hr { + border: 0; + border-top: 1px solid var(--border); + margin: 1.25em 0; +} +.doc-email-richbody.richtext-mode hr.richtext-page-break { + height: 8px; + border-top: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); + border-bottom: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); + margin: 1.6em 0; + opacity: 0.8; +} +.doc-email-richbody.richtext-mode table { + width: 100%; + margin: 12px 0; + border-collapse: collapse; + table-layout: fixed; +} +.doc-email-richbody.richtext-mode th, +.doc-email-richbody.richtext-mode td { + min-width: 48px; + padding: 7px 8px; + border: 1px solid var(--border); + vertical-align: top; + overflow-wrap: anywhere; +} +.doc-email-richbody.richtext-mode th { + background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 85%, var(--fg) 15%); + font-weight: 600; +} +.doc-email-richbody.richtext-mode code, +.doc-email-richbody.richtext-mode span[style*="OdysseusInlineCode"] { + padding: 0.12em 0.35em; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + border-radius: 3px; + background: color-mix(in srgb, var(--fg) 8%, transparent); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + white-space: break-spaces; +} +.doc-email-richbody.richtext-mode pre { + margin: 1em 0; + padding: 12px 14px; + overflow-x: auto; + border: 1px solid var(--border); + border-radius: 4px; + background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 82%, #000 18%) !important; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + line-height: 1.5; + white-space: pre-wrap; + tab-size: 2; +} + +::highlight(doc-find-results) { + color: inherit; + background-color: color-mix(in srgb, #facc15 48%, transparent); +} + +::highlight(doc-find-current) { + color: inherit; + background-color: color-mix(in srgb, var(--accent, #ef4444) 68%, #facc15); +} + +::highlight(doc-ai-selections) { + color: inherit; + background-color: color-mix(in srgb, var(--accent, #ef4444) 30%, transparent); +} +.doc-email-richbody img.richtext-image { + display: block; + margin: 12px 0; + max-width: 100%; + height: auto; + border-radius: 4px; + cursor: pointer; +} +.doc-email-richbody figure.richtext-image { + margin: 12px 0; + max-width: 100%; +} +.doc-email-richbody figure.richtext-image:not(:has(img)) { display: none; } +.doc-email-richbody figure.richtext-image img.richtext-image { + display: block; + margin-top: 0; + margin-bottom: 0; + max-width: 100%; + height: auto; + border-radius: 4px; +} +.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-60) .richtext-image-caption { width: 60%; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-35) .richtext-image-caption { width: 35%; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-left) .richtext-image-caption { margin-left: 0; margin-right: auto; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-center) .richtext-image-caption { margin-left: auto; margin-right: auto; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-right) .richtext-image-caption { margin-left: auto; margin-right: 0; } +.doc-email-richbody img.richtext-image-size-100 { width: 100%; } +.doc-email-richbody img.richtext-image-size-60 { width: 60%; } +.doc-email-richbody img.richtext-image-size-35 { width: 35%; } +.doc-email-richbody img.richtext-image-align-left { margin-left: 0; margin-right: auto; } +.doc-email-richbody img.richtext-image-align-center { margin-left: auto; margin-right: auto; } +.doc-email-richbody img.richtext-image-align-right { margin-left: auto; margin-right: 0; } +.doc-email-richbody img.richtext-image[data-editor-image-selected] { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 3px; +} +.doc-rich-image-toolbar { + position: fixed; + display: inline-flex; + align-items: center; + gap: 2px; + padding: 2px; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); + z-index: 10032; +} +.doc-rich-image-toolbar button { + width: 24px; + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + border: 0; + border-radius: 3px; + background: transparent; + color: inherit; + cursor: pointer; +} +.doc-rich-image-toolbar button:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--accent, var(--red)); +} +.doc-rich-image-toolbar .doc-rich-image-remove:hover { + color: var(--accent, var(--red)); +} +.doc-rich-image-source-menu { + position: fixed; + width: 260px; + min-width: 260px; + padding: 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 4px 16px color-mix(in srgb, var(--fg) 20%, transparent); + z-index: 10033; + font-size: 11px; +} +.doc-rich-image-source-title { + padding: 5px 7px 4px; + color: var(--fg); + font-weight: 700; + opacity: .75; +} +.doc-rich-image-source-menu > button, +.doc-rich-gallery-item { + width: 100%; + display: flex; + align-items: center; + gap: 7px; + padding: 7px 8px; + border: 0; + border-radius: 4px; + background: transparent; + color: var(--fg); + font: inherit; + text-align: left; + cursor: pointer; +} +.doc-rich-image-source-menu > button[data-image-source="computer"] { + padding-left: 9px; +} +.doc-rich-image-source-menu > button:hover, +.doc-rich-gallery-item:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--accent, var(--red)); +} +.doc-rich-gallery-list { + display: grid; + gap: 2px; + max-height: 300px; + overflow-y: auto; +} +.doc-rich-gallery-item { + min-height: 44px; + min-width: 0; +} +.doc-rich-gallery-item > span:last-child { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.doc-rich-gallery-thumb { + width: 40px; + height: 40px; + flex: 0 0 40px; + border: 1px solid var(--border); + border-radius: 4px; + background-position: center; + background-size: cover; + background-repeat: no-repeat; +} +.rich-color-letter { + border-bottom: 2px solid var(--accent, var(--red)); + font-weight: 700; + line-height: 14px; +} +.rich-color-swatch { + width: 13px !important; + height: 13px !important; + border: 1px solid color-mix(in srgb, var(--fg) 35%, transparent); + border-radius: 50%; +} +.rich-highlight-swatch { border-radius: 50%; } +.rich-color-swatch.is-transparent { + background: + linear-gradient(135deg, transparent 43%, var(--red) 44%, var(--red) 56%, transparent 57%), + var(--bg); +} +.rich-color-palette-menu { + min-width: min(292px, calc(100vw - 16px)); +} +.rich-color-palette-menu .rich-color-reset { + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + margin-bottom: 3px; + padding-bottom: 7px; +} +.rich-color-palette-grid { + display: grid; + grid-template-columns: repeat(2, minmax(126px, 1fr)); + gap: 0 4px; + padding: 0 4px; +} +.rich-color-palette-group { + min-width: 0; +} +.rich-color-palette-label { + padding: 4px 8px 3px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 9px; + font-weight: 600; + text-transform: uppercase; +} +.rich-color-palette-group .doc-overflow-item { + min-width: 0; + padding-inline: 8px; +} +.rich-toolbar-color-custom { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-top: 3px; + padding: 7px 9px 4px 10px; + border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + color: var(--fg); + font-size: 11px; +} +.rich-toolbar-color-input.cp-swatch-input { + width: 22px; + height: 22px; + flex: 0 0 22px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 28%, var(--border)); + border-radius: 50%; + cursor: pointer; +} +.rich-toolbar-color-input.cp-swatch-input:focus-visible { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 2px; +} + +.rich-toolbar-slider-row { + display: grid; + grid-template-columns: auto minmax(104px, 1fr) 42px; + align-items: center; + gap: 9px; + min-width: 220px; + padding: 10px; + color: var(--fg); + font-size: 11px; +} +.rich-toolbar-range { + width: 100%; + min-width: 0; + height: 8px; + margin: 0; + border: 0; + border-radius: 999px; + background: color-mix(in srgb, var(--fg) 25%, transparent); + accent-color: var(--red); + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} +.rich-toolbar-range::-webkit-slider-thumb { + width: 9px; + height: 9px; + border: 0; + border-radius: 50%; + background: var(--red); + cursor: pointer; + -webkit-appearance: none; + appearance: none; + transition: width 0.12s ease, height 0.12s ease; +} +.rich-toolbar-range:hover::-webkit-slider-thumb, +.rich-toolbar-range:focus::-webkit-slider-thumb, +.rich-toolbar-range:active::-webkit-slider-thumb { + width: 16px; + height: 16px; +} +.rich-toolbar-range::-moz-range-thumb { + width: 9px; + height: 9px; + border: 0; + border-radius: 50%; + background: var(--red); + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.rich-toolbar-range:hover::-moz-range-thumb, +.rich-toolbar-range:focus::-moz-range-thumb, +.rich-toolbar-range:active::-moz-range-thumb { + width: 16px; + height: 16px; +} +.rich-toolbar-range:focus-visible { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 4px; +} +.rich-toolbar-slider-value { + width: 42px; + box-sizing: border-box; + padding: 2px 3px; + border: 1px solid transparent; + border-radius: 3px; + background: transparent; + text-align: right; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-variant-numeric: tabular-nums; + -webkit-appearance: textfield; + appearance: textfield; +} +.rich-toolbar-slider-value::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +.rich-toolbar-slider-value:focus { + border-color: var(--accent, var(--red)); + outline: none; + color: var(--fg); +} + +.rich-script-icon, +.rich-font-icon, +.rich-font-size-icon { + display: inline-block; + line-height: 1; + font-size: 11px; +} +.rich-highlight-icon { + display: block; + flex-shrink: 0; + color: var(--accent-primary, var(--red)); +} + +.rich-script-icon sup, +.rich-script-icon sub { + font-size: 7px; + line-height: 0; +} + +.rich-font-icon { + font-family: Georgia, serif; + font-size: 12px; +} + +.rich-font-size-icon { + min-width: 16px; + font-size: 10px; + font-weight: 600; + font-variant-numeric: tabular-nums; + text-align: center; +} +.rich-align-center { text-align: center; } +.rich-align-right { text-align: right; } +.doc-email-richbody .email-quoted-history { + margin-top: 14px; + padding: 10px 0 10px 12px; + border-left: 3px solid var(--border); + color: color-mix(in srgb, var(--fg) 70%, transparent); + font-size: 0.92em; + line-height: 1.45; + white-space: pre-wrap; + user-select: text; + pointer-events: none; +} +.doc-email-richbody h1, .doc-email-richbody h2, .doc-email-richbody h3, +.doc-email-richbody h4, .doc-email-richbody h5, .doc-email-richbody h6 { margin: 0.4em 0; } +.email-more-menu { + position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; + min-width: 160px; background: var(--bg); border: 1px solid var(--border); + border-radius: 6px; box-shadow: 0 -4px 12px rgba(0,0,0,0.2); padding: 4px; + z-index: 100; +} +.email-send-btn { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 20%, transparent); + color: var(--accent-primary, var(--red)); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); + border-radius: 6px; + padding: 0 14px; height: 28px; font-size: 11px; font-weight: 600; cursor: pointer; + font-family: inherit; transition: all 0.15s; white-space: nowrap; + display: inline-flex; align-items: center; gap: 6px; +} +/* Split send button: [ Send │ ▾ ] — the caret drops the send-options menu UP. */ +.email-send-split { position: relative; display: inline-flex; } +.email-send-split .email-send-main { + border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; +} +.email-send-split .email-send-caret { + border-top-left-radius: 0; border-bottom-left-radius: 0; + padding: 0 8px; gap: 0; + /* its left border is the single divider between the two halves */ + border-left: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); +} +.email-send-split .email-send-caret svg { transition: transform 0.15s ease; } +.email-send-split .email-send-caret[aria-expanded="true"] svg { transform: none; } +.email-send-btn svg { flex-shrink: 0; } +.email-send-btn:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); + border-color: var(--accent-primary, var(--red)); +} +.email-draft-btn { + background: none; border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border-radius: 6px; + padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; + font-family: inherit; transition: all 0.15s; white-space: nowrap; +} +.email-draft-btn:hover { border-color: var(--fg); color: var(--fg); } +.email-discard-btn { + background: transparent; + /* Dim the label text via color-mix instead of `opacity:0.6` on the whole + button — opacity multiplies through to the SVG and washes out the accent + X glyph. Per-element color dimming keeps the X at full accent strength. */ + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 14px; font-size: 12px; cursor: pointer; font-family: inherit; +} +/* The ✕ glyph itself reads in accent. */ +.email-discard-btn svg { color: var(--accent-primary, var(--red)); opacity: 1; } +.email-discard-btn:hover { opacity: 1; color: var(--red, #e55); border-color: var(--red, #e55); } + +/* Compose email "more" menu button — icon only, matches draft btn height */ +#doc-email-more-btn { + background: none; border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border-radius: 6px; + width: 28px; height: 28px; padding: 0; + cursor: pointer; font-family: inherit; transition: all 0.15s; + display: inline-flex; align-items: center; justify-content: center; +} +#doc-email-more-btn:hover { + border-color: var(--fg); color: var(--fg); +} +.email-more-wrap { display: inline-flex; } +.email-attachments { + display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 0 58px; +} +.email-attachment-chip { + display: inline-flex; align-items: center; gap: 4px; + padding: 4px 8px; font-size: 11px; background: var(--hover-bg, rgba(255,255,255,0.05)); + border: 1px solid var(--border); border-radius: 12px; color: var(--fg); + text-decoration: none; cursor: pointer; transition: background 0.15s, border-color 0.15s; + max-width: 200px; min-width: 0; +} +.email-attachment-chip:hover { + background: color-mix(in srgb, var(--accent) 15%, transparent); + border-color: var(--accent); +} +.email-attachment-chip.is-expanded { + max-width: 90vw; +} +.email-attachment-chip > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + flex: 1 1 auto; min-width: 0; +} +.email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-attachment-chip-related { + border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); +} +/* "Open in editor" launch icon — same prominent style on desktop AND mobile + (was 24px / dim / no border on desktop, easy to miss). Accent-tinted + background + border makes it read as a real action. */ +.email-attachment-open, +.email-attachment-download, +.email-attachment-calendar-open, +.cal-event-email-source { + display: inline-flex; align-items: center; gap: 4px; + height: 22px; padding: 0 9px; border-radius: 999px; + margin-left: 6px; flex: 0 0 auto; + min-width: 52px; justify-content: center; + font-size: 10px; font-weight: 500; letter-spacing: 0.02em; + color: var(--accent-primary, var(--red)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); + cursor: pointer; transition: background 0.1s, border-color 0.1s, color 0.1s; +} +.email-attachment-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-open svg, +.email-attachment-open > svg, +.email-attachment-download svg, +.email-attachment-download > svg, +.email-attachment-calendar-open svg, +.email-attachment-calendar-open > svg, +.cal-event-email-source svg, +.cal-event-email-source > svg { + width: 12px; height: 12px; + opacity: 0.9; +} +.email-attachment-open:hover, +.email-attachment-download:hover, +.email-attachment-calendar-open:hover, +.cal-event-email-source:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 70%, transparent); +} +.email-attachment-download { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-calendar-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.cal-event-email-source { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-open-label { line-height: 1; } +.email-attachment-chip.is-expanded .email-attachment-open, +.email-attachment-chip.is-expanded .email-attachment-download, +.email-attachment-chip.is-expanded .email-attachment-calendar-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-chip.is-expanded .email-attachment-open-label { + display: none; +} +@media (max-width: 768px) { + .doc-email-collapse-btn { + background: inherit; + } + /* Mobile: keep the pill but ensure a comfortable touch target. */ + .email-attachment-open { + height: 26px; padding: 0 10px; + min-height: 26px !important; + min-width: 58px; + } + .email-attachments, + .email-compose-atts { + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-left: 0; + padding-bottom: 2px; + } + .email-attachments::-webkit-scrollbar, + .email-compose-atts::-webkit-scrollbar { + display: none; + } + /* Attachment chip body — modest minimum height so the open icon sits + neatly without dominating. */ + .email-attachment-chip, + .email-compose-chip { + flex: 0 0 auto; + padding: 6px 8px !important; + min-height: 36px !important; + } + .email-attachment-chip.is-expanded { + flex-wrap: nowrap; + align-items: center; + max-width: min(92vw, 360px); + } + .email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + flex: 1 1 auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.25; + min-width: 0; + } + .email-attachment-chip.is-expanded .att-size { + margin-left: 0; + } + .email-attachment-chip.is-expanded .email-attachment-open, + .email-attachment-chip.is-expanded .email-attachment-download, + .email-attachment-chip.is-expanded .email-attachment-calendar-open { + width: 28px; + min-width: 28px; + padding: 0; + margin-left: 4px; + } + .email-compose-chip .compose-chip-name { + max-width: 190px; + } +} + +/* Compose attachment chips (when sending new email) */ +.email-compose-atts { + display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; + padding: 6px 0 0 0; +} +.email-compose-chip { + display: inline-flex; align-items: center; gap: 4px; + flex: 0 1 auto; + min-width: 0; + max-width: min(240px, 100%); + height: 24px; min-height: 24px; box-sizing: border-box; + padding: 2px 3px 2px 7px; font-size: 11px; + background: var(--hover-bg, rgba(255,255,255,0.05)); + border: 1px solid var(--border); border-radius: 12px; color: var(--fg); +} +.email-compose-chip .compose-chip-name { max-width: 180px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.email-compose-chip .compose-chip-remove { + background: none; border: none; color: var(--fg); opacity: 0.5; + height: 18px; font-size: 14px; line-height: 18px; padding: 0 3px; cursor: pointer; + flex-shrink: 0; +} +.email-compose-chip .compose-chip-remove:hover { opacity: 1; color: var(--red, #e55); } + +.pdf-annotation-line-value { + width: 48px; + height: 22px; + box-sizing: border-box; + padding: 0 5px; + border: 1px solid var(--border); + border-radius: 4px; + outline: none; + background: var(--bg-elev, var(--panel)); + color: var(--fg); + font-family: inherit; + font-size: 10px; + font-weight: 500; + line-height: 1; + text-align: right; +} +.pdf-annotation-line-value:hover { border-color: color-mix(in srgb, var(--fg) 38%, var(--border)); } +.pdf-annotation-line-value:focus { border-color: var(--accent, var(--red)); } + +.email-odysseus-attach-menu { + position: fixed; + z-index: 10050; + width: 300px; + max-width: calc(100vw - 16px); + max-height: min(420px, calc(100vh - 16px)); + padding: 7px; + border: 1px solid var(--border); + border-radius: 7px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 12px 30px rgba(0,0,0,0.28); + overflow: hidden; +} +.email-odysseus-attach-local { + width: 100%; + height: 30px; + display: flex; + align-items: center; + gap: 7px; + padding: 0 9px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, var(--border)); + border-radius: 5px; + background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-local:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); +} +.email-odysseus-attach-tabs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 5px; + margin: 7px 0; +} +.email-odysseus-attach-tabs button { + height: 26px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border: 1px solid var(--border); + border-radius: 5px; + background: transparent; + color: var(--fg); + opacity: 0.72; + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-tabs button.active { + opacity: 1; + color: var(--accent-primary, var(--red)); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); +} +.email-odysseus-attach-tabs button svg { + flex-shrink: 0; +} +.email-odysseus-attach-search-wrap { + height: 28px; + display: flex; + align-items: center; + gap: 6px; + margin: -1px 0 7px; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 5px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + color: color-mix(in srgb, var(--fg) 48%, transparent); +} +.email-odysseus-attach-search-wrap:focus-within { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + color: var(--accent-primary, var(--red)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, transparent); +} +.email-odysseus-attach-search { + min-width: 0; + flex: 1; + border: 0; + outline: 0; + background: transparent; + color: var(--fg); + font: inherit; + font-size: 11px; +} +.email-odysseus-attach-search::placeholder { + color: color-mix(in srgb, var(--fg) 38%, transparent); +} +.email-odysseus-attach-list { + max-height: 320px; + overflow: auto; + display: flex; + flex-direction: column; + gap: 4px; +} +.email-odysseus-attach-row { + width: 100%; + min-height: 38px; + display: flex; + align-items: center; + gap: 8px; + padding: 6px 7px; + border: 1px solid transparent; + border-radius: 5px; + background: transparent; + color: var(--fg); + text-align: left; + font: inherit; + cursor: pointer; +} +.email-odysseus-attach-row:hover { + background: var(--hover-bg, rgba(255,255,255,0.06)); + border-color: var(--border); +} +.email-odysseus-attach-row.is-selected { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); +} +.email-odysseus-attach-dot { + width: 8px; + height: 8px; + flex: 0 0 auto; + display: inline-block; + border: 1px solid var(--border); + border-radius: 50%; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.email-odysseus-attach-row.is-selected .email-odysseus-attach-dot { + border-color: var(--accent-primary, var(--red)); + background: var(--accent-primary, var(--red)); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); +} +.email-odysseus-attach-icon, +.email-odysseus-attach-thumb { + width: 28px; + height: 28px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 7%, transparent); + overflow: hidden; +} +.email-odysseus-attach-main { + min-width: 0; + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} +.email-odysseus-attach-title, +.email-odysseus-attach-meta { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-odysseus-attach-title { + font-size: 11px; + line-height: 1.2; +} +.email-odysseus-attach-meta { + font-size: 10px; + line-height: 1.15; + opacity: 0.52; +} +.email-odysseus-attach-empty { + padding: 14px 8px; + color: var(--muted, #888); + font-size: 11px; + text-align: center; +} +.email-odysseus-attach-actions { + display: flex; + align-items: center; + gap: 6px; + margin-top: 2px; + padding-top: 5px; + border-top: 1px solid color-mix(in srgb, var(--border) 75%, transparent); + background: var(--bg); + position: sticky; + bottom: 0; +} +.email-odysseus-attach-count { + flex: 1; + min-width: 0; + color: var(--muted, #888); + font-size: 11px; +} +.email-odysseus-attach-selected { + height: 28px; + min-width: 72px; + padding: 0 12px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + background: var(--accent-primary, var(--red)); + color: #fff; + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-selected svg { + flex-shrink: 0; +} + +.email-cc-toggle { + background: none; border: none; color: var(--fg); + opacity: 0.4; font-size: 11px; cursor: pointer; + padding: 4px 8px; font-family: inherit; +} +.email-cc-toggle:hover { + opacity: 1; + color: var(--accent, #4a9eff); + background: none !important; +} + +@media (max-width: 768px) { + .doc-email-collapse-btn { + display: flex; + } +} + +.email-autocomplete { + position: absolute; top: 100%; left: 58px; right: 0; z-index: 1000; + background: var(--bg); border: 1px solid var(--border); border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 240px; overflow-y: auto; + margin-top: 2px; +} +.contact-suggestion { + display: flex; justify-content: space-between; gap: 8px; + padding: 6px 10px; font-size: 12px; cursor: pointer; + border-bottom: 1px solid var(--border); +} +.contact-suggestion:last-child { border-bottom: none; } +.contact-suggestion:hover, +.contact-suggestion.active, +.contact-suggestion[aria-selected="true"] { + background: color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 15%, transparent); + outline: 1px solid color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 40%, transparent); + outline-offset: -1px; +} +.contact-suggestion .contact-name { font-weight: 600; color: var(--fg); } +.contact-suggestion .contact-email { opacity: 0.6; font-size: 11px; } +.contact-suggestion-status { + padding: 7px 10px; + color: color-mix(in srgb, var(--fg) 65%, transparent); + font-size: 11px; + cursor: default; +} +.contact-suggestion-status.is-loading { opacity: 0.72; } +.contact-suggestion-status.is-unavailable { + color: color-mix(in srgb, var(--red) 82%, var(--fg)); +} +.email-ai-reply-btn { + background: none; + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; + font-family: inherit; transition: all 0.15s; + display: inline-flex; align-items: center; white-space: nowrap; +} +.email-ai-reply-btn:hover { + border-color: var(--accent, #4a9eff); + color: var(--accent, #4a9eff); +} +/* Align select/new buttons in popup library toolbar */ +#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn, #email-lib-compose-btn { position: relative; top: -4px; } +/* Select + Refresh sit slightly lower than Compose on desktop. */ +#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -2px; } + +#email-lib-modal.email-settings-mode .email-accounts-row, +#email-lib-modal.email-settings-mode .admin-card > .memory-toolbar, +#email-lib-modal.email-settings-mode #email-lib-bulk, +#email-lib-modal.email-settings-mode #email-lib-grid, +#email-lib-modal.email-settings-mode #email-lib-sync-status, +#email-lib-modal.email-settings-mode #email-lib-fab { + display: none !important; +} +#email-lib-settings-page[hidden] { + display: none !important; +} +#email-lib-modal.email-settings-mode #email-lib-settings-page { + display: flex; +} +.email-settings-page { + display: flex; + flex: 1; + min-height: 0; + min-width: 0; + flex-direction: column; + gap: 6px; + overflow: hidden; + padding: 2px 0 8px; + width: 100%; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; +} +.settings-email-preferences-card { + display: contents; +} +.settings-email-preferences-card .email-settings-section { + margin: 0 0 10px; + padding: 14px 12px 12px; + background: var(--panel); +} +.settings-email-preferences-card .email-settings-section:last-child { + margin-bottom: 0; +} +.settings-email-preferences-card .email-settings-style-section { + margin-bottom: 4px; +} +.settings-email-default-card #settings-email-default-host { + display: flex; + justify-content: flex-end; + min-width: 0; + margin-left: auto; +} +.settings-email-default-card .settings-email-default-title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} +.settings-email-default-card .settings-email-default-title > span { + min-width: 0; +} +.settings-email-default-card .settings-email-default-title > span > svg { + color: var(--accent, var(--red)); +} +.settings-email-default-card .email-settings-account-picker { + margin-left: 0 !important; +} +.settings-email-default-card .email-settings-support-copy { + position: relative; + top: 6px; +} +.settings-email-default-spacer { + height: 12px; +} +.settings-email-default-card .cookbook-srv-default-label { + position: relative; + top: 2px; +} +.settings-email-default-card .cookbook-srv-default-icon > svg { + position: relative; + top: 7px; +} +.settings-email-settings-legacy { + display: none; +} + +/* Keep the standalone Email Settings cards aligned with the expandable + preference cards below them. */ +[data-settings-panel="email"] > .admin-card { + padding-top: 14px; +} +.email-settings-page-host { + display: flex; + flex: 0 0 auto; + min-height: 0; + padding: 0; + overflow: visible; +} +.settings-email-preferences-card > .email-settings-page-host { width: 100%; } +.email-settings-page-head { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + min-height: 32px; + padding-bottom: 2px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); +} +.email-settings-account-picker { + min-width: 0; + max-width: 100%; +} +.email-settings-account-picker select { + min-width: 0; + max-width: 100%; +} +.email-settings-back { + position: relative; + top: -2px; +} +.email-settings-header-back { + background: transparent; + color: color-mix(in srgb, var(--fg) 58%, transparent); + border: 1px solid transparent; + width: auto; + height: 24px; + padding: 0 8px 0 6px; + align-items: center; + justify-content: center; + gap: 4px; + cursor: pointer; + border-radius: 999px; + flex-shrink: 0; + position: relative; + font-size: 11px; + font-weight: 600; + line-height: 1; +} +.email-settings-header-back:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); + border-color: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--fg); +} +.email-settings-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; +} +.email-settings-title { + display: inline-flex; + align-items: center; + gap: 6px; + flex: 1 1 auto; + width: 100%; + min-width: 0; + font-size: 12px; + font-weight: 700; +} +.email-settings-title svg { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-settings-close { + width: 24px; + height: 24px; + border: 1px solid var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 70%, transparent); + cursor: pointer; + font-size: 14px; + line-height: 1; +} +.email-settings-close:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)); +} +.email-settings-body { + display: flex; + flex-direction: column; + flex: 1 1 auto; + gap: 6px; + padding-top: 4px; + box-sizing: border-box; + min-width: 0; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + overscroll-behavior: contain; +} +.email-settings-loading, +.email-settings-error { + font-size: 12px; + color: color-mix(in srgb, var(--fg) 65%, transparent); + padding: 8px 2px; +} +.email-settings-loading { + display: inline-flex; + align-items: center; + gap: 7px; +} +.email-settings-loading-css-whirlpool { + display: inline-block; + width: 14px; + height: 14px; + box-sizing: border-box; + border: 1.5px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + border-top-color: var(--accent, var(--red)); + border-right-color: color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); + border-radius: 50%; + animation: whirlpool-spin 0.72s linear infinite; + flex: 0 0 auto; +} +.email-settings-loading-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: var(--accent, var(--red)); +} +.email-settings-section { + display: flex; + flex-direction: column; + flex: 0 0 auto; + gap: 7px; + margin: 0; + padding: 14px 8px 8px; + min-width: 0; + box-sizing: border-box; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + transition: opacity 0.15s ease, background 0.15s ease; +} +.email-settings-section.is-disabled { + opacity: 0.58; + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +.email-settings-section-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.email-settings-section-head > div:first-child { + min-width: 0; + flex: 1 1 auto; +} +.email-settings-section-head > .email-settings-enabled-control, +.email-settings-section-head > button { + margin-left: auto; +} +.email-settings-section-toggle { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + width: 100%; + min-width: 0; + padding: 0; + border: 0; + background: transparent; + color: inherit; + text-align: left; + cursor: pointer; +} +.email-settings-section-toggle:hover, +.email-settings-section-toggle:active { + background: transparent; +} +.email-settings-section-toggle-copy { + display: flex; + flex-direction: column; + align-items: flex-start; + min-width: 0; + flex: 1 1 auto; +} +.email-settings-section-toggle > .email-settings-enabled-control { + margin-left: auto; + flex: 0 0 auto; +} +.email-settings-section-chevron { + flex: 0 0 auto; + color: color-mix(in srgb, var(--fg) 62%, transparent); + transition: transform .14s ease, color .14s ease; +} +.email-settings-section-toggle:hover .email-settings-section-chevron, +.email-settings-section-toggle:focus-visible .email-settings-section-chevron { + color: var(--accent, var(--red)); +} +.email-settings-section-title { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 14px; + line-height: 1; + font-weight: 600; + letter-spacing: 0; + margin: 0; + padding: 0; +} +.email-settings-section-title svg { + color: var(--accent, var(--red)); + opacity: 0.6; + flex-shrink: 0; +} +.email-settings-account-scope { + margin-left: 4px; + font-size: 11px; + font-weight: 500; + color: color-mix(in srgb, var(--fg) 60%, transparent); +} +.email-settings-section-body { + display: flex; + flex-direction: column; + gap: 7px; + min-width: 0; + width: 100%; +} +.email-settings-section[open] > .email-settings-section-body { + min-height: 0; + flex: 0 0 auto; + height: auto; + max-height: none; + overflow: visible; + overscroll-behavior: contain; + padding: 0 4px 4px 0; + scrollbar-width: thin; +} +.email-settings-section[open] { + min-height: 0; + flex: 0 0 auto; +} +.email-settings-auto-reply-section[open], +.email-settings-cleanup-section[open]:has(.email-unsub-card) { + flex: 1 1 auto; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + align-content: stretch; +} +.email-settings-auto-reply-section[open] > .email-settings-section-body, +.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body { + flex: 0 0 auto; + height: 100%; + max-height: 100%; + overflow-y: auto; +} +.email-settings-section[open] > .email-settings-section-body > * { + flex: 0 0 auto; +} +.email-settings-section-body-head { + display: flex; + justify-content: flex-end; +} +.email-settings-display-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.email-settings-display-title { + display: inline-flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.email-settings-display-title > svg { + flex: 0 0 auto; + color: var(--accent, var(--red)); +} +.email-settings-display-title-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + position: relative; + top: 10px; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-display-title-row .email-settings-section-title { + flex: 1 1 auto; + width: auto; + padding-bottom: 0; + border-bottom: 0; +} +.email-settings-style-section .email-settings-section-title { + display: flex; + width: 100%; + position: relative; + top: 0; + padding-bottom: 8px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-style-section .email-settings-section-head, +.email-settings-style-section .email-settings-section-body { + position: static; +} +.email-settings-style-section .email-settings-section-head { margin-top: 8px; } +.email-settings-style-section .email-settings-section-body { margin-top: 12px; } +.email-settings-style-section .email-settings-section-desc { + margin-top: 16px; + top: 0; +} +.email-settings-auto-reply-section .email-settings-section-title, +.email-settings-cleanup-section .email-settings-section-title { + display: flex; + width: 100%; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-cleanup-section .email-settings-section-title { + position: relative; + top: 10px; +} +.email-settings-auto-reply-section .email-settings-section-desc, +.email-settings-cleanup-section .email-settings-section-desc { + margin-top: 10px; +} +.email-settings-display-section .email-settings-section-desc, +.email-settings-cleanup-section .email-settings-section-desc, +.email-settings-inline-images-section .email-settings-section-desc { + margin-top: 12px; + top: 12px; +} +.email-settings-auto-reply-title-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + position: relative; + top: 10px; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-auto-reply-section .email-settings-section-head { + margin-top: 8px; +} +.email-settings-auto-reply-section .email-settings-section-body { + margin-top: 8px; +} +.email-settings-auto-reply-section .email-settings-section-desc { + margin-top: 12px; + top: 0; +} +.email-settings-display-section .email-settings-section-desc { + margin-top: 10px; + top: 10px; +} +.email-settings-auto-reply-section .email-settings-section-body { + margin-top: 14px; +} +.email-settings-auto-reply-title-row .email-settings-section-title { + flex: 1 1 auto; + width: auto; + padding-bottom: 0; + border-bottom: 0; +} +.email-settings-support-copy { + color: color-mix(in srgb, var(--fg) 65%, transparent); + font-size: 11px; + line-height: 1.3; +} +.email-settings-clean-btn > svg { + color: var(--accent, var(--red)); +} +.email-settings-clean-btn { + height: 32px; +} +.email-unsub-rescan-btn { + height: 32px; +} +.email-settings-clean-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 7px; + min-width: 0; + margin-left: auto; +} +.email-settings-clean-status { + display: none; + font-size: 10.5px !important; + flex: 0 1 auto; + width: auto !important; + max-width: min(52vw, 360px); + min-width: 0; + overflow: hidden; + text-align: right; + white-space: nowrap; + text-overflow: ellipsis; + position: relative; + top: 2px; +} +.email-settings-clean-status.is-busy { + order: 0; + align-self: center; + width: auto !important; + max-width: 100%; + margin-right: auto; + text-align: left; +} +.email-settings-clean-btn { + flex: 0 0 auto; +} +.email-settings-default .cookbook-srv-default-icon > svg { + position: relative; + top: 3px; +} +.email-unsub-auto-safe-btn > svg { + position: relative; + top: 2px; +} +.email-unsub-auto-safe-btn { + height: 29px; +} +.email-unsub-status, +.email-unsub-panel-status { + position: relative; + top: 2px; +} +.email-unsub-panel-status { + flex: 1 1 220px; + min-width: 0; + margin-right: auto; + overflow: hidden; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-size: 11px; + line-height: 1.3; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-unsub-panel-status.is-error { + color: var(--red, #e57373); +} +.email-unsub-card .email-tag { + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); +} +.email-unsub-duplicate-badge { + position: relative; + top: 2px; +} +.email-unsub-accent-icon { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-unsub-card { + position: relative; + transition: opacity .16s ease; +} +.email-unsub-card-details { + display: flex; + flex-direction: column; + gap: 7px; +} +.email-unsub-card-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 24px; + height: 24px; + padding: 0; + border: 0; + background: transparent; + color: color-mix(in srgb, var(--fg) 62%, transparent); + cursor: pointer; +} +.email-unsub-card-toggle:hover, +.email-unsub-card-toggle:focus-visible { + color: var(--accent, var(--red)); +} +.email-unsub-card-toggle > svg { + transition: transform .14s ease; +} +.email-unsub-done-badge { + display: inline-flex; + align-items: center; + gap: 3px; +} +.email-unsub-done-btn { + display: inline-flex; + align-items: center; + gap: 3px; + flex: 0 0 auto; + padding: 3px 6px; + position: relative; + top: -6px; +} +.email-unsub-card-details .email-unsub-done-btn { + top: 0; +} +.email-unsub-done-btn > svg { + color: var(--accent, var(--red)); +} +.email-unsub-ignore-btn { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 22px; + height: 22px; + padding: 0; + position: absolute; + top: 5px; + right: 6px; + color: color-mix(in srgb, var(--fg) 58%, transparent); +} +.email-unsub-card-details .email-unsub-ignore-btn { + position: static; +} +.email-unsub-ignore-btn:hover { + color: var(--accent, var(--red)); +} +.email-unsub-remaining-actions { + display: inline-flex; + align-items: center; + gap: 7px; + flex-wrap: wrap; +} +.email-unsub-remaining-actions button { + display: inline-flex; + align-items: center; + gap: 4px; + position: relative; + top: -2px; +} +.email-unsub-remaining-actions svg { + flex: 0 0 auto; +} +.email-unsub-link-btn { + position: relative; + top: 2px; +} +.email-unsub-status.is-error { + color: var(--accent, var(--red)) !important; +} +.email-settings-section-desc { + position: relative; + top: 8px; + margin-top: 4px; + font-size: 10.5px; + line-height: 1.25; + color: color-mix(in srgb, var(--fg) 55%, transparent); +} +.email-settings-inline-images-section .email-settings-section-desc { + margin-bottom: 4px; +} +.email-settings-inline-images-spacer { + height: 12px; +} +.email-settings-display-spacer { + height: 10px; +} +.email-settings-display-section .email-settings-section-desc { + margin-top: 10px; + top: 10px; +} +.email-settings-display-section, +.email-settings-inline-images-section { + padding-bottom: 18px; +} +.email-settings-cleanup-section .email-settings-section-desc { + margin-top: 14px; + top: 14px; +} +.email-settings-auto-reply-section .email-settings-section-desc { + margin-top: 12px; + top: 10px; +} +.email-settings-toggle, +.email-settings-check { + display: flex; + align-items: center; + gap: 7px; + font-size: 12px; + line-height: 1.25; +} +.email-settings-toggle { + flex-shrink: 0; +} +.email-settings-enabled-control { + display: inline-flex; + align-items: center; + gap: 7px; + flex-shrink: 0; +} +.email-settings-enabled-state { + font-size: 10px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.email-settings-section.is-enabled .email-settings-enabled-state { + color: var(--accent, var(--red)); +} +.email-settings-section.is-disabled .email-settings-enabled-state { + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.email-settings-display-section.is-enabled .email-settings-enabled-state { + color: var(--accent, var(--red)); +} +.email-settings-check { + min-height: 24px; + padding: 3px 0; +} +.email-settings-check > span:first-child { + min-width: 0; + margin-right: auto; +} +.email-settings-check-spacer { + flex: 1; +} +.email-settings-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 7px; + min-width: 0; +} +.email-settings-grid label, +.email-settings-field { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; +} +.email-settings-grid span, +.email-settings-field > span { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0; + color: color-mix(in srgb, var(--fg) 56%, transparent); +} +.email-settings-page input[type="date"], +.email-settings-page input[type="datetime-local"], +.email-settings-page select, +.email-settings-page textarea { + width: 100%; + min-width: 0; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + color: var(--fg); + font: inherit; + font-size: 12px; +} +.email-settings-page input[type="date"], +.email-settings-page input[type="datetime-local"], +.email-settings-page select { + height: 28px; + padding: 0 7px; +} +.email-settings-page textarea { + resize: vertical; + min-height: 72px; + padding: 7px; + line-height: 1.35; +} +.email-settings-static-value { + min-height: 28px; + display: flex; + align-items: center; + padding: 0 7px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--panel, var(--bg)) 74%, transparent); + color: color-mix(in srgb, var(--fg) 72%, transparent); + font-size: 12px; +} +.email-settings-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 8px; + margin-top: 2px; +} + +/* Email Settings actions use the same quiet outlined treatment everywhere: + the Settings links, the modal settings page, and unsubscribe actions. */ +[data-settings-panel="email"] #set-email-open-integrations, +[data-settings-panel="email"] #set-email-open-tasks, +[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn, +#email-lib-settings-page .email-settings-section button.memory-toolbar-btn { + background: transparent; + border: 1px solid var(--border); + color: var(--fg); + font-family: inherit; + font-weight: 400; +} +[data-settings-panel="email"] #set-email-open-integrations:hover, +[data-settings-panel="email"] #set-email-open-tasks:hover, +[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn:hover, +#email-lib-settings-page .email-settings-section button.memory-toolbar-btn:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); + color: var(--fg); +} +[data-settings-panel="email"] #set-email-open-integrations > svg, +[data-settings-panel="email"] #set-email-open-tasks > svg { + color: var(--accent, var(--red)); + opacity: 1 !important; +} +[data-settings-panel="email"] .email-style-settings-extract, +[data-settings-panel="email"] .email-style-settings-save, +#email-lib-settings-page .email-style-settings-extract, +#email-lib-settings-page .email-style-settings-save { + background: transparent; + border: 1px solid var(--border); + color: var(--fg); + font-weight: 400; +} +[data-settings-panel="email"] .email-style-settings-extract:hover, +[data-settings-panel="email"] .email-style-settings-save:hover, +#email-lib-settings-page .email-style-settings-extract:hover, +#email-lib-settings-page .email-style-settings-save:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); + color: var(--fg); +} +.email-settings-status, +.email-style-settings-status { + margin-right: auto; + font-size: 11px; + color: color-mix(in srgb, var(--fg) 62%, transparent); +} +.email-settings-status.is-success, +.email-style-settings-status.is-success { + color: var(--color-success, #4caf50); + position: relative; + top: 2px; +} +.email-settings-status.is-error, +.email-style-settings-status.is-error { + color: var(--accent, var(--red)); + position: relative; + top: 2px; +} +.email-settings-save, +.email-style-settings-save { + height: 28px; + min-width: 64px; + justify-content: center; + display: inline-flex; + align-items: center; + gap: 5px; +} +.email-settings-save { + height: 32px; +} +.email-style-settings-extract { + height: 32px; +} +.email-style-settings-save { + height: 32px; +} +.email-settings-save svg, +.email-style-settings-save svg { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-style-settings-extract svg { + color: var(--accent, var(--red)); + position: relative; + top: 2px; +} +.email-settings-task-setting { + color: color-mix(in srgb, var(--fg) 72%, transparent); +} +.email-settings-inline-link { + appearance: none; + border: 0; + padding: 0; + background: transparent; + color: var(--accent, var(--red)); + font: inherit; + text-decoration: underline; + cursor: pointer; +} +.email-style-extract-help { + width: 18px; + height: 18px; + min-width: 18px; + padding: 0; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: color-mix(in srgb, var(--fg) 68%, transparent); + display: inline-flex; + align-items: center; + justify-content: center; + font: inherit; + font-size: 11px; + font-weight: 700; + line-height: 1; + cursor: help; +} +.email-style-extract-help:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); +} +@media (max-width: 420px) { + .email-settings-grid { + grid-template-columns: 1fr; + } +} + + +/* ── Notes ── */ +/* Notes panel — body-level flex sibling of chat-container */ +body.notes-view .chat-container { flex: 1; min-width: 0; } + +/* Mobile: notes panel takes over full screen */ +@media (max-width: 768px) { + body.notes-view .notes-pane { + position: fixed; + inset: 0; + max-width: 100%; + width: 100% !important; + /* Desktop sets height: min(80vh, 820px) which wins over inset:0's bottom:0 + and leaves a gap at the bottom of the viewport. Force full height here. */ + height: 100dvh !important; + max-height: 100dvh !important; + z-index: 170; + /* Bottom-sheet treatment like the document editor: stroked, rounded top, + slides up from the bottom. */ + border: 1px solid var(--border); + border-bottom: none; + border-radius: 14px 14px 0 0; + /* Pane itself never scrolls — its inner .notes-pane-body is the scroller. + Two nested scrollers caused the body's flex:1 to lose its height bound + on Firefox mobile and the last row got clipped off-screen. */ + overflow: hidden; + animation: sheet-enter 0.25s cubic-bezier(0.2, 0.8, 0.2, 1) both; + transform-origin: bottom center; + } + /* Required for the flex:1 body to actually constrain to remaining pane + height instead of expanding to its content (default min-height:auto on + flex items). Without this both grid and list views overflow the viewport + bottom on phones. */ + body.notes-view .notes-pane-body { + min-height: 0; + -webkit-overflow-scrolling: touch; + } + body.notes-view #notes-divider { + display: none; + } + body.notes-view .notes-mobile-grabber { + display: block; + flex-shrink: 0; + height: 18px; + position: relative; + background: var(--bg); + touch-action: none; + cursor: grab; + } + body.notes-view .notes-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; + } + body.notes-view .chat-container { + display: none; + } + body.notes-view .mobile-new-chat-btn, + body.notes-view .hamburger-btn { + display: none !important; + } +} +/* ── Mobile notes UX ──────────────────────────────────── + Tiles become read-only previews on touch ≤768px wide. + Tap opens a fullscreen overlay where editing happens; long-press + toggles drag-to-reorder. Wired up in static/js/notes.js. */ +body.notes-mobile-mode .note-card-corner, +body.notes-mobile-mode .note-card-corner-edit, +body.notes-mobile-mode .note-card-corner-archive, +body.notes-mobile-mode .note-card-corner-copy, +body.notes-mobile-mode .note-card-corner-trash, +body.notes-mobile-mode .note-card-corner-unarchive, +body.notes-mobile-mode .note-card-corner-done, +body.notes-mobile-mode .note-card-edit-corner, +body.notes-mobile-mode .note-card-done, +body.notes-mobile-mode .note-card-actions, +body.notes-mobile-mode .note-cl-quickadd, +body.notes-mobile-mode .note-card .note-checkbox-rm, +/* Panel-fullscreen toggle is redundant on mobile — the panel always + fills the viewport already. */ +body.notes-mobile-mode #notes-fullscreen-toggle { + display: none !important; +} + +/* Header toggle icons (archive view, list/grid switch) — defaults are + tuned for the dense desktop header; on mobile bump them slightly so + they read as tap targets without dominating the header. */ +body.notes-mobile-mode #notes-archive-toggle, +body.notes-mobile-mode #notes-view-toggle { + width: 30px; + height: 30px; + padding: 6px; +} +body.notes-mobile-mode #notes-archive-toggle svg, +body.notes-mobile-mode #notes-view-toggle svg { + width: 17px; + height: 17px; +} +/* The whole card is the tap target on mobile — bigger feedback */ +body.notes-mobile-mode .note-card { + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform 0.12s ease, box-shadow 0.12s ease; +} + +/* Drag-to-reorder mode — desaturate the grid and sweep a subtle + shimmer across every tile so it's obvious you're in rearrange mode + (the Google Keep "active edit" cue). Also disable scroll inside the + panel so the drag works smoothly. */ +body.notes-drag-mode .notes-pane-body { + overflow: hidden; + touch-action: none; +} diff --git a/static/css/style-part-07.css b/static/css/15-notes.css similarity index 55% rename from static/css/style-part-07.css rename to static/css/15-notes.css index 0c6fc8fee..3c4103273 100644 --- a/static/css/style-part-07.css +++ b/static/css/15-notes.css @@ -1,3002 +1,19 @@ -/* Fragment 8/9 of the former static/style.css. +/* 15-notes - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -.doc-editor-textarea, -#doc-editor-code.email-mode .doc-editor-textarea { - color: var(--fg) !important; - caret-color: var(--fg) !important; - z-index: 1; -} - -/* Emoji picker (monochrome icon picker) */ -.emoji-picker-btn { - background: none; border: none; color: var(--fg); opacity: 0.35; - padding: 4px 7px; cursor: pointer; min-height: 28px; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.1s; -} -.emoji-picker-btn:hover { opacity: 1; } -.emoji-picker { - width: min(300px, 92vw); max-height: min(300px, 55vh); - background: var(--panel); border: 1px solid var(--border); border-radius: 8px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3); - display: flex; flex-direction: column; overflow: hidden; -} -.emoji-picker-search { - padding: 8px 12px; background: var(--panel); border: none; - border-bottom: 1px solid var(--border); color: var(--fg); - font-size: 12px; font-family: inherit; outline: none; -} -.emoji-picker-groups { overflow-y: auto; padding: 4px 0; flex: 1; } -.emoji-picker-group { padding: 0 6px 4px 6px; } -.emoji-picker-group-name { - font-size: 10px; font-weight: 600; opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.5px; - padding: 4px 4px 2px 4px; -} -.emoji-picker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; } -.emoji-picker-item { - background: none; border: none; cursor: pointer; - padding: 4px; border-radius: 4px; - display: flex; align-items: center; justify-content: center; - color: var(--fg); transition: background 0.1s; -} -.emoji-picker-item:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } -.emoji-picker-item svg { width: 16px; height: 16px; } - -.email-avatar { - width: 28px; height: 28px; border-radius: 50%; background: var(--accent, #4a9eff); - color: #fff; display: flex; align-items: center; justify-content: center; - font-size: 12px; font-weight: 600; flex-shrink: 0; margin-top: 1px; -} -.email-item-content { flex: 1; min-width: 0; overflow: hidden; } -.email-item-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } -.email-sender { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.email-filter-chip { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; - margin: 4px 8px 6px; - padding: 4px 8px; - border-radius: 12px; - font-size: 11px; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - color: var(--fg); -} -.email-filter-chip-clear { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 14px; - line-height: 1; - padding: 0 2px; - opacity: 0.6; -} -.email-date { font-size: 10px; opacity: 0.5; white-space: nowrap; flex-shrink: 0; } -.email-subject { font-size: 11px; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } -.email-sent-chip, -.email-folder-chip { - display: inline-flex; - align-items: center; - height: 14px; - padding: 0 5px; - margin-right: 5px; - border-radius: 999px; - font-size: 8px; - line-height: 1; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - vertical-align: 1px; -} -.email-folder-chip { - position: relative; - top: 4px; - color: color-mix(in srgb, var(--fg) 75%, transparent); - background: rgba(127, 127, 127, 0.13); - border-color: rgba(127, 127, 127, 0.24); -} -.email-meta-date-group { - display: inline-flex; - align-items: center; - gap: 5px; - white-space: nowrap; -} -.email-meta-date-group .email-folder-chip { - margin-right: 0; -} -.email-card-expanded .email-sent-chip, -.email-card-reader .email-sent-chip { - position: relative; - top: 4px; -} -.email-tags { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 3px; margin-left: 6px; vertical-align: middle; position: relative; top: -2px; } -.email-card-tags { - flex: 0 0 auto; - min-width: 0; - max-width: 48%; - position: relative; - top: -4px; -} -.email-card-expanded .email-tags, -.email-card-reader .email-tags { top: -4px; } -.doclib-card-expanded .email-card-tags { top: -4px; } -.email-card-expanded .email-card-titlerow .email-tag, -.email-card-expanded .email-card-titlerow .email-tags-more { - transform: translateY(-2px); -} -@media (max-width: 768px) { - .email-card-expanded .email-card-titlerow .email-tag, - .email-card-expanded .email-card-titlerow .email-tags-more { - transform: translateY(2px); - } -} -.email-card-tags.email-tags-expanded { - position: absolute; - z-index: 4; - right: calc(var(--email-status-width, 44px) + 6px); - top: -5px; - max-width: min(58%, 260px); - padding: 4px 5px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent); -} -.email-tags-more { - appearance: none; - border: 0; - box-sizing: border-box; - border-radius: 999px; - background: rgba(127, 127, 127, 0.16); - color: var(--fg); - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 1px; - height: 13px !important; - min-height: 0 !important; - min-width: 23px; - padding: 1px 4px; - font-family: inherit; - font-size: 9px; - line-height: 1; - margin: 0; - opacity: 0.68; - position: relative; - top: 0; - vertical-align: middle; -} -.email-tags-more svg { display: block; flex: 0 0 auto; } -.email-tags-expanded .email-tags-more svg { transform: rotate(180deg); } -.email-tags-toggle-inline { - position: absolute; - right: 94px; - top: 50%; - transform: translateY(calc(-50% - 5px)); - height: 20px !important; - min-width: 34px; - padding: 2px 6px !important; - font-size: 10px !important; - line-height: 1 !important; - opacity: 0.62; - border-radius: 999px !important; -} -.email-tags-toggle-inline.active { - opacity: 1; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 36%, transparent); -} -.email-tags-toggle-inline.email-tags-empty { - color: var(--warning, #d6b35a); - background: color-mix(in srgb, var(--warning, #d6b35a) 12%, transparent); - border-color: color-mix(in srgb, var(--warning, #d6b35a) 34%, transparent); -} -.email-tag { - display: inline-block; - font-size: 9px; - line-height: 1; - padding: 2px 5px; - border-radius: 8px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.3px; - background: rgba(127, 127, 127, 0.18); - color: var(--fg); -} -.email-tag-clickable { - appearance: none; - border: 0; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - font-family: inherit; - font-size: 9px; - font-weight: 500; - height: 13px !important; - min-height: 0 !important; - line-height: 1; - margin: 0; - padding: 2px 5px !important; - text-transform: uppercase; - vertical-align: middle; - flex: 0 0 auto; -} -.email-tag-work { background: rgba(96, 165, 250, 0.22); color: #60a5fa; } -.email-tag-personal { background: rgba(74, 222, 128, 0.22); color: #4ade80; } -.email-tag-finance { background: rgba(250, 204, 21, 0.22); color: #facc15; } -.email-tag-bills { background: rgba(251, 146, 60, 0.22); color: #fb923c; } -.email-tag-receipt { background: rgba(251, 146, 60, 0.22); color: #fb923c; } -.email-tag-travel { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } -.email-tag-newsletter { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } -.email-tag-promo, -.email-tag-marketing { background: rgba(244, 114, 182, 0.22); color: #f472b6; } -.email-tag-notification { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } -.email-tag-security { background: rgba(248, 113, 113, 0.22); color: #f87171; } -.email-tag-urgent { background: color-mix(in srgb, var(--color-error, #e06c75) 25%, transparent); color: var(--color-error, #e06c75); font-weight: 600; } -.email-tag-reply-soon { background: rgba(240, 173, 78, 0.22); color: #f0ad4e; } -.email-tag-social { background: rgba(56, 189, 248, 0.22); color: #38bdf8; } -.email-tag-shopping { background: rgba(236, 72, 153, 0.22); color: #ec4899; } -.email-tag-calendar { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } -.email-menu-wrap { position: relative; flex-shrink: 0; } -.email-menu-btn { - background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; - padding: 4px; transition: opacity 0.15s; - position: relative; top: 0px; -} -.email-dropdown { - position: absolute; right: 0; top: 100%; z-index: 9999; - min-width: 120px; background: var(--bg); border: 1px solid var(--border); - border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); padding: 4px 0; -} -.email-loading { padding: 16px; text-align: center; font-size: 12px; opacity: 0.5; } -.email-load-more { padding: 8px; text-align: center; } -.email-load-more-btn { - background: transparent; border: 1px solid var(--border); border-radius: 4px; - color: var(--fg); padding: 4px 12px; font-size: 11px; cursor: pointer; -} -.email-load-more-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } -.email-spinner { - display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); - border-top-color: #fff; border-radius: 50%; animation: email-spin 0.6s linear infinite; - vertical-align: middle; margin-right: 4px; -} - -/* Email section doesn't collapse — hide the auto-injected chevron */ -#email-section .section-collapse-btn { display: none !important; } - -#email-section:hover #email-compose-btn { - opacity: 0.7 !important; -} -#email-compose-btn:hover { - opacity: 1 !important; - background: none !important; -} -#email-compose-btn svg { - width: 11px; - height: 11px; - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); -} -#email-compose-btn .list-item-plus-label { - top: calc(50% - 0.5px); - color: var(--fg); - font-size: 10px; -} -#email-compose-btn:hover svg { - transform: rotate(180deg) scale(1.15); -} - -/* Library row "+" — identical to the email compose "+": hidden until the row - is hovered, no accent box, and the same rotate-in animation. */ -#library-new-doc-btn, -#notes-new-note-btn { - opacity: 0.4 !important; - transition: opacity 0.15s ease; -} -#tool-library-btn:hover #library-new-doc-btn, -#tool-notes-btn:hover #notes-new-note-btn { - opacity: 0.7 !important; -} -#library-new-doc-btn:hover, -#notes-new-note-btn:hover { - opacity: 1 !important; - background: none !important; -} -#library-new-doc-btn svg, -#notes-new-note-btn svg { - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); -} -#library-new-doc-btn .list-item-plus-label, -#notes-new-note-btn .list-item-plus-label { - top: calc(50% + 1px); -} -#library-new-doc-btn:hover svg, -#notes-new-note-btn:hover svg { - transform: rotate(180deg) scale(1.15); -} - - -/* MD toolbar attach button — matches other toolbar buttons */ -.md-toolbar-attach-btn { - background: none; border: none; color: var(--fg); opacity: 0.35; - padding: 4px 7px; cursor: pointer; min-height: 28px; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.1s; -} -.md-toolbar-attach-btn:hover { opacity: 1; } - -/* Email reader icon buttons — vertical icon + label stack. */ -.memory-toolbar-btn.reader-icon-btn { - width: auto; - height: 44px; - /* padding: 4px 2px; */ - position: relative; - top: 1px; - display: inline-flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 3px; - flex: 0 0 auto; -} -.memory-toolbar-btn.reader-icon-btn svg { width: 18px; height: 18px; } -.memory-toolbar-btn.reader-icon-btn[data-act="reply"] svg { - color: inherit; - stroke: currentColor; -} -.memory-toolbar-btn.reader-icon-btn.active { - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - color: var(--accent, var(--red)); -} -.memory-toolbar-btn.reader-icon-btn[data-act="more"] { - color: inherit; -} -.memory-toolbar-btn.reader-icon-btn[data-act="more"] svg { fill: currentColor; } -.memory-toolbar-btn.reader-icon-btn.reader-more-active { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); - color: inherit; -} -/* Cookbook Serve dropdown triggers (the cached-model kebab + the saved-config - arrow) — accent-colored to signal they open a menu, with a filled tint when - their dropdown is open. Matches the email reader More button pattern. */ -.hwfit-cached-menu-btn, -.cookbook-saved-arrow { - color: var(--accent, var(--red)); - /* Base .memory-item-btn is flex with align-items but no justify-content, - so the kebab SVG sat left-aligned inside the button instead of being - horizontally centered. Pin it center. */ - justify-content: center; -} -.hwfit-cached-menu-btn.cookbook-menu-active, -.cookbook-saved-arrow.cookbook-menu-active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - color: var(--accent, var(--red)); -} - -/* Card action menus share one hit target and baseline. Keep this late in the - cascade because modal buttons and Cookbook Serve add their own offsets. */ -.memory-item-actions > .memory-item-btn, -.memory-item-actions .hwfit-cached-menu-btn { - box-sizing: border-box; - width: 28px; - min-width: 28px; - height: 22px; - min-height: 22px; - margin: 0 !important; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - color: var(--color-muted); - font-size: 11px; - display: inline-flex; - align-items: center; - align-self: center; - justify-content: center; - position: relative; - top: 0 !important; - transform: none !important; - line-height: 1; - transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s; -} -.memory-item-actions .hwfit-cached-menu-btn:hover { - opacity: 1 !important; - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); - color: var(--fg); -} -.memory-item-actions .hwfit-cached-menu-btn { - transform: translateY(2px) !important; -} - -@media (min-width: 769px) { - .task-card .memory-item-actions { - top: 6px; - } - .task-card .memory-item-actions > .memory-item-btn { - transform: none !important; - } -} - -/* All kebab menus use the same muted tone as the expand/collapse chevron. */ -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -) { - color: var(--color-muted, var(--fg)) !important; -} -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -) svg { - color: inherit; - fill: currentColor; -} -/* Card kebabs are lightweight menu affordances, not bordered buttons. The - old base hover rule gave only some library cards a boxed outline while - chats, tasks, and cookbook stayed borderless. */ -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -):hover { - background: none !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1; -} -/* Skills used a separate, larger kebab with a different hover treatment. - Match every other card action trigger. */ -.skill-kebab-btn { - box-sizing: border-box; - width: 28px; - min-width: 28px; - height: 22px; - min-height: 22px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - color: var(--color-muted) !important; - opacity: 0.55; - top: -2px; -} -@media (max-width: 768px) { - .memory-item-actions > .memory-item-btn, - .memory-item-actions .hwfit-cached-menu-btn { - width: 40px; - min-width: 40px; - height: 40px; - min-height: 40px; - } - /* Card menus all need the same comfortable tap target. These components - previously escaped the shared action-button sizing through more-specific - desktop selectors. */ - .memory-menu-btn, - .skill-kebab-btn, - .task-card .memory-item-actions .memory-item-btn { - width: 40px !important; - min-width: 40px !important; - height: 40px !important; - min-height: 40px !important; - } - .memory-menu-btn { opacity: 0.7; } - /* Document cards seat their actions on a slightly higher title baseline - than chats/research cards. Bring only that kebab down 4px. */ - #doclib-modal .doclib-card[data-doc-id] > .doclib-document-header .memory-item-actions .memory-item-btn { - transform: translateY(-2px) !important; - } -} -/* Mobile: center the kebab/menu dropdowns in the viewport so they don't - slide left/right as the window width changes (they were positioned via - inline `right: ...px` relative to the kebab button). Stable position is - easier to land on by touch. The inline `top:` set by the menu code is - preserved; we only override the horizontal axis. */ -@media (max-width: 768px) { - .email-card-dropdown, - .cookbook-saved-menu, - .cookbook-dep-menu { - left: 50% !important; - right: auto !important; - transform: translateX(-50%); - } -} -/* Mobile-only Cancel item appended to dropdowns (kebab/More menus). On - desktop outside-click closes them cleanly; on touch that's fiddly, so - give an explicit Cancel row. Visually separated from the action items - above by a top divider + slight muted color. */ -.dropdown-cancel-mobile { display: none; } -@media (max-width: 768px) { - .dropdown-cancel-mobile { - display: flex; - border-top: 1px solid var(--border); - margin-top: 4px; - padding-top: 8px; - opacity: 0.85; - } -} -/* Launch-command Copy button morphs into Cancel once Launch has been clicked. */ -.hwfit-serve-copy.hwfit-serve-cancel { - border-color: var(--color-danger, #e06c75) !important; - color: var(--color-danger, #e06c75) !important; -} -.hwfit-serve-copy.hwfit-serve-cancel:hover { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); -} - -/* One visual contract for card kebab menus. These menus are built by several - modules, and native button defaults previously made identical rows render - with different fonts, heights, and hover movement. */ -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) { - box-sizing: border-box; - padding: 4px !important; - color: var(--fg); - background: var(--panel, var(--bg)) !important; - border: 1px solid var(--border) !important; - border-radius: 8px !important; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important; - font-family: inherit !important; - font-size: 11px !important; - line-height: 1.2; -} -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) > .dropdown-item-compact { - box-sizing: border-box; - width: 100%; - min-height: 28px; - padding: 6px 8px; - gap: 10px; - border: 0; - border-radius: 6px; - color: var(--fg); - background: transparent; - font: inherit !important; - line-height: 1.2; - text-align: left; - white-space: nowrap; -} -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) > .dropdown-item-compact:hover:not(:disabled) { - color: var(--fg); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - transform: none; -} -@media (max-width: 768px) { - :is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu - ) > .dropdown-item-compact { - min-height: 44px; - padding: 12px !important; - gap: 10px !important; - font-size: 14px !important; - } -} -/* Email modal title unread badge — small accent pill next to "Email". */ -.email-lib-unread-badge { - display: inline-block; - font-size: 0.55em; - font-weight: 700; - background: var(--accent-primary, var(--red)); - color: var(--bg, #1a1a1a); - border-radius: 9px; - padding: 1px 7px; - margin-left: 8px; - vertical-align: 2px; - line-height: 1.3; - letter-spacing: 0.02em; - cursor: pointer; -} -.email-lib-unread-badge.email-lib-away-badge { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); - padding: 0 7px; -} -.email-lib-auto-reply-badge { - display: inline-block; - margin-left: 7px; - padding: 1px 7px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border)); - border-radius: 9px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - color: var(--accent, var(--red)); - font-size: 0.55em; - font-weight: 700; - line-height: 1.3; - letter-spacing: 0.01em; - vertical-align: 2px; - cursor: pointer; -} -.email-lib-auto-reply-status-dot { - width: 6px; - height: 6px; - margin-left: 6px; - border-radius: 50%; - background: var(--accent, var(--red)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); - vertical-align: 2px; -} -.modal-header h4 > .email-lib-auto-reply-badge { - font-size: 0.65em; - white-space: nowrap; - vertical-align: 1px; -} -#email-lib-modal.email-lib-unread-active .doclib-modal-content { - box-shadow: - 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent), - 0 0 18px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), - var(--shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.35)); -} -#email-lib-modal .email-lib-header-actions .minimize-btn { - position: relative; - left: 6px; -} -/* Per-card prev/next nav — only visible on the currently expanded card, - sits at the right of the title row next to the done check. */ -.email-card-nav-arrows { - display: none; - margin-left: auto; - gap: 2px; - flex-shrink: 0; - align-items: center; - position: relative; - top: -3px; - left: 19px; -} -.doclib-card.email-card-expanded .email-card-nav-arrows { - display: inline-flex; -} -.email-card-header-menu { - display: none; - align-items: center; - justify-content: center; - flex-shrink: 0; - width: 28px; - height: 28px; - padding: 0; - border: none; - border-radius: 6px; - background: none; - color: var(--fg); - opacity: 0.65; - cursor: pointer; - position: relative; - top: -3px; - transition: opacity 0.12s, background 0.12s; -} -.doclib-card.email-card-expanded .email-card-header-menu { - display: inline-flex; -} -.email-card-header-menu:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.email-card-nav-btn { - background: none; - border: none; - color: var(--fg); - opacity: 0.65; - cursor: pointer; - padding: 6px 8px; - border-radius: 6px; - display: inline-flex; - align-items: center; - justify-content: center; - transition: opacity 0.12s, background 0.12s; -} -.email-card-nav-btn:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} - -/* ── Email document editor ── */ -.doc-email-header { - display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; - border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; -} -.doc-email-fields { - display: flex; - flex-direction: column; - gap: 6px; - min-height: 0; -} -.doc-email-collapse-btn { - width: 100%; - min-height: 24px; - display: none; - align-items: center; - gap: 7px; - padding: 2px 4px 3px; - border: none; - background: transparent; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - opacity: 0.72; - text-align: left; -} -.doc-email-collapse-btn:hover { opacity: 1; color: var(--accent, var(--red)); } -.doc-email-collapse-btn svg { - flex-shrink: 0; - opacity: 0.65; - transition: transform 0.14s ease; -} -.doc-email-collapse-summary { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - opacity: 0.74; -} -.doc-email-header.doc-email-header-collapsed .doc-email-collapse-btn svg { - transform: rotate(180deg); -} -.email-field { display: flex; align-items: center; gap: 8px; position: relative; } -.email-field label { font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.5; min-width: 50px; text-align: right; flex-shrink: 0; } -.email-field input { - flex: 1; width: 100%; background: transparent; border: 1px solid var(--border); border-radius: 4px; - padding: 5px 8px; font-size: 13px; font-family: inherit; color: var(--fg); outline: none; - min-width: 0; -} -.email-field input:focus { border-color: var(--accent, #4a9eff); } -@container docpane (max-width: 460px) { - .doc-email-header .email-field { - display: grid; - grid-template-columns: 1fr; - gap: 3px; - align-items: stretch; - } - .doc-email-header .email-field label { - min-width: 0; - text-align: left; - } - .doc-email-header .email-field input { - width: 100%; - } -} -/* Cc toggle and attach button are absolute so they don't steal width - from the To input. Anchored to the input's vertical center + 4px - nudge so the label sits where the user expects it. */ -.email-field .email-cc-toggle { - position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); - z-index: 2; -} -/* Accent-tinted prefix label (To / Cc / Bcc / Subject) sitting at - the left edge of each input. Replaces both the old tag - and the all-placeholder accent. */ -.email-field .email-field-prefix { - position: absolute; - left: 10px; - top: 50%; - transform: translateY(-50%); - font-size: 11px; - font-weight: 600; - color: var(--accent-primary, var(--red)); - pointer-events: none; - z-index: 1; - line-height: 1; -} -.email-field input { padding-left: 44px; } -.email-field #doc-email-subject { padding-left: 64px; } -/* X close button inside Cc / Bcc fields. */ -.email-field .email-cc-close { - position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); - background: none; border: none; color: var(--fg); - opacity: 0.4; cursor: pointer; - width: 22px; height: 22px; - display: inline-flex; align-items: center; justify-content: center; - border-radius: 4px; - padding: 0; - z-index: 2; -} -.email-field .email-cc-close:hover { - opacity: 1; - color: var(--accent, var(--red)); - background: color-mix(in srgb, currentColor 10%, transparent); -} -.email-field input { padding-right: 60px; } -.email-field #doc-email-cc, .email-field #doc-email-bcc { padding-right: 32px; } -.email-field #doc-email-subject { padding-right: 8px; } - -.doc-email-actions { - display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; - border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; - align-items: center; - position: relative; - z-index: 30; - pointer-events: auto; -} -#doc-actions-footer .email-send-split { - margin-left: auto; -} -.doc-save-state-icon { display: none; flex: 0 0 auto; } -.doc-save-button[data-save-state="dirty"] { color: var(--accent, var(--red)); } -.doc-save-button[data-save-state="saving"] { color: var(--fg-muted, var(--fg)); } -.doc-save-button[data-save-state="saved"] { color: var(--fg); } -.doc-save-button[data-save-state="error"] { color: var(--color-danger, #e06c75); } -.doc-stats-wrap { - position: relative; - margin-left: 0; - flex: 0 0 auto; -} -.doc-stats-btn { - height: 28px; - border: 0; - border-radius: 4px; - padding: 0 7px; - background: transparent; - color: var(--fg-muted, var(--fg)); - font: inherit; - font-size: 10px; - cursor: pointer; - white-space: nowrap; -} -.doc-stats-btn:hover, -.doc-stats-btn[aria-expanded="true"] { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 7%, transparent); -} -.doc-stats-btn.doc-stats-selected { - color: var(--accent, var(--red)); - font-weight: 600; -} -.doc-stats-popover { - position: fixed; - right: auto; - bottom: auto; - width: 190px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); - color: var(--fg); - font-size: 11px; -} -.doc-stats-scope { - padding-bottom: 6px; - margin-bottom: 5px; - border-bottom: 1px solid var(--border); - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - text-transform: uppercase; -} -.doc-stats-row { - min-height: 22px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 14px; -} -/* WYSIWYG email body — what the recipient sees, edited in place. */ -.doc-email-richbody { - flex: 1; - min-height: 0; - overflow-y: auto; - padding: 12px 16px; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.6; - color: var(--fg); - background: var(--bg); - outline: none; - white-space: pre-wrap; - text-align: left; - word-wrap: break-word; -} -.doc-email-richbody.richtext-mode { - background: var(--bg); -} -.doc-rich-empty-import { - position: absolute; - top: 90px; - right: 18px; - bottom: auto; - left: auto; - z-index: 2; - display: flex; - pointer-events: none; -} -.doc-rich-empty-import .doc-rich-empty-import-btn { - position: static; - float: none; - margin: 0; - opacity: 1; - pointer-events: auto; - transform: none; -} -.doc-email-richbody.richtext-mode ul.rich-checklist { - list-style: none; - padding-left: 1.8em; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li { - position: relative; - min-height: 1.6em; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li::before { - content: ""; - position: absolute; - left: -1.55em; - top: 0.3em; - width: 0.95em; - height: 0.95em; - box-sizing: border-box; - border: 1.5px solid color-mix(in srgb, var(--fg) 48%, transparent); - border-radius: 3px; - background: var(--sidebar-bg, var(--panel)); - cursor: pointer; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::before { - border-color: var(--accent, var(--red)); - background: var(--accent, var(--red)); -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::after { - content: ""; - position: absolute; - left: -1.27em; - top: 0.44em; - width: 0.28em; - height: 0.5em; - border: solid #fff; - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); - pointer-events: none; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"] { - color: color-mix(in srgb, var(--fg) 62%, transparent); - text-decoration: line-through; -} -.doc-email-richbody.richtext-mode hr { - border: 0; - border-top: 1px solid var(--border); - margin: 1.25em 0; -} -.doc-email-richbody.richtext-mode hr.richtext-page-break { - height: 8px; - border-top: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); - border-bottom: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); - margin: 1.6em 0; - opacity: 0.8; -} -.doc-email-richbody.richtext-mode table { - width: 100%; - margin: 12px 0; - border-collapse: collapse; - table-layout: fixed; -} -.doc-email-richbody.richtext-mode th, -.doc-email-richbody.richtext-mode td { - min-width: 48px; - padding: 7px 8px; - border: 1px solid var(--border); - vertical-align: top; - overflow-wrap: anywhere; -} -.doc-email-richbody.richtext-mode th { - background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 85%, var(--fg) 15%); - font-weight: 600; -} -.doc-email-richbody.richtext-mode code, -.doc-email-richbody.richtext-mode span[style*="OdysseusInlineCode"] { - padding: 0.12em 0.35em; - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 0.9em; - white-space: break-spaces; -} -.doc-email-richbody.richtext-mode pre { - margin: 1em 0; - padding: 12px 14px; - overflow-x: auto; - border: 1px solid var(--border); - border-radius: 4px; - background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 82%, #000 18%) !important; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 0.9em; - line-height: 1.5; - white-space: pre-wrap; - tab-size: 2; -} - -::highlight(doc-find-results) { - color: inherit; - background-color: color-mix(in srgb, #facc15 48%, transparent); -} - -::highlight(doc-find-current) { - color: inherit; - background-color: color-mix(in srgb, var(--accent, #ef4444) 68%, #facc15); -} - -::highlight(doc-ai-selections) { - color: inherit; - background-color: color-mix(in srgb, var(--accent, #ef4444) 30%, transparent); -} -.doc-email-richbody img.richtext-image { - display: block; - margin: 12px 0; - max-width: 100%; - height: auto; - border-radius: 4px; - cursor: pointer; -} -.doc-email-richbody figure.richtext-image { - margin: 12px 0; - max-width: 100%; -} -.doc-email-richbody figure.richtext-image:not(:has(img)) { display: none; } -.doc-email-richbody figure.richtext-image img.richtext-image { - display: block; - margin-top: 0; - margin-bottom: 0; - max-width: 100%; - height: auto; - border-radius: 4px; -} -.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-60) .richtext-image-caption { width: 60%; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-35) .richtext-image-caption { width: 35%; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-left) .richtext-image-caption { margin-left: 0; margin-right: auto; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-center) .richtext-image-caption { margin-left: auto; margin-right: auto; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-right) .richtext-image-caption { margin-left: auto; margin-right: 0; } -.doc-email-richbody img.richtext-image-size-100 { width: 100%; } -.doc-email-richbody img.richtext-image-size-60 { width: 60%; } -.doc-email-richbody img.richtext-image-size-35 { width: 35%; } -.doc-email-richbody img.richtext-image-align-left { margin-left: 0; margin-right: auto; } -.doc-email-richbody img.richtext-image-align-center { margin-left: auto; margin-right: auto; } -.doc-email-richbody img.richtext-image-align-right { margin-left: auto; margin-right: 0; } -.doc-email-richbody img.richtext-image[data-editor-image-selected] { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 3px; -} -.doc-rich-image-toolbar { - position: fixed; - display: inline-flex; - align-items: center; - gap: 2px; - padding: 2px; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 10032; -} -.doc-rich-image-toolbar button { - width: 24px; - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - border: 0; - border-radius: 3px; - background: transparent; - color: inherit; - cursor: pointer; -} -.doc-rich-image-toolbar button:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--accent, var(--red)); -} -.doc-rich-image-toolbar .doc-rich-image-remove:hover { - color: var(--accent, var(--red)); -} -.doc-rich-image-source-menu { - position: fixed; - width: 260px; - min-width: 260px; - padding: 5px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 4px 16px color-mix(in srgb, var(--fg) 20%, transparent); - z-index: 10033; - font-size: 11px; -} -.doc-rich-image-source-title { - padding: 5px 7px 4px; - color: var(--fg); - font-weight: 700; - opacity: .75; -} -.doc-rich-image-source-menu > button, -.doc-rich-gallery-item { - width: 100%; - display: flex; - align-items: center; - gap: 7px; - padding: 7px 8px; - border: 0; - border-radius: 4px; - background: transparent; - color: var(--fg); - font: inherit; - text-align: left; - cursor: pointer; -} -.doc-rich-image-source-menu > button[data-image-source="computer"] { - padding-left: 9px; -} -.doc-rich-image-source-menu > button:hover, -.doc-rich-gallery-item:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--accent, var(--red)); -} -.doc-rich-gallery-list { - display: grid; - gap: 2px; - max-height: 300px; - overflow-y: auto; -} -.doc-rich-gallery-item { - min-height: 44px; - min-width: 0; -} -.doc-rich-gallery-item > span:last-child { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.doc-rich-gallery-thumb { - width: 40px; - height: 40px; - flex: 0 0 40px; - border: 1px solid var(--border); - border-radius: 4px; - background-position: center; - background-size: cover; - background-repeat: no-repeat; -} -.rich-color-letter { - border-bottom: 2px solid var(--accent, var(--red)); - font-weight: 700; - line-height: 14px; -} -.rich-color-swatch { - width: 13px !important; - height: 13px !important; - border: 1px solid color-mix(in srgb, var(--fg) 35%, transparent); - border-radius: 50%; -} -.rich-highlight-swatch { border-radius: 50%; } -.rich-color-swatch.is-transparent { - background: - linear-gradient(135deg, transparent 43%, var(--red) 44%, var(--red) 56%, transparent 57%), - var(--bg); -} -.rich-color-palette-menu { - min-width: min(292px, calc(100vw - 16px)); -} -.rich-color-palette-menu .rich-color-reset { - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - margin-bottom: 3px; - padding-bottom: 7px; -} -.rich-color-palette-grid { - display: grid; - grid-template-columns: repeat(2, minmax(126px, 1fr)); - gap: 0 4px; - padding: 0 4px; -} -.rich-color-palette-group { - min-width: 0; -} -.rich-color-palette-label { - padding: 4px 8px 3px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 9px; - font-weight: 600; - text-transform: uppercase; -} -.rich-color-palette-group .doc-overflow-item { - min-width: 0; - padding-inline: 8px; -} -.rich-toolbar-color-custom { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-top: 3px; - padding: 7px 9px 4px 10px; - border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - color: var(--fg); - font-size: 11px; -} -.rich-toolbar-color-input.cp-swatch-input { - width: 22px; - height: 22px; - flex: 0 0 22px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 28%, var(--border)); - border-radius: 50%; - cursor: pointer; -} -.rich-toolbar-color-input.cp-swatch-input:focus-visible { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 2px; -} - -.rich-toolbar-slider-row { - display: grid; - grid-template-columns: auto minmax(104px, 1fr) 42px; - align-items: center; - gap: 9px; - min-width: 220px; - padding: 10px; - color: var(--fg); - font-size: 11px; -} -.rich-toolbar-range { - width: 100%; - min-width: 0; - height: 8px; - margin: 0; - border: 0; - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 25%, transparent); - accent-color: var(--red); - cursor: pointer; - -webkit-appearance: none; - appearance: none; -} -.rich-toolbar-range::-webkit-slider-thumb { - width: 9px; - height: 9px; - border: 0; - border-radius: 50%; - background: var(--red); - cursor: pointer; - -webkit-appearance: none; - appearance: none; - transition: width 0.12s ease, height 0.12s ease; -} -.rich-toolbar-range:hover::-webkit-slider-thumb, -.rich-toolbar-range:focus::-webkit-slider-thumb, -.rich-toolbar-range:active::-webkit-slider-thumb { - width: 16px; - height: 16px; -} -.rich-toolbar-range::-moz-range-thumb { - width: 9px; - height: 9px; - border: 0; - border-radius: 50%; - background: var(--red); - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.rich-toolbar-range:hover::-moz-range-thumb, -.rich-toolbar-range:focus::-moz-range-thumb, -.rich-toolbar-range:active::-moz-range-thumb { - width: 16px; - height: 16px; -} -.rich-toolbar-range:focus-visible { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 4px; -} -.rich-toolbar-slider-value { - width: 42px; - box-sizing: border-box; - padding: 2px 3px; - border: 1px solid transparent; - border-radius: 3px; - background: transparent; - text-align: right; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-variant-numeric: tabular-nums; - -webkit-appearance: textfield; - appearance: textfield; -} -.rich-toolbar-slider-value::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -.rich-toolbar-slider-value:focus { - border-color: var(--accent, var(--red)); - outline: none; - color: var(--fg); -} - -.rich-script-icon, -.rich-font-icon, -.rich-font-size-icon { - display: inline-block; - line-height: 1; - font-size: 11px; -} -.rich-highlight-icon { - display: block; - flex-shrink: 0; - color: var(--accent-primary, var(--red)); -} - -.rich-script-icon sup, -.rich-script-icon sub { - font-size: 7px; - line-height: 0; -} - -.rich-font-icon { - font-family: Georgia, serif; - font-size: 12px; -} - -.rich-font-size-icon { - min-width: 16px; - font-size: 10px; - font-weight: 600; - font-variant-numeric: tabular-nums; - text-align: center; -} -.rich-align-center { text-align: center; } -.rich-align-right { text-align: right; } -.doc-email-richbody .email-quoted-history { - margin-top: 14px; - padding: 10px 0 10px 12px; - border-left: 3px solid var(--border); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.92em; - line-height: 1.45; - white-space: pre-wrap; - user-select: text; - pointer-events: none; -} -.doc-email-richbody h1, .doc-email-richbody h2, .doc-email-richbody h3, -.doc-email-richbody h4, .doc-email-richbody h5, .doc-email-richbody h6 { margin: 0.4em 0; } -.email-more-menu { - position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; - min-width: 160px; background: var(--bg); border: 1px solid var(--border); - border-radius: 6px; box-shadow: 0 -4px 12px rgba(0,0,0,0.2); padding: 4px; - z-index: 100; -} -.email-send-btn { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 20%, transparent); - color: var(--accent-primary, var(--red)); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); - border-radius: 6px; - padding: 0 14px; height: 28px; font-size: 11px; font-weight: 600; cursor: pointer; - font-family: inherit; transition: all 0.15s; white-space: nowrap; - display: inline-flex; align-items: center; gap: 6px; -} -/* Split send button: [ Send │ ▾ ] — the caret drops the send-options menu UP. */ -.email-send-split { position: relative; display: inline-flex; } -.email-send-split .email-send-main { - border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; -} -.email-send-split .email-send-caret { - border-top-left-radius: 0; border-bottom-left-radius: 0; - padding: 0 8px; gap: 0; - /* its left border is the single divider between the two halves */ - border-left: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); -} -.email-send-split .email-send-caret svg { transition: transform 0.15s ease; } -.email-send-split .email-send-caret[aria-expanded="true"] svg { transform: none; } -.email-send-btn svg { flex-shrink: 0; } -.email-send-btn:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); - border-color: var(--accent-primary, var(--red)); -} -.email-draft-btn { - background: none; border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border-radius: 6px; - padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; - font-family: inherit; transition: all 0.15s; white-space: nowrap; -} -.email-draft-btn:hover { border-color: var(--fg); color: var(--fg); } -.email-discard-btn { - background: transparent; - /* Dim the label text via color-mix instead of `opacity:0.6` on the whole - button — opacity multiplies through to the SVG and washes out the accent - X glyph. Per-element color dimming keeps the X at full accent strength. */ - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; font-family: inherit; -} -/* The ✕ glyph itself reads in accent. */ -.email-discard-btn svg { color: var(--accent-primary, var(--red)); opacity: 1; } -.email-discard-btn:hover { opacity: 1; color: var(--red, #e55); border-color: var(--red, #e55); } - -/* Compose email "more" menu button — icon only, matches draft btn height */ -#doc-email-more-btn { - background: none; border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border-radius: 6px; - width: 28px; height: 28px; padding: 0; - cursor: pointer; font-family: inherit; transition: all 0.15s; - display: inline-flex; align-items: center; justify-content: center; -} -#doc-email-more-btn:hover { - border-color: var(--fg); color: var(--fg); -} -.email-more-wrap { display: inline-flex; } -.email-attachments { - display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 0 58px; -} -.email-attachment-chip { - display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px; font-size: 11px; background: var(--hover-bg, rgba(255,255,255,0.05)); - border: 1px solid var(--border); border-radius: 12px; color: var(--fg); - text-decoration: none; cursor: pointer; transition: background 0.15s, border-color 0.15s; - max-width: 200px; min-width: 0; -} -.email-attachment-chip:hover { - background: color-mix(in srgb, var(--accent) 15%, transparent); - border-color: var(--accent); -} -.email-attachment-chip.is-expanded { - max-width: 90vw; -} -.email-attachment-chip > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - flex: 1 1 auto; min-width: 0; -} -.email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-attachment-chip-related { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); -} -/* "Open in editor" launch icon — same prominent style on desktop AND mobile - (was 24px / dim / no border on desktop, easy to miss). Accent-tinted - background + border makes it read as a real action. */ -.email-attachment-open, -.email-attachment-download, -.email-attachment-calendar-open, -.cal-event-email-source { - display: inline-flex; align-items: center; gap: 4px; - height: 22px; padding: 0 9px; border-radius: 999px; - margin-left: 6px; flex: 0 0 auto; - min-width: 52px; justify-content: center; - font-size: 10px; font-weight: 500; letter-spacing: 0.02em; - color: var(--accent-primary, var(--red)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); - cursor: pointer; transition: background 0.1s, border-color 0.1s, color 0.1s; -} -.email-attachment-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-open svg, -.email-attachment-open > svg, -.email-attachment-download svg, -.email-attachment-download > svg, -.email-attachment-calendar-open svg, -.email-attachment-calendar-open > svg, -.cal-event-email-source svg, -.cal-event-email-source > svg { - width: 12px; height: 12px; - opacity: 0.9; -} -.email-attachment-open:hover, -.email-attachment-download:hover, -.email-attachment-calendar-open:hover, -.cal-event-email-source:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 70%, transparent); -} -.email-attachment-download { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-calendar-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.cal-event-email-source { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-open-label { line-height: 1; } -.email-attachment-chip.is-expanded .email-attachment-open, -.email-attachment-chip.is-expanded .email-attachment-download, -.email-attachment-chip.is-expanded .email-attachment-calendar-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-chip.is-expanded .email-attachment-open-label { - display: none; -} -@media (max-width: 768px) { - .doc-email-collapse-btn { - background: inherit; - } - /* Mobile: keep the pill but ensure a comfortable touch target. */ - .email-attachment-open { - height: 26px; padding: 0 10px; - min-height: 26px !important; - min-width: 58px; - } - .email-attachments, - .email-compose-atts { - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-left: 0; - padding-bottom: 2px; - } - .email-attachments::-webkit-scrollbar, - .email-compose-atts::-webkit-scrollbar { - display: none; - } - /* Attachment chip body — modest minimum height so the open icon sits - neatly without dominating. */ - .email-attachment-chip, - .email-compose-chip { - flex: 0 0 auto; - padding: 6px 8px !important; - min-height: 36px !important; - } - .email-attachment-chip.is-expanded { - flex-wrap: nowrap; - align-items: center; - max-width: min(92vw, 360px); - } - .email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - flex: 1 1 auto; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.25; - min-width: 0; - } - .email-attachment-chip.is-expanded .att-size { - margin-left: 0; - } - .email-attachment-chip.is-expanded .email-attachment-open, - .email-attachment-chip.is-expanded .email-attachment-download, - .email-attachment-chip.is-expanded .email-attachment-calendar-open { - width: 28px; - min-width: 28px; - padding: 0; - margin-left: 4px; - } - .email-compose-chip .compose-chip-name { - max-width: 190px; - } -} - -/* Compose attachment chips (when sending new email) */ -.email-compose-atts { - display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; - padding: 6px 0 0 0; -} -.email-compose-chip { - display: inline-flex; align-items: center; gap: 4px; - flex: 0 1 auto; - min-width: 0; - max-width: min(240px, 100%); - height: 24px; min-height: 24px; box-sizing: border-box; - padding: 2px 3px 2px 7px; font-size: 11px; - background: var(--hover-bg, rgba(255,255,255,0.05)); - border: 1px solid var(--border); border-radius: 12px; color: var(--fg); -} -.email-compose-chip .compose-chip-name { max-width: 180px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.email-compose-chip .compose-chip-remove { - background: none; border: none; color: var(--fg); opacity: 0.5; - height: 18px; font-size: 14px; line-height: 18px; padding: 0 3px; cursor: pointer; - flex-shrink: 0; -} -.email-compose-chip .compose-chip-remove:hover { opacity: 1; color: var(--red, #e55); } - -.pdf-annotation-line-value { - width: 48px; - height: 22px; - box-sizing: border-box; - padding: 0 5px; - border: 1px solid var(--border); - border-radius: 4px; - outline: none; - background: var(--bg-elev, var(--panel)); - color: var(--fg); - font-family: inherit; - font-size: 10px; - font-weight: 500; - line-height: 1; - text-align: right; -} -.pdf-annotation-line-value:hover { border-color: color-mix(in srgb, var(--fg) 38%, var(--border)); } -.pdf-annotation-line-value:focus { border-color: var(--accent, var(--red)); } - -.email-odysseus-attach-menu { - position: fixed; - z-index: 10050; - width: 300px; - max-width: calc(100vw - 16px); - max-height: min(420px, calc(100vh - 16px)); - padding: 7px; - border: 1px solid var(--border); - border-radius: 7px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 12px 30px rgba(0,0,0,0.28); - overflow: hidden; -} -.email-odysseus-attach-local { - width: 100%; - height: 30px; - display: flex; - align-items: center; - gap: 7px; - padding: 0 9px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, var(--border)); - border-radius: 5px; - background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-local:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); -} -.email-odysseus-attach-tabs { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 5px; - margin: 7px 0; -} -.email-odysseus-attach-tabs button { - height: 26px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border: 1px solid var(--border); - border-radius: 5px; - background: transparent; - color: var(--fg); - opacity: 0.72; - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-tabs button.active { - opacity: 1; - color: var(--accent-primary, var(--red)); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); -} -.email-odysseus-attach-tabs button svg { - flex-shrink: 0; -} -.email-odysseus-attach-search-wrap { - height: 28px; - display: flex; - align-items: center; - gap: 6px; - margin: -1px 0 7px; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 5px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.email-odysseus-attach-search-wrap:focus-within { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - color: var(--accent-primary, var(--red)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, transparent); -} -.email-odysseus-attach-search { - min-width: 0; - flex: 1; - border: 0; - outline: 0; - background: transparent; - color: var(--fg); - font: inherit; - font-size: 11px; -} -.email-odysseus-attach-search::placeholder { - color: color-mix(in srgb, var(--fg) 38%, transparent); -} -.email-odysseus-attach-list { - max-height: 320px; - overflow: auto; - display: flex; - flex-direction: column; - gap: 4px; -} -.email-odysseus-attach-row { - width: 100%; - min-height: 38px; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 7px; - border: 1px solid transparent; - border-radius: 5px; - background: transparent; - color: var(--fg); - text-align: left; - font: inherit; - cursor: pointer; -} -.email-odysseus-attach-row:hover { - background: var(--hover-bg, rgba(255,255,255,0.06)); - border-color: var(--border); -} -.email-odysseus-attach-row.is-selected { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); -} -.email-odysseus-attach-dot { - width: 8px; - height: 8px; - flex: 0 0 auto; - display: inline-block; - border: 1px solid var(--border); - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.email-odysseus-attach-row.is-selected .email-odysseus-attach-dot { - border-color: var(--accent-primary, var(--red)); - background: var(--accent-primary, var(--red)); - box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); -} -.email-odysseus-attach-icon, -.email-odysseus-attach-thumb { - width: 28px; - height: 28px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 7%, transparent); - overflow: hidden; -} -.email-odysseus-attach-main { - min-width: 0; - flex: 1; - display: flex; - flex-direction: column; - gap: 2px; -} -.email-odysseus-attach-title, -.email-odysseus-attach-meta { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-odysseus-attach-title { - font-size: 11px; - line-height: 1.2; -} -.email-odysseus-attach-meta { - font-size: 10px; - line-height: 1.15; - opacity: 0.52; -} -.email-odysseus-attach-empty { - padding: 14px 8px; - color: var(--muted, #888); - font-size: 11px; - text-align: center; -} -.email-odysseus-attach-actions { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - padding-top: 5px; - border-top: 1px solid color-mix(in srgb, var(--border) 75%, transparent); - background: var(--bg); - position: sticky; - bottom: 0; -} -.email-odysseus-attach-count { - flex: 1; - min-width: 0; - color: var(--muted, #888); - font-size: 11px; -} -.email-odysseus-attach-selected { - height: 28px; - min-width: 72px; - padding: 0 12px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - background: var(--accent-primary, var(--red)); - color: #fff; - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-selected svg { - flex-shrink: 0; -} - -.email-cc-toggle { - background: none; border: none; color: var(--fg); - opacity: 0.4; font-size: 11px; cursor: pointer; - padding: 4px 8px; font-family: inherit; -} -.email-cc-toggle:hover { - opacity: 1; - color: var(--accent, #4a9eff); - background: none !important; -} - -@media (max-width: 768px) { - .doc-email-collapse-btn { - display: flex; - } -} - -.email-autocomplete { - position: absolute; top: 100%; left: 58px; right: 0; z-index: 1000; - background: var(--bg); border: 1px solid var(--border); border-radius: 6px; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 240px; overflow-y: auto; - margin-top: 2px; -} -.contact-suggestion { - display: flex; justify-content: space-between; gap: 8px; - padding: 6px 10px; font-size: 12px; cursor: pointer; - border-bottom: 1px solid var(--border); -} -.contact-suggestion:last-child { border-bottom: none; } -.contact-suggestion:hover, -.contact-suggestion.active, -.contact-suggestion[aria-selected="true"] { - background: color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 15%, transparent); - outline: 1px solid color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 40%, transparent); - outline-offset: -1px; -} -.contact-suggestion .contact-name { font-weight: 600; color: var(--fg); } -.contact-suggestion .contact-email { opacity: 0.6; font-size: 11px; } -.contact-suggestion-status { - padding: 7px 10px; - color: color-mix(in srgb, var(--fg) 65%, transparent); - font-size: 11px; - cursor: default; -} -.contact-suggestion-status.is-loading { opacity: 0.72; } -.contact-suggestion-status.is-unavailable { - color: color-mix(in srgb, var(--red) 82%, var(--fg)); -} -.email-ai-reply-btn { - background: none; - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; - font-family: inherit; transition: all 0.15s; - display: inline-flex; align-items: center; white-space: nowrap; -} -.email-ai-reply-btn:hover { - border-color: var(--accent, #4a9eff); - color: var(--accent, #4a9eff); -} -/* Align select/new buttons in popup library toolbar */ -#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn, #email-lib-compose-btn { position: relative; top: -4px; } -/* Select + Refresh sit slightly lower than Compose on desktop. */ -#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -2px; } - -#email-lib-modal.email-settings-mode .email-accounts-row, -#email-lib-modal.email-settings-mode .admin-card > .memory-toolbar, -#email-lib-modal.email-settings-mode #email-lib-bulk, -#email-lib-modal.email-settings-mode #email-lib-grid, -#email-lib-modal.email-settings-mode #email-lib-sync-status, -#email-lib-modal.email-settings-mode #email-lib-fab { - display: none !important; -} -#email-lib-settings-page[hidden] { - display: none !important; -} -#email-lib-modal.email-settings-mode #email-lib-settings-page { - display: flex; -} -.email-settings-page { - display: flex; - flex: 1; - min-height: 0; - min-width: 0; - flex-direction: column; - gap: 6px; - overflow: hidden; - padding: 2px 0 8px; - width: 100%; - box-sizing: border-box; - -webkit-overflow-scrolling: touch; -} -.settings-email-preferences-card { - display: contents; -} -.settings-email-preferences-card .email-settings-section { - margin: 0 0 10px; - padding: 14px 12px 12px; - background: var(--panel); -} -.settings-email-preferences-card .email-settings-section:last-child { - margin-bottom: 0; -} -.settings-email-preferences-card .email-settings-style-section { - margin-bottom: 4px; -} -.settings-email-default-card #settings-email-default-host { - display: flex; - justify-content: flex-end; - min-width: 0; - margin-left: auto; -} -.settings-email-default-card .settings-email-default-title { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} -.settings-email-default-card .settings-email-default-title > span { - min-width: 0; -} -.settings-email-default-card .settings-email-default-title > span > svg { - color: var(--accent, var(--red)); -} -.settings-email-default-card .email-settings-account-picker { - margin-left: 0 !important; -} -.settings-email-default-card .email-settings-support-copy { - position: relative; - top: 6px; -} -.settings-email-default-spacer { - height: 12px; -} -.settings-email-default-card .cookbook-srv-default-label { - position: relative; - top: 2px; -} -.settings-email-default-card .cookbook-srv-default-icon > svg { - position: relative; - top: 7px; -} -.settings-email-settings-legacy { - display: none; -} - -/* Keep the standalone Email Settings cards aligned with the expandable - preference cards below them. */ -[data-settings-panel="email"] > .admin-card { - padding-top: 14px; -} -.email-settings-page-host { - display: flex; - flex: 0 0 auto; - min-height: 0; - padding: 0; - overflow: visible; -} -.settings-email-preferences-card > .email-settings-page-host { width: 100%; } -.email-settings-page-head { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; - min-height: 32px; - padding-bottom: 2px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.email-settings-account-picker { - min-width: 0; - max-width: 100%; -} -.email-settings-account-picker select { - min-width: 0; - max-width: 100%; -} -.email-settings-back { - position: relative; - top: -2px; -} -.email-settings-header-back { - background: transparent; - color: color-mix(in srgb, var(--fg) 58%, transparent); - border: 1px solid transparent; - width: auto; - height: 24px; - padding: 0 8px 0 6px; - align-items: center; - justify-content: center; - gap: 4px; - cursor: pointer; - border-radius: 999px; - flex-shrink: 0; - position: relative; - font-size: 11px; - font-weight: 600; - line-height: 1; -} -.email-settings-header-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); -} -.email-settings-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 8px; -} -.email-settings-title { - display: inline-flex; - align-items: center; - gap: 6px; - flex: 1 1 auto; - width: 100%; - min-width: 0; - font-size: 12px; - font-weight: 700; -} -.email-settings-title svg { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-settings-close { - width: 24px; - height: 24px; - border: 1px solid var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 70%, transparent); - cursor: pointer; - font-size: 14px; - line-height: 1; -} -.email-settings-close:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)); -} -.email-settings-body { - display: flex; - flex-direction: column; - flex: 1 1 auto; - gap: 6px; - padding-top: 4px; - box-sizing: border-box; - min-width: 0; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; - overscroll-behavior: contain; -} -.email-settings-loading, -.email-settings-error { - font-size: 12px; - color: color-mix(in srgb, var(--fg) 65%, transparent); - padding: 8px 2px; -} -.email-settings-loading { - display: inline-flex; - align-items: center; - gap: 7px; -} -.email-settings-loading-css-whirlpool { - display: inline-block; - width: 14px; - height: 14px; - box-sizing: border-box; - border: 1.5px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - border-top-color: var(--accent, var(--red)); - border-right-color: color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); - border-radius: 50%; - animation: whirlpool-spin 0.72s linear infinite; - flex: 0 0 auto; -} -.email-settings-loading-spinner { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - color: var(--accent, var(--red)); -} -.email-settings-section { - display: flex; - flex-direction: column; - flex: 0 0 auto; - gap: 7px; - margin: 0; - padding: 14px 8px 8px; - min-width: 0; - box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - transition: opacity 0.15s ease, background 0.15s ease; -} -.email-settings-section.is-disabled { - opacity: 0.58; - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -.email-settings-section-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.email-settings-section-head > div:first-child { - min-width: 0; - flex: 1 1 auto; -} -.email-settings-section-head > .email-settings-enabled-control, -.email-settings-section-head > button { - margin-left: auto; -} -.email-settings-section-toggle { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - width: 100%; - min-width: 0; - padding: 0; - border: 0; - background: transparent; - color: inherit; - text-align: left; - cursor: pointer; -} -.email-settings-section-toggle:hover, -.email-settings-section-toggle:active { - background: transparent; -} -.email-settings-section-toggle-copy { - display: flex; - flex-direction: column; - align-items: flex-start; - min-width: 0; - flex: 1 1 auto; -} -.email-settings-section-toggle > .email-settings-enabled-control { - margin-left: auto; - flex: 0 0 auto; -} -.email-settings-section-chevron { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); - transition: transform .14s ease, color .14s ease; -} -.email-settings-section-toggle:hover .email-settings-section-chevron, -.email-settings-section-toggle:focus-visible .email-settings-section-chevron { - color: var(--accent, var(--red)); -} -.email-settings-section-title { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 14px; - line-height: 1; - font-weight: 600; - letter-spacing: 0; - margin: 0; - padding: 0; -} -.email-settings-section-title svg { - color: var(--accent, var(--red)); - opacity: 0.6; - flex-shrink: 0; -} -.email-settings-account-scope { - margin-left: 4px; - font-size: 11px; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -.email-settings-section-body { - display: flex; - flex-direction: column; - gap: 7px; - min-width: 0; - width: 100%; -} -.email-settings-section[open] > .email-settings-section-body { - min-height: 0; - flex: 0 0 auto; - height: auto; - max-height: none; - overflow: visible; - overscroll-behavior: contain; - padding: 0 4px 4px 0; - scrollbar-width: thin; -} -.email-settings-section[open] { - min-height: 0; - flex: 0 0 auto; -} -.email-settings-auto-reply-section[open], -.email-settings-cleanup-section[open]:has(.email-unsub-card) { - flex: 1 1 auto; - display: grid; - grid-template-rows: auto minmax(0, 1fr); - align-content: stretch; -} -.email-settings-auto-reply-section[open] > .email-settings-section-body, -.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body { - flex: 0 0 auto; - height: 100%; - max-height: 100%; - overflow-y: auto; -} -.email-settings-section[open] > .email-settings-section-body > * { - flex: 0 0 auto; -} -.email-settings-section-body-head { - display: flex; - justify-content: flex-end; -} -.email-settings-display-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.email-settings-display-title { - display: inline-flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.email-settings-display-title > svg { - flex: 0 0 auto; - color: var(--accent, var(--red)); -} -.email-settings-display-title-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - position: relative; - top: 10px; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-display-title-row .email-settings-section-title { - flex: 1 1 auto; - width: auto; - padding-bottom: 0; - border-bottom: 0; -} -.email-settings-style-section .email-settings-section-title { - display: flex; - width: 100%; - position: relative; - top: 0; - padding-bottom: 8px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-style-section .email-settings-section-head, -.email-settings-style-section .email-settings-section-body { - position: static; -} -.email-settings-style-section .email-settings-section-head { margin-top: 8px; } -.email-settings-style-section .email-settings-section-body { margin-top: 12px; } -.email-settings-style-section .email-settings-section-desc { - margin-top: 16px; - top: 0; -} -.email-settings-auto-reply-section .email-settings-section-title, -.email-settings-cleanup-section .email-settings-section-title { - display: flex; - width: 100%; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-cleanup-section .email-settings-section-title { - position: relative; - top: 10px; -} -.email-settings-auto-reply-section .email-settings-section-desc, -.email-settings-cleanup-section .email-settings-section-desc { - margin-top: 10px; -} -.email-settings-display-section .email-settings-section-desc, -.email-settings-cleanup-section .email-settings-section-desc, -.email-settings-inline-images-section .email-settings-section-desc { - margin-top: 12px; - top: 12px; -} -.email-settings-auto-reply-title-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - position: relative; - top: 10px; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-auto-reply-section .email-settings-section-head { - margin-top: 8px; -} -.email-settings-auto-reply-section .email-settings-section-body { - margin-top: 8px; -} -.email-settings-auto-reply-section .email-settings-section-desc { - margin-top: 12px; - top: 0; -} -.email-settings-display-section .email-settings-section-desc { - margin-top: 10px; - top: 10px; -} -.email-settings-auto-reply-section .email-settings-section-body { - margin-top: 14px; -} -.email-settings-auto-reply-title-row .email-settings-section-title { - flex: 1 1 auto; - width: auto; - padding-bottom: 0; - border-bottom: 0; -} -.email-settings-support-copy { - color: color-mix(in srgb, var(--fg) 65%, transparent); - font-size: 11px; - line-height: 1.3; -} -.email-settings-clean-btn > svg { - color: var(--accent, var(--red)); -} -.email-settings-clean-btn { - height: 32px; -} -.email-unsub-rescan-btn { - height: 32px; -} -.email-settings-clean-actions { - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; - gap: 7px; - min-width: 0; - margin-left: auto; -} -.email-settings-clean-status { - display: none; - font-size: 10.5px !important; - flex: 0 1 auto; - width: auto !important; - max-width: min(52vw, 360px); - min-width: 0; - overflow: hidden; - text-align: right; - white-space: nowrap; - text-overflow: ellipsis; - position: relative; - top: 2px; -} -.email-settings-clean-status.is-busy { - order: 0; - align-self: center; - width: auto !important; - max-width: 100%; - margin-right: auto; - text-align: left; -} -.email-settings-clean-btn { - flex: 0 0 auto; -} -.email-settings-default .cookbook-srv-default-icon > svg { - position: relative; - top: 3px; -} -.email-unsub-auto-safe-btn > svg { - position: relative; - top: 2px; -} -.email-unsub-auto-safe-btn { - height: 29px; -} -.email-unsub-status, -.email-unsub-panel-status { - position: relative; - top: 2px; -} -.email-unsub-panel-status { - flex: 1 1 220px; - min-width: 0; - margin-right: auto; - overflow: hidden; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-size: 11px; - line-height: 1.3; - text-align: left; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-unsub-panel-status.is-error { - color: var(--red, #e57373); -} -.email-unsub-card .email-tag { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); -} -.email-unsub-duplicate-badge { - position: relative; - top: 2px; -} -.email-unsub-accent-icon { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-unsub-card { - position: relative; - transition: opacity .16s ease; -} -.email-unsub-card-details { - display: flex; - flex-direction: column; - gap: 7px; -} -.email-unsub-card-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 24px; - height: 24px; - padding: 0; - border: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 62%, transparent); - cursor: pointer; -} -.email-unsub-card-toggle:hover, -.email-unsub-card-toggle:focus-visible { - color: var(--accent, var(--red)); -} -.email-unsub-card-toggle > svg { - transition: transform .14s ease; -} -.email-unsub-done-badge { - display: inline-flex; - align-items: center; - gap: 3px; -} -.email-unsub-done-btn { - display: inline-flex; - align-items: center; - gap: 3px; - flex: 0 0 auto; - padding: 3px 6px; - position: relative; - top: -6px; -} -.email-unsub-card-details .email-unsub-done-btn { - top: 0; -} -.email-unsub-done-btn > svg { - color: var(--accent, var(--red)); -} -.email-unsub-ignore-btn { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 22px; - height: 22px; - padding: 0; - position: absolute; - top: 5px; - right: 6px; - color: color-mix(in srgb, var(--fg) 58%, transparent); -} -.email-unsub-card-details .email-unsub-ignore-btn { - position: static; -} -.email-unsub-ignore-btn:hover { - color: var(--accent, var(--red)); -} -.email-unsub-remaining-actions { - display: inline-flex; - align-items: center; - gap: 7px; - flex-wrap: wrap; -} -.email-unsub-remaining-actions button { - display: inline-flex; - align-items: center; - gap: 4px; - position: relative; - top: -2px; -} -.email-unsub-remaining-actions svg { - flex: 0 0 auto; -} -.email-unsub-link-btn { - position: relative; - top: 2px; -} -.email-unsub-status.is-error { - color: var(--accent, var(--red)) !important; -} -.email-settings-section-desc { - position: relative; - top: 8px; - margin-top: 4px; - font-size: 10.5px; - line-height: 1.25; - color: color-mix(in srgb, var(--fg) 55%, transparent); -} -.email-settings-inline-images-section .email-settings-section-desc { - margin-bottom: 4px; -} -.email-settings-inline-images-spacer { - height: 12px; -} -.email-settings-display-spacer { - height: 10px; -} -.email-settings-display-section .email-settings-section-desc { - margin-top: 10px; - top: 10px; -} -.email-settings-display-section, -.email-settings-inline-images-section { - padding-bottom: 18px; -} -.email-settings-cleanup-section .email-settings-section-desc { - margin-top: 14px; - top: 14px; -} -.email-settings-auto-reply-section .email-settings-section-desc { - margin-top: 12px; - top: 10px; -} -.email-settings-toggle, -.email-settings-check { - display: flex; - align-items: center; - gap: 7px; - font-size: 12px; - line-height: 1.25; -} -.email-settings-toggle { - flex-shrink: 0; -} -.email-settings-enabled-control { - display: inline-flex; - align-items: center; - gap: 7px; - flex-shrink: 0; -} -.email-settings-enabled-state { - font-size: 10px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.email-settings-section.is-enabled .email-settings-enabled-state { - color: var(--accent, var(--red)); -} -.email-settings-section.is-disabled .email-settings-enabled-state { - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.email-settings-display-section.is-enabled .email-settings-enabled-state { - color: var(--accent, var(--red)); -} -.email-settings-check { - min-height: 24px; - padding: 3px 0; -} -.email-settings-check > span:first-child { - min-width: 0; - margin-right: auto; -} -.email-settings-check-spacer { - flex: 1; -} -.email-settings-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 7px; - min-width: 0; -} -.email-settings-grid label, -.email-settings-field { - display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; -} -.email-settings-grid span, -.email-settings-field > span { - font-size: 10px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0; - color: color-mix(in srgb, var(--fg) 56%, transparent); -} -.email-settings-page input[type="date"], -.email-settings-page input[type="datetime-local"], -.email-settings-page select, -.email-settings-page textarea { - width: 100%; - min-width: 0; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.email-settings-page input[type="date"], -.email-settings-page input[type="datetime-local"], -.email-settings-page select { - height: 28px; - padding: 0 7px; -} -.email-settings-page textarea { - resize: vertical; - min-height: 72px; - padding: 7px; - line-height: 1.35; -} -.email-settings-static-value { - min-height: 28px; - display: flex; - align-items: center; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--panel, var(--bg)) 74%, transparent); - color: color-mix(in srgb, var(--fg) 72%, transparent); - font-size: 12px; -} -.email-settings-actions { - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; - gap: 8px; - margin-top: 2px; -} - -/* Email Settings actions use the same quiet outlined treatment everywhere: - the Settings links, the modal settings page, and unsubscribe actions. */ -[data-settings-panel="email"] #set-email-open-integrations, -[data-settings-panel="email"] #set-email-open-tasks, -[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn, -#email-lib-settings-page .email-settings-section button.memory-toolbar-btn { - background: transparent; - border: 1px solid var(--border); - color: var(--fg); - font-family: inherit; - font-weight: 400; -} -[data-settings-panel="email"] #set-email-open-integrations:hover, -[data-settings-panel="email"] #set-email-open-tasks:hover, -[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn:hover, -#email-lib-settings-page .email-settings-section button.memory-toolbar-btn:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); - color: var(--fg); -} -[data-settings-panel="email"] #set-email-open-integrations > svg, -[data-settings-panel="email"] #set-email-open-tasks > svg { - color: var(--accent, var(--red)); - opacity: 1 !important; -} -[data-settings-panel="email"] .email-style-settings-extract, -[data-settings-panel="email"] .email-style-settings-save, -#email-lib-settings-page .email-style-settings-extract, -#email-lib-settings-page .email-style-settings-save { - background: transparent; - border: 1px solid var(--border); - color: var(--fg); - font-weight: 400; -} -[data-settings-panel="email"] .email-style-settings-extract:hover, -[data-settings-panel="email"] .email-style-settings-save:hover, -#email-lib-settings-page .email-style-settings-extract:hover, -#email-lib-settings-page .email-style-settings-save:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); - color: var(--fg); -} -.email-settings-status, -.email-style-settings-status { - margin-right: auto; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.email-settings-status.is-success, -.email-style-settings-status.is-success { - color: var(--color-success, #4caf50); - position: relative; - top: 2px; -} -.email-settings-status.is-error, -.email-style-settings-status.is-error { - color: var(--accent, var(--red)); - position: relative; - top: 2px; -} -.email-settings-save, -.email-style-settings-save { - height: 28px; - min-width: 64px; - justify-content: center; - display: inline-flex; - align-items: center; - gap: 5px; -} -.email-settings-save { - height: 32px; -} -.email-style-settings-extract { - height: 32px; -} -.email-style-settings-save { - height: 32px; -} -.email-settings-save svg, -.email-style-settings-save svg { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-style-settings-extract svg { - color: var(--accent, var(--red)); - position: relative; - top: 2px; -} -.email-settings-task-setting { - color: color-mix(in srgb, var(--fg) 72%, transparent); -} -.email-settings-inline-link { - appearance: none; - border: 0; - padding: 0; - background: transparent; - color: var(--accent, var(--red)); - font: inherit; - text-decoration: underline; - cursor: pointer; -} -.email-style-extract-help { - width: 18px; - height: 18px; - min-width: 18px; - padding: 0; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: color-mix(in srgb, var(--fg) 68%, transparent); - display: inline-flex; - align-items: center; - justify-content: center; - font: inherit; - font-size: 11px; - font-weight: 700; - line-height: 1; - cursor: help; -} -.email-style-extract-help:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); -} -@media (max-width: 420px) { - .email-settings-grid { - grid-template-columns: 1fr; - } -} - - -/* ── Notes ── */ -/* Notes panel — body-level flex sibling of chat-container */ -body.notes-view .chat-container { flex: 1; min-width: 0; } - -/* Mobile: notes panel takes over full screen */ -@media (max-width: 768px) { - body.notes-view .notes-pane { - position: fixed; - inset: 0; - max-width: 100%; - width: 100% !important; - /* Desktop sets height: min(80vh, 820px) which wins over inset:0's bottom:0 - and leaves a gap at the bottom of the viewport. Force full height here. */ - height: 100dvh !important; - max-height: 100dvh !important; - z-index: 170; - /* Bottom-sheet treatment like the document editor: stroked, rounded top, - slides up from the bottom. */ - border: 1px solid var(--border); - border-bottom: none; - border-radius: 14px 14px 0 0; - /* Pane itself never scrolls — its inner .notes-pane-body is the scroller. - Two nested scrollers caused the body's flex:1 to lose its height bound - on Firefox mobile and the last row got clipped off-screen. */ - overflow: hidden; - animation: sheet-enter 0.25s cubic-bezier(0.2, 0.8, 0.2, 1) both; - transform-origin: bottom center; - } - /* Required for the flex:1 body to actually constrain to remaining pane - height instead of expanding to its content (default min-height:auto on - flex items). Without this both grid and list views overflow the viewport - bottom on phones. */ - body.notes-view .notes-pane-body { - min-height: 0; - -webkit-overflow-scrolling: touch; - } - body.notes-view #notes-divider { - display: none; - } - body.notes-view .notes-mobile-grabber { - display: block; - flex-shrink: 0; - height: 18px; - position: relative; - background: var(--bg); - touch-action: none; - cursor: grab; - } - body.notes-view .notes-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; - } - body.notes-view .chat-container { - display: none; - } - body.notes-view .mobile-new-chat-btn, - body.notes-view .hamburger-btn { - display: none !important; - } -} -/* ── Mobile notes UX ──────────────────────────────────── - Tiles become read-only previews on touch ≤768px wide. - Tap opens a fullscreen overlay where editing happens; long-press - toggles drag-to-reorder. Wired up in static/js/notes.js. */ -body.notes-mobile-mode .note-card-corner, -body.notes-mobile-mode .note-card-corner-edit, -body.notes-mobile-mode .note-card-corner-archive, -body.notes-mobile-mode .note-card-corner-copy, -body.notes-mobile-mode .note-card-corner-trash, -body.notes-mobile-mode .note-card-corner-unarchive, -body.notes-mobile-mode .note-card-corner-done, -body.notes-mobile-mode .note-card-edit-corner, -body.notes-mobile-mode .note-card-done, -body.notes-mobile-mode .note-card-actions, -body.notes-mobile-mode .note-cl-quickadd, -body.notes-mobile-mode .note-card .note-checkbox-rm, -/* Panel-fullscreen toggle is redundant on mobile — the panel always - fills the viewport already. */ -body.notes-mobile-mode #notes-fullscreen-toggle { - display: none !important; -} - -/* Header toggle icons (archive view, list/grid switch) — defaults are - tuned for the dense desktop header; on mobile bump them slightly so - they read as tap targets without dominating the header. */ -body.notes-mobile-mode #notes-archive-toggle, -body.notes-mobile-mode #notes-view-toggle { - width: 30px; - height: 30px; - padding: 6px; -} -body.notes-mobile-mode #notes-archive-toggle svg, -body.notes-mobile-mode #notes-view-toggle svg { - width: 17px; - height: 17px; -} -/* The whole card is the tap target on mobile — bigger feedback */ -body.notes-mobile-mode .note-card { - cursor: pointer; - -webkit-tap-highlight-color: transparent; - transition: transform 0.12s ease, box-shadow 0.12s ease; -} - -/* Drag-to-reorder mode — desaturate the grid and sweep a subtle - shimmer across every tile so it's obvious you're in rearrange mode - (the Google Keep "active edit" cue). Also disable scroll inside the - panel so the drag works smoothly. */ -body.notes-drag-mode .notes-pane-body { - overflow: hidden; - touch-action: none; -} body.notes-drag-mode .note-card { cursor: grab; transform: scale(0.985); @@ -6010,3 +3027,1036 @@ body.notes-drag-mode .note-card { align-items: center; gap: 6px; } +.note-cl-dot { + width: 8px; + height: 8px; + border-radius: 50%; + border: 1.5px solid color-mix(in srgb, var(--fg) 30%, transparent); + flex-shrink: 0; + cursor: pointer; + transition: all 0.15s; +} +.note-cl-dot:hover { border-color: var(--fg); transform: scale(1.2); } +.note-cl-row.done .note-cl-dot { + background: var(--accent); + border-color: var(--accent); +} +.note-cl-row.done .note-cl-text { + opacity: 0.4; + background: linear-gradient(currentColor, currentColor) no-repeat; + background-size: 0 1px; + background-position: 0 calc(50% - 1px); + animation: cl-strike 0.32s ease-out forwards; + transition: opacity 0.2s ease; +} +/* Draws the strikethrough line left-to-right when the row is marked + done, instead of snapping it in full-width on the same frame. */ +@keyframes cl-strike { + to { background-size: 100% 1px; } +} +.note-cl-text { + background: transparent; + border: none; + border-bottom: 1px solid var(--border); + color: var(--fg); + font-size: 12px; + padding: 3px 0 3px 3px; + flex: 1 1 auto; + width: auto; + min-width: 0; + outline: none; +} +.note-cl-text:focus { border-color: var(--fg); } +.note-cl-rm { + background: none; + border: none; + color: var(--fg); + opacity: 0.3; + cursor: pointer; + font-size: 14px; + padding: 0; + width: 24px; + height: 14px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: flex-end; + line-height: 1; +} +.note-cl-rm:hover { opacity: 0.8; color: var(--red); } +.note-cl-add { + background: none; + border: none; + color: var(--fg); + opacity: 0.4; + cursor: pointer; + font-size: 11px; + padding: 4px 0; + text-align: left; +} + +/* ── Calendar ── */ +.cal-modal-content { width:min(680px, 94vw); max-height:88vh; overflow-x:hidden; position:relative; } +#calendar-modal .cal-modal-content { animation: none; } +/* Was overflow-y:auto with grid + day-detail flowing naturally. Now uses a + true splitter layout: the body is a flex column that doesn't scroll + itself; the grid takes the remaining space and scrolls if needed; the + day-detail has an explicit height (driven by the splitter drag via + --cal-detail-h) and shrinks the grid visually as it grows. */ +#cal-body { display:flex; flex-direction:column; gap:8px; overflow:hidden; padding:0; min-height:0; } +#cal-body > .cal-grid { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overflow-x: hidden; } +@media (max-width: 768px) { + /* Let the calendar pane shrink to nothing on mobile so the day-detail + splitter can be dragged all the way to the top, hiding the calendar + entirely. Without min-height:0 the grid (or the week-wrap below) + refuses to shrink past its built-in floor. */ + #cal-body > .cal-grid, + #cal-body > .cal-wk-wrap { + min-height: 0; + max-height: none; + } + /* Let the week grid fill the calendar pane and scroll its hours internally + instead of overflowing #cal-body (which clips it and feels cramped). */ + #cal-body > .cal-wk-wrap { + flex: 1 1 auto; + overflow: auto; + } +} +.cal-toolbar { display:flex; align-items:center; gap:6px; margin-bottom:7px; flex-wrap:wrap; line-height:1; max-width:100%; row-gap:6px; } + +/* Quick-add bar: natural-language event creation. Sits directly under + the toolbar; focused with `Q`; opens the bespoke form pre-filled. */ +.cal-quickadd-row { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 8px; + padding: 1px 10px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + transition: border-color 0.15s, background 0.15s; + position: relative; +} +/* Two-tone placeholder hint: "Quick add" in accent, the example in grey, both + at one low opacity. Overlays the (empty) input; hidden once the user types. */ +.cal-quickadd-hint { + position: absolute; + left: 10px; + right: 10px; + top: 50%; + transform: translateY(-50%); + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--fg); + opacity: 0.5; + pointer-events: none; + white-space: nowrap; + overflow: hidden; +} +.cal-quickadd-hint .qa-hint-accent { color: var(--accent, var(--red)); } +/* ↵ enter glyph in the hint — makes it obvious the field submits on Enter. */ +.cal-quickadd-hint .qa-hint-enter { margin-left: auto; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.8; } +/* Cycling example text fades out before swap, then fades back in. */ +.cal-quickadd-hint .qa-hint-example { + display: block; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-style: italic; + transition: opacity 0.18s ease; +} +.cal-quickadd-hint .qa-hint-example.qa-hint-fade { opacity: 0; } +.cal-title-hint { + position: absolute; + left: 0; + top: 50%; + transform: translateY(calc(-50% - 2px)); + display: none; + align-items: center; + gap: 7px; + font-size: 18px; + line-height: 1.2; + white-space: nowrap; + pointer-events: none; + color: color-mix(in srgb, var(--fg) 42%, transparent); +} +.cal-hero-title:placeholder-shown ~ .cal-title-hint { display: inline-flex; } +.cal-title-hint .cal-title-enter-ico { color: var(--accent, var(--red)); flex-shrink: 0; } +/* Pure-CSS toggle: hide the hint as soon as the field has real text (no + per-keystroke JS, so typing never triggers a re-render / focus loss). */ +.cal-quickadd-input:not(:placeholder-shown) ~ .cal-quickadd-hint { display: none; } +.cal-quickadd-row:focus-within { + border-color: color-mix(in srgb, var(--accent, var(--fg)) 55%, var(--border)); + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); +} +.cal-quickadd-icon { + color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent); + display: inline-flex; + align-items: center; + flex-shrink: 0; + opacity: 0.7; +} +.cal-quickadd-input { + flex: 1; + min-width: 0; + background: transparent; + border: none; + outline: none; + color: var(--fg); + font-family: inherit; + font-size: 12px; + height: 22px; + padding: 0; +} +.cal-quickadd-status { + font-size: 10px; + opacity: 0.55; + font-variant-numeric: tabular-nums; +} +.cal-toolbar > *, .cal-toolbar-nav > *, .cal-toolbar-right > * { vertical-align:middle; } +.cal-toolbar-nav { display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; } +.cal-toolbar-right { + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 6px; + flex: 1 1 100%; + width: 100%; + margin-left: 0; + margin-top: 6px; + flex-wrap: wrap; +} +.cal-title { font-size:13px; font-weight:600; white-space:nowrap; height:24px; line-height:30px; padding:0 6px; display:inline-block; vertical-align:middle; box-sizing:border-box; } +.cal-toolbar-clock { + margin-left: 8px; + color: var(--accent, var(--red)); + font-size: 11px; + font-weight: 600; + font-variant-numeric: tabular-nums; +} +button.cal-nav { background:color-mix(in srgb, var(--fg) 6%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 8px; height:24px; line-height:22px; cursor:pointer; font-size:11px; font-family:inherit; box-sizing:border-box; vertical-align:middle; } +button.cal-nav:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); } +button.cal-nav.cal-toolbar-arrow { + display: none; + align-items: center; + justify-content: center; + width: 24px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + border-radius: 50%; + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + font-size: 18px; + line-height: 1; + opacity: 0.84; +} +.cal-toolbar-arrow-glyph { + position: relative; + top: -1px; + line-height: 1; +} +@media (max-width: 768px) { + button.cal-nav.cal-toolbar-arrow { display: inline-flex !important; } + .cal-toolbar-nav { + flex: 1 1 100%; + width: 100%; + min-width: 0; + } + #cal-next { margin-left: auto; } +} +.cal-week-start-toggle { + display: inline-flex; + align-items: center; + gap: 0; + width: max-content; + padding: 0; + height: 24px; + box-sizing: border-box; + border: 1px solid var(--border); + border-radius: 5px; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.cal-week-start-btn { + box-sizing: border-box; + height: 22px !important; + min-height: 22px !important; + margin-top: 0 !important; + padding: 0 7px; + border: 1px solid transparent; + border-radius: 4px; + background: transparent; + color: color-mix(in srgb, var(--fg) 68%, transparent); + cursor: pointer; + font-family: inherit; + font-size: 10px; + line-height: 20px; + transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; +} +.cal-settings-actions { + display: flex; + justify-content: flex-end; + width: 100%; +} +#cal-settings-panel .cal-week-start-toggle { + display: flex; + margin-left: auto; +} +.cal-settings-sync-status { + position: relative; + top: 2px; + font-size: 11px; + opacity: 0.6; +} +.cal-settings-sync-status.is-error { + color: var(--red); + opacity: 1; +} +.cal-week-start-btn:hover { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.cal-week-start-btn.active, +.cal-week-start-btn.active:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); +} +.cal-side-nav { + position: absolute; + top: var(--cal-side-nav-y, 50%); + transform: translateY(-50%); + width: 32px; + height: 32px; + border-radius: 50%; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + cursor: pointer; + z-index: 6; + opacity: 0; + pointer-events: none; + font-family: inherit; + font-size: 24px; + line-height: 28px; + padding: 0 0 3px; + transition: opacity 0.12s ease, background 0.12s ease, transform 0.12s ease; +} +.cal-modal-content:hover .cal-side-nav, +.cal-side-nav:focus-visible { + opacity: 0.84; + pointer-events: auto; +} +.cal-side-nav:hover { + opacity: 1; + background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); + transform: translateY(-50%) scale(1.04); +} +button.cal-today-btn { font-size:10px; opacity:0.5; } +button.cal-add-btn { background:var(--accent); color:#fff; border:none; border-radius:50%; width:24px; height:24px; line-height:22px; font-size:18px; cursor:pointer; flex-shrink:0; padding:0; box-sizing:border-box; font-family:inherit; vertical-align:middle; text-align:center; } +button.cal-add-btn.cal-add-btn-text { + width:auto; min-width:0; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 12px; + padding: 0 10px 0 6px; + display: inline-flex; + align-items: center; + gap: 4px; + height: 24px; + line-height: 1; + margin-top: 0; + transition: background 0.15s, border-color 0.15s; +} +button.cal-add-btn.cal-add-btn-text:hover { + background: color-mix(in srgb, var(--fg) 14%, transparent); + border-color: var(--accent); + opacity: 1; +} +.cal-add-plus { + font-size: 16px; + line-height: 1; + color: var(--accent, var(--red)); + font-weight: 500; + display: inline-block; + transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + padding-bottom: 2px; +} +button.cal-add-btn.cal-add-btn-text:hover .cal-add-plus { + transform: rotate(180deg); +} +.cal-add-label { font-size:11px; font-weight:600; line-height:1; opacity:0.85; } +/* Small variant of the toolbar's +New button. Inherits the .cal-add-btn-text + styles (pill with "+" + "New" + spring-rotate on the +), so the + animation is identical. Just shrunk a bit. */ +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { + height: 20px; + border-radius: 10px; + /* At rest: tight, equal padding around the "+" so it sits centred + with no trailing gap. On hover the right pad and the flex gap grow + to make room for the revealed "New". */ + padding: 0 5px; + gap: 0; + transition: padding 0.25s ease, gap 0.25s ease, background 0.15s, border-color 0.15s; +} +/* Don't touch .cal-add-plus on the small variant — the toolbar +New is + the reference and we want the same rotation centre / motion. The label + is shrunk and hidden until hover so the pill is compact at rest. */ +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { + font-size: 10px; + max-width: 0; + margin-left: 0; + opacity: 0; + overflow: hidden; + white-space: nowrap; + transition: max-width 0.25s ease, opacity 0.18s ease, margin-left 0.25s ease; +} +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover .cal-add-label { + max-width: 40px; + margin-left: 3px; + opacity: 0.85; +} +.cal-add-btn:hover { opacity:0.85; } + +/* Mobile: bump the +New pill and the +/- zoom buttons up to a tap-friendly + size so they're easy to hit with a thumb. */ +@media (max-width: 768px) { + /* Toolbar +New (relocated by JS into the quickadd row on mobile) */ + button.cal-add-btn.cal-add-btn-text { + height: 36px; + border-radius: 18px; + padding: 0 14px 0 12px; + gap: 6px; + flex-shrink: 0; + } + /* Day-detail +New: just a "+" on mobile — drop the label entirely. */ + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { + width: 28px; + height: 28px; + border-radius: 50%; + padding: 0; + gap: 0; + justify-content: center; + } + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { + display: none; + } + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { + font-size: 18px; + padding-bottom: 0; + } + .cal-add-plus { font-size: 22px; padding-bottom: 1px; } + .cal-add-label { font-size: 13px; } + .cal-wk-zoom { + width: 36px; + height: 36px; + font-size: 20px; + border-radius: 8px; + opacity: 0.85; + } + /* The +New button sits as a sibling of the quickadd row inside a flex + wrapper, so it's clearly outside the input's bordered box. The row + itself keeps its own styling. */ + .cal-quickadd-wrap { + display: flex; + align-items: stretch; + gap: 8px; + margin: 0 0 8px; + } + .cal-quickadd-wrap > #cal-quickadd-row { + flex: 1; + min-width: 0; + margin: 0; + } + .cal-quickadd-wrap > .cal-add-btn-text { + align-self: stretch; + flex-shrink: 0; + } + /* Nudge the "+" glyph up one pixel on the round day-detail button so it + sits visually centred inside the circle. */ + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { + transform: translateY(-1px); + } + /* Event form: bigger Create / Cancel buttons so they're easy to tap. */ + .cal-form-actions button.cal-btn { + height: 44px; + padding: 0 20px; + font-size: 14px; + border-radius: 8px; + min-width: 96px; + } + /* Left-align the day/month + tags row in the day-detail panel and + the calendar/category chip row so they sit flush against the + left edge on a narrow screen. */ + .cal-detail-header { + justify-content: flex-start; + gap: 12px; + } + .cal-filters { + justify-content: flex-start; + } +} + +/* Refresh button spin — driven by JS toggling .cal-syncing on the button. + transform-box + transform-origin keep the rotation pivoted at the SVG's + own centre so it spins in place instead of orbiting / drifting up. */ +#cal-sync svg, +#email-lib-refresh-btn svg { display: block; transform-box: fill-box; transform-origin: 50% 50%; } +/* Brief checkmark flash after a refresh completes. Lasts ~900ms in JS; + the small bounce + accent color give a clear "done" cue. */ +#cal-sync.cal-sync-done svg, +#email-lib-refresh-btn.email-lib-refresh-done svg { + color: var(--accent, #2ea043); + animation: refresh-check-pop 0.32s ease-out; +} +@keyframes refresh-check-pop { + 0% { transform: scale(0.55); opacity: 0.4; } + 60% { transform: scale(1.18); opacity: 1; } + 100% { transform: scale(1); opacity: 1; } +} +#cal-sync.cal-syncing svg, +#email-lib-refresh-btn.email-lib-refreshing svg { animation: spin 0.6s linear infinite; } +.email-undone-toggle.active, +.email-attach-toggle.active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); + color: var(--accent, var(--red)); + font-weight: 600; +} +/* Hide the gallery-style reader-btn labels on desktop — only the mobile + @media block re-displays them under each icon. Desktop keeps compact + icon-only reader buttons. */ +.reader-btn-label { + display: inline-block; + font-size: 9px; + font-weight: 500; + line-height: 1; + letter-spacing: 0.02em; + white-space: nowrap; + opacity: 0.85; +} + +/* Inline attachment-filter toggle nested inside the search input. The + input has padding-right:34px set inline so its text doesn't slide + under the button. */ +.email-search-wrap { display: flex; align-items: center; } +/* Keep the email search input and the adjacent "Select" button identical in + height regardless of base-vs-mobile overrides — pin both explicitly. */ +.email-search-row .memory-search-input { + height: 32px; + min-height: 32px; + box-sizing: border-box; +} +.email-search-row .email-search-select-btn { + height: 32px; + min-height: 32px; + box-sizing: border-box; + margin-top: 6px; + flex-shrink: 0; +} +/* Select moved into the dropdown row — dock it to the right and match the + selects' vertical nudge (.memory-sort-select has top:3px). */ +.memory-category-filters .email-filter-select-btn, +.memory-category-filters .email-filter-refresh-btn { + position: relative; + top: 3px; + flex-shrink: 0; +} +.memory-category-filters .email-filter-refresh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 8px; +} +.memory-category-filters .email-reminders-clear-btn { + position: relative; + top: -2px; + flex-shrink: 0; +} +.email-attach-toggle-inline { + position: absolute !important; + right: 4px !important; + top: 50%; + transform: translateY(calc(-50% - 5px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-attach-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-attach-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +/* Inline undone-toggle nested inside the search input, sits LEFT of the + attach toggle. Same visual treatment. */ +.email-undone-toggle-inline { + position: absolute !important; + right: 34px !important; + top: 50%; + transform: translateY(calc(-50% - 3px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-reminder-toggle-inline { + position: absolute !important; + right: 64px !important; + top: 50%; + transform: translateY(calc(-50% - 3px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-reminder-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-reminder-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +.email-undone-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-undone-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +/* Round (circular) toggles in the search bar, matching the app's default icon + buttons. Always fully visible (not dimmed until hover). */ +.email-attach-toggle-inline, +.email-undone-toggle-inline, +.email-reminder-toggle-inline { border-radius: 50% !important; opacity: 1 !important; } +.email-search-options-btn { + position: absolute !important; + right: 5px !important; + top: 50% !important; + transform: translateY(calc(-50% - 5px)); + width: 26px !important; + height: 26px !important; + min-width: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 999px !important; + background: transparent !important; + border: 0 !important; + color: inherit !important; + opacity: 0.7; + z-index: 2; +} +.email-search-options-btn:hover, +.email-search-options-btn.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +.email-search-options-menu { + position: absolute; + top: calc(100% + 2px); + right: 0; + z-index: 75; + min-width: 140px; + padding: 4px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--panel, var(--bg)); + box-shadow: 0 8px 24px rgba(0,0,0,0.3); +} +.email-search-options-title { + padding: 5px 9px 4px; + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + opacity: 0.65; + white-space: nowrap; +} +.email-search-options-menu button { + width: 100%; + border: 0; + background: transparent; + color: var(--fg); + display: flex; + align-items: center; + gap: 10px; + height: auto; + min-height: 0; + padding: 6px 8px; + border-radius: 8px; + font-family: inherit; + font-size: 11px; + font-weight: 400; + line-height: normal; + letter-spacing: normal; + text-align: left; + cursor: pointer; +} +.email-search-options-menu button:hover, +.email-search-options-menu button.active { + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + color: var(--accent, var(--red)); +} +.email-search-option-icon { + width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + opacity: 0.5; +} +.email-search-option-icon svg { width: 14px; height: 14px; } +.email-search-date-panel { + width: 220px; + padding: 6px 7px 5px; + border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); +} +.email-search-date-panel input { + width: 100%; + height: 26px; + box-sizing: border-box; + padding: 2px 6px; + border: 1px solid var(--border); + border-radius: 5px; + outline: none; + background: var(--bg); + color: var(--fg); + font: inherit; +} +.email-search-date-panel input:focus { border-color: var(--accent, var(--red)); } +.email-search-date-actions { display: flex; justify-content: flex-end; gap: 5px; } +.email-search-options-menu .email-search-date-actions [data-email-date-apply] { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +@media (max-width: 768px) { + .email-search-options-menu button { + min-height: 44px; + padding: 12px 12px !important; + font-size: 14px !important; + gap: 10px !important; + } + .email-search-options-menu .email-search-option-icon { + width: 18px; + height: 18px; + } + .email-search-options-menu .email-search-option-icon svg { + width: 16px; + height: 16px; + } +} +/* Mobile: enlarge the icons inside the inline search-bar toggles + (done / attachment / reminders) — buttons themselves stay the same, + only the SVG glyph scales up so it's tappable + visible. */ +@media (max-width: 768px) { + .email-filter-btn { + height: 30px; + min-height: 30px; + font-size: 12px; + top: -6px; + padding: 0 9px; + } + .email-folder-btn { + top: -4px; + } + + #email-lib-folder, + .memory-category-filters .email-filter-select-btn, + .memory-category-filters .email-filter-refresh-btn, + .memory-category-filters .email-filter-settings-btn { + height: 30px !important; + min-height: 30px !important; + box-sizing: border-box; + } + + .memory-category-filters .email-filter-refresh-btn, + .memory-category-filters .email-filter-settings-btn { + width: 30px; + padding: 0 !important; + } + .memory-category-filters .email-filter-settings-btn > svg { + width: 15px; + height: 15px; + } + + .email-search-row .memory-search-input { + height: 38px; + min-height: 38px; + } + + #email-lib-chip-bar { + padding-right: 42px !important; + padding-left: 30px !important; + gap: 5px !important; + } + + .email-lib-chip-bar-icon { + width: 14px !important; + height: 14px !important; + left: 9px !important; + } + + #email-lib-search { + font-size: 14px !important; + min-width: 88px !important; + top: 0 !important; + } + + #email-lib-search::placeholder { + font-size: 14px !important; + } + + #email-lib-chip-bar.has-email-lib-pills #email-lib-search { + min-width: 24px !important; + flex: 1 1 24px !important; + } + + .email-lib-pill { + min-height: 24px !important; + height: 24px !important; + line-height: 24px !important; + font-size: 12px !important; + padding: 0 6px 0 8px !important; + max-width: 180px !important; + } + + .email-lib-pill svg { + width: 14px !important; + height: 14px !important; + } + + .email-lib-pill-x { + font-size: 13px !important; + top: -4.5px !important; + } + + .email-lib-filter-pill { + min-height: 24px !important; + height: 24px !important; + line-height: 24px !important; + gap: 4px !important; + padding: 0 6px 0 8px !important; + } + + .email-lib-filter-pill .email-lib-pill-icon { + width: 17px !important; + height: 17px !important; + } + + .email-lib-filter-pill svg { + width: 18px !important; + height: 18px !important; + } + + .email-lib-filter-pill .email-lib-pill-x { + font-size: 13px !important; + top: -4.5px !important; + } + + .email-tags-toggle-inline { + right: 6px; + height: 28px !important; + min-width: 42px; + padding: 0 8px !important; + font-size: 11px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-attach-toggle-inline { + right: 56px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-undone-toggle-inline { + right: 92px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-reminder-toggle-inline { + right: 128px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-attach-toggle-inline svg, + .email-undone-toggle-inline svg, + .email-reminder-toggle-inline svg, + .email-filter-refresh-btn svg { + width: 16px !important; + height: 16px !important; + } +} +.email-accounts-row { + display: flex; + align-items: center; + gap: 10px; + padding: 0 0 6px; +} +.email-accounts-row .doclib-desc { flex-shrink: 0; } +/* Only the direct-child compose button gets pushed right; nested chips + inside #email-lib-accounts pack to the left as normal flex items. */ +.email-accounts-row > .memory-toolbar-btn { flex-shrink: 0; margin-left: auto; } +.email-account-chip { + display: inline-flex; + align-items: center; + gap: 5px; +} +.email-account-chip.email-account-has-unread { + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + color: color-mix(in srgb, var(--accent, var(--red)) 86%, var(--fg)); +} +.email-account-unread-dot { + width: 6px; + height: 6px; + border-radius: 999px; + background: var(--accent, var(--red)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + flex: 0 0 auto; +} +.email-account-chip-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} +.email-account-unread-count { + font-size: 9px; + line-height: 1; + font-weight: 700; + color: var(--accent, var(--red)); + opacity: 0.9; + position: relative; + top: 0; +} +.email-account-away-label { + color: var(--accent, var(--red)); + font-size: 9px; + font-weight: 700; + line-height: 1; + flex: 0 0 auto; +} +.email-accounts-loading-whirlpool { + width: 14px; + height: 14px; + margin: 3px 4px 0 1px; + display: inline-flex; + flex: 0 0 auto; +} +.email-accounts-loading-label { + font-size: 10px; + opacity: 0.55; + position: relative; + top: 2px; + white-space: nowrap; +} +.email-loading-with-label { + /* Fill the email list grid so the spinner lands in the middle of the + email window rather than the top of the empty list section. */ + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + text-align: center; + pointer-events: none; +} +.date-section-header { + display: flex; + align-items: center; + gap: 7px; + padding: 8px 9px 4px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 10px; + font-weight: 700; + letter-spacing: 0; + line-height: 1.2; + text-transform: uppercase; + user-select: none; +} +.date-section-header::after { + content: ''; + flex: 1 1 auto; + height: 1px; + background: color-mix(in srgb, var(--border) 72%, transparent); +} +#session-list .date-section-header { + padding-left: 10px; + padding-right: 8px; + cursor: pointer; +} +#session-list .date-section-header.collapsed { + color: color-mix(in srgb, var(--fg) 36%, transparent); +} +#session-list .date-section-header.collapsed::after { + opacity: 0.45; +} +#session-list .date-section-header:focus-visible { + outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); + outline-offset: 2px; + border-radius: 5px; +} +/* Bootstrap row shown while the session list hydrates, and on load failure. + Reads as a normal list row but is not selectable. */ +.session-list-bootstrap { + cursor: default; + pointer-events: none; +} +#email-lib-grid .date-section-header { + padding: 10px 5px 3px; +} +#email-lib-modal .email-lib-sync-status { + flex: 0 0 auto; + min-height: 14px; + padding: 1px 4px 0; + font-size: 10.5px; + line-height: 1.2; + text-align: center; + opacity: 0.58; + color: var(--muted-text, currentColor); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + top: 4px; +} diff --git a/static/css/16-calendar.css b/static/css/16-calendar.css new file mode 100644 index 000000000..d4112f705 --- /dev/null +++ b/static/css/16-calendar.css @@ -0,0 +1,1175 @@ +/* 16-calendar - 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. + */ +@media (max-width: 768px) { + #email-lib-modal .email-lib-sync-status { + min-height: 13px; + padding-right: 5px; + font-size: 10px; + } +} +/* Nudge the subject + sender/date meta line 4px right inside each + email card so they don't sit flush against the read/unread/done + markers on the left. Includes #email-lib-modal selector to + override the expanded card's `padding: 4px 0 6px` shorthand + which was zeroing out the title's padding-left. */ +#email-lib-grid .email-card-titlerow, +#email-lib-grid .memory-item-meta, +#email-lib-modal .doclib-card-expanded .email-card-titlerow { + padding-left: 4px; +} +.email-loading-label { + font-size: 11px; + opacity: 0.6; +} + +/* Refresh button now lives top-right in the modal header next to the close X. + Borderless (matches the close X), and a fixed square box so the spin and the + refresh→checkmark icon swap never change its size or nudge neighbours. */ +.email-lib-header-actions #email-lib-refresh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + border: none; + background: none; + padding: 0; + width: 24px; + height: 24px; +} +/* Bump the icon up on mobile for a bigger visual — but keep the button BOX at + 24px so it never exceeds the header's natural height (the title/close set + that). A taller box would grow the sticky header and push the list down. */ +@media (max-width: 768px) { + .email-lib-header-actions #email-lib-refresh-btn svg { + width: 17px; + height: 17px; + } +} +.cal-view-toggle { + display: inline-flex; + align-items: stretch; + border: 1px solid var(--border); + border-radius: 5px; + overflow: hidden; + vertical-align: middle; + box-sizing: border-box; + padding: 0; + margin: 0; + line-height: 1; +} +.cal-view-btn + .cal-view-btn { border-left: 1px solid var(--border); } +.cal-view-btn:hover { opacity: 0.75; } +.cal-view-btn.active { + background: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--accent, var(--red)); + opacity: 1; + font-weight: 600; +} +/* Toolbar holds only the toggle button; the chip row renders below the + toolbar on its own line and wraps freely. */ +.cal-filters { + display:flex; + gap:6px; + margin:6px 0 8px; + flex-wrap:wrap; + padding:6px 8px; + border:1px solid color-mix(in srgb, var(--fg) 8%, transparent); + border-radius:8px; + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 4%, transparent), transparent), + color-mix(in srgb, var(--fg) 2.5%, var(--bg)); + box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 4%, transparent); +} +/* Round the native color-input swatch in calendar settings. The outer + wraps a colored fill drawn by the browser; we need to round both + the wrapper and the fill so it actually appears circular. */ +.cal-s-color { border-radius: 50%; overflow: hidden; } +.cal-filter-item { display:inline-flex; align-items:center; gap:4px; cursor:pointer; font-size:10px; padding:2px 8px; line-height:1.4; border-radius:10px; background:color-mix(in srgb, var(--bg) 72%, transparent); border:1px solid color-mix(in srgb, var(--fg) 12%, transparent); transition:opacity 0.15s, background 0.15s, border-color 0.15s, transform 0.15s; } +.cal-filter-item:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); } +.cal-filter-item.cal-filter-important { font-weight:700; color:#e5a33a; border-color:color-mix(in srgb, #e5a33a 40%, transparent); } +/* Match the toolbar's other buttons (cal-nav, cal-view-btn, cal-add-btn-text): + 24px tall, 11px font, 5px corners. */ +.cal-filter-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font-size: 11px; + font-family: inherit; + font-weight: 500; + padding: 0 10px; + height: 24px; + line-height: 1; + border-radius: 5px; + box-sizing: border-box; + vertical-align: middle; + opacity: 0.65; + position: relative; + top: -3px; +} +.cal-filter-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } +.cal-filter-toggle-glyph { + display: inline-block; + position: relative; + top: 0; + margin-right: 3px; + color: var(--accent, var(--red)); +} +.cal-filter-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; } +/* The grid is now a flex column: one row of weekday headers + six + .cal-week-row rows. Each row holds the 7 day cells in its own + internal grid plus the absolute-positioned multi-day overlays. + --bars on a week-row is the number of multi-day events overlapping + it; cells in that row use it to reserve top padding so the bar + overlays don't cover the day numbers or single-event rows. */ +.cal-grid { display:flex; flex-direction:column; gap:1px; background:color-mix(in srgb, var(--fg) 8%, transparent); border-radius:6px; overflow:hidden; } +.cal-week-headers { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } +.cal-week-row { position:relative; display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } +.cal-weekday { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); text-align:center; font-size:10px; font-weight:600; opacity:0.4; padding:5px 0; } +.cal-day { + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 34px), + var(--bg); + min-height:78px; + padding:3px; + cursor:pointer; + position:relative; + transition:background 0.12s, box-shadow 0.12s; + overflow:hidden; +} +.cal-day:hover { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); } +.cal-day.cal-today { + box-shadow: inset 0 0 0 2px var(--accent, var(--red)); + background: var(--bg); + border-radius: 8px; +} +.cal-day.cal-selected:not(.cal-today) .cal-day-num { + color: var(--accent, var(--fg)); + opacity: 1; + font-weight: 700; +} +.cal-day.cal-drag-over { background:color-mix(in srgb, var(--accent) 18%, var(--bg)); } +.cal-day.cal-range-select { background:color-mix(in srgb, var(--accent, var(--red)) 25%, var(--bg)); box-shadow:inset 0 0 0 1px var(--accent, var(--red)); } +.cal-day-num { font-size:10px; font-weight:600; display:block; margin-bottom:1px; opacity:0.7; line-height:1.2; } +.cal-dots { display:flex; flex-direction:row; align-items:center; gap:2px; flex-wrap:nowrap; margin-bottom:1px; height:6px; } +.cal-dot { width:5px; height:5px; border-radius:50%; display:inline-block; cursor:grab; flex-shrink:0; } +.cal-dot-more { font-size:7px; opacity:0.4; } +.cal-event-row { + display:flex; + align-items:center; + gap:4px; + padding:2px 4px; + border-radius:4px; + cursor:grab; + line-height:1.2; + margin-bottom:2px; + min-height:17px; + background:color-mix(in srgb, var(--fg) 5%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); + animation:cal-event-enter 0.16s ease-out both; + transition:background 0.15s, box-shadow 0.15s, transform 0.15s; +} +.cal-event-row:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); transform:translateY(-1px); } +.cal-event-row-dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } +.cal-event-row-time { font-size:10px; opacity:0.72; font-variant-numeric:tabular-nums; flex-shrink:0; font-weight:600; } +.cal-event-row-name { font-size:10px; opacity:0.96; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; font-weight:600; } +.cal-event-reminder { + display: inline-flex; + width: 12px; + height: 12px; + margin-left: 5px; + vertical-align: -2px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + opacity: 0.9; + flex: 0 0 auto; +} +.cal-event-reminder svg { + width: 100%; + height: 100%; + display: block; +} +.cal-event-row .cal-event-reminder { + width: 10px; + height: 10px; + margin-left: 4px; +} +.cal-event-source { + display:inline-flex; + align-items:center; + justify-content:center; + width:12px; + height:12px; + margin-left:5px; + border-radius:50%; + font-size:7px; + font-weight:800; + line-height:1; + color:var(--cal-event-fg, var(--fg)); + background:color-mix(in srgb, currentColor 16%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); + vertical-align:1px; +} +.cal-event-source svg { + width: 10px; + height: 10px; + display: block; +} +.cal-event-row .cal-event-source { + width:11px; + height:11px; + font-size:7px; + margin-left:4px; + color:color-mix(in srgb, var(--fg) 72%, transparent); +} +.cal-multiday .cal-event-reminder, +.cal-wk-allday-event .cal-event-reminder { + color: var(--cal-event-fg, currentColor); + opacity: 0.88; +} +.cal-multiday .cal-event-source, +.cal-wk-allday-event .cal-event-source { + color: var(--cal-event-fg, currentColor); +} +.cal-event-more { font-size:9px; opacity:0.55; padding-left:6px; } +/* Multi-day bar — positioned as an absolute overlay inside its + .cal-week-row so it spans uninterrupted across every column it + covers. --col is the starting column (0–6), --span is the number + of days it covers within the row, --slot stacks parallel bars. */ +.cal-multiday { + position: absolute; + /* Fractional offsets let timed events that cross midnight render at + true proportional width. start-frac shifts the left edge into the + first day; end-frac trims the right edge inside the last day. + All-day events default to (0, 1) and still fill whole columns. */ + left: calc((var(--col, 0) + var(--start-frac, 0)) * (100% / 7)); + width: calc( + (var(--span, 1) - var(--start-frac, 0) - (1 - var(--end-frac, 1))) + * (100% / 7) + ); + top: calc(2px + var(--slot, 0) * 12px); + height: 12px; + font-size: 9px; + line-height: 12px; + padding: 0 5px; + border-radius: 4px; + color: var(--cal-event-fg, #fff); + font-weight: 700; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: grab; + opacity: 0.98; + z-index: 2; + pointer-events: auto; + box-shadow: 0 1px 2px color-mix(in srgb, #000 18%, transparent); +} +/* Splitter between the month/week grid and the day-tasks panel — drag + vertically to give the day panel more room. Height clamped by the + `--cal-detail-h` variable set on #cal-body by the splitter drag JS. */ +.cal-splitter { + height: 8px; + margin: 6px -10px 0; + cursor: row-resize; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + user-select: none; + touch-action: none; +} +/* Mobile: fat hitbox so the splitter is easy to grab with a thumb, + without changing how the grip itself looks. */ +@media (max-width: 768px) { + .cal-splitter { + height: 24px; + margin-top: 0; + margin-bottom: 0; + } + .cal-splitter-grip { + width: 56px; + height: 4px; + } +} +.cal-splitter-grip { + width: 42px; + height: 3px; + border-radius: 2px; + background: color-mix(in srgb, var(--fg) 25%, transparent); + transition: background 0.12s; +} +.cal-splitter:hover .cal-splitter-grip, +.cal-splitter-dragging .cal-splitter-grip { + background: var(--accent, var(--red)); +} +.cal-day-detail { + margin-top: 4px; + border-top: 1px solid var(--border); + padding-top: 8px; + height: var(--cal-detail-h, 240px); + overflow-y: auto; + overscroll-behavior: contain; + flex-shrink: 0; +} +.cal-detail-header { display:flex; justify-content:space-between; align-items:center; font-size:12px; font-weight:600; margin-bottom:6px; padding-right:8px; } +.cal-empty { font-size:11px; opacity:0.3; padding:4px 0; } +.cal-event-item { display:flex; gap:9px; padding:8px 9px; border-radius:6px; cursor:pointer; align-items:flex-start; background:color-mix(in srgb, var(--fg) 3.5%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); margin-bottom:4px; } +.cal-event-item:hover { background:color-mix(in srgb, var(--fg) 8%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); } +.cal-event-dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:4px; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } +.cal-event-info { flex:1; min-width:0; } +.cal-event-name { font-size:13px; font-weight:700; line-height:1.25; color:color-mix(in srgb, var(--fg) 96%, transparent); overflow-wrap:anywhere; } +.cal-event-tag { + display: inline-block; + font-size: 10px; + font-weight: 500; + padding: 1px 6px; + border-radius: 8px; + border: 1px solid; + margin-left: 6px; + vertical-align: 1px; + opacity: 0.85; + white-space: nowrap; +} +.cal-event-time { font-size:11px; opacity:0.68; line-height:1.35; margin-top:2px; font-weight:500; } +.cal-event-loc { font-size:11px; opacity:0.58; line-height:1.35; margin-top:1px; } +/* ── Week view: hour grid ────────────────────────────────────────── + Vertical hour rail on the left, 7 day columns on the right with their + own all-day strip and an absolute-positioned hour grid below it. */ +.cal-wk-wrap { + display: flex; + flex-direction: row; + gap: 0; + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 3%, transparent), transparent 120px), + var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + /* The wrap itself scrolls so the hour rail and day columns move + together; column headers and the rail spacer are sticky-pinned. */ + overflow: auto; + max-height: calc(100vh - 220px); + min-height: 360px; + position: relative; +} +.cal-wk-now-below-hint { + position: absolute; + height: 2px; + border-radius: 2px 2px 0 0; + background: var(--accent, var(--red)); + pointer-events: none; + z-index: 38; +} +@media (max-width: 768px) { + .cal-wk-wrap { + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + } + .cal-wk-wrap > .cal-wk-rail, + .cal-wk-wrap > .cal-wk-cols { + transform: translateY(var(--wk-edge-pull, 0)); + } + .cal-wk-wrap.cal-wk-edge-release > .cal-wk-rail, + .cal-wk-wrap.cal-wk-edge-release > .cal-wk-cols { + transition: transform 0.14s cubic-bezier(0.2, 0.75, 0.25, 1); + } + .cal-wk-wrap.cal-wk-pull-top { + border-top-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); + } + .cal-wk-wrap.cal-wk-pull-bottom { + border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); + } +} +.cal-wk-rail { + flex: 0 0 56px; + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + background: color-mix(in srgb, var(--fg) 2%, var(--bg)); + position: sticky; + left: 0; + z-index: 5; +} +.cal-wk-rail-spacer { + /* Match the 32px day header. The 24px all-day row gets its own spacer + below, so the zoom controls sit high without shifting the hour grid. */ + height: 32px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 0; + background: color-mix(in srgb, var(--fg) 2%, var(--bg)); + z-index: 9; + display: flex; + align-items: center; + justify-content: center; + gap: 2px; + padding: 0 4px; +} +.cal-wk-rail-allday-spacer { + height: 24px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 32px; + z-index: 8; + background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); +} +.cal-wk-zoom { + position: relative; + top: -3px; + width: 22px; + height: 22px; + border: 1px solid var(--border); + background: var(--bg); + color: var(--fg); + border-radius: 50%; + font-family: inherit; + font-size: 14px; + font-weight: 600; + line-height: 1; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + opacity: 0.55; + transition: opacity 0.15s, background 0.15s, border-color 0.15s; +} +.cal-wk-zoom:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--fg)) 50%, var(--border)); +} +.cal-wk-rail-cell { + font-size: 11px; + color: color-mix(in srgb, var(--fg) 55%, transparent); + padding: 4px 8px 0; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); + flex-shrink: 0; + display: flex; + align-items: flex-start; + justify-content: flex-end; + font-variant-numeric: tabular-nums; +} +.cal-wk-cols { + flex: 1; + display: grid; + grid-template-columns: repeat(7, 1fr); + position: relative; +} +.cal-wk-col { + border-left: 1px solid var(--border); + display: flex; + flex-direction: column; + min-width: 0; +} +.cal-wk-col:first-child { border-left: none; } +.cal-wk-col-head { + height: 32px; + padding: 0 6px; + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + flex-shrink: 0; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); + position: sticky; + top: 0; + z-index: 40; +} +/* Today on a Sunday still wins the accent treatment. */ +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); } +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dn, +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dt { color: var(--accent, var(--fg)); } +.cal-wk-dn { font-weight: 600; opacity: 0.6; } +.cal-wk-allday { + height: 24px; + padding: 2px 4px; + display: flex; + flex-direction: row; + gap: 2px; + align-items: center; + background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); + border-bottom: 1px solid var(--border); + flex-shrink: 0; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; + position: sticky; + top: 32px; + z-index: 39; +} +.cal-wk-allday-event { + font-size: 10.5px; + font-weight: 700; + padding: 3px 6px; + border-radius: 4px; + color: var(--cal-event-fg, var(--fg)); + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* Hour grid body */ +.cal-wk-grid { + position: relative; + flex: 0 0 auto; + cursor: cell; + user-select: none; + background: + repeating-linear-gradient( + to bottom, + transparent 0, + transparent calc(var(--wk-hour-px, 52px) / 2 - 1px), + color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--wk-hour-px, 52px) / 2), + transparent calc(var(--wk-hour-px, 52px) / 2 + 1px) + ); +} +.cal-wk-cell { + border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); + box-sizing: border-box; +} +.cal-wk-cell:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); } +/* "Now" line */ +.cal-wk-now { + position: absolute; + left: 0; right: 0; + height: 0; + border-top: 2px solid color-mix(in srgb, var(--accent, var(--red, #f87171)) 90%, transparent); + pointer-events: none; + z-index: 7; + filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent)); +} +/* Event blocks */ +.cal-wk-block { + position: absolute; + left: calc((var(--lane, 0) * (100% / var(--lane-count, 1))) + 2px); + width: calc((100% / var(--lane-count, 1)) - 4px); + right:auto; + border-left: 4px solid var(--fg); + border-radius: 5px; + padding: 5px 7px 7px; + font-size: 12px; + line-height: 1.3; + cursor: pointer; + /* `clip` preserves the rounded-card boundary without creating an overflow + container, allowing the label inside to stick to the week scroller. */ + overflow: clip; + z-index: 2; + box-shadow: 0 2px 6px color-mix(in srgb, #000 14%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); + animation:cal-event-enter 0.18s ease-out both; + transition: width 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), height 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), filter 0.12s, transform 0.12s, box-shadow 0.16s; + box-sizing: border-box; +} +.cal-wk-block:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); } +.cal-wk-block-label { + position: sticky; + top: 37px; + z-index: 1; + min-width: 0; + pointer-events: none; +} +.cal-wk-has-allday .cal-wk-block-label { + top: 61px; +} +.cal-wk-block-name { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color:color-mix(in srgb, var(--fg) 96%, transparent); } +.cal-wk-block-time { font-size: 10.5px; opacity: 0.82; font-variant-numeric: tabular-nums; margin-top: 2px; font-weight:600; } +@media (hover: hover) and (pointer: fine) { + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left { + left: auto; + right: calc(100% - var(--lane-right) + 2px); + } + .cal-wk-block.cal-wk-block-crowded:hover { + width: var(--hover-width, 170px); + height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; + overflow: clip; + z-index: 30; + filter: brightness(1.08); + transform: translateY(var(--hover-shift-y, -2px)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-hover-width-only:hover { + height: var(--event-height) !important; + transform: translateY(-1px); + } + .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-label { + position: absolute; + top: 5px; + width: calc(var(--hover-width, 170px) - 18px); + animation: cal-wk-hover-label-reveal 0.2s ease-out both; + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left:hover .cal-wk-block-label { + left: auto; + right: 7px; + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-right:hover .cal-wk-block-label { + left: 7px; + right: auto; + } + .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-name { + display: block; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + } +} +@media (max-width: 768px) { + /* Mobile week view: the hour rail already shows the time, so drop it from the + card and let the title wrap to fill the freed space (more readable text). */ + .cal-wk-block-time { display: none; } + .cal-wk-block-name { + white-space: normal; + line-height: 1.15; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left { + left: auto; + right: calc(100% - var(--lane-right) + 2px); + } + .cal-wk-block.cal-wk-mobile-expanded { + width: var(--hover-width, 170px); + height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; + overflow: clip; + z-index: 30; + filter: brightness(1.08); + transform: translateY(var(--hover-shift-y, 0)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); + } + .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-label { + position: absolute; + top: 5px; + width: calc(var(--hover-width, 170px) - 18px); + animation: cal-wk-hover-label-reveal 0.2s ease-out both; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left .cal-wk-block-label { + left: auto; + right: 7px; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-right .cal-wk-block-label { + left: 7px; + right: auto; + } + .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-name { + display: block; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + -webkit-line-clamp: unset; + } +} +/* Resize handle: a 6 px hit zone along the block's bottom edge. The + visible 2-px stripe only shows on hover so blocks at rest stay clean. */ +.cal-wk-block-resize { + position: absolute; + left: 4px; + right: 4px; + bottom: 0; + height: 6px; + cursor: ns-resize; + z-index: 3; +} +/* Drag-to-create ghost */ +.cal-wk-ghost { + position: absolute; + left: 2px; + right: 2px; + background: color-mix(in srgb, var(--accent, var(--fg)) 28%, transparent); + border: 1px dashed color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent); + border-radius: 4px; + pointer-events: none; + z-index: 3; + font-size: 10px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + color: var(--accent, var(--fg)); + font-variant-numeric: tabular-nums; +} +.cal-wk-slot-selected { + background: color-mix(in srgb, var(--accent, var(--fg)) 20%, var(--bg)); + border-style: solid; + border-color: var(--accent, var(--fg)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--fg)) 20%, transparent); + overflow: visible; + pointer-events: auto; + cursor: pointer; +} +.cal-wk-slot-resize { + position: absolute; + left: 14%; + right: 14%; + bottom: -7px; + height: 14px; + z-index: 4; + cursor: ns-resize; + touch-action: none; + pointer-events: auto; +} +.cal-year { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; } +.cal-year-month { background:color-mix(in srgb, var(--fg) 3%, var(--bg)); border:1px solid var(--border); border-radius:6px; padding:6px; cursor:pointer; transition:background 0.15s, border-color 0.15s, transform 0.15s; } +.cal-year-month:hover { background:color-mix(in srgb, var(--fg) 8%, var(--bg)); border-color:var(--accent); transform:translateY(-1px); } +.cal-year-month-title { font-size:11px; font-weight:600; text-align:center; margin-bottom:4px; opacity:0.7; } +.cal-year-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; } +.cal-year-wd { font-size:7px; text-align:center; opacity:0.3; padding:1px 0; } +.cal-year-cell { font-size:8px; text-align:center; padding:2px 0; min-height:14px; border-radius:2px; transition:transform 0.12s, background 0.12s; } +.cal-year-day:hover { background:var(--accent, var(--red)); color:#fff; opacity:1; transform:scale(1.15); font-weight:700; z-index:2; position:relative; } +.cal-year-today { color:var(--accent, var(--red)); font-weight:700; opacity:1; } +.cal-year-has { opacity:1; background:color-mix(in srgb, var(--accent) 15%, transparent); } +.cal-year-has.cal-year-today { background:color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } +.cal-loading { display:flex; justify-content:center; padding:40px 0; } +.cal-badge { background:var(--accent); border-radius:50%; width:6px; height:6px; margin-left:auto; display:inline-block; flex-shrink:0; } +/* Search input — in-panel variant lives inside the day-detail panel and + spans its width; no width animation since growing/shrinking on every + keystroke would jitter beside the events list. */ +.cal-search-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:0 8px; height:24px; color:var(--fg); font-size:11px; font-family:inherit; width:120px; box-sizing:border-box; transition:width 0.15s; } +.cal-search-input:focus { outline:none; border-color:var(--accent); width:180px; } +.cal-search-input.cal-day-search { width:100%; margin-bottom:0; transition:none; padding-left: 28px; } +/* Wrap for the search input so the magnifying-glass icon can be absolute- + positioned inside the field. */ +.cal-search-wrap { + position: relative; + display: block; + margin-bottom: 6px; +} +.cal-search-wrap .cal-search-icon { + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + color: var(--accent, var(--red)); +} +.cal-day-search-meta { font-size:10px; opacity:0.5; margin:0 2px 6px; } +.cal-search-results { display:flex; flex-direction:column; gap:4px; } +.cal-search-count { font-size:10px; opacity:0.5; padding:4px 2px 8px; } +/* Agenda view */ +.cal-agenda { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-left:4px; } +.cal-agenda-day { display:flex; flex-direction:column; gap:5px; position:relative; padding-left:14px; } +.cal-agenda-date { font-size:12px; font-weight:800; opacity:0.72; padding:5px 2px 3px; border-bottom:1px solid var(--border); } +.cal-agenda-today-badge { + display: inline-block; + margin-left: 6px; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + padding: 1px 6px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + vertical-align: 1px; +} +.cal-agenda-empty { font-size:11px; opacity:0.4; padding:8px; font-style:italic; } +.cal-agenda-event { + display:flex; + gap:9px; + padding:9px 10px; + border-radius:6px; + cursor:pointer; + align-items:center; + position:relative; + background:color-mix(in srgb, var(--fg) 3.5%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); + animation:cal-event-enter 0.18s ease-out both; + transition:background 0.15s, box-shadow 0.15s, transform 0.15s; +} +.cal-agenda-event:hover { + background:color-mix(in srgb, var(--fg) 8%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); + transform:translateX(2px); +} +.cal-agenda-event .cal-event-more, +.cal-event-item .cal-event-more { opacity:0; transition:opacity 0.15s; } +.cal-event-item { position:relative; } + +/* ── Personal Assistant ── */ +.sidebar-assistant-entry { gap: 6px; min-height: 29px; box-sizing: border-box; align-items: center; } +.sidebar-assistant-entry .sidebar-action-icon { position: relative; left: -2px; } +#sidebar-assistant-gear:hover { opacity: 0.8 !important; } +.assistant-settings-form { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; } +.assistant-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: color-mix(in srgb, var(--fg) 70%, transparent); } +.assistant-field > span { font-size: 11px; opacity: 0.6; } +.assistant-field input[type="text"], +.assistant-field input[type="time"], +.assistant-field select, +.assistant-field textarea { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 5px; + padding: 7px 10px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + box-sizing: border-box; +} +.assistant-field textarea { resize: vertical; min-height: 60px; } +.assistant-field input:focus, +.assistant-field select:focus, +.assistant-field textarea:focus { outline: none; border-color: var(--accent, var(--red)); } +.assistant-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } +.assistant-field-row > .assistant-field { flex: 1 1 180px; } +.assistant-field-check { flex-direction: row !important; align-items: center; gap: 6px; padding-bottom: 7px; } +.assistant-checkins { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); } +.assistant-checkins h5 { margin: 0 0 2px; font-size: 12px; font-weight: 600; opacity: 0.8; } +.assistant-checkin-row { + display: flex; + flex-direction: column; + gap: 4px; + padding: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); +} +.assistant-checkin-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } +.assistant-checkin-head input[type="text"], +.assistant-checkin-head input[type="time"] { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 4px; + padding: 4px 8px; + color: var(--fg); + font-size: 12px; + font-family: inherit; +} +.assistant-checkin-head input[type="text"] { flex: 1; min-width: 120px; } +.assistant-checkin-head input[type="time"] { width: 90px; } +.assistant-checkin-run { + background: none; + border: 1px solid var(--border); + color: var(--fg); + font-family: inherit; + font-size: 11px; + padding: 4px 8px; + border-radius: 4px; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s; +} +.assistant-checkin-run:hover { opacity: 1; } +.assistant-checkin-prompt { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 8px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + resize: vertical; + min-height: 48px; +} +.assistant-checkin-prompt:focus { outline: none; border-color: var(--accent, var(--red)); } +.assistant-checkin-meta { font-size: 10px; opacity: 0.45; } +.assistant-settings-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--border); +} +.assistant-tools-grid { + display: flex; + flex-direction: column; + gap: 6px; + max-height: 240px; + overflow-y: auto; + padding: 6px; + border: 1px solid var(--border); + border-radius: 6px; + margin-top: 4px; +} +.assistant-tool-group { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} +.assistant-tool-group-label { + font-size: 10px; + font-weight: 600; + opacity: 0.5; + text-transform: uppercase; + letter-spacing: 0.03em; + width: 100%; + margin-bottom: 1px; +} +.assistant-tool-item { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 11px; + padding: 2px 6px; + border: 1px solid var(--border); + border-radius: 4px; + cursor: pointer; + transition: background 0.1s, border-color 0.1s; + user-select: none; +} +.assistant-tool-item:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.assistant-tool-item:has(input:checked) { + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 10%, transparent); +} +.assistant-tool-item input[type="checkbox"] { + width: 12px; + height: 12px; + margin: 0; +} + +/* Empty state */ +.cal-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px 20px; text-align:center; gap:10px; } +.cal-empty-title { font-size:14px; font-weight:600; opacity:0.8; } +.cal-empty-msg { font-size:12px; opacity:0.5; max-width:320px; line-height:1.5; margin-bottom:8px; } +.cal-allday-switch { margin:0; flex-shrink:0; } +/* Stack the "All day" label above its toggle so it doesn't get squeezed / + truncated next to the date inputs in the row. */ +.cal-allday-ctrl { display:flex; flex-direction:column; align-items:center; gap:3px; flex-shrink:0; } +.cal-allday-label { font-size:10px; opacity:0.5; white-space:nowrap; line-height:1; } +.cal-form { display:flex; flex-direction:column; gap:8px; } +.cal-form-title { font-size:13px; font-weight:600; margin-bottom:2px; } +.cal-title-wrap { position: relative; } +/* Calendar-picker select sits 2px higher; its border-left/background tint is + set in JS to the chosen calendar's colour. */ +.cal-f-cal-select { position: relative; top: -4px; } + +/* ── Bespoke event form ──────────────────────────────────────────── + Big clock-face date/time hero with the title under it. Details panel + stays collapsed until the title gets focus or "Add details" is clicked. +*/ +.cal-form-bespoke { + position: relative; + width: min(720px, calc(100% - 24px)); + max-width: none; + margin: 12px auto; + padding: 18px 28px 14px; + box-sizing: border-box; + background: var(--bg); + /* Optional per-event tint set by JS via --ev-color. When unset, falls + back to the theme's neutral border so the card looks like normal. */ + border: 1px solid var(--border); + border-radius: 14px; + box-shadow: 0 8px 24px color-mix(in srgb, var(--fg) 6%, transparent); + transition: border-color 0.18s, box-shadow 0.18s; +} +/* When a colour is picked, lift the card with a soft tinted border + glow + so the choice is unmistakable. */ +.cal-form-bespoke[style*="--ev-color"] { + border-color: color-mix(in srgb, var(--ev-color) 55%, var(--border)); + box-shadow: 0 8px 28px color-mix(in srgb, var(--ev-color) 22%, transparent); +} +.cal-form-bespoke[style*="--ev-color"] .cal-btn-primary { + background: var(--ev-color); + border-color: var(--ev-color); +} +.cal-form-bespoke.cal-form-bg-image .cal-btn-primary { + background: var(--accent, var(--red)); + border-color: var(--accent, var(--red)); + color: var(--bg); +} +.cal-form-close { + position: absolute; top: 10px; right: 12px; + background: none; border: none; color: var(--fg); + opacity: 0.35; cursor: pointer; font-size: 20px; line-height: 1; + padding: 4px 8px; border-radius: 6px; +} +.cal-form-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 8%, transparent); } +.cal-form-mobile-cancel { display: none; } + +/* "Today is …" header pinned at the top of the form. Keeps you oriented + even when the event date you're picking is way off in the future. */ +.cal-form-today { + text-align: center; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + opacity: 0.45; + margin-bottom: 0; +} + +/* Hero — clock face + date label. Spacing between the two lives entirely + on the clock's bottom margin so it's predictable. */ +.cal-hero { + display: flex; flex-direction: column; align-items: center; + gap: 0; margin-bottom: 12px; +} +.cal-hero-time { + display: inline-flex; align-items: baseline; gap: 8px; + font-family: ui-monospace, SFMono-Regular, "Menlo", "Consolas", monospace; + font-variant-numeric: tabular-nums; + font-size: 56px; + letter-spacing: 0.02em; + font-weight: 200; + color: var(--fg); + line-height: 1; + /* Behaves like a button — strip native chrome but keep it tappable. */ + background: none; + border: none; + /* Pill that reads as a real hit target for a 56-px clock face. + Top padding kept tight so the clock sits close to the "Today is" line. */ + padding: 6px 38px 18px; + margin: 0 0 24px; + border-radius: 18px; + cursor: pointer; + line-height: 1; + transition: background 0.15s, color 0.15s; +} +/* The clock is a `` and a global `button:hover` rule paints + every button's background on hover. Suppress it explicitly. */ +.cal-hero-time:hover { background: transparent; border-color: transparent; } +.cal-hero-clock { font-feature-settings: "tnum"; display: inline-flex; align-items: baseline; } +/* Per-segment hover so it's clear hh vs mm are individually clickable. */ +.cal-hero-clock-hh, .cal-hero-clock-mm { + display: inline-block; + border-radius: 6px; + padding: 0 2px; + transition: background 0.12s; +} +.cal-hero-clock-hh:hover, .cal-hero-clock-mm:hover { + background: color-mix(in srgb, var(--fg) 9%, transparent); +} +.cal-hero-sep { padding: 0 2px; opacity: 0.55; pointer-events: none; } +.cal-hero-ampm { + font-size: 14px; + letter-spacing: 0.12em; + opacity: 0.55; + font-weight: 500; + text-transform: uppercase; +} +.cal-hero-date { + font-size: 16px; + opacity: 0.6; + letter-spacing: 0.04em; + background: none; + border: none; + color: inherit; + font-family: inherit; + padding: 4px 10px; + border-radius: 6px; + cursor: pointer; + margin: 7px 0 0; /* extra nudge below the clock */ + transition: background 0.15s, opacity 0.15s; +} +.cal-hero-date:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); opacity: 0.85; } + +/* Title input — flat, large, no chrome until focused */ +.cal-input.cal-hero-title { + font-size: 18px; + text-align: left; + padding: 10px 12px 10px 0; + background: transparent; + border: 1px solid transparent; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); + border-radius: 0; + margin-bottom: 4px; +} + +/* Details panel — animated reveal via max-height + opacity. */ +.cal-form-bespoke .cal-form-details { + display: block; + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 240ms ease, opacity 180ms ease, margin 220ms ease; + margin-top: 0; +} +/* The detail children themselves keep the existing flex/gap rules from + the regular .cal-form, so we don't restyle inputs/rows here. */ +.cal-form-bespoke .cal-form-details > * + * { margin-top: 8px; } + + +/* Location row: input + Apple Maps pin link. Pin only lights up when the + field has content; clicking opens maps.apple.com (native Maps app on + Apple devices, web fallback elsewhere). All theme vars. */ +.cal-loc-row { + display: flex; + align-items: stretch; + gap: 6px; +} +.cal-loc-input-wrap { + position: relative; + flex: 1; + min-width: 0; +} +.cal-loc-hint { + position: absolute; + inset: 0 auto 0 10px; + display: none; + align-items: center; + gap: 5px; + pointer-events: none; + color: color-mix(in srgb, var(--fg) 42%, transparent); + font-size: 12px; +} +.cal-loc-map { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + flex-shrink: 0; + border: 1px solid var(--border); + border-radius: 5px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + text-decoration: none; + transition: background 0.15s, border-color 0.15s, opacity 0.15s; +} +.cal-loc-map:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); border-color: var(--accent, var(--red)); } + +@media (max-width: 520px) { + .cal-hero-time { font-size: 44px; } + .cal-form-bespoke { margin: 16px 12px; padding: 32px 16px 14px; } + .cal-form-mobile-cancel { + display: inline-flex; + position: absolute; + top: 8px; + right: 8px; + width: 30px; + height: 30px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + border-radius: 999px; + background: color-mix(in srgb, var(--bg) 82%, transparent); + color: var(--fg); + opacity: 0.72; + cursor: pointer; + z-index: 3; + -webkit-tap-highlight-color: transparent; + } + .cal-form-mobile-cancel:active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); + } +} +.cal-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:7px 10px; color:var(--fg); font-size:12px; width:100%; box-sizing:border-box; } +.cal-input:focus { outline:none; border-color:var(--accent); } +.cal-input-time { width:auto; flex:1; } +.cal-form-row { display:flex; gap:8px; align-items:center; } +.cal-form-actions { display:flex; gap:8px; justify-content:flex-end; margin-top:4px; } diff --git a/static/css/style-part-08.css b/static/css/17-research.css similarity index 57% rename from static/css/style-part-08.css rename to static/css/17-research.css index df8418a28..d2fa2b1d1 100644 --- a/static/css/style-part-08.css +++ b/static/css/17-research.css @@ -1,2206 +1,19 @@ -/* Fragment 9/9 of the former static/style.css. +/* 17-research - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -.note-cl-dot { - width: 8px; - height: 8px; - border-radius: 50%; - border: 1.5px solid color-mix(in srgb, var(--fg) 30%, transparent); - flex-shrink: 0; - cursor: pointer; - transition: all 0.15s; -} -.note-cl-dot:hover { border-color: var(--fg); transform: scale(1.2); } -.note-cl-row.done .note-cl-dot { - background: var(--accent); - border-color: var(--accent); -} -.note-cl-row.done .note-cl-text { - opacity: 0.4; - background: linear-gradient(currentColor, currentColor) no-repeat; - background-size: 0 1px; - background-position: 0 calc(50% - 1px); - animation: cl-strike 0.32s ease-out forwards; - transition: opacity 0.2s ease; -} -/* Draws the strikethrough line left-to-right when the row is marked - done, instead of snapping it in full-width on the same frame. */ -@keyframes cl-strike { - to { background-size: 100% 1px; } -} -.note-cl-text { - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - color: var(--fg); - font-size: 12px; - padding: 3px 0 3px 3px; - flex: 1 1 auto; - width: auto; - min-width: 0; - outline: none; -} -.note-cl-text:focus { border-color: var(--fg); } -.note-cl-rm { - background: none; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - font-size: 14px; - padding: 0; - width: 24px; - height: 14px; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: flex-end; - line-height: 1; -} -.note-cl-rm:hover { opacity: 0.8; color: var(--red); } -.note-cl-add { - background: none; - border: none; - color: var(--fg); - opacity: 0.4; - cursor: pointer; - font-size: 11px; - padding: 4px 0; - text-align: left; -} - -/* ── Calendar ── */ -.cal-modal-content { width:min(680px, 94vw); max-height:88vh; overflow-x:hidden; position:relative; } -#calendar-modal .cal-modal-content { animation: none; } -/* Was overflow-y:auto with grid + day-detail flowing naturally. Now uses a - true splitter layout: the body is a flex column that doesn't scroll - itself; the grid takes the remaining space and scrolls if needed; the - day-detail has an explicit height (driven by the splitter drag via - --cal-detail-h) and shrinks the grid visually as it grows. */ -#cal-body { display:flex; flex-direction:column; gap:8px; overflow:hidden; padding:0; min-height:0; } -#cal-body > .cal-grid { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overflow-x: hidden; } -@media (max-width: 768px) { - /* Let the calendar pane shrink to nothing on mobile so the day-detail - splitter can be dragged all the way to the top, hiding the calendar - entirely. Without min-height:0 the grid (or the week-wrap below) - refuses to shrink past its built-in floor. */ - #cal-body > .cal-grid, - #cal-body > .cal-wk-wrap { - min-height: 0; - max-height: none; - } - /* Let the week grid fill the calendar pane and scroll its hours internally - instead of overflowing #cal-body (which clips it and feels cramped). */ - #cal-body > .cal-wk-wrap { - flex: 1 1 auto; - overflow: auto; - } -} -.cal-toolbar { display:flex; align-items:center; gap:6px; margin-bottom:7px; flex-wrap:wrap; line-height:1; max-width:100%; row-gap:6px; } - -/* Quick-add bar: natural-language event creation. Sits directly under - the toolbar; focused with `Q`; opens the bespoke form pre-filled. */ -.cal-quickadd-row { - display: flex; - align-items: center; - gap: 8px; - margin: 0 0 8px; - padding: 1px 10px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - transition: border-color 0.15s, background 0.15s; - position: relative; -} -/* Two-tone placeholder hint: "Quick add" in accent, the example in grey, both - at one low opacity. Overlays the (empty) input; hidden once the user types. */ -.cal-quickadd-hint { - position: absolute; - left: 10px; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: flex; - align-items: center; - gap: 4px; - font-size: 12px; - color: var(--fg); - opacity: 0.5; - pointer-events: none; - white-space: nowrap; - overflow: hidden; -} -.cal-quickadd-hint .qa-hint-accent { color: var(--accent, var(--red)); } -/* ↵ enter glyph in the hint — makes it obvious the field submits on Enter. */ -.cal-quickadd-hint .qa-hint-enter { margin-left: auto; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.8; } -/* Cycling example text fades out before swap, then fades back in. */ -.cal-quickadd-hint .qa-hint-example { - display: block; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-style: italic; - transition: opacity 0.18s ease; -} -.cal-quickadd-hint .qa-hint-example.qa-hint-fade { opacity: 0; } -.cal-title-hint { - position: absolute; - left: 0; - top: 50%; - transform: translateY(calc(-50% - 2px)); - display: none; - align-items: center; - gap: 7px; - font-size: 18px; - line-height: 1.2; - white-space: nowrap; - pointer-events: none; - color: color-mix(in srgb, var(--fg) 42%, transparent); -} -.cal-hero-title:placeholder-shown ~ .cal-title-hint { display: inline-flex; } -.cal-title-hint .cal-title-enter-ico { color: var(--accent, var(--red)); flex-shrink: 0; } -/* Pure-CSS toggle: hide the hint as soon as the field has real text (no - per-keystroke JS, so typing never triggers a re-render / focus loss). */ -.cal-quickadd-input:not(:placeholder-shown) ~ .cal-quickadd-hint { display: none; } -.cal-quickadd-row:focus-within { - border-color: color-mix(in srgb, var(--accent, var(--fg)) 55%, var(--border)); - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); -} -.cal-quickadd-icon { - color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent); - display: inline-flex; - align-items: center; - flex-shrink: 0; - opacity: 0.7; -} -.cal-quickadd-input { - flex: 1; - min-width: 0; - background: transparent; - border: none; - outline: none; - color: var(--fg); - font-family: inherit; - font-size: 12px; - height: 22px; - padding: 0; -} -.cal-quickadd-status { - font-size: 10px; - opacity: 0.55; - font-variant-numeric: tabular-nums; -} -.cal-toolbar > *, .cal-toolbar-nav > *, .cal-toolbar-right > * { vertical-align:middle; } -.cal-toolbar-nav { display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; } -.cal-toolbar-right { - display: inline-flex; - align-items: center; - justify-content: flex-start; - gap: 6px; - flex: 1 1 100%; - width: 100%; - margin-left: 0; - margin-top: 6px; - flex-wrap: wrap; -} -.cal-title { font-size:13px; font-weight:600; white-space:nowrap; height:24px; line-height:30px; padding:0 6px; display:inline-block; vertical-align:middle; box-sizing:border-box; } -.cal-toolbar-clock { - margin-left: 8px; - color: var(--accent, var(--red)); - font-size: 11px; - font-weight: 600; - font-variant-numeric: tabular-nums; -} -button.cal-nav { background:color-mix(in srgb, var(--fg) 6%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 8px; height:24px; line-height:22px; cursor:pointer; font-size:11px; font-family:inherit; box-sizing:border-box; vertical-align:middle; } -button.cal-nav:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); } -button.cal-nav.cal-toolbar-arrow { - display: none; - align-items: center; - justify-content: center; - width: 24px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 1; - opacity: 0.84; -} -.cal-toolbar-arrow-glyph { - position: relative; - top: -1px; - line-height: 1; -} -@media (max-width: 768px) { - button.cal-nav.cal-toolbar-arrow { display: inline-flex !important; } - .cal-toolbar-nav { - flex: 1 1 100%; - width: 100%; - min-width: 0; - } - #cal-next { margin-left: auto; } -} -.cal-week-start-toggle { - display: inline-flex; - align-items: center; - gap: 0; - width: max-content; - padding: 0; - height: 24px; - box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 5px; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.cal-week-start-btn { - box-sizing: border-box; - height: 22px !important; - min-height: 22px !important; - margin-top: 0 !important; - padding: 0 7px; - border: 1px solid transparent; - border-radius: 4px; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - cursor: pointer; - font-family: inherit; - font-size: 10px; - line-height: 20px; - transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; -} -.cal-settings-actions { - display: flex; - justify-content: flex-end; - width: 100%; -} -#cal-settings-panel .cal-week-start-toggle { - display: flex; - margin-left: auto; -} -.cal-settings-sync-status { - position: relative; - top: 2px; - font-size: 11px; - opacity: 0.6; -} -.cal-settings-sync-status.is-error { - color: var(--red); - opacity: 1; -} -.cal-week-start-btn:hover { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.cal-week-start-btn.active, -.cal-week-start-btn.active:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); -} -.cal-side-nav { - position: absolute; - top: var(--cal-side-nav-y, 50%); - transform: translateY(-50%); - width: 32px; - height: 32px; - border-radius: 50%; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - cursor: pointer; - z-index: 6; - opacity: 0; - pointer-events: none; - font-family: inherit; - font-size: 24px; - line-height: 28px; - padding: 0 0 3px; - transition: opacity 0.12s ease, background 0.12s ease, transform 0.12s ease; -} -.cal-modal-content:hover .cal-side-nav, -.cal-side-nav:focus-visible { - opacity: 0.84; - pointer-events: auto; -} -.cal-side-nav:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); - transform: translateY(-50%) scale(1.04); -} -button.cal-today-btn { font-size:10px; opacity:0.5; } -button.cal-add-btn { background:var(--accent); color:#fff; border:none; border-radius:50%; width:24px; height:24px; line-height:22px; font-size:18px; cursor:pointer; flex-shrink:0; padding:0; box-sizing:border-box; font-family:inherit; vertical-align:middle; text-align:center; } -button.cal-add-btn.cal-add-btn-text { - width:auto; min-width:0; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 12px; - padding: 0 10px 0 6px; - display: inline-flex; - align-items: center; - gap: 4px; - height: 24px; - line-height: 1; - margin-top: 0; - transition: background 0.15s, border-color 0.15s; -} -button.cal-add-btn.cal-add-btn-text:hover { - background: color-mix(in srgb, var(--fg) 14%, transparent); - border-color: var(--accent); - opacity: 1; -} -.cal-add-plus { - font-size: 16px; - line-height: 1; - color: var(--accent, var(--red)); - font-weight: 500; - display: inline-block; - transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); - padding-bottom: 2px; -} -button.cal-add-btn.cal-add-btn-text:hover .cal-add-plus { - transform: rotate(180deg); -} -.cal-add-label { font-size:11px; font-weight:600; line-height:1; opacity:0.85; } -/* Small variant of the toolbar's +New button. Inherits the .cal-add-btn-text - styles (pill with "+" + "New" + spring-rotate on the +), so the - animation is identical. Just shrunk a bit. */ -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { - height: 20px; - border-radius: 10px; - /* At rest: tight, equal padding around the "+" so it sits centred - with no trailing gap. On hover the right pad and the flex gap grow - to make room for the revealed "New". */ - padding: 0 5px; - gap: 0; - transition: padding 0.25s ease, gap 0.25s ease, background 0.15s, border-color 0.15s; -} -/* Don't touch .cal-add-plus on the small variant — the toolbar +New is - the reference and we want the same rotation centre / motion. The label - is shrunk and hidden until hover so the pill is compact at rest. */ -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { - font-size: 10px; - max-width: 0; - margin-left: 0; - opacity: 0; - overflow: hidden; - white-space: nowrap; - transition: max-width 0.25s ease, opacity 0.18s ease, margin-left 0.25s ease; -} -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover .cal-add-label { - max-width: 40px; - margin-left: 3px; - opacity: 0.85; -} -.cal-add-btn:hover { opacity:0.85; } - -/* Mobile: bump the +New pill and the +/- zoom buttons up to a tap-friendly - size so they're easy to hit with a thumb. */ -@media (max-width: 768px) { - /* Toolbar +New (relocated by JS into the quickadd row on mobile) */ - button.cal-add-btn.cal-add-btn-text { - height: 36px; - border-radius: 18px; - padding: 0 14px 0 12px; - gap: 6px; - flex-shrink: 0; - } - /* Day-detail +New: just a "+" on mobile — drop the label entirely. */ - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { - width: 28px; - height: 28px; - border-radius: 50%; - padding: 0; - gap: 0; - justify-content: center; - } - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { - display: none; - } - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { - font-size: 18px; - padding-bottom: 0; - } - .cal-add-plus { font-size: 22px; padding-bottom: 1px; } - .cal-add-label { font-size: 13px; } - .cal-wk-zoom { - width: 36px; - height: 36px; - font-size: 20px; - border-radius: 8px; - opacity: 0.85; - } - /* The +New button sits as a sibling of the quickadd row inside a flex - wrapper, so it's clearly outside the input's bordered box. The row - itself keeps its own styling. */ - .cal-quickadd-wrap { - display: flex; - align-items: stretch; - gap: 8px; - margin: 0 0 8px; - } - .cal-quickadd-wrap > #cal-quickadd-row { - flex: 1; - min-width: 0; - margin: 0; - } - .cal-quickadd-wrap > .cal-add-btn-text { - align-self: stretch; - flex-shrink: 0; - } - /* Nudge the "+" glyph up one pixel on the round day-detail button so it - sits visually centred inside the circle. */ - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { - transform: translateY(-1px); - } - /* Event form: bigger Create / Cancel buttons so they're easy to tap. */ - .cal-form-actions button.cal-btn { - height: 44px; - padding: 0 20px; - font-size: 14px; - border-radius: 8px; - min-width: 96px; - } - /* Left-align the day/month + tags row in the day-detail panel and - the calendar/category chip row so they sit flush against the - left edge on a narrow screen. */ - .cal-detail-header { - justify-content: flex-start; - gap: 12px; - } - .cal-filters { - justify-content: flex-start; - } -} - -/* Refresh button spin — driven by JS toggling .cal-syncing on the button. - transform-box + transform-origin keep the rotation pivoted at the SVG's - own centre so it spins in place instead of orbiting / drifting up. */ -#cal-sync svg, -#email-lib-refresh-btn svg { display: block; transform-box: fill-box; transform-origin: 50% 50%; } -/* Brief checkmark flash after a refresh completes. Lasts ~900ms in JS; - the small bounce + accent color give a clear "done" cue. */ -#cal-sync.cal-sync-done svg, -#email-lib-refresh-btn.email-lib-refresh-done svg { - color: var(--accent, #2ea043); - animation: refresh-check-pop 0.32s ease-out; -} -@keyframes refresh-check-pop { - 0% { transform: scale(0.55); opacity: 0.4; } - 60% { transform: scale(1.18); opacity: 1; } - 100% { transform: scale(1); opacity: 1; } -} -#cal-sync.cal-syncing svg, -#email-lib-refresh-btn.email-lib-refreshing svg { animation: spin 0.6s linear infinite; } -.email-undone-toggle.active, -.email-attach-toggle.active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); - color: var(--accent, var(--red)); - font-weight: 600; -} -/* Hide the gallery-style reader-btn labels on desktop — only the mobile - @media block re-displays them under each icon. Desktop keeps compact - icon-only reader buttons. */ -.reader-btn-label { - display: inline-block; - font-size: 9px; - font-weight: 500; - line-height: 1; - letter-spacing: 0.02em; - white-space: nowrap; - opacity: 0.85; -} - -/* Inline attachment-filter toggle nested inside the search input. The - input has padding-right:34px set inline so its text doesn't slide - under the button. */ -.email-search-wrap { display: flex; align-items: center; } -/* Keep the email search input and the adjacent "Select" button identical in - height regardless of base-vs-mobile overrides — pin both explicitly. */ -.email-search-row .memory-search-input { - height: 32px; - min-height: 32px; - box-sizing: border-box; -} -.email-search-row .email-search-select-btn { - height: 32px; - min-height: 32px; - box-sizing: border-box; - margin-top: 6px; - flex-shrink: 0; -} -/* Select moved into the dropdown row — dock it to the right and match the - selects' vertical nudge (.memory-sort-select has top:3px). */ -.memory-category-filters .email-filter-select-btn, -.memory-category-filters .email-filter-refresh-btn { - position: relative; - top: 3px; - flex-shrink: 0; -} -.memory-category-filters .email-filter-refresh-btn { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0 8px; -} -.memory-category-filters .email-reminders-clear-btn { - position: relative; - top: -2px; - flex-shrink: 0; -} -.email-attach-toggle-inline { - position: absolute !important; - right: 4px !important; - top: 50%; - transform: translateY(calc(-50% - 5px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-attach-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-attach-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -/* Inline undone-toggle nested inside the search input, sits LEFT of the - attach toggle. Same visual treatment. */ -.email-undone-toggle-inline { - position: absolute !important; - right: 34px !important; - top: 50%; - transform: translateY(calc(-50% - 3px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-reminder-toggle-inline { - position: absolute !important; - right: 64px !important; - top: 50%; - transform: translateY(calc(-50% - 3px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-reminder-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-reminder-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -.email-undone-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-undone-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -/* Round (circular) toggles in the search bar, matching the app's default icon - buttons. Always fully visible (not dimmed until hover). */ -.email-attach-toggle-inline, -.email-undone-toggle-inline, -.email-reminder-toggle-inline { border-radius: 50% !important; opacity: 1 !important; } -.email-search-options-btn { - position: absolute !important; - right: 5px !important; - top: 50% !important; - transform: translateY(calc(-50% - 5px)); - width: 26px !important; - height: 26px !important; - min-width: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 999px !important; - background: transparent !important; - border: 0 !important; - color: inherit !important; - opacity: 0.7; - z-index: 2; -} -.email-search-options-btn:hover, -.email-search-options-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -.email-search-options-menu { - position: absolute; - top: calc(100% + 2px); - right: 0; - z-index: 75; - min-width: 140px; - padding: 4px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--panel, var(--bg)); - box-shadow: 0 8px 24px rgba(0,0,0,0.3); -} -.email-search-options-title { - padding: 5px 9px 4px; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - opacity: 0.65; - white-space: nowrap; -} -.email-search-options-menu button { - width: 100%; - border: 0; - background: transparent; - color: var(--fg); - display: flex; - align-items: center; - gap: 10px; - height: auto; - min-height: 0; - padding: 6px 8px; - border-radius: 8px; - font-family: inherit; - font-size: 11px; - font-weight: 400; - line-height: normal; - letter-spacing: normal; - text-align: left; - cursor: pointer; -} -.email-search-options-menu button:hover, -.email-search-options-menu button.active { - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - color: var(--accent, var(--red)); -} -.email-search-option-icon { - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - opacity: 0.5; -} -.email-search-option-icon svg { width: 14px; height: 14px; } -.email-search-date-panel { - width: 220px; - padding: 6px 7px 5px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.email-search-date-panel input { - width: 100%; - height: 26px; - box-sizing: border-box; - padding: 2px 6px; - border: 1px solid var(--border); - border-radius: 5px; - outline: none; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.email-search-date-panel input:focus { border-color: var(--accent, var(--red)); } -.email-search-date-actions { display: flex; justify-content: flex-end; gap: 5px; } -.email-search-options-menu .email-search-date-actions [data-email-date-apply] { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -@media (max-width: 768px) { - .email-search-options-menu button { - min-height: 44px; - padding: 12px 12px !important; - font-size: 14px !important; - gap: 10px !important; - } - .email-search-options-menu .email-search-option-icon { - width: 18px; - height: 18px; - } - .email-search-options-menu .email-search-option-icon svg { - width: 16px; - height: 16px; - } -} -/* Mobile: enlarge the icons inside the inline search-bar toggles - (done / attachment / reminders) — buttons themselves stay the same, - only the SVG glyph scales up so it's tappable + visible. */ -@media (max-width: 768px) { - .email-filter-btn { - height: 30px; - min-height: 30px; - font-size: 12px; - top: -6px; - padding: 0 9px; - } - .email-folder-btn { - top: -4px; - } - - #email-lib-folder, - .memory-category-filters .email-filter-select-btn, - .memory-category-filters .email-filter-refresh-btn, - .memory-category-filters .email-filter-settings-btn { - height: 30px !important; - min-height: 30px !important; - box-sizing: border-box; - } - - .memory-category-filters .email-filter-refresh-btn, - .memory-category-filters .email-filter-settings-btn { - width: 30px; - padding: 0 !important; - } - .memory-category-filters .email-filter-settings-btn > svg { - width: 15px; - height: 15px; - } - - .email-search-row .memory-search-input { - height: 38px; - min-height: 38px; - } - - #email-lib-chip-bar { - padding-right: 42px !important; - padding-left: 30px !important; - gap: 5px !important; - } - - .email-lib-chip-bar-icon { - width: 14px !important; - height: 14px !important; - left: 9px !important; - } - - #email-lib-search { - font-size: 14px !important; - min-width: 88px !important; - top: 0 !important; - } - - #email-lib-search::placeholder { - font-size: 14px !important; - } - - #email-lib-chip-bar.has-email-lib-pills #email-lib-search { - min-width: 24px !important; - flex: 1 1 24px !important; - } - - .email-lib-pill { - min-height: 24px !important; - height: 24px !important; - line-height: 24px !important; - font-size: 12px !important; - padding: 0 6px 0 8px !important; - max-width: 180px !important; - } - - .email-lib-pill svg { - width: 14px !important; - height: 14px !important; - } - - .email-lib-pill-x { - font-size: 13px !important; - top: -4.5px !important; - } - - .email-lib-filter-pill { - min-height: 24px !important; - height: 24px !important; - line-height: 24px !important; - gap: 4px !important; - padding: 0 6px 0 8px !important; - } - - .email-lib-filter-pill .email-lib-pill-icon { - width: 17px !important; - height: 17px !important; - } - - .email-lib-filter-pill svg { - width: 18px !important; - height: 18px !important; - } - - .email-lib-filter-pill .email-lib-pill-x { - font-size: 13px !important; - top: -4.5px !important; - } - - .email-tags-toggle-inline { - right: 6px; - height: 28px !important; - min-width: 42px; - padding: 0 8px !important; - font-size: 11px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-attach-toggle-inline { - right: 56px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-undone-toggle-inline { - right: 92px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-reminder-toggle-inline { - right: 128px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-attach-toggle-inline svg, - .email-undone-toggle-inline svg, - .email-reminder-toggle-inline svg, - .email-filter-refresh-btn svg { - width: 16px !important; - height: 16px !important; - } -} -.email-accounts-row { - display: flex; - align-items: center; - gap: 10px; - padding: 0 0 6px; -} -.email-accounts-row .doclib-desc { flex-shrink: 0; } -/* Only the direct-child compose button gets pushed right; nested chips - inside #email-lib-accounts pack to the left as normal flex items. */ -.email-accounts-row > .memory-toolbar-btn { flex-shrink: 0; margin-left: auto; } -.email-account-chip { - display: inline-flex; - align-items: center; - gap: 5px; -} -.email-account-chip.email-account-has-unread { - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - color: color-mix(in srgb, var(--accent, var(--red)) 86%, var(--fg)); -} -.email-account-unread-dot { - width: 6px; - height: 6px; - border-radius: 999px; - background: var(--accent, var(--red)); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - flex: 0 0 auto; -} -.email-account-chip-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} -.email-account-unread-count { - font-size: 9px; - line-height: 1; - font-weight: 700; - color: var(--accent, var(--red)); - opacity: 0.9; - position: relative; - top: 0; -} -.email-account-away-label { - color: var(--accent, var(--red)); - font-size: 9px; - font-weight: 700; - line-height: 1; - flex: 0 0 auto; -} -.email-accounts-loading-whirlpool { - width: 14px; - height: 14px; - margin: 3px 4px 0 1px; - display: inline-flex; - flex: 0 0 auto; -} -.email-accounts-loading-label { - font-size: 10px; - opacity: 0.55; - position: relative; - top: 2px; - white-space: nowrap; -} -.email-loading-with-label { - /* Fill the email list grid so the spinner lands in the middle of the - email window rather than the top of the empty list section. */ - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - text-align: center; - pointer-events: none; -} -.date-section-header { - display: flex; - align-items: center; - gap: 7px; - padding: 8px 9px 4px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 10px; - font-weight: 700; - letter-spacing: 0; - line-height: 1.2; - text-transform: uppercase; - user-select: none; -} -.date-section-header::after { - content: ''; - flex: 1 1 auto; - height: 1px; - background: color-mix(in srgb, var(--border) 72%, transparent); -} -#session-list .date-section-header { - padding-left: 10px; - padding-right: 8px; - cursor: pointer; -} -#session-list .date-section-header.collapsed { - color: color-mix(in srgb, var(--fg) 36%, transparent); -} -#session-list .date-section-header.collapsed::after { - opacity: 0.45; -} -#session-list .date-section-header:focus-visible { - outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); - outline-offset: 2px; - border-radius: 5px; -} -/* Bootstrap row shown while the session list hydrates, and on load failure. - Reads as a normal list row but is not selectable. */ -.session-list-bootstrap { - cursor: default; - pointer-events: none; -} -#email-lib-grid .date-section-header { - padding: 10px 5px 3px; -} -#email-lib-modal .email-lib-sync-status { - flex: 0 0 auto; - min-height: 14px; - padding: 1px 4px 0; - font-size: 10.5px; - line-height: 1.2; - text-align: center; - opacity: 0.58; - color: var(--muted-text, currentColor); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - position: relative; - top: 4px; -} -@media (max-width: 768px) { - #email-lib-modal .email-lib-sync-status { - min-height: 13px; - padding-right: 5px; - font-size: 10px; - } -} -/* Nudge the subject + sender/date meta line 4px right inside each - email card so they don't sit flush against the read/unread/done - markers on the left. Includes #email-lib-modal selector to - override the expanded card's `padding: 4px 0 6px` shorthand - which was zeroing out the title's padding-left. */ -#email-lib-grid .email-card-titlerow, -#email-lib-grid .memory-item-meta, -#email-lib-modal .doclib-card-expanded .email-card-titlerow { - padding-left: 4px; -} -.email-loading-label { - font-size: 11px; - opacity: 0.6; -} - -/* Refresh button now lives top-right in the modal header next to the close X. - Borderless (matches the close X), and a fixed square box so the spin and the - refresh→checkmark icon swap never change its size or nudge neighbours. */ -.email-lib-header-actions #email-lib-refresh-btn { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - border: none; - background: none; - padding: 0; - width: 24px; - height: 24px; -} -/* Bump the icon up on mobile for a bigger visual — but keep the button BOX at - 24px so it never exceeds the header's natural height (the title/close set - that). A taller box would grow the sticky header and push the list down. */ -@media (max-width: 768px) { - .email-lib-header-actions #email-lib-refresh-btn svg { - width: 17px; - height: 17px; - } -} -.cal-view-toggle { - display: inline-flex; - align-items: stretch; - border: 1px solid var(--border); - border-radius: 5px; - overflow: hidden; - vertical-align: middle; - box-sizing: border-box; - padding: 0; - margin: 0; - line-height: 1; -} -.cal-view-btn + .cal-view-btn { border-left: 1px solid var(--border); } -.cal-view-btn:hover { opacity: 0.75; } -.cal-view-btn.active { - background: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--accent, var(--red)); - opacity: 1; - font-weight: 600; -} -/* Toolbar holds only the toggle button; the chip row renders below the - toolbar on its own line and wraps freely. */ -.cal-filters { - display:flex; - gap:6px; - margin:6px 0 8px; - flex-wrap:wrap; - padding:6px 8px; - border:1px solid color-mix(in srgb, var(--fg) 8%, transparent); - border-radius:8px; - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 4%, transparent), transparent), - color-mix(in srgb, var(--fg) 2.5%, var(--bg)); - box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 4%, transparent); -} -/* Round the native color-input swatch in calendar settings. The outer - wraps a colored fill drawn by the browser; we need to round both - the wrapper and the fill so it actually appears circular. */ -.cal-s-color { border-radius: 50%; overflow: hidden; } -.cal-filter-item { display:inline-flex; align-items:center; gap:4px; cursor:pointer; font-size:10px; padding:2px 8px; line-height:1.4; border-radius:10px; background:color-mix(in srgb, var(--bg) 72%, transparent); border:1px solid color-mix(in srgb, var(--fg) 12%, transparent); transition:opacity 0.15s, background 0.15s, border-color 0.15s, transform 0.15s; } -.cal-filter-item:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); } -.cal-filter-item.cal-filter-important { font-weight:700; color:#e5a33a; border-color:color-mix(in srgb, #e5a33a 40%, transparent); } -/* Match the toolbar's other buttons (cal-nav, cal-view-btn, cal-add-btn-text): - 24px tall, 11px font, 5px corners. */ -.cal-filter-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font-size: 11px; - font-family: inherit; - font-weight: 500; - padding: 0 10px; - height: 24px; - line-height: 1; - border-radius: 5px; - box-sizing: border-box; - vertical-align: middle; - opacity: 0.65; - position: relative; - top: -3px; -} -.cal-filter-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } -.cal-filter-toggle-glyph { - display: inline-block; - position: relative; - top: 0; - margin-right: 3px; - color: var(--accent, var(--red)); -} -.cal-filter-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; } -/* The grid is now a flex column: one row of weekday headers + six - .cal-week-row rows. Each row holds the 7 day cells in its own - internal grid plus the absolute-positioned multi-day overlays. - --bars on a week-row is the number of multi-day events overlapping - it; cells in that row use it to reserve top padding so the bar - overlays don't cover the day numbers or single-event rows. */ -.cal-grid { display:flex; flex-direction:column; gap:1px; background:color-mix(in srgb, var(--fg) 8%, transparent); border-radius:6px; overflow:hidden; } -.cal-week-headers { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } -.cal-week-row { position:relative; display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } -.cal-weekday { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); text-align:center; font-size:10px; font-weight:600; opacity:0.4; padding:5px 0; } -.cal-day { - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 34px), - var(--bg); - min-height:78px; - padding:3px; - cursor:pointer; - position:relative; - transition:background 0.12s, box-shadow 0.12s; - overflow:hidden; -} -.cal-day:hover { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); } -.cal-day.cal-today { - box-shadow: inset 0 0 0 2px var(--accent, var(--red)); - background: var(--bg); - border-radius: 8px; -} -.cal-day.cal-selected:not(.cal-today) .cal-day-num { - color: var(--accent, var(--fg)); - opacity: 1; - font-weight: 700; -} -.cal-day.cal-drag-over { background:color-mix(in srgb, var(--accent) 18%, var(--bg)); } -.cal-day.cal-range-select { background:color-mix(in srgb, var(--accent, var(--red)) 25%, var(--bg)); box-shadow:inset 0 0 0 1px var(--accent, var(--red)); } -.cal-day-num { font-size:10px; font-weight:600; display:block; margin-bottom:1px; opacity:0.7; line-height:1.2; } -.cal-dots { display:flex; flex-direction:row; align-items:center; gap:2px; flex-wrap:nowrap; margin-bottom:1px; height:6px; } -.cal-dot { width:5px; height:5px; border-radius:50%; display:inline-block; cursor:grab; flex-shrink:0; } -.cal-dot-more { font-size:7px; opacity:0.4; } -.cal-event-row { - display:flex; - align-items:center; - gap:4px; - padding:2px 4px; - border-radius:4px; - cursor:grab; - line-height:1.2; - margin-bottom:2px; - min-height:17px; - background:color-mix(in srgb, var(--fg) 5%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); - animation:cal-event-enter 0.16s ease-out both; - transition:background 0.15s, box-shadow 0.15s, transform 0.15s; -} -.cal-event-row:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); transform:translateY(-1px); } -.cal-event-row-dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } -.cal-event-row-time { font-size:10px; opacity:0.72; font-variant-numeric:tabular-nums; flex-shrink:0; font-weight:600; } -.cal-event-row-name { font-size:10px; opacity:0.96; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; font-weight:600; } -.cal-event-reminder { - display: inline-flex; - width: 12px; - height: 12px; - margin-left: 5px; - vertical-align: -2px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - opacity: 0.9; - flex: 0 0 auto; -} -.cal-event-reminder svg { - width: 100%; - height: 100%; - display: block; -} -.cal-event-row .cal-event-reminder { - width: 10px; - height: 10px; - margin-left: 4px; -} -.cal-event-source { - display:inline-flex; - align-items:center; - justify-content:center; - width:12px; - height:12px; - margin-left:5px; - border-radius:50%; - font-size:7px; - font-weight:800; - line-height:1; - color:var(--cal-event-fg, var(--fg)); - background:color-mix(in srgb, currentColor 16%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); - vertical-align:1px; -} -.cal-event-source svg { - width: 10px; - height: 10px; - display: block; -} -.cal-event-row .cal-event-source { - width:11px; - height:11px; - font-size:7px; - margin-left:4px; - color:color-mix(in srgb, var(--fg) 72%, transparent); -} -.cal-multiday .cal-event-reminder, -.cal-wk-allday-event .cal-event-reminder { - color: var(--cal-event-fg, currentColor); - opacity: 0.88; -} -.cal-multiday .cal-event-source, -.cal-wk-allday-event .cal-event-source { - color: var(--cal-event-fg, currentColor); -} -.cal-event-more { font-size:9px; opacity:0.55; padding-left:6px; } -/* Multi-day bar — positioned as an absolute overlay inside its - .cal-week-row so it spans uninterrupted across every column it - covers. --col is the starting column (0–6), --span is the number - of days it covers within the row, --slot stacks parallel bars. */ -.cal-multiday { - position: absolute; - /* Fractional offsets let timed events that cross midnight render at - true proportional width. start-frac shifts the left edge into the - first day; end-frac trims the right edge inside the last day. - All-day events default to (0, 1) and still fill whole columns. */ - left: calc((var(--col, 0) + var(--start-frac, 0)) * (100% / 7)); - width: calc( - (var(--span, 1) - var(--start-frac, 0) - (1 - var(--end-frac, 1))) - * (100% / 7) - ); - top: calc(2px + var(--slot, 0) * 12px); - height: 12px; - font-size: 9px; - line-height: 12px; - padding: 0 5px; - border-radius: 4px; - color: var(--cal-event-fg, #fff); - font-weight: 700; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: grab; - opacity: 0.98; - z-index: 2; - pointer-events: auto; - box-shadow: 0 1px 2px color-mix(in srgb, #000 18%, transparent); -} -/* Splitter between the month/week grid and the day-tasks panel — drag - vertically to give the day panel more room. Height clamped by the - `--cal-detail-h` variable set on #cal-body by the splitter drag JS. */ -.cal-splitter { - height: 8px; - margin: 6px -10px 0; - cursor: row-resize; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - user-select: none; - touch-action: none; -} -/* Mobile: fat hitbox so the splitter is easy to grab with a thumb, - without changing how the grip itself looks. */ -@media (max-width: 768px) { - .cal-splitter { - height: 24px; - margin-top: 0; - margin-bottom: 0; - } - .cal-splitter-grip { - width: 56px; - height: 4px; - } -} -.cal-splitter-grip { - width: 42px; - height: 3px; - border-radius: 2px; - background: color-mix(in srgb, var(--fg) 25%, transparent); - transition: background 0.12s; -} -.cal-splitter:hover .cal-splitter-grip, -.cal-splitter-dragging .cal-splitter-grip { - background: var(--accent, var(--red)); -} -.cal-day-detail { - margin-top: 4px; - border-top: 1px solid var(--border); - padding-top: 8px; - height: var(--cal-detail-h, 240px); - overflow-y: auto; - overscroll-behavior: contain; - flex-shrink: 0; -} -.cal-detail-header { display:flex; justify-content:space-between; align-items:center; font-size:12px; font-weight:600; margin-bottom:6px; padding-right:8px; } -.cal-empty { font-size:11px; opacity:0.3; padding:4px 0; } -.cal-event-item { display:flex; gap:9px; padding:8px 9px; border-radius:6px; cursor:pointer; align-items:flex-start; background:color-mix(in srgb, var(--fg) 3.5%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); margin-bottom:4px; } -.cal-event-item:hover { background:color-mix(in srgb, var(--fg) 8%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); } -.cal-event-dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:4px; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } -.cal-event-info { flex:1; min-width:0; } -.cal-event-name { font-size:13px; font-weight:700; line-height:1.25; color:color-mix(in srgb, var(--fg) 96%, transparent); overflow-wrap:anywhere; } -.cal-event-tag { - display: inline-block; - font-size: 10px; - font-weight: 500; - padding: 1px 6px; - border-radius: 8px; - border: 1px solid; - margin-left: 6px; - vertical-align: 1px; - opacity: 0.85; - white-space: nowrap; -} -.cal-event-time { font-size:11px; opacity:0.68; line-height:1.35; margin-top:2px; font-weight:500; } -.cal-event-loc { font-size:11px; opacity:0.58; line-height:1.35; margin-top:1px; } -/* ── Week view: hour grid ────────────────────────────────────────── - Vertical hour rail on the left, 7 day columns on the right with their - own all-day strip and an absolute-positioned hour grid below it. */ -.cal-wk-wrap { - display: flex; - flex-direction: row; - gap: 0; - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 3%, transparent), transparent 120px), - var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - /* The wrap itself scrolls so the hour rail and day columns move - together; column headers and the rail spacer are sticky-pinned. */ - overflow: auto; - max-height: calc(100vh - 220px); - min-height: 360px; - position: relative; -} -.cal-wk-now-below-hint { - position: absolute; - height: 2px; - border-radius: 2px 2px 0 0; - background: var(--accent, var(--red)); - pointer-events: none; - z-index: 38; -} -@media (max-width: 768px) { - .cal-wk-wrap { - -webkit-overflow-scrolling: touch; - overscroll-behavior-y: contain; - } - .cal-wk-wrap > .cal-wk-rail, - .cal-wk-wrap > .cal-wk-cols { - transform: translateY(var(--wk-edge-pull, 0)); - } - .cal-wk-wrap.cal-wk-edge-release > .cal-wk-rail, - .cal-wk-wrap.cal-wk-edge-release > .cal-wk-cols { - transition: transform 0.14s cubic-bezier(0.2, 0.75, 0.25, 1); - } - .cal-wk-wrap.cal-wk-pull-top { - border-top-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); - } - .cal-wk-wrap.cal-wk-pull-bottom { - border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); - } -} -.cal-wk-rail { - flex: 0 0 56px; - border-right: 1px solid var(--border); - display: flex; - flex-direction: column; - background: color-mix(in srgb, var(--fg) 2%, var(--bg)); - position: sticky; - left: 0; - z-index: 5; -} -.cal-wk-rail-spacer { - /* Match the 32px day header. The 24px all-day row gets its own spacer - below, so the zoom controls sit high without shifting the hour grid. */ - height: 32px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 0; - background: color-mix(in srgb, var(--fg) 2%, var(--bg)); - z-index: 9; - display: flex; - align-items: center; - justify-content: center; - gap: 2px; - padding: 0 4px; -} -.cal-wk-rail-allday-spacer { - height: 24px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 32px; - z-index: 8; - background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); -} -.cal-wk-zoom { - position: relative; - top: -3px; - width: 22px; - height: 22px; - border: 1px solid var(--border); - background: var(--bg); - color: var(--fg); - border-radius: 50%; - font-family: inherit; - font-size: 14px; - font-weight: 600; - line-height: 1; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - opacity: 0.55; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.cal-wk-zoom:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--fg)) 50%, var(--border)); -} -.cal-wk-rail-cell { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 55%, transparent); - padding: 4px 8px 0; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); - flex-shrink: 0; - display: flex; - align-items: flex-start; - justify-content: flex-end; - font-variant-numeric: tabular-nums; -} -.cal-wk-cols { - flex: 1; - display: grid; - grid-template-columns: repeat(7, 1fr); - position: relative; -} -.cal-wk-col { - border-left: 1px solid var(--border); - display: flex; - flex-direction: column; - min-width: 0; -} -.cal-wk-col:first-child { border-left: none; } -.cal-wk-col-head { - height: 32px; - padding: 0 6px; - display: flex; - align-items: center; - gap: 6px; - font-size: 11px; - flex-shrink: 0; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); - position: sticky; - top: 0; - z-index: 40; -} -/* Today on a Sunday still wins the accent treatment. */ -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); } -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dn, -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dt { color: var(--accent, var(--fg)); } -.cal-wk-dn { font-weight: 600; opacity: 0.6; } -.cal-wk-allday { - height: 24px; - padding: 2px 4px; - display: flex; - flex-direction: row; - gap: 2px; - align-items: center; - background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); - border-bottom: 1px solid var(--border); - flex-shrink: 0; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: none; - position: sticky; - top: 32px; - z-index: 39; -} -.cal-wk-allday-event { - font-size: 10.5px; - font-weight: 700; - padding: 3px 6px; - border-radius: 4px; - color: var(--cal-event-fg, var(--fg)); - cursor: pointer; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Hour grid body */ -.cal-wk-grid { - position: relative; - flex: 0 0 auto; - cursor: cell; - user-select: none; - background: - repeating-linear-gradient( - to bottom, - transparent 0, - transparent calc(var(--wk-hour-px, 52px) / 2 - 1px), - color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--wk-hour-px, 52px) / 2), - transparent calc(var(--wk-hour-px, 52px) / 2 + 1px) - ); -} -.cal-wk-cell { - border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); - box-sizing: border-box; -} -.cal-wk-cell:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); } -/* "Now" line */ -.cal-wk-now { - position: absolute; - left: 0; right: 0; - height: 0; - border-top: 2px solid color-mix(in srgb, var(--accent, var(--red, #f87171)) 90%, transparent); - pointer-events: none; - z-index: 7; - filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent)); -} -/* Event blocks */ -.cal-wk-block { - position: absolute; - left: calc((var(--lane, 0) * (100% / var(--lane-count, 1))) + 2px); - width: calc((100% / var(--lane-count, 1)) - 4px); - right:auto; - border-left: 4px solid var(--fg); - border-radius: 5px; - padding: 5px 7px 7px; - font-size: 12px; - line-height: 1.3; - cursor: pointer; - /* `clip` preserves the rounded-card boundary without creating an overflow - container, allowing the label inside to stick to the week scroller. */ - overflow: clip; - z-index: 2; - box-shadow: 0 2px 6px color-mix(in srgb, #000 14%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); - animation:cal-event-enter 0.18s ease-out both; - transition: width 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), height 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), filter 0.12s, transform 0.12s, box-shadow 0.16s; - box-sizing: border-box; -} -.cal-wk-block:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); } -.cal-wk-block-label { - position: sticky; - top: 37px; - z-index: 1; - min-width: 0; - pointer-events: none; -} -.cal-wk-has-allday .cal-wk-block-label { - top: 61px; -} -.cal-wk-block-name { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color:color-mix(in srgb, var(--fg) 96%, transparent); } -.cal-wk-block-time { font-size: 10.5px; opacity: 0.82; font-variant-numeric: tabular-nums; margin-top: 2px; font-weight:600; } -@media (hover: hover) and (pointer: fine) { - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left { - left: auto; - right: calc(100% - var(--lane-right) + 2px); - } - .cal-wk-block.cal-wk-block-crowded:hover { - width: var(--hover-width, 170px); - height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; - overflow: clip; - z-index: 30; - filter: brightness(1.08); - transform: translateY(var(--hover-shift-y, -2px)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-hover-width-only:hover { - height: var(--event-height) !important; - transform: translateY(-1px); - } - .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-label { - position: absolute; - top: 5px; - width: calc(var(--hover-width, 170px) - 18px); - animation: cal-wk-hover-label-reveal 0.2s ease-out both; - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left:hover .cal-wk-block-label { - left: auto; - right: 7px; - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-right:hover .cal-wk-block-label { - left: 7px; - right: auto; - } - .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-name { - display: block; - white-space: normal; - overflow: visible; - text-overflow: clip; - overflow-wrap: anywhere; - } -} -@media (max-width: 768px) { - /* Mobile week view: the hour rail already shows the time, so drop it from the - card and let the title wrap to fill the freed space (more readable text). */ - .cal-wk-block-time { display: none; } - .cal-wk-block-name { - white-space: normal; - line-height: 1.15; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left { - left: auto; - right: calc(100% - var(--lane-right) + 2px); - } - .cal-wk-block.cal-wk-mobile-expanded { - width: var(--hover-width, 170px); - height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; - overflow: clip; - z-index: 30; - filter: brightness(1.08); - transform: translateY(var(--hover-shift-y, 0)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); - } - .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-label { - position: absolute; - top: 5px; - width: calc(var(--hover-width, 170px) - 18px); - animation: cal-wk-hover-label-reveal 0.2s ease-out both; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left .cal-wk-block-label { - left: auto; - right: 7px; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-right .cal-wk-block-label { - left: 7px; - right: auto; - } - .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-name { - display: block; - white-space: normal; - overflow: visible; - text-overflow: clip; - overflow-wrap: anywhere; - -webkit-line-clamp: unset; - } -} -/* Resize handle: a 6 px hit zone along the block's bottom edge. The - visible 2-px stripe only shows on hover so blocks at rest stay clean. */ -.cal-wk-block-resize { - position: absolute; - left: 4px; - right: 4px; - bottom: 0; - height: 6px; - cursor: ns-resize; - z-index: 3; -} -/* Drag-to-create ghost */ -.cal-wk-ghost { - position: absolute; - left: 2px; - right: 2px; - background: color-mix(in srgb, var(--accent, var(--fg)) 28%, transparent); - border: 1px dashed color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent); - border-radius: 4px; - pointer-events: none; - z-index: 3; - font-size: 10px; - font-weight: 600; - display: flex; - align-items: center; - justify-content: center; - color: var(--accent, var(--fg)); - font-variant-numeric: tabular-nums; -} -.cal-wk-slot-selected { - background: color-mix(in srgb, var(--accent, var(--fg)) 20%, var(--bg)); - border-style: solid; - border-color: var(--accent, var(--fg)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--fg)) 20%, transparent); - overflow: visible; - pointer-events: auto; - cursor: pointer; -} -.cal-wk-slot-resize { - position: absolute; - left: 14%; - right: 14%; - bottom: -7px; - height: 14px; - z-index: 4; - cursor: ns-resize; - touch-action: none; - pointer-events: auto; -} -.cal-year { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; } -.cal-year-month { background:color-mix(in srgb, var(--fg) 3%, var(--bg)); border:1px solid var(--border); border-radius:6px; padding:6px; cursor:pointer; transition:background 0.15s, border-color 0.15s, transform 0.15s; } -.cal-year-month:hover { background:color-mix(in srgb, var(--fg) 8%, var(--bg)); border-color:var(--accent); transform:translateY(-1px); } -.cal-year-month-title { font-size:11px; font-weight:600; text-align:center; margin-bottom:4px; opacity:0.7; } -.cal-year-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; } -.cal-year-wd { font-size:7px; text-align:center; opacity:0.3; padding:1px 0; } -.cal-year-cell { font-size:8px; text-align:center; padding:2px 0; min-height:14px; border-radius:2px; transition:transform 0.12s, background 0.12s; } -.cal-year-day:hover { background:var(--accent, var(--red)); color:#fff; opacity:1; transform:scale(1.15); font-weight:700; z-index:2; position:relative; } -.cal-year-today { color:var(--accent, var(--red)); font-weight:700; opacity:1; } -.cal-year-has { opacity:1; background:color-mix(in srgb, var(--accent) 15%, transparent); } -.cal-year-has.cal-year-today { background:color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } -.cal-loading { display:flex; justify-content:center; padding:40px 0; } -.cal-badge { background:var(--accent); border-radius:50%; width:6px; height:6px; margin-left:auto; display:inline-block; flex-shrink:0; } -/* Search input — in-panel variant lives inside the day-detail panel and - spans its width; no width animation since growing/shrinking on every - keystroke would jitter beside the events list. */ -.cal-search-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:0 8px; height:24px; color:var(--fg); font-size:11px; font-family:inherit; width:120px; box-sizing:border-box; transition:width 0.15s; } -.cal-search-input:focus { outline:none; border-color:var(--accent); width:180px; } -.cal-search-input.cal-day-search { width:100%; margin-bottom:0; transition:none; padding-left: 28px; } -/* Wrap for the search input so the magnifying-glass icon can be absolute- - positioned inside the field. */ -.cal-search-wrap { - position: relative; - display: block; - margin-bottom: 6px; -} -.cal-search-wrap .cal-search-icon { - position: absolute; - left: 8px; - top: 50%; - transform: translateY(-50%); - pointer-events: none; - color: var(--accent, var(--red)); -} -.cal-day-search-meta { font-size:10px; opacity:0.5; margin:0 2px 6px; } -.cal-search-results { display:flex; flex-direction:column; gap:4px; } -.cal-search-count { font-size:10px; opacity:0.5; padding:4px 2px 8px; } -/* Agenda view */ -.cal-agenda { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-left:4px; } -.cal-agenda-day { display:flex; flex-direction:column; gap:5px; position:relative; padding-left:14px; } -.cal-agenda-date { font-size:12px; font-weight:800; opacity:0.72; padding:5px 2px 3px; border-bottom:1px solid var(--border); } -.cal-agenda-today-badge { - display: inline-block; - margin-left: 6px; - font-size: 9px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - vertical-align: 1px; -} -.cal-agenda-empty { font-size:11px; opacity:0.4; padding:8px; font-style:italic; } -.cal-agenda-event { - display:flex; - gap:9px; - padding:9px 10px; - border-radius:6px; - cursor:pointer; - align-items:center; - position:relative; - background:color-mix(in srgb, var(--fg) 3.5%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); - animation:cal-event-enter 0.18s ease-out both; - transition:background 0.15s, box-shadow 0.15s, transform 0.15s; -} -.cal-agenda-event:hover { - background:color-mix(in srgb, var(--fg) 8%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); - transform:translateX(2px); -} -.cal-agenda-event .cal-event-more, -.cal-event-item .cal-event-more { opacity:0; transition:opacity 0.15s; } -.cal-event-item { position:relative; } - -/* ── Personal Assistant ── */ -.sidebar-assistant-entry { gap: 6px; min-height: 29px; box-sizing: border-box; align-items: center; } -.sidebar-assistant-entry .sidebar-action-icon { position: relative; left: -2px; } -#sidebar-assistant-gear:hover { opacity: 0.8 !important; } -.assistant-settings-form { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; } -.assistant-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: color-mix(in srgb, var(--fg) 70%, transparent); } -.assistant-field > span { font-size: 11px; opacity: 0.6; } -.assistant-field input[type="text"], -.assistant-field input[type="time"], -.assistant-field select, -.assistant-field textarea { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 5px; - padding: 7px 10px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - box-sizing: border-box; -} -.assistant-field textarea { resize: vertical; min-height: 60px; } -.assistant-field input:focus, -.assistant-field select:focus, -.assistant-field textarea:focus { outline: none; border-color: var(--accent, var(--red)); } -.assistant-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } -.assistant-field-row > .assistant-field { flex: 1 1 180px; } -.assistant-field-check { flex-direction: row !important; align-items: center; gap: 6px; padding-bottom: 7px; } -.assistant-checkins { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); } -.assistant-checkins h5 { margin: 0 0 2px; font-size: 12px; font-weight: 600; opacity: 0.8; } -.assistant-checkin-row { - display: flex; - flex-direction: column; - gap: 4px; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.assistant-checkin-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } -.assistant-checkin-head input[type="text"], -.assistant-checkin-head input[type="time"] { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 4px; - padding: 4px 8px; - color: var(--fg); - font-size: 12px; - font-family: inherit; -} -.assistant-checkin-head input[type="text"] { flex: 1; min-width: 120px; } -.assistant-checkin-head input[type="time"] { width: 90px; } -.assistant-checkin-run { - background: none; - border: 1px solid var(--border); - color: var(--fg); - font-family: inherit; - font-size: 11px; - padding: 4px 8px; - border-radius: 4px; - cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s; -} -.assistant-checkin-run:hover { opacity: 1; } -.assistant-checkin-prompt { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 4px; - padding: 6px 8px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - resize: vertical; - min-height: 48px; -} -.assistant-checkin-prompt:focus { outline: none; border-color: var(--accent, var(--red)); } -.assistant-checkin-meta { font-size: 10px; opacity: 0.45; } -.assistant-settings-actions { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 8px; - padding-top: 8px; - border-top: 1px solid var(--border); -} -.assistant-tools-grid { - display: flex; - flex-direction: column; - gap: 6px; - max-height: 240px; - overflow-y: auto; - padding: 6px; - border: 1px solid var(--border); - border-radius: 6px; - margin-top: 4px; -} -.assistant-tool-group { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.assistant-tool-group-label { - font-size: 10px; - font-weight: 600; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.03em; - width: 100%; - margin-bottom: 1px; -} -.assistant-tool-item { - display: inline-flex; - align-items: center; - gap: 3px; - font-size: 11px; - padding: 2px 6px; - border: 1px solid var(--border); - border-radius: 4px; - cursor: pointer; - transition: background 0.1s, border-color 0.1s; - user-select: none; -} -.assistant-tool-item:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.assistant-tool-item:has(input:checked) { - border-color: var(--accent); - background: color-mix(in srgb, var(--accent) 10%, transparent); -} -.assistant-tool-item input[type="checkbox"] { - width: 12px; - height: 12px; - margin: 0; -} - -/* Empty state */ -.cal-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px 20px; text-align:center; gap:10px; } -.cal-empty-title { font-size:14px; font-weight:600; opacity:0.8; } -.cal-empty-msg { font-size:12px; opacity:0.5; max-width:320px; line-height:1.5; margin-bottom:8px; } -.cal-allday-switch { margin:0; flex-shrink:0; } -/* Stack the "All day" label above its toggle so it doesn't get squeezed / - truncated next to the date inputs in the row. */ -.cal-allday-ctrl { display:flex; flex-direction:column; align-items:center; gap:3px; flex-shrink:0; } -.cal-allday-label { font-size:10px; opacity:0.5; white-space:nowrap; line-height:1; } -.cal-form { display:flex; flex-direction:column; gap:8px; } -.cal-form-title { font-size:13px; font-weight:600; margin-bottom:2px; } -.cal-title-wrap { position: relative; } -/* Calendar-picker select sits 2px higher; its border-left/background tint is - set in JS to the chosen calendar's colour. */ -.cal-f-cal-select { position: relative; top: -4px; } - -/* ── Bespoke event form ──────────────────────────────────────────── - Big clock-face date/time hero with the title under it. Details panel - stays collapsed until the title gets focus or "Add details" is clicked. -*/ -.cal-form-bespoke { - position: relative; - width: min(720px, calc(100% - 24px)); - max-width: none; - margin: 12px auto; - padding: 18px 28px 14px; - box-sizing: border-box; - background: var(--bg); - /* Optional per-event tint set by JS via --ev-color. When unset, falls - back to the theme's neutral border so the card looks like normal. */ - border: 1px solid var(--border); - border-radius: 14px; - box-shadow: 0 8px 24px color-mix(in srgb, var(--fg) 6%, transparent); - transition: border-color 0.18s, box-shadow 0.18s; -} -/* When a colour is picked, lift the card with a soft tinted border + glow - so the choice is unmistakable. */ -.cal-form-bespoke[style*="--ev-color"] { - border-color: color-mix(in srgb, var(--ev-color) 55%, var(--border)); - box-shadow: 0 8px 28px color-mix(in srgb, var(--ev-color) 22%, transparent); -} -.cal-form-bespoke[style*="--ev-color"] .cal-btn-primary { - background: var(--ev-color); - border-color: var(--ev-color); -} -.cal-form-bespoke.cal-form-bg-image .cal-btn-primary { - background: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); - color: var(--bg); -} -.cal-form-close { - position: absolute; top: 10px; right: 12px; - background: none; border: none; color: var(--fg); - opacity: 0.35; cursor: pointer; font-size: 20px; line-height: 1; - padding: 4px 8px; border-radius: 6px; -} -.cal-form-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cal-form-mobile-cancel { display: none; } - -/* "Today is …" header pinned at the top of the form. Keeps you oriented - even when the event date you're picking is way off in the future. */ -.cal-form-today { - text-align: center; - font-size: 11px; - letter-spacing: 0.08em; - text-transform: uppercase; - opacity: 0.45; - margin-bottom: 0; -} - -/* Hero — clock face + date label. Spacing between the two lives entirely - on the clock's bottom margin so it's predictable. */ -.cal-hero { - display: flex; flex-direction: column; align-items: center; - gap: 0; margin-bottom: 12px; -} -.cal-hero-time { - display: inline-flex; align-items: baseline; gap: 8px; - font-family: ui-monospace, SFMono-Regular, "Menlo", "Consolas", monospace; - font-variant-numeric: tabular-nums; - font-size: 56px; - letter-spacing: 0.02em; - font-weight: 200; - color: var(--fg); - line-height: 1; - /* Behaves like a button — strip native chrome but keep it tappable. */ - background: none; - border: none; - /* Pill that reads as a real hit target for a 56-px clock face. - Top padding kept tight so the clock sits close to the "Today is" line. */ - padding: 6px 38px 18px; - margin: 0 0 24px; - border-radius: 18px; - cursor: pointer; - line-height: 1; - transition: background 0.15s, color 0.15s; -} -/* The clock is a `` and a global `button:hover` rule paints - every button's background on hover. Suppress it explicitly. */ -.cal-hero-time:hover { background: transparent; border-color: transparent; } -.cal-hero-clock { font-feature-settings: "tnum"; display: inline-flex; align-items: baseline; } -/* Per-segment hover so it's clear hh vs mm are individually clickable. */ -.cal-hero-clock-hh, .cal-hero-clock-mm { - display: inline-block; - border-radius: 6px; - padding: 0 2px; - transition: background 0.12s; -} -.cal-hero-clock-hh:hover, .cal-hero-clock-mm:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); -} -.cal-hero-sep { padding: 0 2px; opacity: 0.55; pointer-events: none; } -.cal-hero-ampm { - font-size: 14px; - letter-spacing: 0.12em; - opacity: 0.55; - font-weight: 500; - text-transform: uppercase; -} -.cal-hero-date { - font-size: 16px; - opacity: 0.6; - letter-spacing: 0.04em; - background: none; - border: none; - color: inherit; - font-family: inherit; - padding: 4px 10px; - border-radius: 6px; - cursor: pointer; - margin: 7px 0 0; /* extra nudge below the clock */ - transition: background 0.15s, opacity 0.15s; -} -.cal-hero-date:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); opacity: 0.85; } - -/* Title input — flat, large, no chrome until focused */ -.cal-input.cal-hero-title { - font-size: 18px; - text-align: left; - padding: 10px 12px 10px 0; - background: transparent; - border: 1px solid transparent; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); - border-radius: 0; - margin-bottom: 4px; -} - -/* Details panel — animated reveal via max-height + opacity. */ -.cal-form-bespoke .cal-form-details { - display: block; - max-height: 0; - opacity: 0; - overflow: hidden; - transition: max-height 240ms ease, opacity 180ms ease, margin 220ms ease; - margin-top: 0; -} -/* The detail children themselves keep the existing flex/gap rules from - the regular .cal-form, so we don't restyle inputs/rows here. */ -.cal-form-bespoke .cal-form-details > * + * { margin-top: 8px; } - - -/* Location row: input + Apple Maps pin link. Pin only lights up when the - field has content; clicking opens maps.apple.com (native Maps app on - Apple devices, web fallback elsewhere). All theme vars. */ -.cal-loc-row { - display: flex; - align-items: stretch; - gap: 6px; -} -.cal-loc-input-wrap { - position: relative; - flex: 1; - min-width: 0; -} -.cal-loc-hint { - position: absolute; - inset: 0 auto 0 10px; - display: none; - align-items: center; - gap: 5px; - pointer-events: none; - color: color-mix(in srgb, var(--fg) 42%, transparent); - font-size: 12px; -} -.cal-loc-map { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - flex-shrink: 0; - border: 1px solid var(--border); - border-radius: 5px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - text-decoration: none; - transition: background 0.15s, border-color 0.15s, opacity 0.15s; -} -.cal-loc-map:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); border-color: var(--accent, var(--red)); } - -@media (max-width: 520px) { - .cal-hero-time { font-size: 44px; } - .cal-form-bespoke { margin: 16px 12px; padding: 32px 16px 14px; } - .cal-form-mobile-cancel { - display: inline-flex; - position: absolute; - top: 8px; - right: 8px; - width: 30px; - height: 30px; - align-items: center; - justify-content: center; - padding: 0; - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--bg) 82%, transparent); - color: var(--fg); - opacity: 0.72; - cursor: pointer; - z-index: 3; - -webkit-tap-highlight-color: transparent; - } - .cal-form-mobile-cancel:active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); - } -} -.cal-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:7px 10px; color:var(--fg); font-size:12px; width:100%; box-sizing:border-box; } -.cal-input:focus { outline:none; border-color:var(--accent); } -.cal-input-time { width:auto; flex:1; } -.cal-form-row { display:flex; gap:8px; align-items:center; } -.cal-form-actions { display:flex; gap:8px; justify-content:flex-end; margin-top:4px; } button.cal-btn { background:color-mix(in srgb, var(--fg) 8%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 14px; height:28px; font-size:11px; font-weight:500; cursor:pointer; font-family:inherit; display:inline-flex; align-items:center; justify-content:center; box-sizing:border-box; transition:background 0.15s, border-color 0.15s; } button.cal-btn:hover { background:color-mix(in srgb, var(--fg) 14%, transparent); } button.cal-btn.cal-btn-primary { background:var(--accent, var(--red)); color:var(--bg); border-color:var(--accent, var(--red)); } diff --git a/static/css/style-part-02.css b/static/css/style-part-02.css deleted file mode 100644 index a3ceff612..000000000 --- a/static/css/style-part-02.css +++ /dev/null @@ -1,6011 +0,0 @@ -/* Fragment 3/9 of the former static/style.css. - * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. - * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. - */ - .loading-dot:nth-child(1) { - animation: loading-bounce 1.4s infinite ease-in-out both; - } - .loading-dot:nth-child(2) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.32s; - } - .loading-dot:nth-child(3) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.64s; - } - .session-loading-state { - min-height: 100%; - display: flex; - flex-direction: column; - justify-content: flex-start; - gap: 12px; - color: var(--fg); - font-size: 12px; - pointer-events: none; - position: relative; - padding: 16px 14px 24px; - box-sizing: border-box; - overflow: hidden; - } - .session-loading-state::before { content: none; } - .session-skeleton-bubble { - width: min(78%, 680px); - border: 1px solid color-mix(in srgb, var(--border) 74%, transparent); - border-radius: 10px; - background: color-mix(in srgb, var(--panel, var(--bg)) 72%, transparent); - padding: 10px 12px 12px; - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.14); - } - .session-skeleton-bubble.is-user { - align-self: flex-end; - width: min(64%, 520px); - } - .session-skeleton-bubble.is-ai { - align-self: flex-start; - } - .session-skeleton-line { - position: relative; - overflow: hidden; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - border-radius: 999px; - } - .session-skeleton-line { - height: 10px; - margin-top: 8px; - opacity: 0.62; - } - .session-skeleton-line::after { - content: ''; - position: absolute; - inset: -1px; - transform: translateX(-120%); - background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--fg) 14%, transparent), transparent); - animation: session-skeleton-shimmer 1.25s ease-in-out infinite; - } - .session-skeleton-bubble:nth-child(2) .session-skeleton-line::after { animation-delay: 0.12s; } - .session-skeleton-bubble:nth-child(3) .session-skeleton-line::after { animation-delay: 0.24s; } - .session-skeleton-bubble:nth-child(4) .session-skeleton-line::after { animation-delay: 0.36s; } - @keyframes session-skeleton-shimmer { - 100% { transform: translateX(120%); } - } - @media (max-width: 768px) { - .session-loading-state { padding: 12px 10px 18px; gap: 10px; } - .session-skeleton-bubble { width: 86%; padding: 9px 10px 11px; border-radius: 9px; } - .session-skeleton-bubble.is-user { width: 72%; } - .session-skeleton-line { height: 9px; margin-top: 7px; } - } - @keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } - } - /* Modal styling */ - .modal { - position:fixed; - top:0; left:0; width:100%; height:100%; - background:none; - display:flex; align-items:center; justify-content:center; - z-index:250; - backdrop-filter:none; - pointer-events:none; - } - /* Cookbook always sits above Gallery so the "Serve a model in - Cookbook…" flow (and any other "open Cookbook from inside another - modal") is visible no matter which modal was opened first. */ - #cookbook-modal { z-index: 260; } - .modal.hidden { display:none; } - - /* Tool windows open centered in the CHAT AREA (the space right of the - sidebar + icon rail), rather than the full viewport. - We narrow the overlay to the chat area so its flex-centering lands the - window there; fullscreen / docked states use position:fixed so they - escape this narrowed overlay and still fill the screen. The - --sidebar-w / --icon-rail-w vars track collapse state live, so when a - window (e.g. Cookbook) hides the sidebar this naturally re-centers. - Desktop only — on mobile these are full-screen sheets. */ - @media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal { - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)); - width: calc(100% - (var(--icon-rail-w, 48px) + var(--sidebar-w, 0px))); - box-sizing: border-box; - /* Slide in sync with the sidebar's 0.25s collapse/expand so the - centered window glides instead of jumping when the nav toggles. */ - transition: left 0.25s ease, width 0.25s ease; - } - } - .modal-content { - background:var(--panel); - border:1px solid var(--border); - width:min(520px, 92vw); max-height:85vh; padding:10px; - box-sizing:border-box; font-size:14px; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - letter-spacing: -0.015em; - display:flex; flex-direction:column; - position:relative; - overflow-y:auto; - border-radius:10px; - box-shadow:0 8px 32px rgba(0,0,0,0.45); - pointer-events:auto; - animation: modal-enter 0.25s ease-out both; - } - .memory-modal-content, - .tasks-modal-content, - .preset-modal-content, - #cookbook-modal .modal-content, - #theme-popup, - .doclib-modal-content, - .gallery-modal-content { - container-type: inline-size; - } - .modal-header { - display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; - cursor:grab; user-select:none; - /* Pin the header (with its close button) to the top of the - scrollable modal-content so users can always dismiss the modal - even after scrolling far down — especially important on mobile - where the modal can be taller than the viewport. */ - position: sticky; - top: 0; - z-index: 5; - /* Inherit the modal-content's background so the header matches the - panel body. Many tool modals set their content to var(--bg) inline - while this header was hard-coded to var(--panel) — making the title - strip a different shade in every theme. `inherit` tracks whatever - the content uses (var(--bg) or the default var(--panel)) and stays - opaque, so the sticky header still masks scrolled content. */ - background-color: inherit; - } - .modal-header:active { cursor:grabbing; } - /* Edge/corner window resize (windowResize.js). While a resize is in - progress, suppress text selection and force the active resize cursor - across the whole document so it does not flicker as the pointer passes - over child elements mid-drag. */ - body.window-resizing-active { user-select:none !important; } - body.window-resizing-active * { cursor:inherit !important; } - /* Suppress only TRANSITIONS while resizing so the edge tracks the cursor - crisply. We deliberately do NOT toggle `animation` here: toggling - animation off→on re-triggers the modal open-animation (a scale-in) on - mouseup, which both mis-measures the final size and visibly "pops" the - window. windowResize.js instead kills the one-shot open animation inline - once, in begin(). */ - .window-resizing { - transition:none !important; - } - /* Cookbook opts out of the sticky header on mobile; the title bar scrolls - away with the content instead of following. Background is inherited from - the shared .modal-header rule so frosted/glass themes stay consistent. */ - #cookbook-modal .modal-header { - position: static; - top: auto; - } - .modal-header h4 { - margin:0; - /* Push every header control (opacity slider, minimize, close) into one - group on the right — works whether or not optional controls like the - opacity slider are present, so the minimize button never floats to - the centre when a sibling is hidden. */ - margin-right:auto; - font-size:1rem; - font-weight:600; - letter-spacing:-0.03em; - color:var(--red); - } - /* Headers with an optional Peek control already have the title's - auto-margin grouping all right-side controls. A second auto-margin on - the injected minimize button splits the group and makes it look - different from the other modal headers. */ - .modal-header .theme-opacity-wrap + .modal-minimize-btn { - margin-left: 0 !important; - } - .close-btn, - .modal-close { - background:transparent; - color:color-mix(in srgb, var(--fg) 58%, transparent); - border:1px solid transparent; - font-size:0; - width:24px; - height:24px; - padding:0; - display:inline-flex; - align-items:center; - justify-content:center; - line-height:1; - text-indent:0; - cursor:pointer; - border-radius:999px; - line-height:1; - flex-shrink:0; - position:relative; - } - .close-btn > svg, - .modal-close > svg { - display:none; - } - .close-btn::before, - .modal-close::before { - content:""; - width:10px; - height:10px; - display:block; - background:currentColor; - -webkit-mask: - url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") - center / contain no-repeat; - mask: - url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") - center / contain no-repeat; - } - .close-btn:hover, - .modal-close:hover { - background:color-mix(in srgb, var(--fg) 10%, transparent); - border-color:color-mix(in srgb, var(--fg) 12%, transparent); - color:var(--fg); - } - /* Minimize button — sits beside the close button on every modal */ - .minimize-btn { - background:transparent; - color:color-mix(in srgb, var(--fg) 58%, transparent); - border:1px solid transparent; - font-size:0; - font-weight:700; - width:24px; - height:24px; - padding:0; - display:inline-flex; - align-items:center; - justify-content:center; - line-height:1; - cursor:pointer; - border-radius:999px; - flex-shrink:0; - margin-left:auto; /* push to the right so it docks next to .close-btn */ - margin-right:4px; - } - .minimize-btn::before { - content:""; - width:9px; - height:2px; - border-radius:999px; - background:currentColor; - display:block; - transform:translateY(2px); - } - .modal-minimize-btn.minimize-btn::before { - content:none; - display:none; - } - .minimize-btn:hover { - background:color-mix(in srgb, var(--fg) 10%, transparent); - border-color:color-mix(in srgb, var(--fg) 12%, transparent); - color:var(--fg); - } - @media (max-width: 768px) { - .minimize-btn { display: none !important; } - #modal-dock { display: none !important; } - } - /* Minimized modals are hidden but stay in the DOM with their state intact */ - .modal.minimized { display:none !important; } - /* Bottom dock for minimized modals */ - #modal-dock { - position:fixed; - bottom:var(--composer-clearance, 0px); - left:0; - right:0; - display:flex; - flex-wrap:wrap; - gap:4px; - padding:4px 8px; - z-index:240; - pointer-events:none; - justify-content:flex-start; - } - #modal-dock:empty { display:none; } - .modal-dock-item { - background:var(--panel); - border:1px solid var(--border); - border-bottom:none; - border-radius:6px 6px 0 0; - padding:4px 4px 4px 10px; - display:inline-flex; - align-items:center; - gap:6px; - cursor:pointer; - pointer-events:auto; - font-size:12px; - color:var(--fg); - max-width:220px; - box-shadow:0 -2px 8px rgba(0,0,0,0.25); - transition:background 0.15s; - } - .modal-dock-item:hover { background:var(--bg); } - .modal-dock-label { - white-space:nowrap; - overflow:hidden; - text-overflow:ellipsis; - max-width:180px; - } - .modal-dock-close { - background:transparent; - border:none; - color:var(--fg); - cursor:pointer; - font-size:14px; - padding:0 4px; - line-height:1; - opacity:0.6; - } - .modal-dock-close:hover { color:var(--red); opacity:1; } - .modal-body { flex:1; overflow-y:auto; } - .modal-body button { margin-top:6px; } - /* Styled confirm dialog — keeps backdrop */ - #styled-confirm-overlay { - background:rgba(0,0,0,0.5); - backdrop-filter:blur(4px); - pointer-events:auto !important; - z-index: 99999 !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - top: 0; left: 0; width: 100%; height: 100%; - } - #styled-confirm-overlay .modal-content { - position: relative; - z-index: 10001; - } - .email-attachment-warning-modal { - z-index: 99999 !important; - } - .email-attachment-warning-modal .modal-content { - position: relative; - z-index: 100001; - } - .styled-confirm-box { - width:360px; max-width:90vw; - max-height:none; /* override modal-content's 85vh */ - padding:14px 18px; - } - .styled-confirm-box .modal-header { margin-bottom:4px; } - .styled-confirm-box .modal-body p { - margin:8px 0 12px; color:var(--fg); font-size:0.92rem; line-height:1.45; - white-space:pre-line; - } - .styled-confirm-box .modal-footer { - display:flex; justify-content:flex-end; gap:8px; padding-top:6px; - border-top:1px solid var(--border); margin-top:4px; - } - @media (max-width:768px) { - .styled-confirm-box { - width: 85vw; - padding: 12px 16px; - font-size: 0.88rem; - border-radius: 12px; - } - .styled-confirm-box .modal-header h4 { font-size: 0.9rem; } - .styled-confirm-box .modal-body p { font-size: 0.85rem; margin: 6px 0 10px; } - .styled-confirm-box .modal-footer { gap: 10px; } - .styled-confirm-box .confirm-btn { - flex: 1; - /* More bottom than top padding nudges the label up ~2px so it isn't - sitting low in the taller mobile buttons. */ - padding: 8px 12px 12px; - font-size: 0.85rem; - border-radius: 8px; - text-align: center; - } - } - .confirm-btn { - /* Asymmetric padding nudges the label UP ~2px from where it was (more - bottom than top padding), so the confirm-dialog text isn't sitting low. */ - padding:3px 16px 5px; border-radius:4px; font-size:0.85rem; - cursor:pointer; border:1px solid var(--border); - font-family:inherit; - } - @media (max-width: 820px) { - /* Mobile: flip the asymmetry to shift the text 1 px UP from - centre instead (the bigger touch targets on mobile read better - with the label sitting slightly high). */ - .confirm-btn { padding:2px 16px 4px; } - } - .confirm-btn-secondary { background:var(--bg); color:var(--fg); } - .confirm-btn-secondary:hover { background:var(--border); } - #styled-confirm-cancel .styled-confirm-cancel-label { - position:relative; - top:2px; - } - .confirm-btn-primary { background:var(--accent-primary, var(--red, #4a9eff)); color:#fff; border-color:transparent; } - .confirm-btn-primary:hover { filter:brightness(1.15); } - .confirm-btn-danger { background:var(--color-danger); color:#fff; border-color:transparent; } - .confirm-btn-danger:hover { background:var(--color-error); } - .cookbook-gguf-delete-box { - width:560px; - max-width:92vw; - } - .cookbook-gguf-delete-list { - display:flex; - flex-direction:column; - gap:6px; - max-height:42vh; - overflow:auto; - padding:2px 2px 4px; - } - .cookbook-gguf-delete-row { - display:grid; - grid-template-columns:18px minmax(0,1fr); - gap:7px 8px; - align-items:start; - padding:7px 8px; - border:1px solid var(--border); - border-radius:7px; - background:color-mix(in srgb, var(--panel, var(--bg)) 92%, var(--fg) 8%); - cursor:pointer; - } - .cookbook-gguf-delete-row:hover { - border-color:color-mix(in srgb, var(--accent-primary, var(--fg)) 45%, var(--border)); - } - .cookbook-gguf-delete-cb { - -webkit-appearance:none; - appearance:none; - width:8px !important; - height:8px !important; - min-width:8px; - min-height:8px; - padding:0; - margin:4px 0 0; - border:1px solid var(--border); - border-radius:50%; - background:transparent; - box-sizing:content-box; - cursor:pointer; - transition:background 0.15s, border-color 0.15s, transform 0.12s; - } - .cookbook-gguf-delete-cb:hover { - border-color:var(--accent, var(--red)); - transform:scale(1.12); - } - .cookbook-gguf-delete-cb:checked { - background:var(--accent, var(--red)); - border-color:var(--accent, var(--red)); - } - .cookbook-gguf-delete-main, - .cookbook-gguf-delete-path { - min-width:0; - overflow:hidden; - text-overflow:ellipsis; - white-space:nowrap; - } - .cookbook-gguf-delete-main { - font-size:0.86rem; - color:var(--fg); - } - .cookbook-gguf-delete-path { - grid-column:2; - margin-top:-2px; - font-size:0.74rem; - opacity:0.58; - } - /* Styled prompt — text-input dialog (used in place of window.prompt) */ - #styled-prompt-overlay { - background:rgba(0,0,0,0.5); - backdrop-filter:blur(4px); - pointer-events:auto !important; - z-index: 99999 !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - } - #styled-prompt-overlay .modal-content { - position: relative; - z-index: 10001; - } - .styled-prompt-box { width: min(400px, 92vw); max-width: 100%; box-sizing: border-box; } - .styled-prompt-box .modal-body { padding-top: 4px; } - .styled-prompt-input { - width:100%; - box-sizing:border-box; - margin-top:8px; - padding:9px 12px; - border:1px solid var(--border); - border-radius:6px; - background:var(--bg); - color:var(--fg); - font:inherit; - font-size:0.95rem; - outline:none; - transition: border-color 0.15s, box-shadow 0.15s; - } - .styled-prompt-input:focus { - border-color: var(--accent-primary, var(--red, #4a9eff)); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-primary, var(--red, #4a9eff)) 25%, transparent); - } - @media (max-width:768px) { - .styled-prompt-box { width: 85vw; } - .styled-prompt-input { font-size: 0.9rem; padding: 10px 12px; } - } - /* Scroll navigation buttons */ - .scroll-nav-btn { - position:fixed; - background:var(--panel); - color: var(--accent); - border:none; - border-radius:10px; - width:38px; height:38px; - padding:0; - display:flex; align-items:center; justify-content:center; - font-size:14px; - line-height:1; - font-family:inherit; - cursor:pointer; - opacity:0; - pointer-events:none; - transition: opacity .2s, transform .3s cubic-bezier(0.25, 1, 0.5, 1), background .15s; - z-index:100; - transform: translateY(0); - } - .scroll-nav-btn::before { - content: ''; - position: absolute; - inset: 0; - border-radius: 10px; - padding: 1px; - background: linear-gradient(to bottom, var(--border), color-mix(in srgb, var(--border) 30%, transparent)); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - pointer-events: none; - } - .scroll-nav-btn.agent-working::after, - body.chat-agent-working #scroll-bottom-btn.show::after { - content: ''; - position: absolute; - inset: 0; - border-radius: 10px; - padding: 1px; - background: conic-gradient( - from 0deg, - transparent 0deg, - transparent 245deg, - color-mix(in srgb, var(--accent, var(--red)) 78%, transparent) 315deg, - transparent 360deg - ); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - opacity: 0.65; - pointer-events: none; - animation: scrollAgentWorkingRing 1.05s linear infinite; - } - @keyframes scrollAgentWorkingRing { - to { transform: rotate(360deg); } - } - #scroll-bottom-btn.show { opacity:1; pointer-events:auto; } - .scroll-nav-caret { - display: inline-block; - font-family: Arial, Helvetica, sans-serif; - font-size: 16px; - line-height: 1; - transform: translateY(-1px); - pointer-events: none; - } - @media (hover: hover) and (pointer: fine) { - #scroll-bottom-btn.show:hover { - border-color: color-mix(in srgb, var(--fg) 25%, transparent); - } - } - #scroll-bottom-btn.slide-out { - transform: translateY(20px); - opacity: 0 !important; - pointer-events: none; - } - /* Focus outline for accessibility */ - :focus-visible { - outline: 2px solid var(--red); - outline-offset: 2px; - } - /* Hamburger menu button */ - .hamburger { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - background: none; - border: 1px solid transparent; - border-radius: 6px; - padding: 0; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; - } - .hamburger:hover { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: var(--border); - } - .hamburger span { - display: block; - width: 16px; - height: 2px; - background: var(--fg); - border-radius: 1px; - transition: transform 0.2s, opacity 0.2s; - } - .hamburger span + span { margin-top: 3px; } - /* Agent indicator */ - #agent-indicator { - position: fixed; - top: 20px; - right: 20px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - display: none; - z-index: 100; - cursor: pointer; - transition: all 0.2s ease; - } - #agent-indicator.active { - display: block; - border-color: var(--color-agent-active); - box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); - } - #agent-indicator:hover { - border-color: var(--color-agent-active); - background: var(--panel); - } - #research-toggle-btn:disabled { - opacity: 0.6; - cursor: not-allowed; - } - -/* ── Drag & Drop ── */ - -/* ---------- Color palette (dark / light) ---------- */ - - /* Drag and drop styling */ - .section[dnd-active="true"] { - background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: var(--red) !important; - } - - .section[dnd-over="true"] { - background: color-mix(in srgb, var(--red) 20%, transparent) !important; - border-color: var(--red) !important; - transform: scale(1.02); - } - - .drag-handle { - cursor: grab; - opacity: 0.5; - padding: 0 6px; - user-select: none; - } - - .drag-handle:hover { - opacity: 0.8; - } - - .drag-handle:active { - cursor: grabbing; - } - -/* ── UI Controls (Radio, Presets, Toolbar, Settings) ── */ - - - /* Custom radio button styling */ - .radio-option { - display: flex; - align-items: center; - gap: 10px; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - cursor: pointer; - transition: all 0.2s ease; - } - - .radio-option:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: var(--fg); - } - - .radio-option input[type="radio"] { - appearance: none; - -webkit-appearance: none; - width: 18px; - height: 18px; - border: 2px solid var(--border); - border-radius: 50%; - outline: none; - margin: 0; - background: var(--bg); - transition: all 0.2s ease; - position: relative; - flex-shrink: 0; - } - - .radio-option input[type="radio"]:checked { - border-color: var(--red); - background: var(--red); - } - - .radio-option input[type="radio"]:focus { - box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 30%, transparent); - } - - .radio-label { - color: var(--fg); - font-size: 14px; - user-select: none; - } - - /* Preset buttons */ - .preset-btn { - height: 27.2px; /* 15% smaller than 32px */ - padding: 0 8.5px; /* 15% smaller than 10px */ - margin-left: 4px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font-family: 'Fira Code', monospace; - font-size: 10.2px; /* 15% smaller than 12px */ - cursor: pointer; - transition: all 0.2s ease; - } - - .preset-btn:hover { - background: var(--panel); - border-color: var(--fg); - } - - .preset-btn.active { - background: var(--panel); - border-color: var(--fg); - box-shadow: 0 0 0 1px var(--fg), 0 0 8px color-mix(in srgb, var(--fg) 16%, transparent); - font-weight: 600; - } - - /* All preset buttons use the same blue color */ - .preset-btn { - border-color: var(--red); /* Blue color */ - } - - .preset-btn.active { - border-color: var(--red); /* Blue color when active */ - box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); - } - - /* Custom preset modal: the base .preset-modal-content sets overflow:hidden - (for desktop rounded-corner clipping), which on the mobile sheet clipped - the footer (Start/Cancel) off the bottom with no way to reach it. Let the - whole sheet scroll — same as every other mobile modal (.modal-content is - overflow-y:auto on mobile) — so the footer is always reachable. No flex - changes, so the body can't collapse. */ - #custom-preset-modal .preset-modal-content { - overflow-y: auto !important; - } - #custom-preset-modal .modal-body label { - font-size: 13px; - font-weight: 500; - color: var(--fg); - margin-top: 8px; - margin-bottom: 4px; - display: block; - } - - #custom-preset-modal .modal-body input, - #custom-preset-modal .modal-body textarea { - width: 100%; - margin-bottom: 8px; - box-sizing: border-box; - } - #custom-preset-modal .modal-body textarea, - #custom-preset-modal .modal-body input[type="text"] { - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - padding: 8px 10px; - font-size: 13px; - font-family: inherit; - transition: border-color 0.15s; - } - #custom-preset-modal .modal-body textarea { - resize: vertical; - } - #custom-preset-modal .modal-body textarea:focus, - #custom-preset-modal .modal-body input[type="text"]:focus { - outline: none; - border-color: var(--red); - } - - #custom-preset-modal .modal-footer { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 12px; - padding-top: 10px; - border-top: 1px solid var(--border); - } - - #custom-preset-modal .modal-footer button { - padding: 7px 14px; - border-radius: 6px; - font-size: 12px; - font-weight: 500; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: all 0.15s; - } - #custom-preset-modal .modal-footer button:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - } - - #custom-preset-modal .modal-footer button#save-custom-preset { - /* The theme's accent is stored in --red (theme.js sets --red = accentHex) - and --accent is undefined, so the canonical accent expression is - var(--accent, var(--red)). The old bare var(--accent) resolved to nothing - which, with color:var(--bg), made the button invisible. */ - background: var(--accent, var(--red)); - color: var(--bg); - border-color: var(--accent, var(--red)); - } - #custom-preset-modal .modal-footer button#save-custom-preset:hover { - opacity: 0.85; - } - /* Toolbar visibility tab */ - .toolbar-hint { - font-size: 12px; - color: color-mix(in srgb, var(--fg) 55%, transparent); - margin-bottom: 12px; - } - /* ── Appearance visibility toggles ── */ - .vis-toggles { - display: flex; - flex-direction: column; - } - .vis-row { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - padding: 5px 6px; - border-radius: 4px; - transition: background 0.12s; - } - .vis-row:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); - } - .vis-row input[type="checkbox"] { - display: none; - } - .vis-icon { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - color: color-mix(in srgb, var(--fg) 40%, transparent); - } - .vis-icon-text { - font-size: 10px; - font-weight: 700; - font-family: inherit; - letter-spacing: -0.5px; - } - .vis-label { - flex: 1; - font-size: 12px; - color: var(--fg); - user-select: none; - } - .vis-switch { - position: relative; - width: 30px; - height: 16px; - background: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 8px; - transition: background 0.2s; - flex-shrink: 0; - } - .vis-switch::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 12px; - height: 12px; - background: var(--panel); - border-radius: 50%; - transition: transform 0.2s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); - } - .vis-row input:checked + .vis-switch { - background: var(--red); - } - .vis-row input:checked + .vis-switch::after { - transform: translateX(14px); - } - .vis-row input:checked ~ .vis-icon, - .vis-row:has(input:checked) .vis-icon { - color: var(--fg); - } - .persona-display-name-row .vis-label { - position: relative; - top: -4px; - } - .persona-display-name-row .vis-switch { - position: relative; - left: 4px; - } - .md-attach-paperclip-icon { - position: relative; - top: 1px; - } - /* Compare model selector — match the calendar modal's clean header - (no border underline). */ - #compare-model-overlay .modal-header h4 { - pointer-events: none; - } - /* Compare model selector: keep manually-resized/tiny windows contained. - Picker dropdowns are appended to document.body, so the card itself can - clip and scroll without cropping the dropdown list. */ - #compare-model-overlay .modal-content { - display: flex; - flex-direction: column; - max-height: min(720px, calc(100dvh - 48px)); - overflow: hidden; - min-height: 180px; - } - #compare-model-overlay .modal-body { - overflow: auto; - flex: 1 1 auto; - min-height: 0; - } - .vis-hint { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 30%, transparent); - font-weight: 400; - margin-left: 2px; - } - /* Settings toggle — admin-only lock indicator */ - .ui-vis-lock { - display: none; - } - /* (legacy toolbar-toggle styles removed — now using .vis-* classes) */ - - /* Demo highlight pulse */ - .odysseus-highlight { - outline: 2px solid var(--accent, var(--red)) !important; - outline-offset: 1px; - box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - animation: ody-pulse 1.5s ease-in-out infinite; - z-index: 100; - position: relative; - } - @keyframes ody-pulse { - 0%, 100% { outline-color: var(--accent, var(--red)); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); } - 50% { outline-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); } - } - /* Floating breathing halo. Rendered as a body-level div positioned over - the target, so we don't fight the target's own outline / box-shadow / - overflow chain. JS keeps it in sync with the target's bounding rect. */ - .tour-halo { - position: fixed; - pointer-events: none; - border: 3px solid var(--accent, var(--red)); - border-radius: 10px; - z-index: 10000; - animation: ody-breathe 1.4s ease-in-out infinite; - opacity: 0; - transform: scale(0.94); - transition: opacity 0.35s ease-out, transform 0.35s ease-out; - } - .tour-halo.tour-fade-in { - opacity: 1; - transform: scale(1); - } - @keyframes ody-breathe { - 0%, 100% { - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent), - 0 0 22px 4px color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - border-color: var(--accent, var(--red)); - } - 50% { - box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 30%, transparent), - 0 0 40px 14px color-mix(in srgb, var(--accent, var(--red)) 70%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 80%, transparent); - } - } - /* While the tour is active, lift overflow:hidden on common clipping - ancestors so the halo around a highlighted child isn't cropped. */ - body.tour-active .sidebar, - body.tour-active .sidebar-inner, - body.tour-active .chat-input-bar, - body.tour-active .chat-input-top, - body.tour-active .chat-input-wrap, - body.tour-active .chat-input-right, - body.tour-active .mode-toggle, - body.tour-active .model-picker-wrap { - overflow: visible !important; - } - - /* ── Secret tour hint (drag-to-snap on first modal open) ── */ - .tour-hint { - position: fixed; - z-index: 10002; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 12px 14px 10px; - width: 240px; - font-size: 0.78rem; - line-height: 1.5; - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.32); - opacity: 0; - transform: translateY(-4px); - transition: opacity 0.28s ease-out, transform 0.28s ease-out; - pointer-events: auto; - } - .tour-hint.tour-hint-in { opacity: 1; transform: translateY(0); } - .tour-hint.tour-hint-out { opacity: 0; transform: translateY(-4px); } - .tour-hint-visual { - display: flex; - justify-content: center; - margin-bottom: 8px; - color: var(--accent, var(--red)); - } - .tour-hint-visual svg { display: block; } - .tour-hint-text { margin-bottom: 10px; opacity: 0.92; } - .tour-hint-text b { color: var(--accent, var(--red)); font-weight: 600; } - .tour-hint-dismiss { - display: block; - margin: 0 0 0 auto; - background: none; - border: 1px solid var(--border); - color: var(--fg); - border-radius: 6px; - padding: 3px 12px; - cursor: pointer; - font-family: inherit; - font-size: 0.72rem; - opacity: 0.85; - transition: opacity 0.15s, background 0.15s; - } - .tour-hint-dismiss:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, transparent); } - - /* SVG dance: cursor approaches title bar, drags right, modal snaps to a - right-half zone, holds, returns. 3.2s loop. */ - .th-cursor { animation: th-cursor 3.2s ease-in-out infinite; transform-origin: 0 0; } - .th-modal-group { animation: th-modal-slide 3.2s ease-in-out infinite; transform-origin: 39px 31px; } - .th-zone { animation: th-zone-flash 3.2s ease-in-out infinite; } - @keyframes th-cursor { - 0%, 5% { transform: translate(35px, 32px); } - 18% { transform: translate(35px, 22px); } - 48% { transform: translate(80px, 22px); } - 72% { transform: translate(80px, 22px); } - 90%, 100% { transform: translate(35px, 32px); } - } - @keyframes th-modal-slide { - 0%, 18% { transform: translate(0, 0) scale(1, 1); } - 48% { transform: translate(45px, 0) scale(1, 1); } - 58% { transform: translate(30px, -19px) scale(1.4, 2.4); } - 72% { transform: translate(30px, -19px) scale(1.4, 2.4); } - 90%, 100% { transform: translate(0, 0) scale(1, 1); } - } - @keyframes th-zone-flash { - 0%, 38% { opacity: 0; } - 48% { opacity: 0.22; } - 58%, 72% { opacity: 0; } - 100% { opacity: 0; } - } - @media (prefers-reduced-motion: reduce) { - .th-cursor, .th-modal-group, .th-zone { animation: none !important; } - } - .odysseus-hl-label { - position: absolute; - top: -22px; - left: 8px; - background: var(--red); - color: var(--bg); - font-size: 0.7rem; - padding: 2px 8px; - border-radius: 4px; - white-space: nowrap; - z-index: 101; - pointer-events: none; - } - - /* Generated images inside chat bubbles */ - .msg.generated-image-wrap .body { text-align: center; } - .generated-image { - max-width: 100%; - max-height: 512px; - border-radius: 8px; - cursor: pointer; - transition: transform 0.2s; - display: inline-block; - margin: 0 auto; - } - .generated-image:hover { transform: scale(1.02); } - .generated-image-caption { - font-size: 0.8rem; - opacity: 0.5; - margin-top: 6px; - font-style: italic; - text-align: center; - } - - /* Setup wizard */ - .setup-wizard { padding: 16px; max-width: 500px; } - .setup-wizard .setup-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 12px; color: var(--fg); } - .setup-wizard .setup-label { font-size: 0.85rem; color: var(--fg); opacity: 0.7; margin-bottom: 8px; } - .setup-wizard .setup-presets { display: flex; flex-wrap: wrap; gap: 8px; } - .setup-wizard .setup-preset-btn { - padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; - font-size: 0.85rem; transition: all 0.2s; - } - .setup-wizard .setup-preset-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .setup-wizard .setup-input { - display: block; width: 100%; padding: 8px 12px; margin-bottom: 8px; - background: var(--panel); border: 1px solid var(--border); - border-radius: 6px; color: var(--fg); font-size: 0.85rem; box-sizing: border-box; - } - .setup-wizard .setup-input:focus { border-color: var(--red); outline: none; } - .setup-wizard .setup-connect-btn { - padding: 8px 20px; background: var(--red); color: var(--bg); - border: none; border-radius: 6px; cursor: pointer; font-weight: 600; margin-top: 4px; - } - .setup-wizard .setup-connect-btn:hover { opacity: 0.9; } - .setup-wizard .setup-status { font-size: 0.8rem; margin-top: 8px; color: var(--fg); opacity: 0.7; } - .setup-wizard .setup-model-list { display: flex; flex-wrap: wrap; gap: 8px; } - .setup-wizard .setup-model-btn { - padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; - font-size: 0.85rem; transition: all 0.2s; - } - .setup-wizard .setup-model-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .setup-wizard .setup-step.hidden { display: none; } - - /* Dropdown menu styles */ - .dropdown { - position: absolute; - top: calc(100% + 6px); - right: 0; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 10px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); - z-index: 1000; - display: none; - min-width: 220px; - padding: 6px; - backdrop-filter: blur(12px); - } - - .dropdown.show { - display: block; - animation: dropdown-in 0.15s ease-out; - } - @keyframes dropdown-in { - from { opacity:0; transform:translateY(-6px) scale(0.97); } - to { opacity:1; transform:translateY(0) scale(1); } - } - - .dropdown-item { - padding: 8px 10px; - border-radius: 6px; - cursor: pointer; - transition: background 0.12s ease; - display: flex; - align-items: center; - gap: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - } - - .dropdown-item:last-child { - border-bottom: none; - } - - .dropdown-item .menu-icon { - width: 28px; - height: 28px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - flex-shrink: 0; - } - - .dropdown-item .menu-text h4 { - margin: 0; - font-size: 12.5px; - font-weight: 500; - color: var(--fg); - line-height: 1.3; - } - - .dropdown-item .menu-text p { - margin: 0; - font-size: 10.5px; - color: var(--color-subheader); - line-height: 1.3; - } - - .dropdown-item:hover { - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .dropdown-item:hover .menu-icon { - background: color-mix(in srgb, var(--red) 15%, transparent); - } - - /* Compact dropdown items (session/model context menus) */ - .dropdown-item-compact { - cursor: pointer; - padding: 6px 8px; - font-size: 11px; - border-radius: 8px; - display: flex; - align-items: center; - gap: 10px; - color: var(--fg); - transition: background 0.1s; - } - .dropdown-item-compact:hover { - background: color-mix(in srgb, var(--accent) 10%, transparent); - } - .dropdown-item-compact .dropdown-icon { - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - opacity: 0.5; - } - .dropdown-item-compact .dropdown-icon svg { - width: 14px; - height: 14px; - } - .dropdown-item-compact .dropdown-shortcut { - margin-left: auto; - font-size: 9.5px; - opacity: 0.35; - font-weight: 400; - } - /* Keyboard shortcut hints (⌘+Alt+D etc.) are meaningless on touch — - hide them in the per-chat actions menu on mobile. */ - @media (max-width: 768px) { - .session-dropdown .dropdown-shortcut { display: none; } - } - .dropdown-item-danger { - color: var(--red) !important; - } - .dropdown-item-danger .dropdown-icon { - opacity: 0.7; - } - - /* Inline rename input for sessions */ - .session-rename-input { - width: 100%; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--accent, var(--accent-primary)); - border-radius: 4px; - padding: 2px 6px; - font-family: inherit; - font-size: 12px; - line-height: 1.3; - outline: none; - } - - /* Session dropdown container */ - .session-dropdown-menu { - position: fixed; - z-index: 1000; - display: none; - min-width: auto; - width: max-content; - padding: 4px; - animation: dropdown-in 0.15s ease-out; - } - - /* Folder move submenu */ - .session-folder-submenu { - position: fixed; - z-index: 1001; - display: none; - min-width: auto; - width: max-content; - padding: 4px; - } - - .dropdown-divider { - height: 1px; - background: var(--border); - margin: 4px 8px; - } - - /* Search toggle styles */ - .search-toggle { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 0; - border-bottom: 1px solid var(--border); - margin-bottom: 8px; - } - - .search-provider-label { - font-size: 14px; - color: var(--fg); - } - -/* ── Voice, Search, Themes, Comparison, Censor, Print ── */ - - /* Voice recording styles */ - #mic-btn { - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - transition: all 0.2s ease; - } - - #mic-btn:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: var(--fg); - } - - #mic-btn.recording { - background: var(--color-recording); - border-color: var(--color-recording); - animation: pulse 1.5s infinite; - } - - @keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } - } - - #recording-indicator { - position: fixed; - top: 10px; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin: 10px; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - } - - #recording-indicator.hidden { - display: none !important; - } - - .recording-content { - display: flex; - align-items: center; - gap: 12px; - color: white; - } - - .recording-icon { - color: var(--color-recording); - font-size: 20px; - animation: pulse 1.5s infinite; - } - - .recording-text { - font-size: 16px; - font-weight: 500; - } - - #stop-recording { - background: var(--color-recording); - color: white; - border: none; - border-radius: 6px; - padding: 6px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s ease; - } - - #stop-recording:hover { - background: var(--color-recording-hover); - } - - /* Error state for recording */ - #recording-indicator.error { - background: rgba(173, 26, 26, 0.9); - } - - .recording-error { - color: var(--color-recording); - font-size: 14px; - margin-top: 4px; - } - - @media (max-width: 768px) { - #recording-indicator { - margin: 8px; - padding: 10px; - } - - .recording-text { - font-size: 14px; - } - - #stop-recording { - padding: 6px 10px; - font-size: 12px; - } - } -/* SYNTAX HIGHLIGHTING — uses theme vars from style.css, no hardcoded overrides */ -.hljs { color: var(--hl-fg, #9cdef2); background: none !important; } -pre { background: var(--code-bg, var(--hl-bg, #282c34)) !important; } - - /* ---------- Search overlay (Ctrl+K command palette) ---------- */ - .search-overlay { - position: fixed; - top: 0; left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: flex-start; - justify-content: center; - padding-top: var(--search-overlay-top, 15vh); - z-index: 300; - backdrop-filter: blur(6px); - } - .search-overlay.hidden { display: none; } - .search-popup { - width: 520px; - max-width: 90vw; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 12px; - box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5), 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); - overflow: hidden; - display: flex; - flex-direction: column; - max-height: 60vh; - } - .search-popup input#search-input { - width: 100%; - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - outline: none; - color: var(--fg); - font-size: 16px; - font-family: 'Fira Code', monospace; - padding: 14px 16px; - box-sizing: border-box; - } - .search-popup input#search-input::placeholder { - color: color-mix(in srgb, var(--fg) 30%, transparent); - } - .search-results { - overflow-y: auto; - flex: 1; - padding: 4px; - } - .search-results:empty { - display: none; - } - .search-group-header { - font-size: 10px; - font-weight: 600; - color: var(--color-subheader); - text-transform: uppercase; - letter-spacing: 0.5px; - padding: 10px 12px 4px; - } - .search-result-item { - display: flex; - align-items: baseline; - gap: 8px; - padding: 8px 12px; - border-radius: 6px; - cursor: pointer; - transition: background 0.1s; - } - .search-result-item:hover, - .search-result-item.selected { - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .search-result-role { - font-size: 10px; - font-weight: 600; - color: var(--color-subheader); - flex-shrink: 0; - min-width: 24px; - } - .search-result-snippet { - flex: 1; - font-size: 12px; - color: var(--fg); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .search-result-time { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 35%, transparent); - flex-shrink: 0; - white-space: nowrap; - } - mark.search-highlight { - /* Was orange-on-orange (0.35 alpha bg + orange text) — too low-contrast - to read. Solid accent fill with bg-colored text reads clearly. */ - background: var(--accent, #e8a830); - color: var(--bg, #1a1a1a); - border-radius: 2px; - padding: 0 2px; - font-weight: 600; - } - .search-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 24px; - font-size: 13px; - } - - /* ---------- Theme popup (uses .modal > .modal-content frame) ---------- */ - #theme-modal { z-index: 260; } - #theme-popup .theme-popup-sub { color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 11px; margin-bottom: 10px; } - /* `max-height` instead of a fixed height so the popup shrinks to fit its - content (avoids the leftover whitespace after the time-based switching - card was removed). The inner .theme-tab-panel keeps overflow:auto, so - any taller content still scrolls inside. */ - #theme-popup { overflow-y: hidden; max-height: min(85vh, 600px); } - #theme-popup .modal-header { flex-shrink: 0; } - #theme-popup .admin-tabs { margin: 0 -10px 8px; padding: 0 10px; flex-shrink: 0; } - .theme-tab-panel { overflow-y: auto; min-height: 0; flex: 1; padding-bottom: 10px; } - - .theme-grid { - display: grid; grid-template-columns: repeat(auto-fill, minmax(66px, 1fr)); - gap: 6px; margin-bottom: 12px; - } - .theme-swatch { - border: 2px solid var(--border); border-radius: 8px; cursor: pointer; - padding: 5px; text-align: center; font-size: 0.65rem; color: var(--fg); - transition: border-color 0.15s, transform 0.15s; - } - .theme-swatch:hover { transform: scale(1.06); } - .theme-swatch.active { border-color: var(--red); box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 33%, transparent); } - .theme-swatch-colors { - display: flex; justify-content: center; margin-bottom: 3px; - } - .theme-swatch-colors span { - width: 15px; height: 15px; border-radius: 50%; - margin-left: -5px; - border: 1.5px solid color-mix(in srgb, var(--fg) 12%, transparent); - } - .theme-swatch-colors span:first-child { margin-left: 0; } - .theme-custom-label { font-size: 11px; font-weight: 600; color: color-mix(in srgb, var(--fg) 50%, transparent); text-transform: uppercase; letter-spacing: 0.06em; margin: 10px 0 6px; } - .theme-custom { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; } - .color-row { display: flex; align-items: center; gap: 4px; position: relative; } - .color-row label { font-size: 13px; font-weight: 500; color: var(--fg); opacity: 0.7; flex: 1; } - .color-row input[type="color"], - .color-row input.cp-swatch-input { - width: 24px; height: 24px; border: 1px solid var(--border); border-radius: 50%; - background: none; cursor: pointer; padding: 0; flex-shrink: 0; - overflow: hidden; - -webkit-appearance: none; - appearance: none; - } - .color-row input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } - .color-row input[type="color"]::-webkit-color-swatch { border: none; border-radius: 50%; } - .color-row input[type="color"]::-moz-color-swatch { border: none; border-radius: 50%; } - #theme-bg-effect-color { position: relative; top: 2px; } - .color-row input.cp-swatch-input { - color: transparent; - text-shadow: none; - caret-color: transparent; - font-size: 0; - user-select: none; - } - .color-row input.cp-swatch-input::selection { background: transparent; } - .color-row input.cp-swatch-input:focus { outline: 1px solid var(--red); outline-offset: 1px; } - .color-reset-btn { - width: 24px; height: 24px; border: none; background: none; cursor: pointer; - color: var(--fg); opacity: 0; font-size: 1.15rem; padding: 0; line-height: 1; - transition: opacity 0.15s, color 0.15s; flex-shrink: 0; pointer-events: none; - position: absolute; right: 6px; top: 50%; transform: translateY(-50%); - } - .color-row input[type="color"], - .color-row input.cp-swatch-input { margin-left: auto; } - /* Keep the harmony hex readout on the left, with the swatch dot anchored - to the right like the other color controls. */ - #theme-harmony-card #harmony-accent-hex { - order: -1; - margin-right: auto; - } - .color-row:has(.color-reset-btn.changed) input[type="color"], - .color-row:has(.color-reset-btn.changed) input.cp-swatch-input { margin-right: 28px; } - .color-reset-btn.changed { opacity: 0.4; pointer-events: auto; } - .color-reset-btn.changed:hover { opacity: 1; color: var(--red); } - .theme-custom-divider { - grid-column: 1 / -1; font-size: 11px; color: var(--fg); opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.04em; margin: 6px 0 2px; - border-top: 1px solid var(--border); padding-top: 6px; - } - .theme-swatch[data-custom] { position: relative; overflow: visible; } - /* Accent-coloured circular X — always visible (mobile-friendly), sits - INSIDE the swatch's top-right corner so it never crops into a - neighbouring swatch. */ - .theme-delete-btn { - position: absolute; - top: -2px; - right: -2px; - width: 20px; - height: 20px; - padding: 0; - border: none; - border-radius: 50%; - background: var(--accent, var(--red, #d92534)); - color: #fff; - cursor: pointer; - z-index: 2; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); - transition: transform 0.12s, background 0.12s; - } - .theme-delete-btn:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 80%, white); - transform: scale(1.12); - } - .theme-delete-btn:active { transform: scale(0.95); } - .theme-delete-btn svg { - display: block; - /* Optical nudge — the X reads off-center inside the circle without it. */ - position: relative; - left: 1px; - top: -1px; - } - .theme-save-row { - display: flex; gap: 6px; margin-top: 8px; - } - .theme-save-row input { - flex: 1; padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 12px; font-family: inherit; - } - .theme-save-row input:focus { outline: none; border-color: var(--red); } - .theme-save-row input::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } - .theme-save-row button { - padding: 6px 12px; border: 1px solid var(--red); border-radius: 6px; - background: transparent; color: var(--red); cursor: pointer; - font-size: 12px; font-family: inherit; white-space: nowrap; transition: all 0.15s; - } - .theme-save-row button:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } - .theme-save-error { - font-size: 11px; color: var(--red); margin-top: 2px; display: none; - } - /* Import/Export */ - .theme-io-row { display: flex; gap: 6px; margin-top: 6px; } - .theme-io-btn { - flex: 1; padding: 5px 10px; border: 1px solid var(--border); border-radius: 6px; - background: transparent; color: var(--fg); cursor: pointer; - font-size: 12px; opacity: 0.7; transition: all 0.15s; font-family: inherit; - } - .theme-io-btn:hover { opacity: 1; border-color: var(--fg); background: color-mix(in srgb, var(--fg) 5%, transparent); } - - #memory-import-btn svg, - #memory-export-btn svg { - color: var(--accent, var(--red)); - } - .theme-import-area { - width: 100%; margin-top: 6px; padding: 6px 8px; - border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 0.7rem; - font-family: inherit; resize: vertical; min-height: 48px; - } - .theme-import-area:focus { outline: none; border-color: var(--red); } - .theme-import-area.hidden { display: none; } - .theme-import-actions { display: flex; gap: 6px; margin-top: 4px; } - .theme-import-actions.hidden { display: none; } - /* Font & Density */ - .theme-fd-row { display: flex; gap: 8px; margin-bottom: 8px; } - .theme-fd-group { flex: 1; display: flex; flex-direction: column; gap: 3px; } - .theme-fd-label { font-size: 12px; font-weight: 500; color: var(--fg); opacity: 0.6; } - .theme-fd-select { - padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 12px; - font-family: inherit; cursor: pointer; - } - .theme-fd-select:focus { outline: none; border-color: var(--red); } - @media (max-width: 768px) { - /* The switch is too cramped beside the Font and Density selects on - narrow screens; give Frosted a dedicated row instead. */ - .theme-fd-row:first-of-type { - flex-wrap: wrap; - } - .theme-fd-row:first-of-type #theme-frosted-group { - flex: 1 1 100%; - flex-direction: row; - align-items: center; - justify-content: space-between; - min-width: 0; - } - } - .theme-fd-range { - width: 100%; - max-width: 100%; - box-sizing: border-box; - margin: 0; - padding: 0; - height: 24px; - background: transparent; - cursor: pointer; - -webkit-appearance: none; - appearance: none; - accent-color: var(--red); - } - .theme-fd-range::-webkit-slider-runnable-track { - height: 4px; background: var(--border); border-radius: 2px; - } - .theme-fd-range::-moz-range-track { - height: 4px; background: var(--border); border-radius: 2px; - } - .theme-fd-range::-webkit-slider-thumb { - -webkit-appearance: none; appearance: none; - width: 14px; height: 14px; border-radius: 50%; - background: var(--red); border: none; margin-top: -5px; cursor: pointer; - } - .theme-fd-range::-moz-range-thumb { - width: 14px; height: 14px; border-radius: 50%; - background: var(--red); border: none; cursor: pointer; - } - .theme-fd-range:focus { outline: none; } - /* Color Harmony Generator */ - .theme-harmony-row { display: flex; gap: 8px; align-items: flex-end; } - .harmony-generate-btn { - padding: 5px 14px; border: 1px solid var(--red); border-radius: 6px; - background: transparent; color: var(--red); cursor: pointer; - font-size: 12px; white-space: nowrap; transition: all 0.15s; - font-family: inherit; width: 100%; - } - .harmony-generate-btn:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } - .harmony-preview { - display: flex; height: 20px; border-radius: 6px; overflow: hidden; - margin-top: 8px; border: 1px solid var(--border); - } - .harmony-preview:empty { display: none; border: none; } - .harmony-preview span { flex: 1; } - #theme-reset-btn { - margin-top: 6px; width: 100%; padding: 6px; border: 1px solid var(--border); - border-radius: 6px; background: var(--bg); color: var(--fg); cursor: pointer; - font-size: 12px; font-family: inherit; opacity: 0.7; transition: opacity 0.15s; - } - #theme-reset-btn:hover { opacity: 1; } - .theme-adv-toggle { - margin-top: 10px; margin-bottom: 10px; - padding: 6px 8px; cursor: pointer; - font-size: 11px; color: var(--red); opacity: 0.8; - border: 1px solid var(--border); border-radius: 6px; - transition: opacity 0.15s, background 0.15s; - user-select: none; - } - .theme-adv-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 4%, transparent); } - .theme-adv-toggle .theme-adv-arrow { - display: inline-block; transition: transform 0.2s; font-size: 10px; - margin-right: 4px; - } - .theme-adv-toggle.open .theme-adv-arrow { transform: rotate(90deg); } - .theme-adv-section { margin-top: 8px; margin-bottom: 10px; } - .theme-adv-section.hidden { display: none; } - .theme-adv-group { margin-bottom: 8px; } - .theme-adv-group-label { - font-size: 10px; color: var(--fg); opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; - } - .theme-adv-clear-btn { - margin-top: 6px; width: 100%; padding: 5px; border: 1px solid var(--border); - border-radius: 6px; background: transparent; color: var(--fg); opacity: 0.5; - cursor: pointer; font-size: 12px; font-family: inherit; transition: opacity 0.15s; - } - .theme-adv-clear-btn:hover { opacity: 1; } - - /* Customization is an editor, so keep its sections visually related while - preserving the existing controls and their order. */ - #theme-tab-customize { - display: flex; - flex-direction: column; - gap: 10px; - } - #theme-tab-customize > .admin-card, - #theme-tab-customize > .theme-adv-toggle, - #theme-tab-customize > .theme-adv-section { - margin-top: 0; - margin-bottom: 0; - } - #theme-tab-customize > .admin-card { - padding: 12px; - } - #theme-tab-customize > .admin-card > h2 { - display: flex; - align-items: center; - min-height: 20px; - margin: 0 0 10px; - padding-bottom: 7px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 65%, transparent); - font-size: 13px; - } - #theme-tab-customize .theme-custom { - gap: 6px 14px; - } - #theme-tab-customize .color-row { - min-height: 30px; - padding: 3px 6px; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--border) 55%, transparent); - background: color-mix(in srgb, var(--fg) 2%, transparent); - } - #theme-tab-customize .color-row label { - font-size: 12px; - } - #theme-tab-customize .theme-adv-toggle { - flex-shrink: 0; - margin: 0; - background: color-mix(in srgb, var(--fg) 2%, transparent); - } - #theme-tab-customize .theme-adv-section { - padding: 10px 12px 2px; - border: 1px solid color-mix(in srgb, var(--border) 75%, transparent); - border-radius: 7px; - background: color-mix(in srgb, var(--panel) 55%, transparent); - } - #theme-tab-customize .theme-adv-group { - margin: 0; - padding: 0 0 9px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 48%, transparent); - } - #theme-tab-customize .theme-adv-group + .theme-adv-group { - padding-top: 9px; - } - #theme-tab-customize .theme-adv-group:last-of-type { - border-bottom: 0; - } - #theme-tab-customize .theme-adv-group-label { - margin-bottom: 6px; - color: var(--red); - opacity: 0.8; - font-weight: 600; - } - #theme-tab-customize .theme-fd-row, - #theme-tab-customize .theme-harmony-row { - gap: 12px; - margin-bottom: 10px; - } - #theme-tab-customize .theme-fd-row:last-child, - #theme-tab-customize .theme-harmony-row:last-child { - margin-bottom: 0; - } - #theme-tab-customize .theme-fd-group { - min-width: 0; - } - #theme-tab-customize .theme-fd-select { - min-height: 30px; - } - #theme-tab-customize #theme-harmony-card .theme-fd-label { - font-size: 11px; - } - #theme-tab-customize .theme-save-row, - #theme-tab-customize .theme-io-row { - gap: 8px; - } - #theme-tab-customize #theme-reset-btn { - margin-top: 8px; - } - @media (max-width: 560px) { - #theme-tab-customize .theme-custom { - grid-template-columns: 1fr; - } - #theme-tab-customize .theme-fd-row, - #theme-tab-customize .theme-harmony-row { - flex-direction: column; - align-items: stretch; - } - #theme-tab-customize .theme-fd-group, - #theme-tab-customize .theme-harmony-row .theme-fd-group { - width: 100%; - } - } - - - /* Mobile: bottom sheet modals — slide up from bottom */ - @media (max-width: 768px) { - .modal { - align-items: flex-end; - background: rgba(0,0,0,0.4); - pointer-events: auto; - /* Anchor the bottom sheet to the DYNAMIC viewport. The base overlay is - position:fixed; height:100% = the layout viewport, whose bottom sits - UNDER Firefox Android's bottom URL bar — so flex-end parked the sheet - (and its footer / Start button) beneath the bar. dvh excludes the bar - so the footer lands above it. Extra safe-area pad for iOS home bar. */ - height: 100dvh; - padding-bottom: env(safe-area-inset-bottom, 0px); - } - /* Confirm dialog stays centered, not a bottom sheet */ - #styled-confirm-overlay { - align-items: center; - } - .styled-confirm-box { - border-radius: 12px !important; - border: 1px solid var(--border) !important; - animation: modal-enter 0.25s ease-out both !important; - max-height: none !important; - } - .styled-confirm-box.modal-closing { - animation: modal-exit 0.18s ease-in both !important; - } - .styled-confirm-box::before { - display: none !important; - } - .styled-confirm-box .close-btn { - display: none !important; - } - #theme-popup { - width: 100% !important; - height: 65vh !important; - max-height: 65vh !important; - top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important; - position: fixed !important; - border-radius: 14px 14px 0 0; - border: none; - border-top: 1px solid var(--border); - padding: 6px 12px 12px; - animation: sheet-enter 0.2s ease-out forwards; - overflow-y: hidden !important; - } - #theme-popup .theme-tab-panel { - touch-action: pan-y; - overscroll-behavior: contain; - -webkit-overflow-scrolling: touch; - } - #theme-popup.sheet-ready { - animation: none; - } - #theme-popup.modal-closing { - animation: sheet-exit 0.15s ease-in both !important; - } - .modal-content, - .memory-modal-content, - .settings-modal-content, - #compare-model-overlay .modal-content { - width: 100% !important; - /* 85dvh leaves comfortable headroom above the sheet so the user can - still see the chat behind it and the drag handle has breathing - room. Was 65vh (too short, content clipped) → 90vh (too tall, top - hugged the status bar). */ - max-height: 85dvh !important; - max-height: 85vh !important; /* fallback for browsers without dvh */ - height: auto !important; - border-radius: 14px 14px 0 0; - border: none; - border-top: 1px solid var(--border); - padding-top: 6px !important; - /* Clip children to the rounded top corners — otherwise the sticky - modal-header's var(--panel) background paints a darker - rectangle past the radius and the corners look square again. */ - overflow: hidden; - animation: sheet-enter 0.2s ease-out forwards; - } - /* Keep the tall tool drawers on a shared mobile height ceiling. The - final mobile drawer rule below makes this content-sized so shorter - tools remain compact while tall ones leave a visible top gap. */ - #cookbook-modal .modal-content, - #tasks-modal .modal-content, - #calendar-modal .modal-content, - #memory-modal .memory-modal-content, - #settings-modal .settings-modal-content, - #email-lib-modal .modal-content, - #doclib-modal .doclib-modal-content { - max-height: 90vh !important; - max-height: 90dvh !important; - height: auto !important; - /* Reserve the iOS home-indicator / bottom-bar strip so an expanded - skill card's footer (Delete / Edit / Run) isn't hidden under it. - dvh already excludes the URL bar; this handles the safe area. */ - padding-bottom: env(safe-area-inset-bottom, 0px) !important; - } - /* Grab handle pill on the non-standard content classes too. Memory's - desktop rule defines a radial-glow ::before later in the file — - these body-prefixed selectors win the specificity battle on mobile. */ - body .memory-modal-content::before, - body .settings-modal-content::before { - content: ''; - display: block; - position: static; - inset: auto; - width: 36px; height: 4px; - background: var(--fg); - opacity: 0.25; - border-radius: 2px; - margin: 0 auto 4px; - flex-shrink: 0; - padding: 0; - border-top: 10px solid transparent; - border-bottom: 6px solid transparent; - background-clip: padding-box; - animation: none; - } - .memory-modal-content .close-btn, - .memory-modal-content .modal-close, - .settings-modal-content .close-btn, - .settings-modal-content .modal-close { - display: none !important; - } - .memory-modal-content, - .settings-modal-content { - touch-action: pan-y; - overscroll-behavior: contain; - } - /* Library modals use the same bottom-sheet sizing as the other tools. - The shared mobile drawer rule below provides the top breathing room. */ - #cookbook-modal .modal-content, - #calendar-modal .modal-content, - #email-lib-modal .modal-content, - #doclib-modal .modal-content, - #gallery-modal .modal-content, - #tasks-modal .modal-content { - /* vh-first as fallback for very old browsers, then dvh wins so - the modal shrinks/grows with the mobile URL bar. Wrong order - previously made the expanded chat preview extend below the - visible viewport on Chrome/Safari mobile (URL bar covered the - action buttons row). */ - max-height: 90vh !important; - max-height: 90dvh !important; - height: auto !important; - } - /* Anchor those modals to the top so the full height is usable */ - #cookbook-modal, - #calendar-modal, - #email-lib-modal, - #doclib-modal, - #gallery-modal, - #tasks-modal { - padding-top: 0 !important; - align-items: stretch !important; - } - /* Deep Research already gets the swipe grab-handle pill from the - shared `.modal-content::before` rule (the pane carries that class). - The previous header-level pill was redundant and rendered as a - SECOND pill stacked above the real one — removed. */ - /* The inner body must flex to fill the new full height, and the - tasks list inside it must scroll independently — otherwise the - list crops at whatever pre-mobile height the desktop layout had. */ - #tasks-modal .modal-content { - display: flex !important; - flex-direction: column !important; - } - #tasks-modal .modal-body { - flex: 1 1 auto !important; - min-height: 0 !important; - } - /* Memory/Skills: the body lacks flex:1, so on the fixed-height mobile - sheet (overflow:hidden) it grew past the viewport and clipped the - bottom of the skills list + its row action buttons with no way to - scroll there. Bound the body so the inner list scrolls internally. */ - #memory-modal .memory-modal-content { - display: flex !important; - flex-direction: column !important; - } - /* flex-basis MUST be 0 (not auto) — matches the working #doclib-modal - .modal-body. With basis:auto, Firefox (incl. mobile) sizes the body - to its content (~half height) instead of filling, while Chromium - fills it regardless — which is exactly why the skill expand worked - on desktop/Chromium but stuck at ~50% on Firefox mobile. */ - #memory-modal .memory-modal-body { - flex: 1 1 0 !important; - min-height: 0 !important; - overflow: hidden !important; - } - /* Same basis:0 fix down the rest of the skills chain so every layer - fills instead of sizing to content under Firefox. */ - #memory-modal .memory-tab-panel[data-memory-panel="skills"] { - flex: 1 1 0 !important; - min-height: 0 !important; - } - #memory-modal .memory-tab-panel[data-memory-panel="skills"] > .admin-card { - flex: 1 1 0 !important; - min-height: 0 !important; - } - /* The skills modal carries an extra toolbar row (search/sort/select + - bulk bar) the doc/email libraries don't, so the shared 82dvh preview - min-height overflowed here and pushed the expanded footer (Delete / - Edit / Run) off the bottom of the screen. Let flexbox size it from - the resolved card height instead, so the footer always pins inside - the visible area. */ - #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview { - min-height: 0 !important; - } - /* Once enter animation finishes, clear it so inline transform works for dragging */ - .modal-content.sheet-ready { - animation: none; - } - .modal-content.modal-closing { - animation: sheet-exit 0.15s ease-in both !important; - } - /* Grab handle — large touch target, visible pill */ - #theme-popup::before, - .modal-content::before { - content: ''; - display: block; - width: 36px; height: 4px; - background: var(--fg); opacity: 0.25; - border-radius: 2px; - margin: 0 auto 4px; - flex-shrink: 0; - padding: 0; - /* Expand touch target without changing visual size */ - border-top: 10px solid transparent; - border-bottom: 6px solid transparent; - background-clip: padding-box; - } - /* Hide close X on mobile — swipe down to dismiss */ - .modal-content .close-btn, - .modal-content .modal-close, - #theme-popup .close-btn { - display: none !important; - } - /* Hide the auto-injected minimize (_) button on mobile — the dock - chip already represents the minimized state, and the swipe-down - gesture is the canonical minimize action. */ - .modal-minimize-btn, - .minimize-btn, - [data-minimize] { - display: none !important; - } - /* Lock modals to vertical touch only, prevent horizontal dragging */ - .modal-content { - touch-action: pan-y; - overscroll-behavior: contain; - } - .modal-content .cookbook-body, - .modal-content .modal-body { - touch-action: pan-y; - overscroll-behavior: contain; - } - .modal-header { - cursor: default; - touch-action: none; - } - @keyframes sheet-enter { - from { transform: translateY(100%); } - to { transform: translateY(0); } - } - @keyframes sheet-exit { - from { transform: translateY(0); } - to { transform: translateY(100%); } - } - } - - /* ── Model A/B Comparison ── */ - - /* -- Extracted inline-style classes -- */ - .cmp-header-action-btn { - background: none; border: 1px solid var(--border); color: var(--fg); - cursor: pointer; padding: 3px 10px; font-size: 11px; font-weight: 600; - opacity: 0.7; transition: all 0.15s; line-height: 1; border-radius: 4px; - display: inline-flex; align-items: center; font-family: inherit; - } - .cmp-form-control { - padding: 8px; background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; font-size: 0.85em; - } - .cmp-btn-secondary { - background: transparent; color: var(--fg); border: 1px solid var(--border); - border-radius: 6px; cursor: pointer; - } - .cmp-btn-primary { - background: var(--fg); color: var(--bg); border: none; - border-radius: 6px; cursor: pointer; font-weight: 600; - transition: filter 0.12s, background 0.12s, color 0.12s; - } - /* Override global button:hover (which switches bg to var(--panel) = - very dark) — keep the bright primary look and just brighten slightly. */ - .cmp-btn-primary:hover:not(:disabled) { - background: var(--fg); color: var(--bg); - filter: brightness(1.1); - } - .cmp-btn-primary:active:not(:disabled) { filter: brightness(0.95); } - .cmp-btn-secondary:hover:not(:disabled) { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); color: var(--fg); - } - .cmp-model-row { - display: flex; align-items: center; gap: 8px; margin-bottom: 8px; - flex-wrap: wrap; - padding: 2px 0; - transition: margin-left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.15s, border-color 0.15s; - } - .cmp-model-row-duplicate { - margin-left: -4px; - margin-right: -4px; - padding: 4px; - border-radius: 6px; - background: color-mix(in srgb, var(--color-error) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--color-error) 20%, transparent); - } - .cmp-model-row-swap-target { - margin-left: -4px; - margin-right: -4px; - padding: 4px; - border-radius: 6px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); - } - .cmp-model-row-swap-target .cmp-row-label { - color: var(--accent, var(--red)); - opacity: 1; - } - .cmp-row-label { - color: var(--fg); font-size: 0.82em; font-weight: 600; min-width: 20px; - opacity: 0.4; text-align: center; flex-shrink: 0; - } - .cmp-model-meta { - order: 3; - flex: 1 0 calc(100% - 28px); - margin-left: 28px; - margin-top: -4px; - display: flex; - align-items: center; - gap: 5px; - min-width: 0; - font-size: 10px; - line-height: 1.2; - } - .cmp-model-meta-chip, - .cmp-model-meta-warning, - .cmp-model-meta-muted { - display: inline-flex; - align-items: center; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - border-radius: 999px; - padding: 1px 6px; - letter-spacing: 0; - } - .cmp-model-meta-chip { - color: color-mix(in srgb, var(--fg) 60%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - } - .cmp-model-meta-warning { - color: var(--color-error); - background: color-mix(in srgb, var(--color-error) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--color-error) 24%, transparent); - font-weight: 600; - } - .cmp-model-meta-muted { - color: color-mix(in srgb, var(--fg) 36%, transparent); - } - .cmp-rm-btn { - background: none; border: none; color: var(--accent, var(--red)); cursor: pointer; - min-width: 20px; font-size: 16px; font-weight: 600; opacity: 0.3; - transition: all 0.15s; padding: 0; line-height: 1; text-align: center; - position: relative; top: -2px; - order: 2; - margin-left: auto; - align-self: center; - } - .cmp-prov-select { - flex: 0 0 auto; width: 120px; font-size: 0.8em; - } - /* Eval-prompts picker — only shown during compare; absolute top-right - inside .chat-input-top, matching .model-picker-wrap's slot. */ - .chat-input-top > .cmp-eval-wrap { - position: relative; - grid-column: 2; - grid-row: 1; - top: -2px; right: 0; - z-index: 2; - } - .cmp-eval-btn { - display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 11px; font-weight: 500; - font-family: inherit; - cursor: pointer; opacity: 0.75; - transition: opacity 0.15s, border-color 0.15s; - } - .cmp-eval-btn:hover { opacity: 1; border-color: var(--fg); } - .cmp-eval-clear { - display: inline-flex; align-items: center; gap: 4px; margin-left: 4px; - border: 0; background: transparent; color: var(--fg); opacity: .55; - padding: 3px 5px; font: inherit; font-size: 10px; cursor: pointer; - } - .cmp-eval-clear:hover { opacity: 1; } - .cmp-eval-caret { opacity: 0.7; transform: rotate(180deg); } - .cmp-eval-menu { - position: absolute; bottom: calc(100% + 4px); right: 0; - min-width: 220px; max-width: 280px; - max-height: 360px; overflow-y: auto; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 -4px 16px rgba(0,0,0,0.3); - padding: 4px; - z-index: 1000; - } - .cmp-eval-menu.hidden { display: none; } - .cmp-eval-group-label { - font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px; - opacity: 0.45; font-weight: 600; - padding: 6px 8px 2px; - } - .cmp-eval-item { - display: block; width: 100%; - text-align: left; - padding: 5px 8px; - background: none; border: none; - color: var(--fg); font-size: 11px; - font-family: inherit; - border-radius: 4px; - cursor: pointer; - } - .cmp-eval-item:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - } - .cmp-eval-empty { - padding: 10px; text-align: center; - font-size: 11px; opacity: 0.5; - } - /* Tick on items that ship a known expected answer */ - .cmp-eval-item-tick { - float: right; - margin-left: 6px; - font-size: 10px; - color: var(--color-success, #4caf50); - opacity: 0.8; - } - /* Expected-answer panel — floats as its own little window above the - chat-input-bar. Distinct surface, padded, drop-shadow, slightly - lifted so it reads as a separate UI element, not part of the input. */ - .chat-input-bar:has(.cmp-eval-expected) { position: relative; } - .cmp-eval-expected { - position: absolute; - bottom: calc(100% + 8px); - right: 0; - display: inline-flex; align-items: center; gap: 8px; - padding: 8px 12px; - font-size: 11px; - background: var(--panel); - border: 1px solid color-mix(in srgb, var(--color-success, #4caf50) 50%, transparent); - border-radius: 8px; - box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2); - color: var(--fg); - width: fit-content; - z-index: 5; - pointer-events: auto; - } - .cmp-eval-expected.hidden { display: none; } - .cmp-eval-expected-label { - opacity: 0.6; - text-transform: uppercase; - font-size: 9px; - letter-spacing: 0.5px; - font-weight: 600; - } - .cmp-eval-expected-value { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 11px; - } - .cmp-eval-expected-close { - background: none; border: none; color: var(--fg); - font-size: 14px; line-height: 1; padding: 0 0 0 4px; - opacity: 0.5; cursor: pointer; font-family: inherit; - } - .cmp-eval-expected-close:hover { opacity: 1; } - /* Auto-grade badge — stamped on a pane after stream completes when an - eval prompt with a known expected answer was used. */ - .pane-grade-badge { - display: inline-flex; align-items: center; justify-content: center; - width: 18px; height: 18px; - margin: 0 4px; - font-size: 12px; font-weight: 700; - border-radius: 50%; - border: 1px solid currentColor; - flex-shrink: 0; - } - .pane-grade-badge.pass { - color: var(--color-success, #4caf50); - background: color-mix(in srgb, var(--color-success, #4caf50) 12%, transparent); - } - .pane-grade-badge.fail { - color: var(--color-error, #e55); - background: color-mix(in srgb, var(--color-error, #e55) 12%, transparent); - } - - /* Compare probe overlay */ - .compare-probe-overlay { - position: fixed; inset: 0; z-index: 300; - background: rgba(0,0,0,0.5); display: flex; - align-items: center; justify-content: center; - padding: 12px; - box-sizing: border-box; - } - .compare-probe-card { - width: min(460px, calc(100% - 16px)); - max-height: min(620px, calc(100dvh - 24px)); - box-sizing: border-box; - background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px; - display: flex; flex-direction: column; align-items: stretch; gap: 10px; - min-width: 280px; box-shadow: 0 8px 28px rgba(0,0,0,0.28); - overflow: hidden; - font-family: inherit; - } - .compare-probe-title { - font-size: 13px; font-weight: 400; color: var(--fg); - } - .compare-probe-title-label { font-weight: 600; } - .compare-probe-title-status { font-weight: 400; } - .compare-probe-list { - display: grid; grid-template-columns: 1fr 1fr; gap: 6px; min-width: 0; width: 100%; - overflow-y: auto; - } - .compare-probe-row { - display: flex; align-items: center; gap: 6px; padding: 6px 10px; - border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent); - font-size: 12px; transition: background 0.2s; overflow: hidden; - } - .compare-probe-row.fail { - background: color-mix(in srgb, var(--color-error, #f44) 8%, transparent); - } - .compare-probe-spinner { - width: 24px; text-align: center; font-size: 10px; flex-shrink: 0; - letter-spacing: -1px; opacity: 0.6; - } - .compare-probe-spinner.ok { color: var(--color-success); animation: none; } - .compare-probe-spinner.fail { color: var(--color-error, #f44); animation: none; } - .compare-probe-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .compare-probe-status { font-size: 11px; opacity: 0.5; flex-shrink: 0; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .compare-probe-status.ok { color: var(--color-success); opacity: 1; } - .compare-probe-status.fail { color: var(--color-error, #f44); opacity: 1; } - .compare-probe-action-btn { - display: inline-flex; align-items: center; gap: 5px; - min-height: 25px; padding: 3px 8px; background: transparent; color: var(--fg); border: 1px solid var(--border); - border-radius: 5px; cursor: pointer; font-size: 10px; font-family: inherit; - opacity: 0.78; transition: opacity 0.15s, border-color 0.15s, background 0.15s; white-space: nowrap; flex-shrink: 0; - } - .compare-probe-action-btn svg, - .compare-probe-footer-btn svg { width: 12px; height: 12px; flex: 0 0 auto; color: var(--accent, var(--red)); } - .compare-probe-action-btn:hover { opacity: 1; border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--fg) 5%, transparent); } - .compare-probe-feedback { display: flex; flex-direction: column; gap: 5px; } - .compare-probe-feedback:empty { display: none; } - .compare-probe-detail { - display: flex; align-items: center; gap: 6px; min-width: 0; - padding: 6px 8px; font-size: 10px; - background: color-mix(in srgb, var(--color-error, #f44) 6%, transparent); - border-left: 2px solid color-mix(in srgb, var(--color-error, #f44) 72%, transparent); - border-radius: 4px; - } - .compare-probe-detail-icon { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - width: 14px; - height: 14px; - color: var(--accent, var(--red)); - font-family: inherit; - font-size: 11px; - font-weight: 700; - line-height: 1; - } - .compare-probe-detail-message { flex: 1 1 auto; min-width: 0; line-height: 1.35; color: color-mix(in srgb, var(--fg) 72%, transparent); overflow-wrap: anywhere; } - .compare-probe-footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; margin-top: 2px; } - .compare-probe-footer-btn { - display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 5px 10px; - font-family: inherit; font-size: 11px; font-weight: 600; line-height: 1; - } - .compare-probe-start-anyway, - .compare-probe-start-anyway:hover:not(:disabled) { - background: var(--accent, var(--red)); - border: 1px solid var(--accent, var(--red)); - color: var(--bg); - } - .compare-probe-start-anyway svg { color: currentColor; } - .compare-probe-start-anyway span { font-weight: 700; } - @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } - @keyframes pane-shake { - 0%, 100% { transform: translateX(0); } - 15% { transform: translateX(-3px) rotate(-0.5deg); } - 30% { transform: translateX(3px) rotate(0.5deg); } - 45% { transform: translateX(-2px); } - 60% { transform: translateX(2px); } - 75% { transform: translateX(-1px); } - } - - .chat-container.compare-active { - display: flex; - flex-direction: column; - padding: 0; - overflow: hidden; - animation: compare-enter 0.3s ease-out; - } - @keyframes compare-enter { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } - } - .chat-container.compare-active .chat-input-bar { - margin-bottom: 0; - } - .chat-container.compare-active #model-picker-wrap { - display: none !important; - } - .compare-grid { - display: grid; - gap: 4px; - flex: 1 1 0; - min-height: 0; - overflow: hidden; - position: relative; - } - .compare-mobile-tabs { display: none; } - .compare-grid[data-cols="2"] { grid-template-columns: 1fr 1fr; } - .compare-grid[data-cols="3"] { grid-template-columns: 1fr 1fr 1fr; } - .compare-grid[data-cols="4"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="5"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="6"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="7"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="8"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid { grid-auto-rows: 1fr; } - .compare-shuffle-notice { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%) scale(0.94); - z-index: 30; - display: inline-flex; - align-items: center; - gap: 8px; - padding: 10px 14px; - border: 1px solid color-mix(in srgb, var(--red) 28%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 92%, transparent); - color: var(--fg); - box-shadow: 0 10px 30px color-mix(in srgb, #000 24%, transparent); - font-size: 12px; - font-weight: 700; - line-height: 1; - opacity: 0; - pointer-events: none; - transition: opacity 0.14s ease, transform 0.14s ease; - } - .compare-shuffle-notice.show { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - } - .compare-shuffle-notice-icon { - display: inline-flex; - color: var(--red); - animation: compare-shuffle-dice 0.56s ease both; - } - .compare-shuffle-notice-icon svg { - display: block; - width: 18px; - height: 18px; - } - @keyframes compare-shuffle-dice { - 0% { transform: rotate(-18deg) scale(0.9); } - 55% { transform: rotate(380deg) scale(1.08); } - 100% { transform: rotate(360deg) scale(1); } - } - /* Sequential waterfall layout — stacked rows, staggered left, flush right */ - .compare-grid.sequential-layout { - display: flex !important; - flex-direction: column !important; - grid-template-columns: none !important; - gap: 4px; - overflow-y: auto; - } - .compare-grid.sequential-layout .compare-pane { - flex-shrink: 0; - min-height: 200px; - transition: margin-left 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease; - } - /* Herringbone diagonal cut on left side of sequential pane headers */ - .compare-grid.sequential-layout .compare-pane .pane-header { - clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%); - padding-left: 26px; - } - .compare-pane { - display: flex; - flex-direction: column; - border: 1px solid var(--border); - border-radius: 6px; - overflow: hidden; - min-height: 0; - min-width: 0; - position: relative; - transition: border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; - } - .compare-pane::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - z-index: 2; - pointer-events: none; - background: transparent; - transform-origin: left center; - } - .compare-pane.is-streaming { - border-color: color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - } - .compare-pane.is-streaming::before { - background: linear-gradient(90deg, transparent 0%, var(--accent, var(--red)) 35%, var(--red) 50%, var(--accent, var(--red)) 65%, transparent 100%); - animation: compare-pane-progress 1.25s linear infinite; - } - .compare-pane.is-awaiting-input { - border-color: color-mix(in srgb, var(--red) 42%, var(--border)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--red) 12%, transparent); - } - .compare-pane.is-awaiting-input::before { - background: var(--red); - animation: none; - } - .compare-pane.is-done { - border-color: color-mix(in srgb, var(--green, #50fa7b) 20%, var(--border)); - } - .compare-pane.is-failed { - border-color: color-mix(in srgb, var(--color-error) 42%, var(--border)); - } - .compare-pane.is-failed::before { - background: var(--color-error); - } - @keyframes compare-pane-progress { - from { transform: translateX(-100%); } - to { transform: translateX(100%); } - } - .compare-pane .pane-header { - display: flex; - flex-direction: column; - align-items: stretch; - gap: 4px; - padding: 4px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-bottom: 1px solid var(--border); - font-size: 0.82em; - font-weight: 600; - color: var(--fg); - transition: background 0.4s; - flex-shrink: 0; - overflow: visible; - min-width: 0; - flex-wrap: nowrap; - } - .pane-header-row { display: flex; align-items: center; width: 100%; min-width: 0; } - .pane-header-primary { min-height: 24px; } - .pane-header-secondary { min-height: 20px; } - .pane-primary-actions, .pane-stats { display: flex; align-items: center; gap: 4px; min-width: 0; } - .pane-primary-actions { margin-left: auto; flex: 0 0 auto; } - .pane-stats { flex: 1 1 auto; overflow: hidden; } - .compare-header-bar button:hover, - .compare-header-bar button:focus-visible { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)) !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-header-bar button:active { - background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent) !important; - transform: scale(0.96); - } - .compare-more-wrap { position: relative; display: inline-flex; } - .compare-more-btn { - display: inline-flex; align-items: center; justify-content: center; - width: 28px; height: 24px; padding: 0 !important; - border: 1px solid transparent; border-radius: 5px; - background: none; color: var(--fg); cursor: pointer; - opacity: 0.5; transition: opacity .15s, background .15s; - } - .compare-header-bar .compare-close-btn { - width: 24px; height: 24px; min-width: 24px; padding: 0; - border-radius: 999px; font-size: 0; display: inline-flex; - align-items: center; justify-content: center; - } - .compare-header-bar .compare-close-btn:hover, - .compare-header-bar .compare-close-btn:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-header-bar .compare-more-btn:hover, - .compare-header-bar .compare-more-btn:focus-visible, - .compare-header-bar .compare-more-btn[aria-expanded="true"] { - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-more-menu { - position: absolute; top: calc(100% + 5px); right: 0; z-index: 10002; - display: none; flex-direction: column; gap: 3px; min-width: 132px; - padding: 5px; border: 1px solid var(--border); border-radius: 8px; - background: var(--panel, var(--bg)); box-shadow: 0 8px 24px rgba(0,0,0,.3); - } - .compare-more-menu.is-open { display: flex; } - .compare-more-menu > .compare-export-wrap, - .compare-more-menu > button { - width: 100%; display: flex; align-items: center; - } - .compare-more-menu #compare-export-btn, - .compare-more-menu #compare-shuffle-btn, - .compare-more-menu #compare-check-btn, - .compare-more-menu #compare-add-menu-btn { - width: 100%; min-height: 32px; height: 32px; - box-sizing: border-box; justify-content: flex-start; gap: 7px; - padding: 7px 9px !important; - border: 1px solid transparent !important; border-radius: 6px; - background: transparent !important; color: var(--fg) !important; - font-size: 11px; font-weight: 500; opacity: .72; - transition: background .12s ease, opacity .12s ease, color .12s ease; - } - .compare-more-menu #compare-export-btn:hover, - .compare-more-menu #compare-shuffle-btn:hover, - .compare-more-menu #compare-check-btn:hover, - .compare-more-menu #compare-add-menu-btn:hover, - .compare-more-menu #compare-export-btn:focus-visible, - .compare-more-menu #compare-shuffle-btn:focus-visible, - .compare-more-menu #compare-check-btn:focus-visible, - .compare-more-menu #compare-add-menu-btn:focus-visible { - opacity: 1; background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: transparent !important; color: var(--fg) !important; outline: none; - } - .compare-more-menu #compare-export-btn svg, - .compare-more-menu #compare-shuffle-btn svg, - .compare-more-menu #compare-check-btn svg, - .compare-more-menu #compare-add-menu-btn svg { flex: 0 0 auto; opacity: .65; } - .compare-more-menu #compare-export-btn span, - .compare-more-menu #compare-shuffle-btn span, - .compare-more-menu #compare-check-btn span, - .compare-more-menu #compare-add-menu-btn span { - font-size: 11px !important; margin-left: 0 !important; - color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; - } - .compare-more-menu #compare-export-btn, - .compare-more-menu #compare-shuffle-btn, - .compare-more-menu #compare-check-btn, - .compare-more-menu #compare-add-menu-btn { - color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; - } - .compare-more-menu #compare-export-btn svg, - .compare-more-menu #compare-shuffle-btn svg, - .compare-more-menu #compare-check-btn svg, - .compare-more-menu #compare-add-menu-btn svg { color: var(--fg) !important; opacity: .65 !important; } - .compare-export-item:hover, - .compare-export-item:focus-visible { - background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: transparent !important; outline: none; - } - .compare-export-item { display: flex; align-items: center; gap: 8px; } - .compare-export-item > svg { flex: 0 0 auto; opacity: .65; } - .compare-export-menu .compare-export-item, - .compare-export-menu .compare-export-item > span { - color: var(--fg) !important; - opacity: 1 !important; - } - .compare-add-flap { - position: absolute; z-index: 20; right: -8px; top: 50%; - width: 34px; min-width: 34px; height: 30px; box-sizing: border-box; - padding: 0 !important; - display: inline-flex; flex-direction: row; align-items: center; justify-content: center; - gap: 0; border: 1px solid var(--border); - border-radius: 0 7px 7px 0; background: var(--panel, var(--bg)); - color: var(--fg); cursor: pointer; opacity: .7; - box-shadow: 3px 2px 9px color-mix(in srgb, var(--fg) 12%, transparent); - transform: translate(0, -50%); transition: width .18s ease, opacity .15s, background .15s, box-shadow .15s; - } - .compare-add-flap:hover, .compare-add-flap:focus-visible { - width: 70px; opacity: 1; background: color-mix(in srgb, var(--fg) 8%, var(--panel)); - box-shadow: 4px 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); - outline: none; - } - .compare-add-flap span { - max-width: 0; overflow: hidden; white-space: nowrap; margin-left: 0 !important; - font-size: 10px; opacity: 0; transition: max-width .18s ease, margin-left .18s ease, opacity .12s; - } - .compare-add-flap:hover span, .compare-add-flap:focus-visible span { - max-width: 32px; margin-left: 3px !important; opacity: 1; - } - .compare-add-flap svg { flex-shrink: 0; position: relative; left: -3px; } - @media (max-width: 768px) { - .compare-add-flap { - top: 8px; right: 8px; width: auto; min-width: 32px; height: 32px; - padding: 5px 8px !important; flex-direction: row; transform: none; - border-right: 1px solid var(--border); border-radius: 6px; - } - .compare-add-flap span { opacity: 1; } - } - .pane-actions { - display: flex; gap: 4px; align-items: center; margin-left: auto; flex-shrink: 0; - } - .compare-pane-footer { - font-size: 0.72em; opacity: 0.4; padding: 4px 10px; - border-top: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; flex-shrink: 0; - } - /* Per-pane vote button. Sits at the bottom of each compare pane so - the action lives right under the response it judges. */ - .pane-vote-footer { - padding: 6px 8px; - border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex-shrink: 0; - } - .pane-vote-btn { - width: 100%; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 6px 10px; - font-family: inherit; - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: background 0.15s, border-color 0.15s, opacity 0.15s; - } - .pane-vote-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); - border-color: var(--accent, var(--fg)); - } - .pane-vote-btn:disabled { cursor: not-allowed; } - .pane-vote-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; - } - .pane-action-btn { - background: none; border: none; color: var(--fg); cursor: pointer; - opacity: 0.3; padding: 2px; border-radius: 4px; - display: flex; align-items: center; transition: all 0.15s; - } - .pane-action-btn:hover { opacity: 0.8; background: color-mix(in srgb, var(--fg) 6%, transparent); } - /* Pane title as clickable model-swap button */ - .pane-title-btn { - background: none; border: none; cursor: pointer; - font-size: 10px; font-weight: 400; font-family: inherit; - color: var(--fg); padding: 0; - text-align: left; display: flex; align-items: center; gap: 4px; - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - transition: opacity 0.15s; - min-width: 0; flex: 1 1 0; - } - .pane-title-btn:hover { opacity: 0.7; } - .pane-title-caret { font-size: 0.6em; opacity: 0.35; flex-shrink: 0; position: relative; top: 2px; } - .pane-title-btn:hover .pane-title-caret { opacity: 0.7; } - .pane-mode-badge { - display: inline-flex; - align-items: center; - gap: 4px; - flex: 0 0 auto; - height: 18px; - padding: 0 6px; - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - color: color-mix(in srgb, var(--fg) 76%, transparent); - font-size: 9px; - font-weight: 600; - line-height: 1; - letter-spacing: 0; - white-space: nowrap; - } - .pane-mode-dot { - width: 5px; - height: 5px; - border-radius: 50%; - background: currentColor; - opacity: 0.75; - flex: 0 0 auto; - } - .pane-mode-detail { - opacity: 0.52; - font-weight: 500; - } - .pane-mode-badge.pane-mode-icon-only { - width: 20px; - padding: 0; - justify-content: center; - } - .pane-mode-settings { - opacity: 0.65; - cursor: pointer; - } - .pane-settings-btn { width: 20px; height: 20px; justify-content: center; } - .pane-settings-btn[aria-expanded="true"] { opacity: 1; background: color-mix(in srgb, var(--fg) 9%, transparent); } - .response-metrics[data-action="settings"] { cursor: pointer; } - .response-thinking-state { color: color-mix(in srgb, var(--fg) 55%, transparent); } - .pane-mode-settings:hover, - .pane-mode-settings:focus-visible, - .pane-mode-settings[aria-expanded="true"] { - opacity: 1; - color: var(--fg); - background: color-mix(in srgb, var(--fg) 9%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--fg)) 45%, var(--border)); - outline: none; - } - .pane-mode-icon, - .pane-mode-icon svg { - display: block; - width: 12px; - height: 12px; - } - .pane-mode-agent { - color: color-mix(in srgb, var(--fg) 76%, transparent); - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - border-color: color-mix(in srgb, var(--border) 70%, transparent); - } - .pane-mode-search { - color: color-mix(in srgb, var(--accent, var(--red)) 82%, var(--fg)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, var(--border)); - } - .pane-mode-research { - color: color-mix(in srgb, #36c48f 82%, var(--fg)); - background: color-mix(in srgb, #36c48f 10%, var(--bg)); - border-color: color-mix(in srgb, #36c48f 28%, var(--border)); - } - .compare-check-label { - position: relative; - top: -2px; - font-size: 11px; - margin-left: 3px; - } - .compare-check-icon { - position: relative; - top: 1px; - } - #compare-check-btn > .spinner-whirlpool { - position: relative; - top: -2px; - transform: none; - } - .compare-pane .pane-close-btn { opacity: 0.3; } - .compare-pane .pane-close-btn:hover { opacity: 1; color: var(--color-error); } - /* Model swap dropdown under pane title */ - .pane-model-dropdown { - position: absolute; - z-index: 1000; - min-width: 220px; - max-height: 300px; - overflow-y: auto; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 16px rgba(0,0,0,0.3); - padding: 4px; - } - .pane-model-item { - display: block; width: 100%; - box-sizing: border-box; - padding: 8px 10px; font-size: 11px; line-height: 1.3; - text-align: left; background: none; border: none; border-radius: 6px; - color: var(--fg); cursor: pointer; transition: background 0.12s ease; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - } - .pane-model-item:hover, - .pane-model-item:focus-visible { background: color-mix(in srgb, var(--red) 10%, transparent); outline: none; } - .pane-model-item.current { color: var(--red); font-weight: 600; } - .pane-timer { - font-size: 10px; font-weight: 400; opacity: 0.45; font-variant-numeric: tabular-nums; - white-space: nowrap; padding-right: 4px; - } - .pane-summary { - display: inline-flex; - align-items: center; - position: relative; - top: 1px; - min-height: 16px; - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding: 0 5px; - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 9px; - font-weight: 500; - line-height: 1; - font-variant-numeric: tabular-nums; - cursor: pointer; - transition: max-width .15s ease, white-space .15s ease; - } - .pane-summary:empty { display: none; } - .pane-stats-popup { - position: fixed; z-index: 10040; width: 210px; padding: 6px; - background: var(--panel, var(--bg)); color: var(--fg); - border: 1px solid var(--border); border-radius: 7px; - box-shadow: 0 8px 24px rgba(0,0,0,.3); font-size: 11px; - } - .pane-stats-popup-title { padding: 4px 6px 7px; font-weight: 600; border-bottom: 1px solid var(--border); } - .pane-stats-popup-row { display: flex; justify-content: space-between; gap: 12px; padding: 6px; } - .pane-stats-popup-row span { color: color-mix(in srgb, var(--fg) 55%, transparent); } - .pane-stats-popup-row strong { font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; } - /* Metrics stay on the lower row even in dense comparisons. */ - .compare-grid[data-cols="4"] .pane-timer, - .compare-grid[data-cols="5"] .pane-timer, - .compare-grid[data-cols="6"] .pane-timer { - width: auto; order: initial; margin-top: 0; padding-bottom: 0; padding-left: 0; - } - .pane-finish-badge { - font-weight: 600; color: var(--green, #50fa7b); - position: relative; - top: 2px; - } - .compare-pane.winner .pane-header { - background: color-mix(in srgb, var(--green, #50fa7b) 12%, transparent); - border-bottom-color: color-mix(in srgb, var(--green, #50fa7b) 30%, var(--border)); - } - .compare-pane.winner .pane-title { - color: var(--green, #50fa7b); - } - .compare-pane.loser .pane-header { - opacity: 0.5; - } - .confetti-piece { - position: fixed; - pointer-events: none; - z-index: 1000000; - } - .compare-pane.expanded { grid-column: 1 / -1; } - .compare-pane .chat-history { - flex: 1 1 0; - min-height: 0; - overflow-y: auto !important; - overflow-x: hidden; - padding: 8px; - display: flex; - flex-direction: column; - } - .compare-pane .chat-history .msg { - flex-shrink: 0; - } - .compare-gen-image { - max-width: 100%; - max-height: 100%; - object-fit: contain; - border-radius: 4px; - cursor: pointer; - } - .compare-section { - margin-bottom: 14px; - } - .compare-section:last-child { - margin-bottom: 0; - } - .compare-section-label { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.6px; - opacity: 0.5; - margin-bottom: 5px; - font-weight: 600; - } - /* Active-type/mode readout next to "Type:"/"Mode:" — only needed on mobile - (where the tab/toggle text labels are hidden); hidden on desktop. */ - .compare-type-current, - .compare-mode-current { display: none; } - /* Contextual one-liner under the mode toggles describing what you just - changed — empty until the first toggle, then a subtle hint. */ - .compare-mode-hint { - font-size: 11px; - opacity: 0.55; - margin-top: 6px; - min-height: 0; - line-height: 1.3; - } - .compare-mode-hint:empty { display: none; } - .compare-type-tabs .compare-mode-tab > svg, - .compare-type-current > svg { - color: var(--accent, var(--red)); - } - - .compare-mode-tabs { - display: flex; - gap: 4px; - flex-wrap: wrap; - min-width: 0; - } - /* Type tabs match Mode toggles 1:1 (same flex column layout, same metrics) */ - .compare-mode-tab { - display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 56px; height: 38px; flex: 1 1 0; - padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; - cursor: pointer; user-select: none; transition: all 0.15s; - background: none; color: var(--fg); opacity: 0.35; - flex-shrink: 0; gap: 0; - font-family: inherit; - } - .compare-mode-tab > svg { - width: 13px; height: 13px; flex: 0 0 13px; - } - .compare-mode-tab:hover { - opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); - } - .compare-mode-tab.active { - opacity: 1; border-color: var(--fg); - background: color-mix(in srgb, var(--fg) 10%, transparent); - } - .compare-sources-box { - display: flex; align-items: center; gap: 6px; - padding: 6px 10px; margin-bottom: 8px; - border-radius: 6px; font-size: 0.78em; - background: color-mix(in srgb, var(--red) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - cursor: default; - } - .compare-sources-box .sources-label { font-weight: 600; } - /* Compact tool blocks inside compare panes */ - .compare-pane .agent-thread-node { - margin: 4px 0; font-size: 0.85em; - } - .compare-pane .agent-thread-cmd { - max-height: 80px; overflow-y: auto; - } - .compare-pane .agent-tool-output pre { - max-height: 120px; overflow-y: auto; - } - .compare-vote-bar { - display: flex; justify-content: center; gap: 8px; - padding: 8px; border-top: 1px solid var(--border); - flex-shrink: 0; flex-wrap: wrap; - } - .compare-vote-bar.hidden { display: none; } - .compare-vote-btn { - padding: 6px 13px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; font-size: 0.8em; - transition: all 0.15s; white-space: nowrap; - } - .compare-score-label { - position: relative; - top: -2px; - display: inline-block; - } - .compare-vote-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .compare-vote-tie { opacity: 0.7; } - .compare-rematch-btn { - display: flex; align-items: center; gap: 6px; margin-left: 8px; - border-color: color-mix(in srgb, var(--accent, var(--red)) 60%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--fg); opacity: 1; font-weight: 600; - } - .compare-rematch-btn:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent); } - /* Preview button accent in pane header */ - .pane-preview-btn.active { color: var(--red); } - /* Full-pane iframe for HTML preview */ - .compare-pane-iframe { - flex: 1; - width: 100%; - border: none; - border-radius: 0 0 8px 8px; - background: #fff; - } - /* ---- Add-pane "+" button in pane header (last pane only) ---- */ - .pane-add-btn { display: none !important; font-size: 16px; font-weight: 600; } - .compare-pane:last-child .pane-add-btn { display: flex !important; } - /* Dropdown for adding a pane */ - .add-pane-dropdown { - position: absolute; - right: 0; - z-index: 100; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 10px; - max-height: 300px; - overflow-y: auto; - min-width: 220px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); - padding: 6px; - backdrop-filter: blur(12px); - animation: dropdown-in 0.15s ease-out; - } - .add-pane-search { - width: 100%; - padding: 6px 8px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font-size: 0.85em; - box-sizing: border-box; - margin-bottom: 4px; - position: sticky; - top: 0; - z-index: 1; - } - /* Compare toggle buttons — icon + label stacked */ - .compare-toggle-label { - display: block; - font-size: 9px; - line-height: 1; - margin-top: 2px; - font-weight: 500; - } - .compare-blind-toggle, - .compare-save-toggle, - .compare-dice-toggle, - .compare-parallel-toggle, - .compare-reset-toggle { - display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 56px; height: auto; flex: 1 1 0; - padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; - cursor: pointer; user-select: none; transition: all 0.15s; - background: none; color: var(--fg); opacity: 0.35; - flex-shrink: 0; gap: 0; - } - .compare-blind-toggle:hover, - .compare-save-toggle:hover, - .compare-dice-toggle:hover, - .compare-parallel-toggle:hover, - .compare-reset-toggle:hover { - opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); - } - .compare-blind-toggle.active { - opacity: 1; color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - } - .compare-blind-toggle.active > svg { - color: var(--accent, var(--red)); - } - .compare-save-toggle.active { - opacity: 1; color: var(--color-save-green); border-color: var(--color-save-green); - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .compare-dice-toggle.active { - opacity: 1; color: var(--red); border-color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .compare-parallel-toggle { - opacity: 1; color: #e0a050; border-color: #e0a050; - background: rgba(224, 160, 80, 0.1); - } - .compare-parallel-toggle.active { - color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - } - @media (max-width: 520px) { - .compare-toggle-label { display: none; } - /* Tab text labels are hidden on mobile, so spell out the active type next - to "Type:" with its icon. */ - .compare-type-current { - display: inline-flex; - align-items: center; - gap: 4px; - margin-left: 5px; - font-weight: 600; - vertical-align: -3px; - } - .compare-type-current svg { width: 14px; height: 14px; } - /* Mode list — comma-separated, each name already coloured inline. */ - .compare-mode-current { - display: inline; - margin-left: 5px; - font-weight: 600; - } - .compare-blind-toggle, - .compare-save-toggle, - .compare-dice-toggle, - .compare-parallel-toggle, - .compare-reset-toggle { - width: 32px; height: 32px; min-width: 32px; padding: 0; - } - /* The Group tab's seq/parallel toggle is a single full-width button (not - a row of compact icons like the Compare header), so keep its label and - make it a comfortable touch target with the text beside the icon. */ - #group-mode-btn { - flex-direction: row !important; - width: auto !important; - height: auto !important; - min-height: 44px; - padding: 8px 14px !important; - gap: 8px !important; - font-size: 13px; - } - #group-mode-btn .compare-toggle-label { - display: inline !important; - font-size: 13px; - margin-top: 0; - } - #group-mode-btn svg { width: 18px; height: 18px; } - /* Compare header: hide labels + close button, show icons only */ - #compare-shuffle-btn span { - display: none; - } - #compare-shuffle-btn, - #compare-check-btn, - #compare-add-btn { - padding: 3px 6px; - } - /* Save space so the header buttons fit on one row: tighter padding + - smaller labels (icons kept full size). (Score now lives in the vote bar.) */ - .compare-header-bar button { padding: 3px 5px !important; } - .compare-header-bar #compare-check-btn > span, - .compare-header-bar #compare-add-btn > span { - font-size: 10px; margin-left: 2px; - } - /* Compare mobile: keep the close X visible — it's the only way out - now that the hamburger is hidden during compare mode. */ - .compare-header-bar { - padding: 14px 8px 10px 8px !important; - min-height: 44px; - } - .compare-pane .pane-header { - align-items: stretch; - row-gap: 3px; - } - .compare-pane .pane-title-btn { - flex: 1 1 auto; - width: auto; - padding-right: 2px; - } - /* Compare is already visibly in chat mode; the Chat speech-bubble - badge adds a redundant second row on narrow screens. */ - .compare-pane .pane-mode-chat { display: none; } - .compare-pane .pane-timer, - .compare-pane .pane-summary, - .compare-pane .pane-finish-badge, - .compare-pane .pane-mode-badge, - .compare-pane .pane-actions { - order: 1; - align-self: center; - } - .compare-pane .pane-finish-badge { top: 0; } - .compare-pane .pane-summary { - max-width: 110px; - } - .compare-pane .pane-mode-detail { - display: none; - } - .compare-grid[data-cols="4"] .compare-pane .pane-timer, - .compare-grid[data-cols="5"] .compare-pane .pane-timer, - .compare-grid[data-cols="6"] .compare-pane .pane-timer { - width: auto; - order: 1; - margin-top: 0; - padding-bottom: 0; - padding-left: 0; - } - .compare-pane .pane-actions { - margin-left: auto; - } - /* Mode tabs: icons only, centered */ - .compare-mode-tab span { display: none; } - .compare-mode-tabs { justify-content: center; } - /* Header action buttons: hide text labels on Export / Shuffle / - Model on mobile so the close X fits on the right. Score keeps - its "Score" label because its icon (4-square grid) reads too - similar to Shuffle's icon without text. */ - .compare-header-bar #compare-export-btn > span, - .compare-header-bar #compare-shuffle-btn > span { - display: none; - } - /* Override the desktop override: on mobile the model picker overlay - MUST cap its height to the viewport so the dropdown doesn't run - past the bottom of the screen. */ - #compare-model-overlay .modal-content { - max-height: 85dvh !important; - max-height: 85vh !important; - overflow: hidden !important; - } - #compare-model-overlay .modal-body { - overflow: auto !important; - flex: 1 1 auto !important; - min-height: 0 !important; - } - } - /* Hide number input spinners */ - input[type="number"]::-webkit-inner-spin-button, - input[type="number"]::-webkit-outer-spin-button { - -webkit-appearance: none; margin: 0; - } - - /* ── Sensitive info censor ── */ - .censored-item { - filter: blur(5px); - cursor: pointer; - transition: filter 0.2s; - border-radius: 2px; - padding: 0 2px; - background: rgba(255,100,100,0.08); - user-select: none; - } - .censored-item:hover { filter: blur(3px); background: rgba(255,100,100,0.15); } - .censored-item.revealed { - filter: none; - background: rgba(100,255,100,0.08); - user-select: auto; - cursor: text; - } - - #settings-menu-list .list-item.active { - background: color-mix(in srgb, var(--accent) 10%, transparent); - border-color: var(--accent); - } - - /* ── Print / PDF Export ── */ - @media print { - body { background: #fff !important; color: #000 !important; } - #sidebar, .sidebar, #icon-rail, .hamburger-btn, #sidebar-backdrop, .chat-input-bar, .input-bar-wrapper, - #welcome-screen, .chat-top-bar, .chat-meta-overlay, .msg-footer, - .modal, .toast, .overflow-wrapper, .mode-toggle, .incognito-btn, - button, .dropdown, .session-dropdown, - .agent-tool-spinner, .agent-thread-node.running { display: none !important; } - main.chat-container { width: 100% !important; margin: 0 !important; padding: 0 !important; max-height: none !important; overflow: visible !important; } - #chat-history { max-height: none !important; overflow: visible !important; height: auto !important; padding: 0 !important; } - .msg { break-inside: avoid; page-break-inside: avoid; border: none !important; box-shadow: none !important; } - .msg-ai { background: #f5f5f5 !important; color: #000 !important; } - .msg-user { background: #e8e8e8 !important; color: #000 !important; } - .msg .role { color: #333 !important; font-weight: bold; } - .msg .body { color: #000 !important; } - pre, code { background: #f0f0f0 !important; color: #000 !important; border: 1px solid #ccc !important; } - details { display: block !important; } - details[open] summary ~ * { display: block !important; } - details > summary { list-style: none; } - details > summary::before { content: "" !important; } - #chat-history::before { content: attr(data-print-title); display: block; font-size: 1.3em; font-weight: bold; margin-bottom: 1em; color: #000; } - a { color: #000 !important; text-decoration: underline; } - } - - -/* ── Components (from style.css) ── */ - -/* Self-hosted Fira Code font */ -@font-face { font-family: 'Fira Code'; font-weight: 300; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Light.woff2') format('woff2'); } -@font-face { font-family: 'Fira Code'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Regular.woff2') format('woff2'); } -@font-face { font-family: 'Fira Code'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-SemiBold.woff2') format('woff2'); } - -/* Scrollbar styling */ - -/* Code block styling */ -pre, code, .hljs { - font-size: 11px; - line-height: 1.5; -} - -/* WebKit (Chrome, Edge, Safari) */ -/* Utility class for red text */ -.red-text { - color: var(--red); -} - -/* Internal chat links (search results, session references) */ -a.chat-link { - color: var(--hl-function); - text-decoration: none; - border-bottom: 1px dotted var(--hl-function); - cursor: pointer; -} -a.chat-link:hover { - opacity: 0.8; - border-bottom-style: solid; -} - -a.chat-link[href^="#email-"] { - display: inline; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, transparent); - border-radius: 5px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - color: var(--fg); - font-weight: 600; - border-bottom-style: solid; -} -a.chat-link[href^="#email-"]:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -a.chat-link[href^="#notes-more-"], -a.chat-link[href^="#skills-more-"], -a.chat-link[href^="#memories-more-"], -a.chat-link[href^="#events-more-"], -a.chat-link[href^="#sessions-more-"] { - color: var(--muted); - border-bottom: 1px dotted color-mix(in srgb, var(--muted) 55%, transparent); - font-size: 11px; - font-weight: 500; -} -a.chat-link[href^="#notes-more-"]:hover, -a.chat-link[href^="#skills-more-"]:hover, -a.chat-link[href^="#memories-more-"]:hover, -a.chat-link[href^="#events-more-"]:hover, -a.chat-link[href^="#sessions-more-"]:hover { - color: var(--fg); - opacity: 1; - border-bottom-style: solid; -} -.more-list-expanded { - display: block; - max-height: 0; - overflow: hidden; - opacity: 0; - color: inherit; - font: inherit; - line-height: inherit; - transition: max-height 220ms ease, opacity 160ms ease; -} -.more-list-expanded.is-open { - opacity: 1; -} -.more-list-expanded-content { - color: inherit; - font: inherit; - line-height: inherit; -} - -/* Session items */ -.session-item { position: relative; } -.text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.session-menu-btn { padding: 0 2px !important; min-width: 20px; height: 20px; display: inline-flex !important; align-items: center; justify-content: center; background: none !important; border-color: transparent !important; } -.session-menu-btn:hover { background: none !important; border-color: transparent !important; } -@media (max-width: 768px) { - .session-menu-btn { display: none !important; } - .item-drag-handle { display: none !important; } -} -.session-menu-btn svg { transition: transform 0.2s ease; } - -/* First-time swipe hint */ -.swipe-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 0.7rem; - color: var(--color-error, #f44); - opacity: 0.8; - transition: opacity 0.5s ease; - pointer-events: none; - display: flex; - align-items: center; - gap: 4px; - z-index: 2; -} -.swipe-hint-arrow { - animation: swipe-nudge 1s ease-in-out infinite; -} -@keyframes swipe-nudge { - 0%, 100% { transform: translateX(0); } - 50% { transform: translateX(-6px); } -} - -/* Utility classes */ -.muted { opacity: 0.5; } -.muted-sm { opacity: 0.35; font-size: 0.8em; } -.accent-link { color: var(--accent-primary, var(--color-accent)); cursor: pointer; font-size: 0.85em; } -.models-empty-state { text-align: center; padding: 12px 8px; line-height: 1.6; } - -/* Provider logo inside favorite dot */ -.provider-logo { - border: none !important; - background: none !important; - width: 14px !important; height: 14px !important; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.15s; -} -.provider-logo svg { width: 14px; height: 14px; display: block; } -.provider-logo:hover { opacity: 1 !important; transform: scale(1.2); } - -/* Hide session menu button until hover — use width:0 so it doesn't steal space from text */ -.list-item .hamburger { opacity: 0; width: 0; min-width: 0; overflow: hidden; padding: 0 !important; transition: opacity 0.15s, width 0.15s, padding 0.15s; flex-shrink: 0; display: flex; align-items: center; justify-content: center; } -.list-item:hover .hamburger { opacity: 1; width: 24px; min-width: 24px; padding: 0 4px !important; } -@media (max-width: 768px) { - .list-item .hamburger { opacity: 0.5; width: 28px; min-width: 28px; padding: 0 4px !important; } - .list-item .hamburger:active { opacity: 1; } -} - -/* Hamburger menu button styling (overrides default button appearance) */ -button.hamburger { - background: none; - border: none; - padding: 0; - cursor: pointer; -} - -/* ============================================ */ -/* HEADER SIZING FOR CHAT MESSAGES */ -/* ============================================ */ -/* Markdown in chat messages — colorful, scannable */ -.msg h1, .msg h2, .msg h3, .msg h4, .msg h5, .msg h6 { - margin: 0.6em 0 0.3em 0; - line-height: 1.3; - border-bottom: none; - padding-bottom: 0; -} - -.msg h1 { - font-size: 1.15em; - font-weight: 700; - color: var(--hl-keyword, #c678dd); -} - -.msg h2 { - font-size: 1.1em; - font-weight: 600; - color: var(--hl-function, #5b8def); -} - -.msg h3 { - font-size: 1.05em; - font-weight: 600; - color: var(--hl-string, #98c379); -} - -.msg h4 { - font-size: 1.02em; - font-weight: 600; - color: var(--hl-builtin, #e5c07b); -} - -.msg h5 { - font-size: 1em; - font-weight: 600; - color: var(--hl-variable, #61afef); -} - -.msg h6 { - font-size: 0.95em; - font-weight: 600; - color: var(--hl-number, #d19a66); -} - -/* Bold text — subtle accent color */ -.msg strong, .msg b { - color: var(--hl-builtin, #e5c07b); - font-weight: 600; -} - -/* Italic — softer highlight */ -.msg em, .msg i { - color: var(--hl-params, #abb2bf); - font-style: italic; -} - -/* Bold + italic */ -.msg strong em, .msg em strong, -.msg b i, .msg i b, -.msg b em, .msg em b, -.msg strong i, .msg i strong { - color: var(--hl-keyword, #c678dd); -} - -/* Strikethrough */ -.msg del { - color: color-mix(in srgb, var(--fg) 45%, transparent); - text-decoration: line-through; -} - -/* Inline code */ -.msg code:not(pre code) { - color: var(--hl-string, #98c379); - background: color-mix(in srgb, var(--fg) 6%, transparent); - padding: 1px 5px; - border-radius: 4px; - font-size: 0.9em; -} - -/* Blockquotes */ -.msg blockquote { - border-left: 3px solid var(--hl-function, #5b8def); - padding: 4px 12px; - margin: 0.5em 0; - color: color-mix(in srgb, var(--fg) 75%, transparent); - background: var(--bg); - border-radius: 0 6px 6px 0; -} -.msg blockquote p { margin: 0.3em 0; } - -/* Horizontal rules */ -.msg hr { - border: none; - height: 1px; - background: linear-gradient(90deg, transparent, var(--border), transparent); - margin: 0.8em 0; -} - -/* Lists */ -.msg ul, .msg ol { - margin: 0.3em 0 0.3em 1.2em; - padding: 0; -} -.msg li { - margin: 0.15em 0; -} -.msg li::marker { - color: var(--hl-function, #5b8def); -} - -/* Links */ -.msg a { - color: var(--hl-function, #5b8def); - text-decoration: none; - border-bottom: 1px solid rgba(91, 141, 239, 0.3); - transition: border-color 0.15s; -} -.msg a:hover { - border-bottom-color: var(--hl-function, #5b8def); -} -.msg a.is-loading { - opacity: 0.65; - border-bottom-color: var(--accent, #bd93f9); - cursor: progress; -} - -/* Tables */ -.msg table { - border-collapse: collapse; - margin: 0.5em 0; - font-size: 0.9em; - display: block; - width: max-content; - min-width: 100%; - max-width: 100%; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - table-layout: auto; -} -.msg th { - background: color-mix(in srgb, var(--fg) 7%, transparent); - color: var(--hl-keyword, #c678dd); - font-weight: 600; - padding: 6px 12px; - border: 1px solid var(--border); - text-align: left; - min-width: 9ch; - word-break: normal; - overflow-wrap: break-word; -} -.msg td { - padding: 5px 12px; - border: 1px solid var(--border); - min-width: 9ch; - word-break: normal; - overflow-wrap: break-word; -} - -/* Agent UI Styling */ -.agent-controls { - background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 12px; - margin-bottom: 12px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.agent-toggle label { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-weight: 500; - margin-bottom: 8px; -} - -#workflow-selector { - margin-top: 8px; -} - -#workflow-type { - width: 100%; - padding: 6px 12px; - border: 1px solid #ced4da; - border-radius: 4px; - background: white; - font-size: 14px; -} - -.agent-progress { - background: #ffebee; - border: 1px solid #ef9a9a; - border-radius: 6px; - padding: 12px; - margin: 8px 0; - text-align: center; -} - -.agent-working { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - font-style: italic; - color: var(--red); -} - -.loading-dots::after { - content: '...'; - animation: dots 1.5s infinite; -} - -@keyframes dots { - 0%, 20% { opacity: 0; } - 40% { opacity: 0.5; } - 60%, 100% { opacity: 1; } -} - -.workflow-info { - background: #f8f9fa; - border: 1px solid var(--red); - border-radius: 6px; - padding: 8px 12px; - margin: 4px 0; - font-size: 0.9em; - color: var(--red); - text-align: center; -} - -/* Scrollbar uses --red from :root (set at top of file) */ - -/* Loading spinner */ -@keyframes spin { - to { transform: rotate(360deg); } -} -.spinner { - width: 24px; - height: 24px; - margin: 8px auto; - border: 3px solid var(--border); - border-top-color: var(--red); - border-radius: 50%; - animation: spin 0.9s linear infinite; -} - -/* Inline spinner for buttons */ -.btn-spinner { - display: inline-block; - width: 12px; - height: 12px; - border: 2px solid transparent; - border-top-color: currentColor; - border-radius: 50%; - animation: spin 1s linear infinite; - margin-right: 6px; -} - -/* Loading indicator for messages */ -.loading-indicator { - display: flex; - align-items: center; - justify-content: center; - padding: 10px; -} -.loading-dots { - display: flex; - gap: 4px; -} -.loading-dot { - width: 6px; - height: 6px; - background-color: var(--fg); - border-radius: 50%; - opacity: 0.6; -} -.loading-dot:nth-child(1) { - animation: loading-bounce 1.4s infinite ease-in-out both; -} -.loading-dot:nth-child(2) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.32s; -} -.loading-dot:nth-child(3) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.64s; -} -@keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } -} - -/* Hamburger menu button */ -.hamburger { - display: inline-flex; - flex-direction: column; - justify-content: space-between; - width: 24px; - height: 18px !important; - background: none; - border: none; - padding: 0; - cursor: pointer; -} -.hamburger span { - display: block; - width: 100%; - height: 3px; - background: var(--fg); - border-radius: 2px; -} - -/* Agent indicator */ -#agent-indicator { - position: fixed; - top: 20px; - right: 20px; - background: var(--sidebar-bg, var(--panel)); - color: var(--fg); - border: 1px solid var(--border); - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - display: none; - z-index: 100; - cursor: pointer; - transition: all 0.2s ease; -} -#agent-indicator.active { - display: block; - border-color: var(--color-agent-active); - box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); -} -#agent-indicator:hover { - border-color: var(--color-agent-active); - background: var(--panel); -} - -/* Preset buttons */ -.preset-btn { - height: 27.2px; - padding: 0 8.5px; - margin-left: 4px; - border: 1px solid var(--red); - border-radius: 4px; - background: var(--sidebar-bg, var(--panel)); - color: var(--fg); - font-family: inherit; - font-size: 10.2px; - cursor: pointer; - transition: all 0.2s ease; -} - -.preset-btn:hover { - background: var(--panel); - border-color: var(--fg); -} - -.preset-btn.active { - background: var(--panel); - border-color: var(--red); - box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); - font-weight: 600; -} - -/* Custom preset modal — inherits from .modal base class */ - -/* Unified chat input area */ -.chat-input-area { - display: flex; - flex-direction: column; - gap: 8px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin-top: 12px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.chat-controls-row { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; -} - -.chat-controls-left { - display: flex; - align-items: center; - gap: 8px; -} - -.chat-controls-right { - display: flex; - align-items: center; - gap: 8px; -} - -.control-group { - display: flex; - align-items: center; - gap: 4px; -} - -.control-label { - font-size: 11px; - color: var(--fg); - opacity: 0.8; -} - -.toggle-switch { - position: relative; - display: inline-block; - width: 30px; - height: 16px; -} - -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; -} - -.toggle-slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 8px; - transition: background 0.08s; -} - -.toggle-slider:before { - position: absolute; - content: ""; - height: 12px; - width: 12px; - left: 4px; - top: 2px; - background-color: var(--panel); - border-radius: 50%; - transition: transform 0.08s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); -} - -.toggle-switch input:checked + .toggle-slider { - background-color: var(--toggle-active, var(--red)); -} - -.toggle-switch input:checked + .toggle-slider:before { - transform: translateX(14px); -} - -.preset-buttons-row { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: wrap; -} - -.chat-input-form { - display: flex; - gap: 8px; - align-items: flex-end; -} - -#message { - flex: 1; - min-height: 34px; - max-height: 120px; - resize: none; - font-size: 13px !important; - overflow-y: auto !important; - line-height: 1.4 !important; - font-family: inherit !important; -} - -.action-button { - width: 34px; - height: 34px; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - margin: 0; - background: none; - border: 1px solid var(--border); - border-radius: 4px; - cursor: pointer; - color: var(--fg); - transition: all 0.2s ease; -} - -.action-button:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: var(--fg); -} - -.action-button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.action-button.recording { - background: var(--color-recording); - border-color: var(--color-recording); - animation: pulse 1.5s infinite; -} - -@keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } -} - -#stop-icon { - display: none; - width: 14px; - height: 14px; - background: var(--color-recording); - border-radius: 2px; -} - -/* Attachment strip — centered + max-width to match the chat-input-bar below, - otherwise the chip floats flush-left while the input is centered (visible on - desktop where the chat area is wider than 800px). */ -.attach-strip { - display: flex; - gap: 6px; - flex-wrap: wrap; - padding: 2px 8px; - max-width: 800px; - width: 100%; - margin-left: auto; - margin-right: auto; - box-sizing: border-box; -} -.attach-strip:empty { display: none; } - -/* Upload-in-progress feedback: the message bubble shows immediately, so while - the files are still uploading we put a whirlpool ON each attachment chip and - dim the chip's content — making it obvious that file is being sent, not stuck. */ -.attach-strip.attach-uploading .thumb { - position: relative; - pointer-events: none; -} -.attach-strip.attach-uploading .thumb > :not(.thumb-upload-spinner) { - opacity: 0.4; -} -.thumb-upload-spinner { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; - z-index: 3; -} - -.thumb { - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 11%, transparent); - padding: 3px 6px; - font-size: 12px; - display: flex; - gap: 6px; - align-items: center; - border-radius: 4px; - transition: all 0.2s ease; - max-width: 180px; -} -.thumb-img { - max-width: 60px; - max-height: 40px; - border-radius: 3px; - object-fit: cover; -} -.attach-image-preview { - margin: 4px 0; -} -.attach-image-preview img { - box-shadow: 0 1px 4px rgba(0,0,0,0.2); - /* Same border as the chat bubbles. */ - border: 1px solid var(--bubble-border, var(--border)); -} -/* Image chips: image fills the chip, X overlays as a corner accent badge. - Same on desktop and mobile — doc/text chips keep the beside-X layout. */ -.thumb.thumb-image { - position: relative; - padding: 0; -} -.thumb.thumb-image .thumb-img { - max-height: 56px; - display: block; -} -@media (max-width: 768px) { - .thumb.thumb-image .thumb-img { - cursor: pointer; - touch-action: manipulation; - } -} -.thumb.thumb-image button { - position: absolute; - /* Sit on the top-right corner edge as an accent badge. */ - top: -7px; - right: -7px; - width: 24px; - height: 24px; - min-width: 0; - padding: 0; - border: 2px solid var(--bg); - border-radius: 50%; - background: var(--accent-primary, var(--red)); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 15px; - line-height: 1; - z-index: 3; - transition: transform 0.12s ease, filter 0.12s ease, box-shadow 0.12s ease; -} -.thumb.thumb-image button:hover { - transform: scale(1.12); - filter: brightness(1.12); - box-shadow: 0 2px 8px rgba(0,0,0,0.25); -} -.thumb.thumb-image button:active { - transform: scale(0.96); -} - -.attach-crop-overlay { - position: fixed; - inset: 0; - z-index: var(--attach-crop-z, 100001); - background: rgba(0, 0, 0, 0.72); - display: flex; - align-items: center; - justify-content: center; - padding: 14px; - box-sizing: border-box; -} -.attach-crop-panel { - width: min(94vw, 520px); - max-height: min(86vh, 720px); - background: var(--panel, #111); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 18px 50px rgba(0,0,0,0.45); - overflow: hidden; - display: flex; - flex-direction: column; -} -.attach-crop-stage { - position: relative; - min-height: 280px; - height: min(66vh, 560px); - background: #050505; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - touch-action: none; -} -.attach-crop-img { - max-width: 100%; - max-height: 100%; - object-fit: contain; - user-select: none; - pointer-events: none; -} -.attach-crop-box { - position: absolute; - border: 2px solid var(--accent, var(--red)); - box-shadow: 0 0 0 9999px rgba(0,0,0,0.42); - cursor: move; - touch-action: none; - box-sizing: border-box; -} -.attach-crop-box::before, -.attach-crop-box::after { - content: ''; - position: absolute; - inset: 33.333% 0 auto; - border-top: 1px solid rgba(255,255,255,0.38); -} -.attach-crop-box::after { - inset: 66.666% 0 auto; -} -.attach-crop-handle { - position: absolute; - right: -9px; - bottom: -9px; - width: 20px; - height: 20px; - border-radius: 50%; - background: var(--accent, var(--red)); - border: 2px solid var(--bg); - box-sizing: border-box; - cursor: nwse-resize; -} -.attach-crop-actions { - display: flex; - gap: 8px; - padding: 10px; - background: color-mix(in srgb, var(--bg) 78%, transparent); -} -.attach-crop-btn { - flex: 1; - height: 34px; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.attach-crop-primary { - background: var(--accent, var(--red)); - color: #fff; - border-color: var(--accent, var(--red)); -} -@media (max-width: 768px) { - /* Collapsed "N files" badge: use the same corner-X accent badge as image thumbs. */ - .thumb-collapsed { position: relative; } - .thumb-collapsed .thumb-collapsed-x { - position: absolute; - top: -7px; - right: -7px; - width: 24px; - height: 24px; - min-width: 0; - padding: 0; - border: 2px solid var(--bg); - border-radius: 50%; - background: var(--accent-primary, var(--red)); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 15px; - line-height: 1; - z-index: 3; - opacity: 1; - } - /* Bigger remove-X tap target for non-image (doc/text) chips on mobile too. */ - .thumb button { - height: 28px; - min-width: 28px; - font-size: 15px; - } -} -.thumb span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.thumb:hover { - background: color-mix(in srgb, var(--fg) 16%, transparent); - border-color: var(--fg); -} - -.thumb button { - height: 24px; - padding: 0 5px; - font-size: 0.95em; - border-radius: 4px; - color: var(--accent-primary, var(--red)); -} - -.thumb-collapsed { - cursor: pointer; - color: var(--red); - border-color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - font-weight: 600; - gap: 8px; - border-radius: 999px; /* pill — rounder than the square file chips */ - padding-left: 12px; -} -.thumb-collapsed:hover { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.thumb-collapsed-label { white-space: nowrap; } -.thumb-collapsed-x { - height: 24px; padding: 0 7px; font-size: 13px; border-radius: 4px; - color: var(--accent-primary, var(--red)); - background: none; border: none; cursor: pointer; opacity: 0.6; -} -.thumb-collapsed-x:hover { opacity: 1; } - -/* Recording indicator */ -#recording-indicator { - position: fixed; - top: 10px; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin: 10px; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); -} - -#recording-indicator.hidden { - display: none !important; -} - -.recording-content { - display: flex; - align-items: center; - gap: 12px; - color: white; -} - -.recording-icon { - color: var(--color-recording); - font-size: 20px; - animation: pulse 1.5s infinite; -} - -.recording-text { - font-size: 16px; - font-weight: 500; -} - -.stop-recording-btn { - background: var(--color-recording); - color: white; - border: none; - border-radius: 6px; - padding: 6px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s ease; -} - -.stop-recording-btn:hover { - background: var(--color-recording-hover); -} - -#recording-indicator.error { - background: rgba(173, 26, 26, 0.9); -} - -.recording-error { - color: var(--color-recording); - font-size: 14px; - margin-top: 4px; -} - -/* Mermaid diagram containers */ -.mermaid-container { - margin: 12px 0; - padding: 16px; - background: color-mix(in srgb, var(--bg) 95%, var(--fg)); - border: 1px solid var(--border); - border-radius: 8px; - overflow-x: auto; - text-align: center; -} -.mermaid-container svg { max-width: 100%; height: auto; } - -/* KaTeX math overrides */ -.katex-display { margin: 0.8em 0; overflow-x: auto; overflow-y: hidden; } -.katex { font-size: 1.1em; } - -/* Hide thinking sections globally via settings toggle */ -body.hide-thinking .thinking-section { display: none !important; } - -/* Widen chat area via settings toggle */ -body.fullwidth-chat .chat-history { - padding-left: 0 !important; - padding-right: 12px !important; -} - -/* Thinking process styles — colors follow theme accent */ -.msg .body .stream-content { - width: 100%; -} -.thinking-section { - margin: 12px 0; - width: 100%; - max-width: 100%; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - overflow: hidden; - transition: all 0.3s ease; -} - -.thinking-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 6px 12px; - cursor: pointer; - user-select: none; - background: color-mix(in srgb, var(--red) 8%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - transition: background 0.2s ease; -} - -.thinking-header > .token-new { - display: none; -} - -.thinking-header > div:last-child { - flex-shrink: 0; -} - -.thinking-header:hover { - background: color-mix(in srgb, var(--red) 12%, transparent); -} - -.thinking-header-left { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9em; - color: var(--red); - font-weight: 500; - overflow: hidden; - min-width: 0; -} -.thinking-header-left span { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - transition: opacity 0.2s ease; -} - -.thinking-icon { - font-size: 1.1em; -} - -.thinking-toggle { - font-size: 0.9em; - color: var(--red); - transition: transform 0.3s ease; -} -.thinking-toggle::after { - content: '\25BC'; /* ▼ */ -} - -.thinking-toggle.expanded { - transform: rotate(180deg); -} - -.thinking-content { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; - padding: 0 12px; -} - -.thinking-content.expanded { - max-height: 300px; - overflow-y: auto; - padding: 12px; -} - -.thinking-content-inner { - font-size: 0.85em; - color: var(--fg); - opacity: 0.9; - line-height: 1.5; -} -.live-reply-content { - animation: fadeSlideIn 0.3s ease-out; -} -@keyframes fadeSlideIn { - from { opacity: 0; transform: translateY(4px); } - to { opacity: 1; transform: translateY(0); } -} - -/* Thinking indicator animation */ -.thinking-indicator { - display: flex; - align-items: center; - gap: 4px; - color: var(--red); - font-style: italic; - padding: 8px 0; -} - -.thinking-dots::after { - content: '...'; - animation: thinking-dots 1.5s infinite; - display: inline-block; - width: 20px; - text-align: left; -} - -@keyframes thinking-dots { - 0%, 20% { content: '.'; } - 40% { content: '..'; } - 60%, 100% { content: '...'; } -} - -.thinking-complete { - color: var(--red); - font-size: 0.9em; - padding: 4px 0; - opacity: 0.8; -} - -.research-context-continue-hint { - display: inline-flex; - align-items: center; - gap: 4px; - margin: 5px 2px 0; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - opacity: 0.55; -} -.research-context-continue-hint svg { - flex: 0 0 auto; - opacity: 0.8; -} - -/* ── Sources section — collapsible source citations ── */ -.sources-section { - margin: 8px 0 12px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - overflow: hidden; - transition: all 0.3s ease; -} -.sources-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 12px; - cursor: pointer; - background: color-mix(in srgb, var(--red) 8%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - transition: background 0.2s ease; - user-select: none; -} -.sources-header:hover { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.sources-header-left { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.85em; - font-weight: 500; - color: var(--red); -} -.sources-header-left svg { - width: 14px; - height: 14px; - flex-shrink: 0; - opacity: 0.7; -} -.sources-toggle { - font-size: 0.8em; - color: var(--red); - opacity: 0.7; - transition: none; -} -.sources-toggle::after { - content: '\25B6'; /* ▶ right arrow */ -} -.sources-toggle[data-arrow="down"]::after { - content: '\25BC'; /* ▼ down arrow */ -} -.sources-content { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; - padding: 0 10px; -} -.sources-content.expanded { - max-height: 3000px; - padding: 8px 10px; -} -.sources-content-inner { - display: flex; - flex-direction: column; - gap: 4px; -} -.source-link { - display: flex; - align-items: center; - gap: 8px; - padding: 5px 8px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - text-decoration: none; - color: var(--fg); - transition: background 0.15s ease; -} -.source-link:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.source-num { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 20px; - height: 20px; - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 15%, transparent); - color: var(--fg); - font-size: 0.7em; - font-weight: 600; - flex-shrink: 0; -} -.source-title { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 0.82em; -} -.source-domain { - font-size: 0.72em; - opacity: 0.45; - flex-shrink: 0; -} - -.ai-spinner { - color: var(--red); -} -/* Nudge the Tidy button 2px left. */ -#memory-tidy-btn { position: relative; left: -2px; } -/* Tidy button's whirlpool nudge — sits 1px lower so it visually centers on - the Tidy label baseline. */ -#memory-tidy-btn .ai-spinner-whirlpool, -#memory-tidy-btn .spinner-whirlpool { - position: relative; - top: 1px; -} -.list-item.stream-complete { - animation: none; -} -.session-run-state { - flex: 0 0 auto; - margin-left: 5px; - font-size: 10px; - line-height: 1; - color: var(--fg-dim, #888); - opacity: 0.9; -} -.session-run-state.is-working { - color: var(--accent, var(--red)); -} -.session-run-state .session-run-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 !important; - display: inline-flex; - align-items: center; -} -.session-run-state .session-run-whirlpool .ai-spinner-whirlpool { - display: inline-flex; - width: 12px; - height: 12px; -} -.session-run-state.is-done { - color: var(--color-success, #4caf50); - font-weight: 600; -} -.cookbook-notif-active svg { opacity: 1 !important; } - -/* Rail notification dot — pulsing indicator on icon-rail buttons */ -.icon-rail-btn.rail-notify { - opacity: 1 !important; - position: relative; -} -.icon-rail-btn.rail-notify::before { - content: ''; - position: absolute; - top: 5px; - right: 4px; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--accent, var(--red)); - animation: rail-notif-pulse 2s ease-in-out infinite; - z-index: 1; -} -.icon-rail-btn.rail-notify.rail-notify-success::before { - background: var(--color-success, #4caf50); -} -@keyframes rail-notif-pulse { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.4; transform: scale(0.8); } -} -@keyframes stream-complete-pulse { - 0%, 100% { box-shadow: none; } - 50% { box-shadow: inset 0 0 0 1.5px var(--accent); } -} - -/* ===== SLASH COMMAND RESPONSES ===== */ -.msg.msg-system { - padding: 6px 12px; margin: 4px auto 4px 8px; - max-width: 85%; background: none; border-left: 2px solid var(--border); -} -.msg.msg-system .body { padding: 0; } -.msg.msg-system pre { margin: 4px 0; white-space: pre-wrap; font-size: 0.85em; } -.dropdown-divider { - height: 1px; - margin: 3px 6px; - background: color-mix(in srgb, var(--border) 60%, transparent); -} -.msg.stream-done-toast { - cursor: pointer; - border-left-color: var(--accent, var(--border)); - border-radius: 6px; - background: color-mix(in srgb, var(--accent) 6%, transparent); - transition: background 0.15s; -} -.msg.stream-done-toast:hover { - background: color-mix(in srgb, var(--accent) 12%, transparent); -} -.msg.stream-done-toast .body { - display: flex; - align-items: center; - gap: 8px; -} -.stream-done-indicator { - font-family: monospace; - font-size: 1.1em; - line-height: 1; - color: var(--accent); - flex-shrink: 0; - animation: bar-pulse 1.2s ease-in-out infinite; -} -@keyframes bar-pulse { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } -} - -/* ===== AGENT MULTI-BUBBLE ===== */ -.msg.msg-tool { - display: none; /* legacy — hidden, replaced by agent-thread */ -} -.msg.msg-continuation { - margin-top: 2px; -} - -/* ===== AGENT THREAD TIMELINE ===== */ -.agent-thread { - position: relative; - margin: 2px 0 2px 28px; - padding: 4px 0 4px 22px; - max-width: calc(85% - 20px); - box-sizing: border-box; -} -.agent-thread::before { - content: ''; - position: absolute; - left: 5px; - top: 14px; - bottom: 14px; - width: 2px; - background: color-mix(in srgb, var(--red) 18%, transparent); - border-radius: 1px; -} -/* Extend line to connect to chat bubble above/below */ -.agent-thread.has-top::before { - top: -6.5px; -} -.agent-thread.has-bottom::before { - bottom: -5px; -} -.agent-thread.has-ask-user-bottom::before { - bottom: -12px; -} -/* Terminating dot at bottom when no bubble below and last node is expanded */ -.agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { - content: ''; - position: absolute; - /* Centre the 6px dot on the rail: the rail sits at left:5px + 2px wide, - so its centre is 6px from the thread's left edge and the dot's left - edge belongs at 3px — i.e. 3px - padding-left. Keep this in step with - .agent-thread-dot and with the mobile padding override below. */ - left: -19px; - bottom: 5px; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--red); - box-shadow: 0 0 4px 1px color-mix(in srgb, var(--red) 55%, transparent), - 0 0 0 3px color-mix(in srgb, var(--red) 18%, transparent); -} - -/* Synapse pulse — a bright dot traveling down the line */ -.agent-thread::after { - content: ''; - position: absolute; - left: 4px; - width: 4px; - height: 4px; - border-radius: 50%; - background: var(--red); - box-shadow: 0 0 3px 1px color-mix(in srgb, var(--red) 50%, transparent); - pointer-events: none; - top: 0%; - opacity: 0; -} -.agent-thread.streaming::after { - animation: synapse-capped-short 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-top::after { - animation: synapse-capped 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-bottom::after { - animation: synapse-travel-short 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-top.has-bottom::after { - animation: synapse-travel 0.8s ease-in-out infinite; -} -@keyframes synapse-travel { - 0% { top: 0%; opacity: 0; } - 5% { opacity: 0.5; } - 85% { opacity: 0.35; } - 100% { top: 100%; opacity: 0; } -} -@keyframes synapse-capped { - 0% { top: 0%; opacity: 0; } - 5% { opacity: 0.5; } - 70% { opacity: 0.35; top: calc(100% - 20px); } - 100% { opacity: 0; top: calc(100% - 20px); } -} -@keyframes synapse-travel-short { - 0% { top: 14px; opacity: 0; } - 5% { opacity: 0.5; } - 85% { opacity: 0.35; } - 100% { top: 100%; opacity: 0; } -} -@keyframes synapse-capped-short { - 0% { top: 14px; opacity: 0; } - 5% { opacity: 0.5; } - 70% { opacity: 0.35; top: calc(100% - 20px); } - 100% { opacity: 0; top: calc(100% - 20px); } -} -.agent-thread-node { - position: relative; - padding: 5px 0; -} -.agent-thread-node + .agent-thread-node { - margin-top: 2px; -} -/* Supervisor ladder cards — same chrome as tool cards but tinted so the - user can tell at a glance "this is the agent recovering" vs "this is - the agent doing work". Stop rung gets the red accent. */ -.agent-thread-node.supervisor-step .agent-thread-tool { - color: color-mix(in srgb, var(--accent, #c08a3e) 80%, var(--fg)); - font-style: italic; -} -.agent-thread-node.supervisor-step .agent-thread-dot { - background: color-mix(in srgb, var(--accent, #c08a3e) 60%, transparent); -} -.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-tool { - color: var(--red, #d65a5a); -} -.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-dot { - background: color-mix(in srgb, var(--red, #d65a5a) 60%, transparent); -} -.agent-thread-dot { - position: absolute; - left: -20px; - top: 10px; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--red); - border: 2px solid var(--bg); - z-index: 1; -} -.agent-thread-node.running .agent-thread-dot { - background: var(--red); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 25%, transparent); - animation: thread-pulse 1.5s ease-in-out infinite; - top: 10px; -} -@keyframes thread-pulse { - 0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 20%, transparent); } - 50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--red) 10%, transparent); } -} -.agent-thread-node.error .agent-thread-dot { - background: var(--color-error); -} -.agent-thread-header { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 70%, transparent); - user-select: none; - padding: 2px 0; -} -.agent-thread-header:hover { - color: var(--fg); -} -.agent-thread-icon { - font-size: 0.9em; - color: var(--red); -} -.agent-thread-tool-icon { - width: 14px; - height: 14px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - color: color-mix(in srgb, var(--fg) 64%, transparent); - opacity: 0.85; -} -.agent-thread-tool-icon svg { - display: block; - width: 12px; - height: 12px; -} -.agent-thread-tool-icon-link { - color: inherit; - text-decoration: none; - cursor: pointer; -} -.agent-thread-tool-icon-link:hover { - color: var(--red); - opacity: 1; -} -.agent-thread-node.error .agent-thread-icon { - color: var(--color-error); -} -.agent-thread-tool { - font-weight: 600; - color: var(--red); - text-transform: uppercase; - letter-spacing: 0.3px; - font-size: 0.9em; -} -.agent-thread-header-link { - width: 17px; - height: 17px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 17px; - margin-left: -3px; - color: var(--red); - opacity: 0.72; - text-decoration: none; - border: 0; - background: transparent; -} -.agent-thread-header-link:hover { - opacity: 1; - color: var(--red); -} -.agent-thread-header-link svg { - display: block; -} -.agent-thread-header-link-calendar { - transform: translateY(-2px); -} -.msg-ai .body a[href^="#event-"], -.msg-ai .body a[href^="#email-"], -.msg-ai .body a[href^="#document-"], -.msg-ai .body a[href^="#note-"], -.msg-ai .body a[href^="#task-"], -.msg-ai .body a[href^="#skill-"], -.msg-ai .body a[href^="#memory-"], -.msg-ai .body a[href^="#session-"] { - display: inline-flex; - align-items: baseline; - max-width: 100%; - padding: 1px 5px; - border-radius: 5px; - background: color-mix(in srgb, var(--red) 12%, transparent); - color: var(--red); - font-weight: 600; - text-decoration: none; - box-decoration-break: clone; - -webkit-box-decoration-break: clone; -} -.msg-ai .body a[href^="#event-"], -.msg-ai .body a[href^="#note-"], -.msg-ai .body a[href^="#skill-"], -.msg-ai .body a[href^="#memory-"], -.msg-ai .body a[href^="#session-"] { - font-size: 11px; - line-height: 1.35; -} -.msg-ai .body p:has(> .emoji:first-child):has(> a.chat-link[href^="#note-"]) { - margin-top: 0.15em; - margin-bottom: 0.15em; - line-height: 1.35; -} -.msg-ai .body a[href^="#event-"]:hover, -.msg-ai .body a[href^="#email-"]:hover, -.msg-ai .body a[href^="#document-"]:hover, -.msg-ai .body a[href^="#note-"]:hover, -.msg-ai .body a[href^="#task-"]:hover, -.msg-ai .body a[href^="#skill-"]:hover, -.msg-ai .body a[href^="#memory-"]:hover, -.msg-ai .body a[href^="#session-"]:hover { - background: color-mix(in srgb, var(--red) 18%, transparent); - text-decoration: underline; -} -.agent-thread-status { - font-size: 0.85em; - opacity: 0.5; -} -.agent-thread-chevron { - width: 12px; - height: 12px; - flex: 0 0 12px; - display: inline-grid; - place-items: center; - opacity: 0.4; -} -.agent-thread-chevron::before { - content: ""; - width: 0; - height: 0; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 6px solid currentColor; - transform-origin: 50% 50%; - transition: transform 0.2s ease; -} -.agent-thread-node.open .agent-thread-chevron::before { - transform: rotate(90deg); -} -.agent-thread-node.browser-preview-node .agent-thread-chevron { - display: none; -} -.agent-thread-wave { - font-family: monospace; - font-size: 0.85em; - color: var(--red); - letter-spacing: -1px; -} -/* Live "cooking" timer on a running tool — prominent (accent, tabular) so a - long-running command always reads as alive, not frozen. */ -.agent-thread-elapsed { - margin: 0 6px 0 4px; - font-size: 11px; - font-weight: 600; - font-variant-numeric: tabular-nums; - color: var(--accent, var(--red)); -} -/* Stall watchdog banner — shown when the stream has been silent for ~1min. */ -.stall-banner { - display: flex; - align-items: center; - gap: 8px; - margin: 8px auto; - padding: 8px 12px; - max-width: 90%; - font-size: 12px; - border-radius: 8px; - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, var(--bg)); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 40%, transparent); -} -.stall-banner-txt { flex: 1; opacity: 0.85; } -.stall-banner-btn { - font-size: 11px; - font-weight: 600; - padding: 4px 10px; - border-radius: 6px; - border: none; - background: var(--accent, var(--red)); - color: #fff; - cursor: pointer; - flex-shrink: 0; -} -.stall-banner-stop { - background: none; - color: var(--fg); - border: 1px solid var(--border); -} -.agent-thread-content { - display: none; - padding: 4px 0 2px 0; - overflow-x: auto; - overflow-y: hidden; -} -.agent-thread-node.open .agent-thread-content { - display: block; -} -.agent-thread-cmd { - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - padding: 8px 12px; - margin: 4px 0; - color: var(--fg); - font-size: 0.85em; - white-space: pre-wrap; - word-break: break-word; - line-height: 1.4; - overflow-x: auto; -} -.agent-thread-summary { - display: inline-flex; - align-items: center; - gap: 6px; - max-width: 100%; - margin: 4px 0; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 26%, var(--border)); - border-radius: 6px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - color: color-mix(in srgb, var(--fg) 84%, var(--accent, var(--red))); - font-size: 12px; - font-variant-numeric: tabular-nums; - line-height: 1.35; -} -.agent-thread-summary-link { - cursor: pointer; - text-decoration: none; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-thread-summary-link:hover { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} - -.agent-image-progress { - margin: 6px 0 0; -} -.agent-image-progress-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - font-size: 11px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 72%, transparent); -} -.agent-image-progress-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-image-progress-value { - flex-shrink: 0; - font-variant-numeric: tabular-nums; - color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); -} -.agent-image-progress-track { - display: none !important; -} - -.private-browser-preview { - width: 100%; - max-height: min(300px, 36vh); - margin: 6px 0 2px; - display: flex; - flex-direction: column; - overflow: hidden; - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - color: var(--fg); - box-shadow: none; -} -.private-browser-preview-header { - height: 32px; - min-height: 32px; - display: flex; - align-items: center; - gap: 8px; - padding: 0 6px 0 10px; - border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); - background: color-mix(in srgb, var(--red) 10%, transparent); - font-size: 0.85em; - font-weight: 500; - cursor: pointer; -} -.private-browser-preview-title { - flex-shrink: 0; - color: var(--red); - font-size: inherit; - font-weight: inherit; - line-height: 1; - letter-spacing: 0; -} -.private-browser-preview-status { - min-width: 0; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-size: inherit; - line-height: 1; -} -.private-browser-preview-fold { - width: 18px; - height: 18px; - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - left: 2px; - flex-shrink: 0; - padding: 0; - border: 0; - border-radius: 0; - background: transparent; - color: var(--red); - cursor: pointer; - font-size: 0.95em; - font-family: inherit; - font-weight: 500; - line-height: 1; - text-shadow: none; - -webkit-text-stroke: 0; -} -.private-browser-preview-fold:hover { - background: transparent; - color: var(--red); -} -.private-browser-preview-frame { - position: relative; - min-height: 130px; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - background: color-mix(in srgb, #000 72%, var(--bg)); - box-shadow: - inset 0 1px 0 color-mix(in srgb, #fff 8%, transparent), - inset 0 0 0 1px color-mix(in srgb, #000 35%, transparent), - 0 8px 18px color-mix(in srgb, #000 18%, transparent); -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame { - cursor: zoom-in; -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame::after { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - opacity: 0.55; - transition: opacity 150ms ease; - background: - radial-gradient(circle at center, transparent 42%, color-mix(in srgb, #000 30%, transparent) 100%), - color-mix(in srgb, var(--bg) 10%, transparent); -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame:hover::after { - opacity: 0.9; -} -.private-browser-preview.folded .private-browser-preview-frame { - display: none; -} -.private-browser-preview.folded .private-browser-preview-header { - border-bottom: 0; -} -.private-browser-preview-frame img { - width: 100%; - height: auto; - max-height: calc(min(300px, 36vh) - 32px); - display: block; - object-fit: contain; - transform: scale(1.1); - transform-origin: center center; - animation: private-browser-image-in 180ms ease-out both; -} -.private-browser-preview-frame img:not([src]), -.private-browser-preview-frame img[src=""] { - display: none; -} -.private-browser-preview-empty { - padding: 22px 14px; - width: auto; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 7px; - color: color-mix(in srgb, var(--fg) 48%, transparent); - font-size: 11px; - line-height: 1.35; -} -.private-browser-preview-spinner { - width: 13px; - height: 13px; - flex: 0 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.private-browser-preview-spinner .spinner-whirlpool, -.private-browser-preview-spinner .ai-spinner-whirlpool { - margin: 0 !important; -} -@keyframes private-browser-image-in { - from { opacity: 0; transform: scale(1.08); } - to { opacity: 1; transform: scale(1.1); } -} - -@media (max-width: 700px) { - .private-browser-preview-frame img { - max-height: calc(36vh - 32px); - } -} - -/* Mobile: constrain thread content */ -@media (max-width: 768px) { - .agent-thread { - margin-left: 16px; - padding-left: 18px; - max-width: calc(90% - 16px); - } - .agent-thread-cmd { - font-size: 0.78em; - padding: 6px 8px; - max-width: 100%; - overflow-x: auto; - } - .agent-thread-content { - max-width: calc(100vw - 90px); - } - .agent-tool-output pre { - font-size: 0.8em; - max-width: 100%; - overflow-x: auto; - } - .agent-thread-header { - font-size: 0.8em; - } - .agent-thread-dot { - left: -16px; - top: 10px; - } - /* padding-left drops to 18px here, so the terminating dot needs its own - offset (3px - 18px) to stay centred on the rail. */ - .agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { - left: -15px; - } -} - -/* ===== AGENT TOOL OUTPUT (inside thread nodes) ===== */ -.agent-tool-output { - margin-top: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 8px; - overflow: hidden; -} -.agent-tool-output summary { - color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); - cursor: pointer; - font-size: 0.85em; - user-select: none; - padding: 6px 10px; - font-weight: 500; - /* Chevron on the right (like the thinking section) instead of the default - left-side disclosure triangle. */ - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - list-style: none; -} -.agent-tool-output summary::-webkit-details-marker { display: none; } -/* File-write diff — neutral chrome (not the red error tint) + colored lines */ -.agent-tool-diff { - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.agent-tool-diff summary { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-bottom-color: color-mix(in srgb, var(--fg) 12%, transparent); -} -.agent-tool-diff .diff-stat { - font-weight: 600; - opacity: 0.7; - font-family: var(--mono, monospace); -} -/* Collapsed diff summary: filename + +adds/−dels (theme green/red). */ -.agent-tool-diff summary { - display: flex; - align-items: center; - gap: 8px; -} -.agent-tool-diff .diff-file { - font-family: var(--mono, monospace); - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-tool-diff .diff-summary-stats { - margin-left: auto; - font-family: var(--mono, monospace); - font-weight: 600; - flex-shrink: 0; -} -.agent-tool-diff .diff-summary-stats .diff-stat-add { color: var(--green, #2ecc71); } -.agent-tool-diff .diff-summary-stats .diff-stat-del { color: var(--red, #e74c3c); } -.agent-tool-diff .diff-summary-stats .diff-stat-new { color: var(--accent, var(--red)); opacity: 0.85; } -.diff-pre { - margin: 0; - padding: 8px 10px; - overflow-x: auto; - font-family: var(--mono, monospace); - font-size: 0.82em; - line-height: 1.45; -} -.diff-pre span { display: block; white-space: pre; } -.diff-pre .diff-add { background: color-mix(in srgb, #2ecc71 22%, transparent); } -.diff-pre .diff-del { background: color-mix(in srgb, #e74c3c 22%, transparent); } -.diff-pre .diff-hunk { color: var(--accent); opacity: 0.85; } -.diff-pre .diff-meta { opacity: 0.55; } -.diff-pre .diff-ctx { opacity: 0.8; } -/* Suppress the global `summary::before { content: '▶' }` left arrow — this - section uses a right-side chevron instead. */ -.agent-tool-output summary::before { content: none; } -.agent-tool-output summary::after { - content: '\25BC'; /* ▼ */ - color: var(--red); - font-size: 0.9em; - transition: transform 0.3s ease; -} -.agent-tool-output[open] > summary::after { - transform: rotate(180deg); -} -.agent-tool-output summary:hover { - background: color-mix(in srgb, var(--red) 15%, transparent); -} -.agent-thinking-dots .ai-spinner { - font-size: 12px; - letter-spacing: 0.5px; -} - -.agent-tool-output[open] { - background: color-mix(in srgb, var(--red) 6%, transparent); -} -.agent-tool-output[open] > :not(summary) { - animation: detail-reveal 0.25s ease-out both; -} -@keyframes detail-reveal { - from { opacity: 0; transform: translateY(-4px); } - to { opacity: 1; transform: translateY(0); } -} -.agent-tool-output pre { - background: transparent; - border: none; - border-radius: 0; - padding: 10px 14px; - margin-top: 0; - max-height: 300px; - overflow-y: auto; - font-size: 0.95em; - color: var(--fg); - opacity: 0.85; - white-space: pre-wrap; - word-break: break-all; - line-height: 1.5; -} - -/* Mobile responsive styles */ -@media (max-width: 768px) { - #recording-indicator { - margin: 8px; - padding: 10px; - } - - .recording-text { - font-size: 14px; - } - - .stop-recording-btn { - padding: 6px 10px; - font-size: 12px; - } -} -/* ===== PANE STYLES (shared by compare) ===== */ - -.pane-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 0 4px; - border-bottom: 1px solid var(--border); -} -.close-split-btn, -.pane-close-btn { - background: none; - border: none; - color: var(--color-error); - font-size: 18px; - cursor: pointer; - padding: 0 4px; - line-height: 1; - opacity: 0; - transition: opacity 0.15s; -} -.pane-header:hover .close-split-btn, -.pane-header:hover .pane-close-btn { - opacity: 1; -} -.close-split-btn:hover, -.pane-close-btn:hover { - color: var(--color-error-light); -} - -/* Compare pane close controls use the same shared circular close treatment - as modal/title close buttons. */ -.compare-pane .pane-close-btn.close-btn { - width: 24px; - height: 24px; - min-width: 24px; - padding: 0; - border: 1px solid transparent; - border-radius: 999px; - background: transparent; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 0; - opacity: 0.5; - display: inline-flex; - align-items: center; - justify-content: center; - transition: background 0.15s, color 0.15s, opacity 0.15s; -} -.compare-pane .pane-close-btn.close-btn:hover, -.compare-pane .pane-close-btn.close-btn:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); - opacity: 1; - outline: none; -} -.compare-pane .pane-action-btn[data-action="expand"] { - width: 24px; - height: 24px; - min-width: 24px; - padding: 0; - border: 1px solid transparent; - border-radius: 999px; - justify-content: center; - transition: background 0.15s, color 0.15s, opacity 0.15s; -} -.compare-pane .pane-action-btn[data-action="expand"]:hover, -.compare-pane .pane-action-btn[data-action="expand"]:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); - opacity: 1; - outline: none; -} - - -/* ============================================ */ -/* RESEARCH DETAILS EXPANDABLE SECTION - UPDATED */ -/* ============================================ */ - -/* Style the details element */ -details { - background: color-mix(in srgb, var(--hl-string) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--hl-string) 30%, transparent); - border-radius: 8px; - margin: 12px 0; - padding: 0; - overflow: hidden; - transition: all 0.3s ease; -} - -details[open] { - background: color-mix(in srgb, var(--hl-string) 8%, transparent); -} -details[open] > :not(summary) { - animation: detail-reveal 0.25s ease-out both; -} - -/* Style the summary (clickable header) - NO CURSIVE, NORMAL SIZE */ -summary { - cursor: pointer; - padding: 10px 14px; - background: color-mix(in srgb, var(--hl-string) 10%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--hl-string) 20%, transparent); - font-weight: 500; - font-size: 0.95em; - font-style: normal; - font-family: inherit; - color: var(--hl-string); - user-select: none; - list-style: none; - display: flex; - align-items: center; - gap: 8px; - transition: background 0.2s ease; -} - -summary:hover { - background: color-mix(in srgb, var(--hl-string) 15%, transparent); -} - -/* Add custom arrow */ -summary::before { - content: '▶'; - display: inline-block; - transition: transform 0.3s ease; - font-size: 0.75em; /* Smaller arrow */ -} - -details[open] summary::before { - transform: rotate(90deg); -} - -/* Hide default marker in webkit browsers */ -summary::-webkit-details-marker { - display: none; -} - -/* Style the content inside details - SMALLER, MORE COMPACT */ -details > div, -details > p, -details > ul, -details > ol { - padding: 12px 14px; /* Less padding */ - animation: fadeIn 0.3s ease; - font-size: 0.9em; /* Smaller text */ - line-height: 1.5; -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Style research findings inside details - SMALLER HEADINGS */ -details h3 { - margin-top: 12px; - margin-bottom: 6px; - color: var(--hl-string); - font-size: 0.95em; - font-weight: 500; -} - -details h4 { - margin-top: 10px; - margin-bottom: 5px; - color: var(--hl-string); - font-size: 0.9em; - font-weight: 500; -} - -details ul { - margin-left: 18px; - margin-bottom: 10px; -} - -details li { - margin-bottom: 5px; - line-height: 1.4; - font-size: 0.85em; /* Smaller list items */ -} - -details strong { - color: var(--hl-string); - font-weight: 500; -} - -details a { - color: var(--accent); - text-decoration: none; - transition: color 0.2s ease; -} - -details a:hover { - color: var(--color-link-hover); - text-decoration: underline; -} - -/* Research metadata - SMALLER */ -details .research-meta { - font-size: 0.8em; - color: var(--fg); - opacity: 0.8; - margin-top: 4px; -} - -/* Source links - SMALLER */ -details .source-link { - display: inline-block; - margin-top: 3px; - padding: 2px 5px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); - border-radius: 4px; - font-size: 0.68em; - color: var(--accent); -} - -details .source-link:hover { - background: color-mix(in srgb, var(--accent) 20%, transparent); - border-color: var(--accent); -} - -/* Research report links - clean and subtle */ -details a { - color: var(--accent); - text-decoration: none; - font-weight: normal; /* Remove bold */ - font-size: 0.85em; - opacity: 0.9; - transition: all 0.2s ease; - word-break: break-all; /* Break long URLs nicely */ -} - -details a:hover { - color: var(--color-link-hover); - text-decoration: underline; - opacity: 1; -} - -/* ============================================ */ -/* CUSTOM SYNTAX HIGHLIGHTING - Theme-Reactive */ -/* ============================================ */ -.hljs { - background: var(--code-bg, var(--hl-bg, var(--panel))); - color: var(--code-fg, var(--hl-fg, var(--fg))); - padding: 8px; - border-radius: 4px; -} - -/* Keywords & control flow — purple/magenta */ -.hljs-keyword, -.hljs-selector-tag { color: var(--hl-keyword); } - -/* Strings & regex — warm yellow/orange */ -.hljs-string, -.hljs-regexp, -.hljs-addition { color: var(--hl-string); } - -/* Comments & docs — muted. font-style intentionally omitted: italics shift - glyph widths in the highlight overlay relative to the transparent textarea - above it, which makes the caret drift away from the visible character. */ -.hljs-comment, -.hljs-quote, -.hljs-meta { color: var(--hl-comment); } - -/* Functions & method names — blue */ -.hljs-function, -.hljs-title, -.hljs-title.function_, -.hljs-section { color: var(--hl-function); } - -/* Numbers & constants — distinct from strings */ -.hljs-number, -.hljs-literal { color: var(--hl-number, var(--hl-string)); } - -/* Built-ins & types — teal/cyan tint */ -.hljs-built_in, -.hljs-type, -.hljs-class, -.hljs-title.class_ { color: var(--hl-builtin, var(--hl-function)); } - -/* Variables & identifiers — fg with slight distinction */ -.hljs-variable, -.hljs-template-variable, -.hljs-attr { color: var(--hl-variable, var(--hl-fg, var(--fg))); } - -/* Operators & punctuation — slightly dimmed fg */ -.hljs-operator, -.hljs-punctuation { color: var(--hl-fg, var(--fg)); opacity: 0.8; } - -/* Parameters */ -.hljs-params { color: var(--hl-params, var(--hl-fg, var(--fg))); } - -/* Property access, attributes in HTML/XML */ -.hljs-property, -.hljs-selector-class, -.hljs-selector-id { color: var(--hl-variable, var(--hl-function)); } - -/* Tags (HTML) */ -.hljs-tag { color: var(--hl-keyword); } -.hljs-name { color: var(--hl-keyword); } - -/* Deletion/diff */ -.hljs-deletion { color: var(--red); } - -/* Symbol, special */ -.hljs-symbol { color: var(--hl-string); } -.hljs-link { color: var(--hl-function); text-decoration: underline; } - -/* Emphasis — applied globally (chat messages, rendered markdown in docs - preview, etc.) but explicitly NEUTRALIZED in the doc editor's overlay so - the textarea-and-overlay caret alignment stays bit-perfect. Bold / italic - shift glyph widths and that drifts the click-to-row mapping. */ -.hljs-emphasis { font-style: italic; } -.hljs-strong { font-weight: bold; } -.doc-editor-highlight .hljs-emphasis, -.doc-editor-highlight .hljs-strong { - font-style: normal !important; - font-weight: inherit !important; -} diff --git a/static/css/style-part-04.css b/static/css/style-part-04.css deleted file mode 100644 index ce6a9add4..000000000 --- a/static/css/style-part-04.css +++ /dev/null @@ -1,6011 +0,0 @@ -/* Fragment 5/9 of the former static/style.css. - * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. - * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. - */ -.admin-ep-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} - -/* Status messages */ -.admin-error { color: var(--color-error); font-size: 11px; margin-top: 4px; } -.admin-success { color: var(--color-success); font-size: 11px; margin-top: 4px; } -.admin-empty { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 12px; - padding: 10px 0; - text-align: center; -} -/* Endpoint-list empty states ("No endpoints configured" / "None") get a - left-aligned, accent-colored treatment so they read as "this row is your - placeholder, click Add" rather than a centered "nothing here" grey blob. */ -#adm-epList-local .admin-empty, -#adm-epList-api .admin-empty { - text-align: left; - padding: 8px 4px; - color: var(--accent-primary, var(--accent, var(--red))); -} - -/* RAG upload zone */ -.admin-rag-upload-zone { - border: 2px dashed var(--border); - border-radius: 8px; - padding: 14px; - text-align: center; - cursor: pointer; - transition: border-color 0.2s, background 0.2s; - margin-bottom: 8px; -} -.admin-rag-upload-zone:hover, -.admin-rag-upload-zone.dragover { - border-color: var(--red); - background: color-mix(in srgb, var(--red) 7%, transparent); -} -.admin-rag-upload-zone p { - color: color-mix(in srgb, var(--fg) 50%, transparent); - font-size: 11px; - margin-top: 4px; -} -.admin-rag-icon { font-size: 18px; } -.admin-rag-dir-row { - display: flex; - gap: 6px; - margin-bottom: 8px; -} -.admin-rag-dir-row input { - flex: 1; - padding: 5px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-rag-dir-row input:focus { outline: none; border-color: var(--red); } -.admin-rag-dir-row button { - padding: 5px 8px; - border: none; - border-radius: 6px; - background: var(--red); - color: #fff; - cursor: pointer; - font-size: 12px; - white-space: nowrap; -} -.admin-rag-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: 6px; -} -.admin-rag-section-label { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin: 8px 0 4px; -} -.admin-rag-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 8px; - background: var(--bg); - border-radius: 6px; - margin-bottom: 4px; - font-size: 12px; -} -.admin-rag-item-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--fg); -} -.admin-rag-item-meta { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 10px; - margin: 0 6px; - flex-shrink: 0; -} -.admin-rag-status { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-top: 6px; -} - -/* Token reveal */ -.admin-token-reveal { - margin-top: 8px; - padding: 8px; - background: color-mix(in srgb, var(--red) 8%, var(--bg)); - border: 1px solid color-mix(in srgb, var(--red) 25%, var(--border)); - border-radius: 8px; -} - -/* Selects in admin cards */ -.admin-card select { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-card input { - font-family: inherit; - font-size: 12px; -} -.admin-card label { - font-size: 12px; -} - -/* ---- Document Library ---- */ - -.doclib-modal-content { - width: min(600px, 92vw); - max-height: 85vh; - font-size: 12px; - background: var(--bg); -} -/* Anchor doclib modal to top so only the bottom edge moves on resize */ -#doclib-modal { - align-items: flex-start; - padding-top: 8vh; -} -.doclib-modal-content .modal-header h4 { - font-size: 1rem; -} -/* The "open in new tab" email modal inherited the 1rem doclib header, which - read way bigger than the email subject in the regular (inline) reader. Pin - it smaller so the two views are consistent. */ -.email-reader-tab-modal .modal-header h4 { - font-size: 13px; - font-weight: 600; -} -/* Match the sticky modal-header to the doclib modal-content body color - (var(--bg)) instead of the global var(--panel) default — otherwise the - header reads as a darker stripe above the email list. */ -.doclib-modal-content > .modal-header { - background: var(--bg); -} -.doclib-stats { - font-size: 11px; - color: var(--fg-muted); - margin-bottom: 8px; -} -.doclib-toolbar { - display: flex; - gap: 8px; - margin-bottom: 8px; - align-items: center; -} -.doclib-search { - flex: 1; - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - outline: none; - transition: border-color 0.15s; -} -#archive-select-btn { height: auto; padding: 5px 8px; } -.doclib-search:focus { - border-color: var(--red); -} -.doclib-sort { - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - outline: none; - cursor: pointer; -} -.doclib-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 8px; -} -.doclib-chip { - padding: 2px 10px; - border-radius: 12px; - font-size: 10px; - border: 1px solid var(--border); - background: transparent; - color: var(--fg-muted); - cursor: pointer; - user-select: none; - transition: background 0.15s, border-color 0.15s; -} -.doclib-chip:hover { - border-color: var(--red); -} -.doclib-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Document library — language chip row */ -.doclib-lang-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - padding: 2px 0; - max-height: 52px; - overflow-y: auto; -} -.doclib-lang-chips:empty { display: none; } -/* Mobile: keep tag chips on ONE row and scroll horizontally instead of wrapping - into a tall multi-line block (Serve, library — anywhere .doclib-lang-chips is used). */ -@media (max-width: 768px) { - .doclib-lang-chips { - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - max-height: none; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .doclib-lang-chips::-webkit-scrollbar { display: none; } - .doclib-lang-chips > * { flex-shrink: 0; } -} -#doclib-tidy-btn, #doclib-select-btn, #doclib-chats-tidy-btn { - position: relative; - top: -3px; -} -/* Document library — list layout */ -.doclib-grid { - display: flex; - flex-direction: column; - gap: 4px; - max-height: 400px; - overflow-y: auto; - padding: 0; - position: relative; -} - -/* Gallery grid domino-in cascade on open — same recipe as the document/email - libraries. Applied to #gallery-grid in gallery.js on the first render after - each open, removed after the longest delay so re-renders feel instant. */ -.gallery-just-opened > .gallery-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.gallery-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.gallery-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.gallery-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.gallery-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.gallery-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.gallery-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.gallery-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.gallery-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.gallery-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.gallery-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.gallery-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.gallery-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.gallery-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.gallery-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.gallery-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.gallery-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.gallery-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.gallery-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.gallery-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.gallery-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.gallery-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Tasks list domino-in cascade on open — mirrors gallery / doclib. Applied to - #tasks-list in tasks.js on the first render after each open, removed after - the longest delay so re-renders feel instant. */ -.tasks-just-opened > .task-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.tasks-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.tasks-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.tasks-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.tasks-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.tasks-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.tasks-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.tasks-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.tasks-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.tasks-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.tasks-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.tasks-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.tasks-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.tasks-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.tasks-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.tasks-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.tasks-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.tasks-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.tasks-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.tasks-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.tasks-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.tasks-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Document library cascade — same recipe as email library, applied per-tab - the first time content loads (chats / archive / research / documents). - Module-level Set in documentLibrary.js prevents re-firing on tab swaps - or filter/sort re-renders within the same page session. */ -.doclib-just-opened > .memory-item, -.doclib-just-opened > .doclib-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.doclib-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.doclib-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.doclib-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.doclib-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.doclib-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.doclib-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.doclib-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.doclib-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.doclib-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.doclib-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.doclib-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.doclib-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.doclib-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.doclib-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.doclib-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.doclib-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.doclib-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.doclib-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.doclib-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.doclib-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.doclib-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Domino cascade on first open of the email library — mirrors the sidebar - .section-just-expanded recipe so list items spring in staggered. Class - is applied to #email-lib-grid in emailLibrary.js on the first render - only, then removed after the longest delay so re-renders feel instant. */ -.email-lib-just-opened :is(.doclib-card, .date-section-header) { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(1) { animation-delay: 0.02s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(2) { animation-delay: 0.04s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(3) { animation-delay: 0.06s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(4) { animation-delay: 0.08s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(5) { animation-delay: 0.10s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(6) { animation-delay: 0.12s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(7) { animation-delay: 0.14s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(8) { animation-delay: 0.16s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(9) { animation-delay: 0.18s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(10) { animation-delay: 0.20s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(11) { animation-delay: 0.22s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(12) { animation-delay: 0.24s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(13) { animation-delay: 0.26s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(14) { animation-delay: 0.28s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(15) { animation-delay: 0.30s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(16) { animation-delay: 0.32s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(17) { animation-delay: 0.34s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(18) { animation-delay: 0.36s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(19) { animation-delay: 0.38s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(20) { animation-delay: 0.40s; } -/* Cap the cascade at 20 — beyond that they animate together so opening a - long inbox doesn't take forever to settle. */ -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(n+21) { animation-delay: 0.42s; } - -/* Inside the email library modal, the grid needs to grow with the modal — - but `flex: 1 1 0` collapses to 0 when the parent isn't height-constrained - (which is the non-fullscreen default). Use `auto` basis + a sensible - `min-height` floor so it shows ~400px naturally and absorbs more when - the modal is resized / fullscreened. */ -#email-lib-modal .doclib-grid { - max-height: none; - height: auto; - flex: 1 1 auto; - min-height: 400px; -} - -/* ── Mobile compose FAB (email) ── - Replaces the top-right "New" button on phones with a round mail-icon button - bottom-right that collapses to a circle while the list scrolls and springs - back out to "New" when scrolling stops. Desktop is unchanged. */ -.email-lib-fab { display: none; } -@media (max-width: 768px) { - /* The absolute FAB anchors to the email panel card. */ - #email-lib-modal .admin-card { position: relative; } - /* Hide the toolbar "New" button — the FAB is the mobile entry point. */ - #email-lib-compose-btn { display: none; } - - #email-lib-modal .email-lib-fab { - --fab-size: 56px; - display: flex; - align-items: center; - gap: 9px; - /* Hidden until the email list has rendered; JS adds .fab-revealed to pop - it in (scale-from-center). Avoids the button flashing at the top and - sliding down before _positionFab() places it. */ - transform: scale(0); - opacity: 0; - transform-origin: center center; - position: absolute; - right: calc(16px + env(safe-area-inset-right, 0px)); - bottom: calc(18px + env(safe-area-inset-bottom, 0px)); - height: var(--fab-size); - min-width: var(--fab-size); - padding: 0 20px 0 18px; - border: none; - border-radius: 999px; - background: var(--accent-primary, var(--red)); - color: #fff; - font-family: inherit; - font-size: 15px; - font-weight: 600; - line-height: 1; - cursor: pointer; - box-shadow: 0 6px 18px rgba(0,0,0,.38), 0 2px 6px rgba(0,0,0,.28); - z-index: 30; - pointer-events: auto; - /* This (base) transition governs the EXPAND — slower + graceful. */ - transition: - padding 420ms cubic-bezier(0.22, 1, 0.36, 1), - box-shadow 240ms ease, - transform 140ms ease, - background 140ms ease; - will-change: padding, transform; - } - #email-lib-modal .email-lib-fab svg { flex: 0 0 auto; display: block; } - #email-lib-modal .email-lib-fab .email-lib-fab-label { - overflow: hidden; - white-space: nowrap; - max-width: 120px; - opacity: 1; - /* EXPAND timing — label eases out a touch after the width opens up. */ - transition: - max-width 420ms cubic-bezier(0.22, 1, 0.36, 1), - opacity 300ms 60ms cubic-bezier(0.22, 1, 0.36, 1), - margin 420ms cubic-bezier(0.22, 1, 0.36, 1); - } - /* Collapsed (while scrolling) → pure circle, icon only. We DON'T set an - explicit width: the container is auto-width and shrinks smoothly as the - padding + label max-width animate (min-width keeps it circular). Setting a - fixed width here made the collapse snap, since auto↔px width can't tween. - This (.collapsed) transition governs the COLLAPSE — kept snappy. */ - #email-lib-modal .email-lib-fab.collapsed { - padding: 0; - justify-content: center; - transition: - padding 230ms cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 200ms ease, - transform 140ms ease; - } - #email-lib-modal .email-lib-fab.collapsed .email-lib-fab-label { - max-width: 0; - opacity: 0; - margin-left: -9px; - transition: - max-width 230ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), - margin 230ms cubic-bezier(0.4, 0, 0.2, 1); - } - /* Entrance: circle-expand-from-middle pop, played once when revealed. No - `forwards` fill so the resting transform (scale 1) comes from this rule — - that keeps the :active press transform working afterwards. */ - #email-lib-modal .email-lib-fab.fab-revealed { - transform: scale(1); - opacity: 1; - animation: emailFabPop 380ms cubic-bezier(0.34, 1.56, 0.64, 1); - } - #email-lib-modal .email-lib-fab:active { - transform: scale(0.93); - filter: brightness(0.92); - box-shadow: 0 3px 10px rgba(0,0,0,.4); - } -} -@keyframes emailFabPop { - from { transform: scale(0); opacity: 0; } - 60% { transform: scale(1.06); opacity: 1; } - to { transform: scale(1); opacity: 1; } -} -@media (prefers-reduced-motion: reduce) { - #email-lib-modal .email-lib-fab, - #email-lib-modal .email-lib-fab .email-lib-fab-label { transition-duration: 1ms; } - #email-lib-modal .email-lib-fab.fab-revealed { animation: none; } -} -/* When the modal-content has explicit height (fullscreen or user-resized), - drop the floor so the grid sizes purely from flex. */ -#email-lib-modal.email-lib-fullscreen .doclib-grid, -#email-lib-modal .modal-content[style*="height"] .doclib-grid { - min-height: 0; -} -/* Document library: same fix as the email library (#74). When fullscreened - the modal-content gets an inline `height: 100vh`, but the inner - .doclib-grid stays capped at the 400px default so the list looks like a - tiny strip floating in a giant empty modal. Mirror the email recipe: - make the modal a flex column, give the body/admin-card claim of the - remaining height, and let the grid take the rest. Scoped to the - fullscreen class so windowed-mode layout is unchanged. */ -/* Inner flex chain that lets the chats/documents grid claim the full - remaining height of the modal. Applies in BOTH fullscreen AND - right-docked states — without the docked state included, dragging a - fullscreen doclib to the right snap zone breaks the flex layout - because exitFullscreen removes .doclib-fullscreen, and the grid - falls back to its base max-height: 400px showing only ~8 items. - The CSS variable :is() selector lets both states share one rule. */ -#doclib-modal.doclib-fullscreen .doclib-modal-content, -#doclib-modal.modal-right-docked .doclib-modal-content, -#doclib-modal.modal-left-docked .doclib-modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -/* Fullscreen positioning — scoped to NOT apply when also right-docked. - Without this exclusion, dragging a fullscreen doclib to the right snap - zone keeps .doclib-fullscreen on the modal and these !important rules - override the dock's inline styles, leaving the modal stuck fullscreen - instead of becoming a side panel. */ -#doclib-modal.doclib-fullscreen:not(.modal-right-docked) .doclib-modal-content { - position: fixed !important; - top: 0 !important; - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; - right: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; -} -#doclib-modal.doclib-fullscreen .modal-header, -#doclib-modal.doclib-fullscreen .lib-tabs, -#doclib-modal.modal-right-docked .modal-header, -#doclib-modal.modal-right-docked .lib-tabs, -#doclib-modal.modal-left-docked .modal-header, -#doclib-modal.modal-left-docked .lib-tabs { - flex: 0 0 auto; -} -#doclib-modal.doclib-fullscreen .modal-body, -#doclib-modal.modal-right-docked .modal-body, -#doclib-modal.modal-left-docked .modal-body { - flex: 1 1 0; - min-height: 0; - overflow: hidden; - display: flex; - flex-direction: column; -} -#doclib-modal.doclib-fullscreen .admin-card, -#doclib-modal.modal-right-docked .admin-card, -#doclib-modal.modal-left-docked .admin-card { - flex: 1 1 0; - min-height: 0; - display: flex; - flex-direction: column; -} -#doclib-modal.doclib-fullscreen .doclib-grid:not(:has(.doclib-card-expanded)), -#doclib-modal.modal-right-docked .doclib-grid:not(:has(.doclib-card-expanded)), -#doclib-modal.modal-left-docked .doclib-grid:not(:has(.doclib-card-expanded)) { - max-height: none; - height: auto; - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* "Reply" docks the open email modal to the left as a sidebar so the doc - panel (which slides in from the right of the chat area) is visible - side-by-side. The .modal backdrop is already pointer-events:none for - email modals — only the content takes pointer events — so the chat / - doc panel underneath stays interactive. */ -.modal.email-snap-left { - align-items: stretch; - justify-content: flex-start; - left: 0 !important; - width: 100% !important; -} -.modal.email-snap-left .modal-content { - left: var(--email-doc-split-left-x, 0px) !important; - width: var(--email-doc-split-email-w, 420px) !important; - max-width: var(--email-doc-split-email-w, 420px) !important; - border-right: 1px solid var(--border); - box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; -} -@media (min-width: 769px) { - body.email-doc-split-active.doc-view #email-lib-modal, - body.email-doc-split-active.doc-view.email-front #email-lib-modal, - body.email-doc-split-active.doc-view .modal[id^="email-reader-"], - body.email-doc-split-active.doc-view.email-front .modal[id^="email-reader-"] { - z-index: 150 !important; - } - body.email-doc-split-active #email-lib-modal.email-snap-left, - body.email-doc-split-active #email-lib-modal.modal-left-docked { - left: var(--email-doc-split-left-x, 0px) !important; - width: var(--email-doc-split-email-w, 420px) !important; - overflow: hidden !important; - z-index: 155 !important; - } - body.email-doc-split-active #email-lib-modal.email-snap-left:not(.modal-dragging) .modal-content, - body.email-doc-split-active #email-lib-modal.modal-left-docked:not(.modal-dragging) .modal-content, - body.email-doc-split-active .modal[id^="email-reader-"].email-snap-left:not(.modal-dragging) .modal-content, - body.email-doc-split-active .modal[id^="email-reader-"].modal-left-docked:not(.modal-dragging) .modal-content { - position: absolute !important; - left: 0 !important; - top: 0 !important; - bottom: 0 !important; - right: auto !important; - width: var(--email-doc-split-email-w, 420px) !important; - max-width: var(--email-doc-split-email-w, 420px) !important; - height: 100vh !important; - max-height: 100vh !important; - transform: none !important; - margin: 0 !important; - } - body.email-doc-split-active.doc-view .doc-divider { - display: none !important; - } - body.email-doc-split-active.doc-view .doc-editor-pane { - position: fixed !important; - left: var(--email-doc-split-right-x, 420px) !important; - right: var(--right-dock-w, 0px) !important; - top: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - z-index: 260 !important; - margin-top: 0 !important; - transform: none !important; - border-left: none !important; - } -} - -/* Edge docking. Docked panels are fixed to the viewport edge; the workspace - panes reserve room with margins so left + right docks can be active at the - same time without skewing the entire body box. */ -body.right-dock-active { - padding-right: 0; -} -body.left-dock-active { - padding-left: 0; -} -body.left-dock-active:not(.email-doc-split-active) .chat-container { - margin-left: var(--left-dock-w, 0px); -} -body.right-dock-active .chat-container, -body.right-dock-active:not(.email-doc-split-active) .doc-editor-pane { - margin-right: var(--right-dock-w, 0px); -} -.modal.modal-right-docked { - align-items: stretch; - justify-content: flex-end; -} -.modal.modal-right-docked .modal-content { - border-left: 1px solid var(--border); - box-shadow: -4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; -} -.modal.modal-left-docked { - align-items: stretch; - justify-content: flex-start; -} -.modal.modal-left-docked .modal-content { - border-right: 1px solid var(--border); - box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; - /* Pin transitions OFF on the dock's positioning properties. The wide - sidebar collapse/expand changes --sidebar-w instantly, which means - the docked modal's `left: calc(...)` jumps by ~240px. Any CSS - transition on `left` (inherited or otherwise) would animate that - jump as a fly-across. Same defense for right-docked / fullscreen - panels in case future themes add a generic transition. */ - transition: none !important; -} -.modal.modal-right-docked .modal-content, -#email-lib-modal.email-lib-fullscreen .modal-content, -#doclib-modal.doclib-fullscreen .doclib-modal-content { - transition: none !important; -} -.modal.modal-right-docked .email-reader-header, -.modal.modal-left-docked .email-reader-header { - flex-direction: column; - /* Stretch the meta to the full header width so the absolute - action cluster's right:0 lands at the right edge (was sizing - meta to chip content width and centering it). */ - align-items: stretch; - gap: 6px; -} -/* Docked panes: 4px between From→To and 4px between To→Cc. - Meta has its own 2px gap (From→details) so an extra 2px on - details margin-top gives a 4px From→To distance, and gap:4 on - the details flex column gives 4px between To and Cc. */ -.modal.modal-right-docked .email-reader-meta-details, -.modal.modal-left-docked .email-reader-meta-details { - margin-top: 2px; - gap: 4px; -} -/* Docked: always show To/Cc — there's vertical room reserved for - the action cluster already and the chevron toggle just adds - friction. Force the hidden attribute open and hide the chevron. */ -.modal.modal-right-docked .email-reader-meta-details[hidden], -.modal.modal-left-docked .email-reader-meta-details[hidden] { - display: flex !important; -} -.modal.modal-right-docked .email-reader-meta-toggle, -.modal.modal-left-docked .email-reader-meta-toggle { - display: none; -} -.modal.modal-right-docked .email-reader-header > .email-reader-actions, -.modal.modal-left-docked .email-reader-header > .email-reader-actions { - position: static; - align-self: flex-end; - background: none; - padding-left: 0; -} -/* Docked modal: keep all meta rows as flex so From / To / Cc - labels align at the same x. Chips already overflow-scroll - inside each row when there are too many. No per-row margin-top - here — the spacing between rows comes entirely from - .email-reader-meta-details gap so From→To and To→Cc match. */ -.modal.modal-right-docked .email-reader-meta-row:not(.email-reader-meta-from), -.modal.modal-left-docked .email-reader-meta-row:not(.email-reader-meta-from) { - padding-right: 0; -} -.modal.modal-right-docked .email-reader-meta-row strong, -.modal.modal-left-docked .email-reader-meta-row strong { - min-width: 0; -} -.modal.modal-right-docked .recipient-chip, -.modal.modal-left-docked .recipient-chip { - max-width: 100%; -} -.archive-list { - margin-top: 8px; - border-top: 1px solid var(--border); - padding-top: 8px; -} -#archive-modal .memory-sort-select, -#archive-modal .memory-toolbar-btn { - height: 32px; -} -.archive-menu-btn { - position: relative; - top: 2px; -} -#archive-chips .doclib-chip { - height: 22px; - display: inline-flex; - align-items: center; - font-size: 10px; - padding: 0 8px; -} -/* Library tab panels — Chats / Archive / Research / Documents share the - same toolbar dimensions so the sort dropdown + Select button line up - identically across tabs. */ -#doclib-panel-chats .memory-sort-select, -#doclib-panel-archive .memory-sort-select, -#doclib-panel-research .memory-sort-select, -[data-doclib-panel="documents"] .memory-sort-select { - height: 24px; - font-size: 11px; - width: 86px; -} -#doclib-panel-chats .memory-toolbar-btn, -#doclib-panel-archive .memory-toolbar-btn, -#doclib-panel-research .memory-toolbar-btn, -[data-doclib-panel="documents"] .memory-toolbar-btn { - height: 24px; - font-size: 11px; - position: relative; - top: -3px; -} -/* Research's Recent (sort) + Select sat 1px lower than the other tabs — nudge up. */ -#doclib-panel-research .memory-sort-select { position: relative; top: 1.5px; } -#doclib-panel-research .memory-toolbar-btn { top: -4.5px; } -.library-sort-picker { - width: 108px; - flex: 0 0 108px; - top: 1px; -} -.library-sort-picker .library-sort-btn { - top: 0; - width: 100%; -} -.library-sort-picker .library-sort-menu { - top: calc(100% + 4px); - width: max(100%, 154px); -} -.library-sort-picker .library-sort-item[aria-selected="true"] { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); -} -#doclib-panel-chats #doclib-chats-tidy-btn, -#doclib-panel-chats #doclib-chats-select-btn, -#doclib-panel-archive #doclib-arc-select-btn { - top: 1px; -} -#doclib-panel-chats .doclib-chip-scroll-arrow { - top: calc(50% - 9px) !important; -} -[data-doclib-panel="documents"] .doclib-chip-scroll-arrow { - top: calc(50% - 9px) !important; -} -#doclib-modal .doclib-chip-scroll-arrow { - top: calc(50% - 19px) !important; -} -[data-doclib-panel="documents"] #doclib-tidy-btn, -[data-doclib-panel="documents"] #doclib-select-btn { - top: 1px; -} -#doclib-panel-research #doclib-research-tidy-btn, -#doclib-panel-research #doclib-research-select-btn { - top: -0.5px; -} -#doclib-panel-research .library-sort-picker { top: -0.5px; } -[data-doclib-panel] { padding-top: 6px !important; } -#doclib-panel-chats, #doclib-panel-archive { padding-top: 14px !important; } -#doclib-panel-chats .memory-desc, #doclib-panel-archive .memory-desc, #doclib-panel-research .memory-desc { margin-top: 7px; } -#doclib-panel-chats .memory-search-input, -#doclib-panel-archive .memory-search-input { - height: 30px !important; - min-height: 30px !important; - flex-shrink: 0; - font-size: 11px; -} -/* Unified search bar across Library tabs + Memory modal — same height, - same magnifying-glass icon at the start, consistent padding. */ -#doclib-panel-chats .memory-search-input, -#doclib-panel-archive .memory-search-input, -#doclib-panel-research .memory-search-input, -[data-doclib-panel="documents"] .memory-search-input, -#memory-modal .memory-search-input, -#tasks-modal .memory-search-input { - height: 30px; - min-height: 30px; - font-size: 11px; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: 9px center; - padding-left: 28px; -} -#doclib-panel-chats .doclib-chip { - height: 28px; -} -#doclib-panel-archive .doclib-chip { - height: 28px; -} -#doclib-panel-chats .doclib-chip, -#doclib-panel-archive .doclib-chip { - display: inline-flex; - align-items: center; - font-size: 9px; - padding: 0 8px; - border-radius: 10px; -} -.doclib-grid:has(.doclib-card-expanded) > .doclib-card:not(.doclib-card-expanded) { - display: none; -} -/* Hide everything except the grid when a card is expanded */ -.admin-card:has(.doclib-card-expanded) > *:not(.doclib-grid):not(.hwfit-cached-list) { - opacity: 0; - max-height: 0; - overflow: hidden; - pointer-events: none; - margin: 0 !important; - padding: 0 !important; - transition: opacity 0.12s ease, max-height 0.2s ease; -} -.admin-card:has(.doclib-card-expanded) > .memory-bulk-bar { - display: none; -} -.admin-card:has(.doclib-card-expanded) > .doclib-grid, -.admin-card:has(.doclib-card-expanded) > .hwfit-cached-list { - max-height: none !important; - min-height: 0 !important; - overflow: visible !important; -} -/* Firefox-mobile (no :has()) fallback: when reading an email, fully remove the - list-mode chrome (accounts row, toolbar, bulk bar, desc) — display:none so no - residual flex-gap leaves an empty strip above the reader — and zero the - admin-card gap so the grid/reader starts flush at the top. */ -#email-lib-modal.email-reading .admin-card > *:not(.doclib-grid):not(.hwfit-cached-list) { - display: none !important; -} -#email-lib-modal .admin-card:has(.doclib-card-expanded), -#email-lib-modal.email-reading .admin-card { - gap: 0 !important; - padding: 0 !important; - margin-bottom: 0 !important; - border: 0 !important; - border-radius: 0 !important; - background: transparent !important; -} -/* Override for chat-rows + research-rows: those expand inline with a - bounded preview (max-height: 60vh), so the grid must stay clipped - and scrollable. Without this scoping the overflow:visible above lets - the expanded chat preview escape the grid and overlap whatever's - underneath/beside the modal (e.g., a docked sibling panel) — what - the user calls "merging with other windows". */ -.admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - overflow: hidden auto !important; -} -/* Email modal needs the grid to STAY a constrained flex container when - expanded — overflow:visible (set above for cookbook) lets content escape - instead of letting the expanded reader fill via flex:1. We keep the same - max-height:none + min-height:0 but flip overflow back so the reader - actually fills the modal. */ -#email-lib-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid, -#email-lib-modal.email-reading .admin-card > .doclib-grid { - overflow: hidden !important; - display: flex !important; - flex: 1 1 auto !important; -} -#email-lib-modal.email-reading #email-lib-grid > .date-section-header { - display: none !important; -} -#email-lib-modal.email-reading .doclib-modal-content { - min-height: var(--email-reading-modal-min-h, auto); -} -#email-lib-modal .doclib-card.doclib-card-expanded { - flex: 1 1 auto !important; - height: 100% !important; - /* Neutral frame on the active email — the accent border + glow felt - overbearing on desktop; the size jump alone is enough signal. */ - border: 1px solid var(--border) !important; - box-shadow: 0 6px 18px rgba(0,0,0,0.12) !important; - animation: none !important; -} -/* Desktop-only, ONLY on the currently-expanded email card. Nudges the title - row down 6px / right 2px, bolds the subject, hides the timestamp from the - meta line (the reader header carries its own date), and pulls the prev/next - nav arrows in 4px. */ -@media (min-width: 769px) { - #email-lib-modal .doclib-card-expanded .email-card-titlerow, - #email-lib-modal .doclib-card-expanded .email-card-titlerow > * { - position: relative; - top: 6px; - left: 2px; - } - /* Smaller subject — the reader header below already carries weight. */ - #email-lib-modal .doclib-card-expanded .email-card-titlerow .memory-item-title { - font-weight: 700; - font-size: 12px; - } - /* A bit more breathing room around the title row so the smaller bold text - doesn't crowd the meta line below. (Timestamp kept — user changed mind.) */ - #email-lib-modal .doclib-card-expanded .email-card-titlerow { - padding: 4px 0 6px; - line-height: 1.35; - } - /* Nudge the date down/left in the meta row, and give the meta line +4px of - extra bottom space so the shifted date isn't clipped. */ - #email-lib-modal .doclib-card-expanded .email-meta-date { - position: relative; - top: 4px; - left: 0; - } - #email-lib-modal .doclib-card-expanded .memory-item-meta { - padding-bottom: 4px; - } - #email-lib-modal .doclib-card-expanded .email-card-nav-arrows { - position: relative; - left: -4px; - } -} -/* Skills modal: same mechanism as email above. The default expanded-grid - rule (overflow:visible, no flex) lets the card expand inline to ~content - height instead of filling — which is why the skill preview stopped at - partial height. Flip the grid back to a constrained flex container and - let the expanded card fill it, exactly like the email reader. */ -#memory-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid { - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - flex: 1 1 auto !important; -} -#memory-modal .doclib-card.doclib-card-expanded { - flex: 1 1 auto !important; - height: 100% !important; -} -#email-lib-modal .modal-content { - transition: width 0.22s ease-out, height 0.22s ease-out; -} -@media (min-width: 769px) { - body:not(.email-doc-split-active) #email-lib-modal:not(.email-lib-fullscreen):not(.modal-left-docked):not(.modal-right-docked) .modal-content:not([style*="height"]) { - min-height: min(560px, 85vh); - } -} - -/* Serve panel specifically: the admin-card inline style is - `overflow:hidden` (so the toolbar/header don't drift), and the list - inside has overflow:visible. On short windows that combination - clipped the cards off the bottom with no scrollbar. Make the list - itself the scroll surface so the rest of the card stays put. */ -.cookbook-group[data-backend-group="Serve"] > .admin-card { - min-height: 0; -} -.cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list, -.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list { - flex: 1 1 0; - min-height: 0; - overflow-y: auto !important; - overscroll-behavior: contain; -} -.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list .doclib-card.doclib-card-expanded { - flex: 0 0 auto !important; - overflow: visible !important; -} -/* Launch model cards use the same domino entrance as the other library lists - when a cached-model scan finishes. The class is removed after the one-shot - entrance, so filtering and sorting stay instantaneous. */ -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(1) { animation-delay: 0.04s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(2) { animation-delay: 0.08s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(3) { animation-delay: 0.12s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(4) { animation-delay: 0.16s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(5) { animation-delay: 0.20s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(6) { animation-delay: 0.24s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(7) { animation-delay: 0.28s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(8) { animation-delay: 0.32s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(9) { animation-delay: 0.36s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(10) { animation-delay: 0.40s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(11) { animation-delay: 0.44s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(12) { animation-delay: 0.48s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(n+13) { animation-delay: 0.48s; } -@media (prefers-reduced-motion: reduce) { - #cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { - animation: none; - } -} -/* Drag-and-drop visual hint for the email compose pane. Subtle accent - outline + tinted overlay so it's obvious files will attach if dropped. */ -.doc-editor-pane.email-dragover { - outline: 2px dashed var(--accent, #2563eb); - outline-offset: -8px; - background: color-mix(in srgb, var(--accent, #2563eb) 6%, transparent); -} -.doc-editor-pane.email-dragover::after { - content: 'Drop to attach'; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: var(--accent, #2563eb); - color: #fff; - padding: 8px 16px; - border-radius: 6px; - font-size: 12px; - pointer-events: none; - z-index: 1000; -} - -/* Email reader window — make the body always fill the available vertical - space regardless of how the window is positioned/resized. The user can - drag the bottom-right corner to resize; the body re-flexes automatically. */ -.email-window-modal .modal-content { - display: flex !important; - flex-direction: column !important; - resize: both; - overflow: hidden; - min-width: 320px; - min-height: 240px; - /* When user resizes/drags so the window covers a side, force the inner - body to fill all the way to the bottom. The default max-height: 85vh - (set inline by _makeDraggable's exitFullscreen) and 80vh from the - opener both cap at the *viewport* — if the browser window then changes - size (e.g. OS aero-snap), the modal stays at the old height and - truncates rows. Drop the cap whenever an explicit width OR height has - been written inline by the resize handle / drag handler. */ -} -.email-window-modal .modal-content[style*="width"], -.email-window-modal .modal-content[style*="height"] { - max-height: calc(100vh - 16px) !important; -} -.email-window-modal .email-window-body { - flex: 1 1 auto !important; - min-height: 0 !important; - /* Explicit basis of 0 makes the body grow to fill remaining space rather - than its content's intrinsic height (which was capping the thread). */ - flex-basis: 0 !important; - max-height: none !important; - overflow: auto !important; - overscroll-behavior: contain; -} - -/* Individual email reader window — fullscreen mode (drag-to-top snap or - double-click header). Same recipe as the library modal: pin to viewport - edges and let the inner body absorb the remaining height. */ -body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .modal-content { - position: fixed !important; - inset: 0 !important; - left: 0 !important; - top: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; - display: flex !important; - flex-direction: column !important; -} -body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .email-window-body { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; -} - -/* Email library modal: fullscreen mode + larger mobile sheet (mirrors the - cookbook treatment so the toolbar and bulk actions stay reachable). - Leaves whichever navigation strip is currently visible on the left - (the wide sidebar OR the icon rail — they're mutually exclusive, so - one of the two CSS vars is 0 at any given time, set by init.js). */ -body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { - position: fixed !important; - top: 0 !important; - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; - right: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; -} -/* Mobile: the /email route adds .email-lib-fullscreen, but the desktop full- - bleed rule above squares off the corners and offsets the email by the icon - rail. On phones the email should be the normal bottom-sheet — full width, - rounded top corners. Same specificity as the rule above + later in source, - so it wins on mobile. */ -@media (max-width: 768px) { - body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { - left: 0 !important; - right: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - height: 100dvh !important; - max-height: 100dvh !important; - border-radius: 14px 14px 0 0 !important; - border-top: 1px solid var(--border) !important; - } -} -/* Make the inner panes actually grow to fill the fullscreen container - instead of staying at their natural size. The body owns the remaining - height below the header; the admin-card + grid then expand into it. */ -#email-lib-modal.email-lib-fullscreen .modal-body { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; -} -#email-lib-modal.email-lib-fullscreen .modal-body > .admin-card { - flex: 1 1 auto !important; - min-height: 0 !important; -} -#email-lib-modal.email-lib-fullscreen .doclib-grid { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow: auto !important; -} -@media (max-width: 768px) { - /* Mobile email modal sizing — keep in sync with the rule earlier in the - file. 75dvh tall always so flex children (the expanded email reader) - have a real height to grow into. */ - #email-lib-modal .modal-content { - max-height: 90dvh !important; - max-height: 90vh !important; - height: 90dvh !important; - height: 90vh !important; - } - /* Inner panes grow to fill the modal-content — without flex:1 on the - body, the expanded email reader sits in a tiny box because there's - nothing pushing it to take the remaining height. overflow:hidden + - min-height:0 lets each layer pass its constraints down. */ - #email-lib-modal .modal-body, - #email-lib-modal .admin-card { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; - max-height: none !important; - } - #email-lib-modal .doclib-grid { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow-y: auto !important; - } - /* modal-content keeps its scroll OFF here — the inner flex children - (doclib-grid for the list view, email-reader-body for the expanded - reader) own the scroll surfaces. Without this, double-scroll layouts - trap touches and the reader can't claim full height. */ - #email-lib-modal .modal-content { - overflow: hidden !important; - -webkit-overflow-scrolling: touch; - overscroll-behavior: contain; - touch-action: pan-y; - } - /* Attachment row on phones: cap to ~2 rows so it never dominates the - reader. If more chips exist, the row scrolls vertically. Smaller chip - padding + max-width keeps each chip compact so two rows usually fit - everything anyway. */ - #email-lib-modal .email-reader-atts { - padding: 4px 10px !important; - gap: 3px !important; - max-height: calc(2 * 22px + 12px); /* 2 rows × chip height + padding */ - overflow-y: auto; - align-content: flex-start; - } -#email-lib-modal .email-attachment-chip { - padding: 1px 6px !important; - font-size: 10px !important; - max-width: min(240px, calc(100vw - 44px)) !important; - line-height: 18px !important; - } -} - - -/* Mobile: only ONE scroll surface inside the cookbook modal. The - modal-content is the scroller. Everything inside (cookbook-body, - cookbook-group, all the inner lists/panels) gets overflow: visible - so touch-pan never gets trapped in a nested scroller. - Without this, three levels of overflow:auto + max-height combinations - produce dead-zone areas where swipes do nothing. */ -@media (max-width: 768px) { - #cookbook-modal .modal-content { - overflow-y: auto !important; - -webkit-overflow-scrolling: touch; - overscroll-behavior: contain; - touch-action: pan-y; - } - #cookbook-modal .cookbook-body, - #cookbook-modal .cookbook-group, - #cookbook-modal .cookbook-section-body, - #cookbook-modal .hwfit-cached-list, - #cookbook-modal .doclib-grid, - #cookbook-modal .hwfit-list, - #cookbook-modal .hwfit-panel-cmd { - overflow: visible !important; - max-height: none !important; - min-height: 0 !important; - } - /* Running tmux output: cap how tall an expanded card gets (a long-lived job - can leave thousands of lines) so it doesn't balloon, but keep its own - scroll so you can read it. Outputs default collapsed on mobile now, so - you're usually looking at one expanded card at a time — no wall of nested - scrollers, and you collapse it to move past. */ - #cookbook-modal .cookbook-output-pre, - #cookbook-modal .cookbook-task .cookbook-output-pre { - max-height: 45vh !important; - min-height: 0 !important; - overflow-y: auto !important; - overflow-x: hidden !important; - overscroll-behavior: auto; /* chain to the modal at the scroll boundary */ - } - /* cookbook-body's flex:1 was needed when it owned scrolling — drop it - so the inner content drives modal-content's scroll height. */ - #cookbook-modal .cookbook-body { - flex: 0 0 auto !important; - height: auto !important; - } - /* Launch tab's cached-list normally has `flex: 1 1 0; min-height: 0` - (so it fills the modal on desktop). On mobile the parent now has - `height: auto`, which collapses `flex: 1 1 0` to ZERO PX — - models render but the list area is invisible because the flex - basis is 0 and there's no free space to grow into. Switch to - content-sized flex so the list grows with its children. */ - #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list, - #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list { - flex: 0 0 auto !important; - overflow: visible !important; - } -} -#cookbook-modal .hwfit-cached-list { - flex-shrink: 0; -} -.memory-toolbar { - transition: opacity 0.12s ease, max-height 0.2s ease; - max-height: 120px; -} -.doclib-card { - background: color-mix(in srgb, var(--fg) 3%, transparent); - border: 1px solid var(--border); - border-radius: 8px; - cursor: pointer; - transition: all 0.15s, opacity 0.3s, max-height 0.3s ease; - position: relative; -} -.doclib-card.doclib-card-deleting { - opacity: 0; - max-height: 0 !important; - overflow: hidden; - margin: 0; - padding: 0; - border-color: transparent; -} -.doclib-card.selected, -/* Universal selection highlight: any card-shaped row (documents, chats, - archive, research, skills, memories, tasks) gets the same accent outline - when its checkbox is checked. Done via :has() so the highlight follows - selection without per-renderer JS changes. The canonical checkbox class - is .memory-select-cb across every list. */ -.memory-item:has(.memory-select-cb:checked), -.doclib-card:has(.memory-select-cb:checked), -.doclib-chat-row:has(.memory-select-cb:checked) { - border-color: color-mix(in srgb, var(--red) 40%, transparent); - background: color-mix(in srgb, var(--red) 4%, transparent); - /* 2px accent outline overlaid on the 1px border — reads as a thicker - selected border without shifting layout (every card would otherwise - need a 2px transparent border to keep the same width). */ - outline: 2px solid var(--accent-primary, var(--red)); - outline-offset: -1px; -} -.doclib-card-header { - display: flex; - align-items: center; - padding: 7px 8px; - gap: 6px; - min-width: 0; -} -/* Mobile only: push the unexpanded email card's title-row text down 4px so - it sits visually centered with the surrounding card padding. Targets the - actual title-row class (.email-card-titlerow) — the earlier attempt used - .doclib-card-header which the email card builds differently. */ -@media (max-width: 768px) { - #email-lib-modal .doclib-card:not(.doclib-card-expanded) .email-card-titlerow { - margin-top: 4px; - } -} -.doclib-card-title { - font-size: 11px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; - flex: 1; -} -.doclib-card-ver { - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - color: var(--accent, var(--red)); - font-size: 9px; - flex-shrink: 0; - font-weight: 600; - letter-spacing: 0.3px; - min-width: 24px; - text-align: left; - box-sizing: border-box; -} -.doclib-card-ver-muted { - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.doclib-card-lang { - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - color: var(--accent); - font-size: 9px; - text-align: left; - flex-shrink: 0; - min-width: 52px; - box-sizing: border-box; -} -.doclib-card-session { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 60px; - width: 100px; - max-width: 100px; - font-size: 9px; - color: var(--fg-muted); - flex-shrink: 0; - text-align: left; -} -.doclib-card-time { - flex-shrink: 0; - opacity: 0.5; - font-size: 9px; - color: var(--fg-muted); - min-width: 50px; - text-align: left; -} -/* Footer — used by archive cards */ -.doclib-card-footer { - display: contents; -} -/* Preview — hidden by default, shown on expand */ -.doclib-card-preview { - display: none; - position: relative; - padding: 8px 12px; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.5; - color: var(--code-fg, var(--hl-fg, var(--fg))); - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin: 0; -} -.doclib-card-preview pre { - margin: 0; - white-space: pre-wrap; - word-break: break-all; -} -.doclib-card-expanded .doclib-card-preview pre { - flex: 1 1 0; - overflow-y: auto; - min-height: 0; -} -.doclib-card-preview code.hljs { - background: none; - padding: 0; -} -/* Expanded-only action bar — inside preview. Buttons shifted up 4px by - trimming the row's top margin so they sit closer to the preview text. */ -.doclib-card-expanded-actions { - display: none; - align-items: flex-start; - gap: 6px; - padding: 8px 0 2px; - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 4px; -} -.doclib-card-expanded-actions > .doclib-card-action-btn, -.doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - position: relative; - top: -4px; -} -.doclib-action-group { - display: flex; - flex-direction: column; - gap: 3px; -} -.doclib-action-btn-row { - display: flex; - gap: 6px; -} -.doclib-action-hint-row { - display: flex; - gap: 6px; -} -/* Match the chat/research footer buttons exactly: flat, bordered, - app font, accent on hover. */ -.doclib-card-action-btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - box-sizing: border-box; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - cursor: pointer; - /* These buttons live inside .doclib-card-preview, which forces a - monospace font. The chat/research footer buttons instead inherit the - modal font, which is the app font (Fira Code) on mobile and Inter on - desktop (.modal-content's Inter rule is inside @media min-width:769px). - Mirror that here so all three footers match in both contexts. */ - font-family: var(--font-family, 'Fira Code', monospace); - transition: border-color 0.15s, color 0.15s; -} -@media (min-width: 769px) { - .doclib-card-action-btn { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - letter-spacing: -0.015em; - } -} -.doclib-card-action-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -.doclib-card-text-btn-danger { - color: var(--color-danger, #e06c75); - border-color: var(--color-danger, #e06c75); -} -.doclib-card-text-btn-danger:hover { - color: #ff4d4d; - border-color: #ff4d4d; -} -.doclib-card-expanded-actions > .doclib-card-action-btn { - font-size: 10px; - padding: 3px 8px; -} -/* Push the Open/Clone group to the right edge of the action bar — Delete - anchors the left, primary actions sit opposite. */ -.doclib-card-expanded-actions > .doclib-action-group { - margin-left: auto; -} -.doclib-card-expanded-actions > .doclib-expanded-delete-btn, -.doclib-card-expanded-actions > .doclib-expanded-archive-btn { - left: 0 !important; -} -.doclib-expanded-mobile-more { display: none; } -.doclib-btn-hint { - font-weight: normal; - opacity: 0.35; - font-size: 8px; - min-width: 58px; - padding: 0 8px; - box-sizing: border-box; -} -@media (max-width: 768px) { - .doclib-btn-hint { display: none !important; } -} - -/* Chat row expand preview — matches the documents-tab expand style. */ -.doclib-card-chevron { - display: inline-flex; - align-items: center; - justify-content: center; - align-self: center; - flex-shrink: 0; - width: 14px; - height: 14px; - line-height: 0; - opacity: 0.3; - transform: rotate(0deg); - transition: transform 0.2s ease, opacity 0.15s; -} -.doclib-card-chevron svg { display: block; } -.doclib-card-expanded .doclib-card-chevron, -.task-card.expanded .doclib-card-chevron { - opacity: 0.8; - transform: rotate(180deg); -} -.doclib-card > .memory-item-actions { order: 1; } -.doclib-card > .doclib-card-chevron { order: 2; } -.doclib-chat-header .memory-item-actions, -.task-card .memory-item-actions { order: 2; } -.doclib-chat-header .doclib-card-chevron, -.task-card .doclib-card-chevron { order: 3; } -.hwfit-cached-list > .doclib-card > .memory-item-actions { order: 2; } -.hwfit-cached-list > .doclib-card > .doclib-card-chevron { order: 3; } -#email-lib-modal .doclib-card > .memory-item-actions { order: 1; align-self: center; } -#email-lib-modal .doclib-card > .doclib-card-chevron { display: none; } -.doclib-chat-row { flex-direction: column; align-items: stretch; } -.doclib-chat-row .doclib-card-chevron { - display: inline-flex; - opacity: 0.55; - transition: transform 0.2s ease, opacity 0.15s; - flex-shrink: 0; -} -.doclib-chat-row:not(.doclib-card-expanded) .doclib-card-chevron { - transform: rotate(0deg); -} -.doclib-chat-row.doclib-card-expanded .doclib-card-chevron { - opacity: 0.8; - transform: rotate(180deg); -} -/* Release .memory-item's base max-height: 200px when a chat row is - expanded. Without this, the row clips at 200px on desktop and the - preview's messages/actions visually spill past the row's box — - "colliding" with the next chat in the column. The mobile takeover - below already handles this via the same override; this rule is the - desktop-or-any-viewport equivalent. */ -.doclib-chat-row.doclib-card-expanded { - max-height: none; - overflow: visible; -} -/* When a chat row is expanded, take over the grid the same way documents - do: hide siblings, hide the admin-card toolbar, and let this row plus - its preview claim the full available height. .memory-item's default - max-height: 200px must be overridden or the preview will be tiny. */ -/* Mobile-only "card takeover" for an expanded chat — hides siblings, - hides the admin-card toolbar, and lets this row + its preview claim - the full available height. On desktop the expanded preview just - renders inline below the row with its messages constrained by - max-height + overflow:auto so the user gets a normal scroll. */ -@media (max-width: 820px) { - .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - flex: 1 1 auto !important; - max-height: none !important; - overflow-y: auto !important; - } - .doclib-chat-row.doclib-card-expanded { - flex: 1 1 auto !important; - max-height: none !important; - min-height: 0 !important; - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - align-items: stretch !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-header { - flex: 0 0 auto !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - padding-bottom: 4px !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { - -webkit-overflow-scrolling: touch; - max-height: none !important; - flex: 1 1 auto !important; - min-height: 0 !important; - } -} -.doclib-chat-preview { - font-size: 11px; - padding: 0 4px 6px; -} -.doclib-chat-preview .doclib-chat-open-btn { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - color: var(--fg-muted); - border: 1px solid var(--border); - background: none; - cursor: pointer; - font-family: inherit; - transition: border-color 0.15s, color 0.15s; -} -.doclib-chat-preview .task-completed-report-btn { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); -} -.doclib-chat-preview .doclib-chat-open-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -.doclib-chat-preview-messages { - margin-top: 6px; - max-height: 320px; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - padding-right: 4px; -} -/* Desktop expanded state — match the documents-tab behavior: hide - sibling chats, hide the admin-card toolbar/header, and let the - expanded row + its messages preview claim the full modal height. - Chat rows use `memory-item doclib-chat-row` (no `doclib-card` class) - so the document-tab's global rules at line 12284-12304 skip them — - these mirror those rules scoped to chat rows. The mobile media query - above (820px) keeps the same takeover behavior for phones. */ -@media (min-width: 821px) { - .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - flex: 1 1 auto !important; - max-height: none !important; - overflow: hidden auto !important; - } - .doclib-chat-row.doclib-card-expanded { - flex: 1 1 auto !important; - max-height: none !important; - min-height: 0 !important; - display: flex !important; - flex-direction: column !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { - flex: 1 1 auto !important; - min-height: 0 !important; - display: flex !important; - flex-direction: column !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow-y: auto !important; - } -} -.doclib-chat-preview-actions { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 8px; - padding: 2px 0 2px; - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 2px; - flex-shrink: 0; - /* Whole action footer nudged down 5px. */ - position: relative; - top: 5px; -} -.doclib-chat-delete-btn, -.doclib-chat-archive-btn, -.doclib-chat-restore-btn, -.doclib-chat-discuss-btn, -.doclib-chat-copy-btn { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - background: none; - cursor: pointer; - font-family: inherit; - transition: border-color 0.15s, color 0.15s; -} -.doclib-chat-archive-btn, -.doclib-chat-restore-btn, -.doclib-chat-discuss-btn, -.doclib-chat-copy-btn { - color: var(--fg-muted); - border: 1px solid var(--border); -} -.doclib-chat-restore-btn:hover { - color: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -.doclib-chat-archive-btn:hover, -.doclib-chat-discuss-btn:hover, -.doclib-chat-copy-btn:hover { - color: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -.doclib-chat-delete-btn { - color: var(--color-danger, #e06c75); - border: 1px solid var(--color-danger, #e06c75); -} -.doclib-chat-delete-btn:hover { - color: #ff4d4d; - border-color: #ff4d4d; -} -/* When a chat is archived there's no Archive button, so Delete becomes - the only left-side action and needs the auto margin to push Open right. */ -.doclib-chat-preview-actions:not(:has(.doclib-chat-archive-btn)) > .doclib-chat-delete-btn { - margin-right: auto; -} -/* Hide the "..." menu while the chat card is expanded — archive + - delete live in the preview footer instead. */ -.doclib-chat-row.doclib-card-expanded ._chat-menu { display: none; } -.doclib-chat-preview-actions.doclib-chat-preview-actions-top { - border-top: none; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 4px; - margin-bottom: 6px; - padding: 0 0 6px; - justify-content: flex-start; -} -.doclib-chat-preview-messages .doclib-chat-msg { - margin: 4px 0 10px; - padding-left: 8px; - border-left: 2px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.doclib-chat-preview-messages .doclib-chat-msg.user { - border-left-color: color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); -} -.doclib-chat-preview-messages .doclib-chat-msg-role { - font-size: 9px; - font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - opacity: 0.55; - margin-bottom: 2px; -} -.doclib-chat-preview-messages .doclib-chat-msg.user .doclib-chat-msg-role { - color: var(--accent, var(--red)); - opacity: 0.85; -} -.doclib-chat-preview-messages .doclib-chat-msg-body { - white-space: pre-wrap; - word-break: break-word; - opacity: 0.85; - line-height: 1.45; -} -/* Chat-bubble preview — mirrors the real chat layout. User bubbles hug - right with an accent tint; assistant bubbles hug left with a neutral - panel tint and a small model tag at the top. */ -.doclib-chat-bubble-row { - display: flex; - margin: 6px 0; -} -.doclib-chat-bubble-row.user { justify-content: flex-end; } -.doclib-chat-bubble-row.assistant { justify-content: flex-start; } -.doclib-chat-bubble { - max-width: 85%; - padding: 6px 10px 8px; - border-radius: 14px; - font-size: 11px; - line-height: 1.45; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.doclib-chat-bubble-row.user .doclib-chat-bubble { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); - border-bottom-right-radius: 4px; -} -.doclib-chat-bubble-row.assistant .doclib-chat-bubble { - border-bottom-left-radius: 4px; -} -.doclib-chat-msg-model { - display: inline-block; - font-size: 8px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; - opacity: 0.55; - margin-bottom: 3px; - padding: 1px 6px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg-muted, var(--fg)); -} -.doclib-chat-bubble-body { word-break: break-word; } -.doclib-chat-bubble-body p { margin: 4px 0; } -.doclib-chat-bubble-body p:first-child { margin-top: 0; } -.doclib-chat-bubble-body p:last-child { margin-bottom: 0; } -.doclib-chat-bubble-body pre { - font-size: 10px; - margin: 4px 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--bg) 60%, transparent); - border-radius: 6px; - overflow-x: auto; -} -.doclib-chat-bubble-body code { - font-size: 10px; - padding: 1px 4px; - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.doclib-chat-bubble-body pre code { background: none; padding: 0; } -.doclib-chat-bubble-body ul, -.doclib-chat-bubble-body ol { - margin: 4px 0; - padding-left: 18px; -} -.doclib-card-collapse-chevron { - display: none; - align-items: center; - justify-content: center; - width: 24px; - height: 22px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: transparent; - color: var(--color-muted, var(--fg)); - font: inherit; - opacity: 0.3; - cursor: pointer; - flex-shrink: 0; - margin: 0 0 0 4px; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.doclib-card-collapse-chevron:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); -} -.cookbook-serve-title { - display: flex; - align-items: center; - min-width: 0; -} -.cookbook-serve-title .doclib-card-collapse-chevron { - display: inline-flex; - margin-left: auto; - margin-top: 0; -} -.doclib-card-expanded .doclib-card-collapse-chevron { - display: inline-flex; -} -/* Expanded card — fills the whole grid */ -.doclib-card.doclib-card-expanded { - flex: 1; - min-height: 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); - border: 1px solid var(--border); - border-radius: 8px; - display: flex; - flex-direction: column; - overflow: hidden; - animation: doclib-expand 0.2s ease-out; -} -.doclib-card.doclib-card-expanded { - flex-direction: column !important; - align-items: stretch !important; - max-height: none !important; - gap: 4px !important; -} -.doclib-card.doclib-card-expanded > .memory-item-actions { display: none; } -/* Non-preview children of an expanded doc card carry an inline flex:1 from - the row layout. With the card now flex-column those children would steal - half the height and shrink the preview — pin them at auto height. */ -.doclib-card.doclib-card-expanded > div:not(.doclib-card-preview):not(.doclib-card-header):not(.memory-item-actions):not(.email-card-reader) { - flex: 0 0 auto !important; -} -/* The email reader IS the scroll container — keep its flex:1 so its - internal body can claim the rest of the card's height and scroll. */ -.doclib-card.doclib-card-expanded > .email-card-reader { - flex: 1 1 auto !important; - min-height: 0 !important; -} -.doclib-card.doclib-card-expanded .doclib-card-preview { - display: flex; - flex-direction: column; - flex: 1 1 auto !important; - /* The preview matches chat rows: document content scrolls, while the - footer stays in the card rather than becoming the last scroll item. */ - overflow: hidden; - min-height: 0; -} -.doclib-card.doclib-card-expanded .doclib-card-expanded-actions { - display: flex; - flex-shrink: 0; -} -/* Document cards can be expanded in the normal windowed library, where the - grid has no definite height. A zero-basis then collapses to nothing - while the footer remains visible. Keep the document text's intrinsic height - in that layout; bounded/fullscreen layouts can still shrink it and scroll. */ -#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview > pre { - flex: 1 1 auto; - min-height: 1.5em; -} - -/* Collapsible skills section headers (Your skills / Built-in). */ -.skills-section-header { - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - user-select: none; - padding: 6px 4px 4px; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.06em; - opacity: 0.6; - font-weight: 600; -} -.skills-section-header:hover { opacity: 0.9; } -.skills-section-chevron { transition: transform 0.15s ease; flex-shrink: 0; } -.skills-section-header.collapsed .skills-section-chevron { transform: rotate(-90deg); } -.skills-section-count { - margin-left: auto; - opacity: 0.6; - font-variant-numeric: tabular-nums; - font-weight: normal; -} -.skill-card-section-hidden { display: none !important; } -/* Warning banner shown when previewing/editing a built-in capability. */ -.skill-builtin-warn { - font-size: 9.5px; - line-height: 1.45; - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 35%, transparent); - border-radius: 6px; - padding: 6px 9px; - margin: 0 0 8px; - flex-shrink: 0; -} - -/* #skills-list wears both .memory-list and .doclib-grid. doclib-grid's - max-height:400px would cap the list AND clamp an expanded card — keep - the memory-list fill-the-modal behaviour instead. */ -#skills-list.doclib-grid { - max-height: none; - flex: 1; - min-height: 0; -} -#skills-list.doclib-grid:has(.doclib-card-expanded) { - overflow: hidden; /* expanded card owns its own scroll */ -} -/* When a skill is expanded, hide the sibling Add-Skill form card so the - expanded SKILL.md uses the full panel. Otherwise the two admin-cards - split the height ~50/50 and the expanded skill is stuck in its half - while the Add form idles in the other. */ -.memory-tab-panel[data-memory-panel="skills"]:has(.doclib-card-expanded) > .admin-card:nth-of-type(2) { - display: none !important; -} - -/* Skills cards reuse the doclib expand/footer machinery. The SKILL.md - preview + the edit textarea need to fill the expanded card and own - their own scroll, same as a document preview. */ -/* Collapsed skill rows used a smaller (0.9em code) title, so the click row - read slimmer than document/chat/library cards. Give the header a min-height - so the tap target matches the other library items. */ -.skill-card > .skill-card-header { - min-height: 53px; - box-sizing: border-box; - display: flex; - align-items: center; - gap: 6px; -} -/* Skill rows share Tasks' card hover colors. Draft state belongs in the - status badge, not in a second opacity layer across the whole row. */ -/* Name + description column. Name wraps to 2 lines (skill slugs are long — - use the space rather than truncating to "check-model-downl…"). */ -.skill-card-textcol { - flex: 1 1 auto; - min-width: 0; - overflow: hidden; -} -.skill-card-title-row { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.skill-card-name { - font-family: inherit; - font-weight: 500; - font-size: 12px; - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - word-break: normal; - line-height: normal; -} -.skill-card-title-row .skill-card-name { min-width: 0; } -.skill-status-published { - width: 20px; - height: 20px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 15%, transparent); - border-color: color-mix(in srgb, var(--color-success, #4ade80) 32%, transparent); - border-radius: 50%; -} -.skill-stats .skill-status-published { - width: 15px; - height: 15px; - margin-left: 3px; - vertical-align: middle; -} -.skill-stats .skill-status-published svg { - width: 9px; - height: 9px; -} -.skill-card-desc { - font-size: 10px; - opacity: 0.4; - margin-top: -1px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.skills-audit-model-field { - display: flex; - align-items: center; - gap: 8px; - margin-top: 10px; - font-size: 11px; -} -.skills-audit-model-field > span { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 68%, transparent); -} -.skills-audit-model-field select { - min-width: 0; - flex: 1; - height: 26px; - padding: 0 6px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.skill-card-tags { - display: flex; - gap: 4px; - flex-wrap: wrap; - margin-top: 4px; -} -.skill-tag-pill { - padding: 1px 5px; - font-size: 9px; -} -.skill-usage-pill { - margin-left: auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.skill-tag-filter-chip { cursor: pointer; } - -@media (max-width: 768px) { - .skill-card > .skill-card-header { - align-items: flex-start; - } - .skill-card-textcol { - min-width: 0; - } - .skill-card-tags { - max-width: 100%; - overflow: hidden; - } - .skill-card-tags .skill-tag-pill { - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -} -/* Right cluster — pills (right-aligned), stats, then the menu/chevron. */ -.skill-card-right { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; - margin-left: auto; -} -.skill-card-right .memory-cat-badge { flex-shrink: 0; } -.skill-status-bin { - background: color-mix(in srgb, var(--color-danger, #d9534f) 20%, transparent); - color: var(--color-danger, #d9534f); -} -.skill-model-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-model-student { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.skill-audit-pass { - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 16%, transparent); -} -.skill-audit-needs_work { - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 16%, transparent); -} -.skill-audit-fail { - color: var(--color-danger, #e06c75); - background: color-mix(in srgb, var(--color-danger, #e06c75) 16%, transparent); -} -.skill-audit-inconclusive, -.skill-audit-skipped, -.skill-audit-unknown { - color: color-mix(in srgb, var(--fg) 64%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-model-teacher { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 24%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-necessity-duplicate { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 22%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-trivial { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 62%, transparent); - border-color: color-mix(in srgb, var(--fg) 16%, transparent); -} -.skill-necessity-irrelevant { - background: color-mix(in srgb, var(--color-danger, #e06c75) 18%, transparent); - color: var(--color-danger, #e06c75); -} -.skills-summary-strip { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - margin: -3px 0 8px; -} -.skills-summary-strip::-webkit-scrollbar { display: none; } -.skills-summary-strip:empty { - display: none; -} -.skills-summary-chip { - display: inline-flex; - align-items: center; - gap: 5px; - height: 23px; - padding: 0 8px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 10px; - line-height: 1; - cursor: pointer; - flex: 0 0 auto; -} -.skills-summary-chip:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.skills-summary-chip.active { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - color: var(--fg); -} -.skills-summary-chip strong { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10px; - color: var(--fg); -} - -/* Shared filter/tag strip contract. These rows all sit immediately below - their screen's search or primary toolbar and stay one line at every width. */ -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -) { - align-items: center; - flex-wrap: nowrap !important; - gap: 6px; - width: 100%; - max-width: 100%; - min-width: 0; - max-height: none; - overflow-x: auto !important; - overflow-y: hidden !important; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - box-sizing: border-box; - padding: 0; - margin: -3px 0 8px; -} -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -)::-webkit-scrollbar { display: none; } -:is( - .skills-summary-strip > .skills-summary-chip, - #memory-category-filters > .skills-summary-chip, - .memory-category-filters > .memory-cat-chip, - .doclib-lang-chips > :is(.memory-cat-chip, .skills-summary-chip), - .doclib-chips > .doclib-chip, - .notes-labels-bar > .notes-label-chip, - .tasks-activity-filters > .memory-cat-chip, - .gallery-tag-chips > .gallery-chip, - .gallery-album-chips > .gallery-chip, - .gallery-ai-tags > .gallery-ai-chip, - .cal-filters > .cal-filter-item -) { - flex: 0 0 auto; - box-sizing: border-box; - height: 23px !important; - min-height: 23px; - padding: 0 8px !important; - border-radius: 6px !important; - font-size: 10px !important; - line-height: 1; - white-space: nowrap; -} -.doclib-lang-chips { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.doclib-chip-scroll-frame { - position: relative; - display: flex; - align-items: center; - flex: 1 1 100%; - min-width: 0; - max-width: 100%; -} -.doclib-chip-scroll-frame > .chip-scroll-strip { - flex: 1 1 auto; - min-width: 0; - margin-left: 0; - margin-right: 0; -} -.doclib-chip-scroll-arrow { - display: none; - position: absolute; - top: calc(50% - 1px) !important; - transform: translateY(-50%); - margin-top: 0; - z-index: 2; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - box-sizing: border-box; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 18px; - cursor: pointer; - opacity: 0.84; - transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease; -} -.doclib-chip-scroll-arrow-glyph { - display: inline-block; - position: relative; - top: -1px; - line-height: 1; -} -.doclib-chip-scroll-arrow:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); -} -@media (max-width: 768px) { - .doclib-chip-scroll-arrow { display: none !important; } -} -.doclib-chip-scroll-frame.has-overflow > .doclib-chip-scroll-arrow:not([hidden]) { - display: inline-flex; -} -.doclib-chip-scroll-arrow.left { left: 0; } -.doclib-chip-scroll-arrow.right { right: 0; } -.doclib-chip-scroll-frame.has-overflow > .chip-scroll-strip { - padding-left: 0 !important; - padding-right: 22px !important; -} -.doclib-chip-scroll-frame > .chip-scroll-strip.is-pointer-dragging { - cursor: grabbing; - user-select: none; -} -.doclib-chip-scroll-frame > .chip-scroll-strip:not(.is-pointer-dragging) { - cursor: grab; -} -/* Calendar tags are wrapped by chipScroll.js after render. Keep that wrapper - content-sized inside the calendar's column layout; the default flex growth - otherwise turns the one-line tag strip into a large empty panel. */ -#cal-body > .doclib-chip-scroll-frame:has(> .cal-filters) { - flex: 0 0 auto; - min-height: 0; -} -/* The completed-task filters are a compact toolbar, not a flexible panel. - Without this override the shared chip frame grows into the available - height of the card, which is especially visible the first time the mobile - view switches to Completed. */ -.tasks-completed-card > .doclib-chip-scroll-frame:has(> #tasks-completed-status-chips) { - flex: 0 0 auto; - min-height: 0; -} -/* The active Tasks filter row is also a compact toolbar. Keep its wrapper - content-sized so changing a filter cannot make the mobile panel grow a - large empty gap between the chips and the task cards. */ -.admin-card > .doclib-chip-scroll-frame:has(> #tasks-filter-chips) { - flex: 0 0 auto; - min-height: 0; -} -#memory-category-filters { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.cookbook-serve-tag-label { - display: inline-flex; - align-items: center; - min-width: 0; -} -#serve-search, -#hwfit-cache-select, -#serve-tags { - position: relative; - top: -2px; -} -#hwfit-cache-select { - top: -4px; -} -/* Calendar previously framed its tags as a separate card. It now follows the - same unframed placement as the other filter strips. */ -.cal-filters { - border: 0; - background: none; - box-shadow: none; -} -.skill-duplicate-keep { - background: color-mix(in srgb, var(--color-success, #4ade80) 22%, transparent); - color: var(--color-success, #4ade80); -} -.skill-duplicate-group { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - color: color-mix(in srgb, var(--fg) 62%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-duplicate-lower { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); - color: var(--color-danger, #e06c75); -} -.skill-stats { - font-size: 9px; - font-family: monospace; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.skill-turns-saved { - font-size: 9px; - opacity: 0.82; - font-variant-numeric: tabular-nums; - white-space: nowrap; - flex: 0 0 auto; -} -.skill-expanded-savings { - display: flex; - align-items: center; - min-height: 18px; - margin: 0 0 5px; -} -.skill-turns-positive { - color: color-mix(in srgb, var(--color-success, #4ade80) 82%, var(--fg)); -} -.skill-turns-zero, -.skill-turns-unknown { - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.skill-turns-negative { - color: color-mix(in srgb, var(--color-danger, #e06c75) 80%, var(--fg)); -} -.skill-conf { - display: inline-block; - min-width: 4ch; - text-align: right; - font-weight: 700; - color: var(--accent, var(--red)); -} -.skill-verified, -.skill-teachermark, -.skill-needsmark { - display: inline-flex; - align-items: center; - vertical-align: middle; - margin-right: 3px; -} -.skill-verified { color: var(--accent, #4ade80); } -.skill-teachermark { color: var(--color-warning, #f0ad4e); } -.skill-needsmark { color: var(--color-danger, #e06c75); cursor: help; } - -/* The card currently being processed by an "Audit now" run glows + pulses so - it's obvious which one the audit is on. */ -@keyframes skill-audit-pulse { - 0%, 100% { box-shadow: 0 0 6px 0 color-mix(in srgb, var(--accent, #4ade80) 55%, transparent); } - 50% { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent, #4ade80) 85%, transparent); } -} -.skill-card.skill-audit-active { - border-color: var(--accent, #4ade80) !important; - animation: skill-audit-pulse 1.3s ease-in-out infinite; -} - -/* A test is running for this skill — the app's whirlpool spinner is injected - next to the name from JS (see _setCardRunning) so the collapsed/folded card - still makes it obvious work is in progress. */ -/* Collapsed bar shows the kebab menu; expanded shows an up-chevron to - collapse. (Toggled by the .doclib-card-expanded class.) */ -.skill-kebab-btn { - display: inline-flex; - align-items: center; - justify-content: center; - background: none; - border: none; - padding: 4px; - margin: 0; - cursor: pointer; - color: var(--fg); - opacity: 0.5; - border-radius: 5px; - flex-shrink: 0; - position: relative; - top: -2px; -} -.skill-kebab-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-chevron-up { display: none; align-items: center; opacity: 0.5; flex-shrink: 0; } -/* Kebab dropdown */ -.skill-kebab-menu { - position: fixed; - /* z-index is set inline via topPortalZ() at open time (#4720); a static - value here loses once the modal bring-to-front counter climbs past it. */ - min-width: auto; - width: max-content; - padding: 4px; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 6px 20px rgba(0,0,0,0.22); - display: flex; - flex-direction: column; - gap: 1px; -} -.skill-kebab-item { - display: flex; - align-items: center; - gap: 10px; - width: 100%; - padding: 6px 8px; - background: none; - border: none; - border-radius: 8px; - cursor: pointer; - color: var(--fg); - font-size: 11px; - text-align: left; -} -.skill-kebab-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-kebab-item.danger { color: var(--color-error, #e55); } -.skill-kebab-item.danger:hover { background: color-mix(in srgb, var(--color-error, #e55) 15%, transparent); } -/* Section labels separating "Your skills" from the read-only "Built-in - capabilities" list. */ -.skills-section-label { - font-size: 10px; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.04em; - margin: 10px 2px 4px; - flex-shrink: 0; -} -.skills-section-label:first-child { margin-top: 0; } -/* When a learned skill is expanded the grid hides sibling cards; hide the - section labels too so they don't orphan above the collapsed list. */ -#skills-list:has(.doclib-card-expanded) .skills-section-label { display: none; } -/* Built-in cards are read-only — no expand/hover-pointer affordance. */ -.skill-builtin-card { cursor: default; } -.skill-builtin-card:hover { border-color: var(--border); } -.skill-card-preview .skill-md-pre { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - white-space: pre-wrap; - word-break: break-word; -} -.skill-preview-copy { - position: absolute; - top: 6px; - right: 8px; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - padding: 0; - opacity: 0; - color: var(--fg); - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - cursor: pointer; - transition: opacity .15s, color .15s, border-color .15s; -} -.skill-card-preview:hover .skill-preview-copy { opacity: 0.7; } -.skill-preview-copy:hover, -.skill-preview-copy:focus-visible { opacity: 1 !important; } -.skill-preview-copy.copied { - opacity: 1; - color: var(--color-save-green, #4caf50); - border-color: var(--color-save-green, #4caf50); -} -/* :has()-free expand layout. skills.js adds .skills-has-expanded to the - skills admin-card when a skill opens, so this works on engines that don't - support :has() (e.g. older Firefox mobile, where the expand stuck at - ~50%). Hide everything but the list so the grid fills the card; the - absolutely-positioned expanded card (mobile) then fills the grid. */ -.admin-card.skills-has-expanded > *:not(#skills-list) { - display: none !important; -} -.admin-card.skills-has-expanded > #skills-list.doclib-grid { - flex: 1 1 auto !important; - height: 100% !important; - min-height: 0 !important; - overflow: hidden !important; -} -/* Expand animation — a clean opacity fade. The mobile card snaps to a - full-screen overlay (position:absolute), so any translate/scale on it - reads as a jarring "explosion"; a pure fade just appears smoothly. (No - transform also keeps skills.js's height measurement accurate.) */ -@keyframes skill-card-expand { - from { opacity: 0; } - to { opacity: 1; } -} -.skill-card.doclib-card-expanded { - animation: skill-card-expand 0.18s ease-out; -} -/* Switching directly from one expanded card to another: no fade (it would - show the previous card collapsing through the semi-transparent new one). */ -.skill-card.doclib-card-expanded.skill-expand-instant { - animation: none !important; -} -@media (prefers-reduced-motion: reduce) { - .skill-card.doclib-card-expanded, - .skill-card.doclib-card-expanded .doclib-card-preview { - animation: none !important; - } -} -/* The preview WRAPPER inherits flex:1 (grow) from the generic doclib expand - rule — that's what stretched the card and left a void / pushed the footer - out. Size it to content (shrink + scroll only when long) so the footer - sits right under the preview and stays fully visible. */ -.skill-card.doclib-card-expanded .doclib-card-preview { - flex: 0 1 auto !important; - min-height: 0; - /* The footer lives INSIDE this wrapper. Don't let the wrapper scroll, or - the footer scrolls off with the content. Keep it clipped; the - inside is the scroller, so the footer stays pinned at the bottom. */ - overflow: hidden !important; -} -.skill-card.doclib-card-expanded .skill-md-pre { - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them - back down 4px so they're vertically centered in the skill card footer. */ -.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn, -.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - top: 0; -} -/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */ -#skills-bulk-bar .memory-toolbar-btn { - position: relative; - top: -2px; -} -/* ── Audit-all progress panel ── */ -.skills-audit-panel { - border: 1px solid var(--border); - border-radius: 8px; - padding: 8px 10px; - margin: 6px 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } -.skills-audit-title { font-size: 12px; font-weight: 600; } -.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; } -.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; } -.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; } -.skills-audit-log { - max-height: 22vh; overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10.5px; line-height: 1.5; opacity: 0.8; -} -/* ── Skill test monitor + AI eval verdict ── */ -.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; } -.skill-test-log { - flex: 1 1 auto; - min-height: 120px; - max-height: 48vh; - overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.45; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - white-space: pre-wrap; - word-break: break-word; -} -.skill-test-log > div { margin: 1px 0; } -.skill-test-meta { opacity: 0.5; } -.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; } -.skill-test-round { opacity: 0.6; margin-top: 6px !important; } -.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; } -.skill-test-out { opacity: 0.7; padding-left: 10px; } -.skill-test-say { white-space: pre-wrap; } -.skill-test-err { color: var(--color-danger, #e06c75); } -.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } -.skill-eval-badge { - font-size: 10px; font-weight: 700; letter-spacing: 0.03em; - padding: 2px 8px; border-radius: 10px; flex-shrink: 0; -} -.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); } -.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); } -.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); } -.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); } -.skill-eval-summary { font-size: 11px; opacity: 0.85; } -.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; } -.skill-eval-issues li { margin: 2px 0; } -.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } -.skill-eval-approve.suggested { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; -} -/* Already published — the button confirms the state (click to unpublish). */ -.skill-eval-approve.is-approved { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; - background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important; -} -/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title, - When to use, How, Tags) is accent-colored while the rest stays muted — real - placeholders can't be partially colored. The native placeholder is a single - space so :placeholder-shown still toggles the overlay. */ -.skill-ph-wrap { position: relative; } -.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; } -.skill-rich-ph { - position: absolute; - left: 11px; right: 11px; top: 50%; - transform: translateY(-50%); - pointer-events: none; - font-size: 12px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 40%, transparent); - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -/* Textarea: align the hint to the first line instead of vertical-centering. */ -.skill-rich-ph-top { top: 7px; transform: none; } -.skill-rich-ph .k { color: var(--accent, var(--red)); } -/* Keep the memory-input enter hint pinned to the field's right edge instead - of letting it drift after the example text. */ -#new-memory-input ~ .skill-rich-ph > svg { - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - margin-left: 0 !important; -} -/* Hide the overlay once the field has real content. */ -.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; } -.skill-md-editor { - flex: 1 1 auto; - min-height: 0; - width: 100%; - box-sizing: border-box; - resize: none; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); -} -.doclib-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -/* Unified loading row across every Library tab (Chats / Documents / Research / - Archive): one opacity, one size, with the whirlpool spinner next to it. */ -.lib-loading-row { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 28px 16px; - font-size: 12px; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.pdf-loading-state { - min-height: 120px; - margin: 0; - box-sizing: border-box; - flex-direction: column; - gap: 10px; - border: 0; - border-radius: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - box-shadow: none; - font-size: 12px; - font-weight: 600; - letter-spacing: 0.01em; -} -.pdf-loading-state .ai-spinner-whirlpool { - color: var(--accent, var(--red)); - opacity: 0.95; -} -.pdf-loading-state .ai-spinner-whirlpool canvas { - display: block; -} -.doclib-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg-muted); - font-size: 11px; - cursor: pointer; - transition: border-color 0.15s, color 0.15s; - flex-shrink: 0; - position: relative; - top: 10px; -} -/* Soft dark gradient above the button — blends the list content into the - load-more strip instead of a hard cutoff. Spans the modal width and - sits behind/above the button. */ -.doclib-load-more::before { - content: ""; - position: absolute; - left: 50%; - transform: translateX(-50%); - bottom: 100%; - margin-bottom: 4px; - width: min(600px, 92vw); - height: 34px; - pointer-events: none; - background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent); -} -.doclib-load-more:hover { - border-color: var(--red); - color: var(--red); -} -.doclib-inline-load-more { - border: 0; - border-radius: 0; - padding: 0; - background: none; - top: 0; - font-size: 9px !important; - line-height: 1.2 !important; -} - -/* Document library toolbar buttons */ -.doclib-toolbar-btn { - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - font-size: 11px; - padding: 5px 10px; - border-radius: 6px; - cursor: pointer; - transition: all 0.15s; - white-space: nowrap; - font-family: inherit; - display: inline-flex; - align-items: center; - gap: 3px; -} -.doclib-toolbar-btn:hover { - color: var(--fg); - border-color: var(--fg); -} -.doclib-toolbar-btn.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -.doclib-toolbar-btn.danger { - color: var(--color-error, #e55); -} -.doclib-toolbar-btn.danger:hover:not(:disabled) { - border-color: var(--color-error, #e55); -} -.doclib-toolbar-btn:disabled { - opacity: 0.4; - cursor: default; -} - -/* Bulk action bar */ -.doclib-bulk-bar { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--red) 5%, transparent); - font-size: 10px; - margin-bottom: 8px; -} -.doclib-bulk-bar.hidden { - display: none; -} -.doclib-bulk-check-all { - display: flex; - align-items: center; - gap: 3px; - cursor: pointer; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-size: 10px; -} -#doclib-bulk-bar .memory-toolbar-btn { - position: relative; - top: -3px; -} - -/* Custom checkboxes */ -.doclib-select-cb, -.doclib-bulk-check-all input { - -webkit-appearance: none; - appearance: none; - width: 13px; - height: 13px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - cursor: pointer; - flex-shrink: 0; - margin: 0; - position: relative; - transition: all 0.15s; -} -.doclib-select-cb:hover, -.doclib-bulk-check-all input:hover { - border-color: var(--red); -} -.doclib-select-cb:checked, -.doclib-bulk-check-all input:checked { - background: var(--red); - border-color: var(--red); -} -.doclib-select-cb:checked::after, -.doclib-bulk-check-all input:checked::after { - content: ''; - position: absolute; - left: 3px; - top: 0px; - width: 4px; - height: 8px; - border: solid var(--bg); - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); -} - - -/* + button outside scroll area (when doc is on right side) */ - -@media (max-width: 600px) { - .doclib-toolbar { - flex-direction: column; - } - .doclib-card-session, - .doclib-card-time { - display: none; - } - .doclib-card-preview { - max-height: 40vh; - } -} - -/* Collapse card tags before they compete with attachment/favorite controls. */ -.email-card-tags .email-tags-more { - color: var(--email-tags-more-color, var(--fg)); - background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent); -} -.email-tags-more-single, -[data-email-tags-count-mini] { display: none; } -.email-tags-more-single svg { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag, -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra { - display: none; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single, -.email-card-tags.email-tags-expanded .email-tags-more-single { - display: inline-flex; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini], -.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; } - -/* Keep the reader actions in drafting order. */ -.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; } -.email-reader-actions-inline > [data-act="reply"] { order: 2; } -.email-reader-actions-inline > [data-act="reply-all"] { order: 3; } -.email-reader-actions-inline > [data-act="forward"] { order: 4; } -.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed { - display: none !important; -} - -/* ── Archive browser ── */ -.archive-list { - grid-template-columns: 1fr !important; - gap: 4px !important; -} -.archive-row { - flex-direction: row !important; -} -.archive-row .doclib-card-header { - flex: 1; - padding: 8px 10px; -} -.archive-row .doclib-card-title { - flex: 1; - min-width: 0; -} -/* Archive column layout */ -.archive-header { - display: flex; - align-items: center; - padding: 4px 10px; - gap: 6px; - font-size: 9px; - font-weight: 600; - opacity: 0.35; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.archive-col-title { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: -4px; -} -.archive-col-msgs { - width: 40px; - flex-shrink: 0; - text-align: left; - margin-left: -8px; -} -.archive-col-model { - width: 90px; - flex-shrink: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.archive-col-time { - width: 55px; - flex-shrink: 0; - text-align: right; -} -.archive-col-menu { - width: 24px; - flex-shrink: 0; -} -.archive-menu-btn { - background: transparent; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - padding: 4px; - line-height: 0; - border-radius: 4px; - transition: background .15s, opacity .15s; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - margin-left: 4px; - vertical-align: middle; - height: 100%; -} -.archive-menu-btn svg { - display: block; - margin-top: -6px; -} -.archive-menu-btn:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - opacity: 1; -} - -/* ── Gallery (image library) ── */ - -.gallery-modal-content { - width: min(820px, 94vw); - max-height: 92vh; - font-size: 12px; -} -/* Keep the Gallery frame anchored while tabs or bulk-selection controls - change the content height. Centering the frame made every shorter tab move - the whole window vertically. */ -@media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal, - #cookbook-modal { - align-items: flex-start; - padding-top: 4vh; - box-sizing: border-box; - } -} -/* While the Edit tab is active the editor needs a *definite* height so - the right-side tool panel (`.ge-right-panel { overflow-y:auto }`) - scrolls INTERNALLY. Without this the modal only has `max-height`, so - `.gallery-editor { height:100% }` can't resolve — the editor grows to - its full content height, overflows the modal body, and the modal-body - scrollbar that appears can't be wheel-scrolled because the editor's - `overscroll-behavior:contain` blocks the chain. Pinning a real height - here makes the panel bounded and scrollable as designed. Scoped to - the editor view (matched via the container's inline `display:flex`) - so the Photos/Albums views keep sizing to their content. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) { - height: 92vh; -} -/* The editor tab owns the modal body's scroll context. Keep the landing - chooser and the mounted editor inside the available sheet height so a long - saved-project list cannot push the launch controls below the viewport. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body { - display: flex; - min-height: 0; - overflow: hidden; -} -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container { - min-height: 0; - overflow: hidden; -} -/* Photo-detail view sizing (issue #314). - The detail view is rendered as a `position:absolute; inset:0` overlay - *inside* `.gallery-images-container`, painted over the photo grid. Because - it's absolutely positioned it can't contribute to the container's height — - the container (and therefore the overlay's `inset:0` box) collapses to the - height of the grid sitting behind it. When the library only has a few - photos that grid is short, so the detail view is crushed: the image is - clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a - tiny, internally-scrolling strip. (With a large library the grid is tall, - which is why it looked fine in the demo video but cramped for users with - few photos.) - Fix: when the detail view is open, hide the grid-view siblings and drop the - overlay into normal flow. The container — and the window, up to its 92vh - max-height — then sizes to the detail's own content (image + metadata), so - nothing is clipped or squeezed regardless of how many photos exist. Scoped - via the detail element's inline `display:flex` so the grid / albums views - keep sizing to their own content. Works on both desktop and the mobile - full-screen sheet. */ -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) { - display: none !important; -} -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail { - position: static; -} -/* Containing block for the photo-detail overlay — keeps it inside the body - so it sits below the modal header and the tab strip instead of covering them. */ -.gallery-images-container { - position: relative; -} -.gallery-modal-content .modal-header h4 { - font-size: 1rem; -} -.gallery-stats { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-bottom: 4px; -} -.gallery-toolbar { - display: flex; - gap: 6px; - margin-bottom: 4px; - align-items: center; -} -.gallery-toolbar-break { display: none; } -/* Keep the hint anchored while independently seating the input in the row. */ -.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; } -.gallery-search { - position: relative; - top: 4px; - flex: 1; - width: 100%; - padding: 4px 8px; - padding-right: 78px; /* room for the enter hint */ - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - font: inherit; - font-size: 12px; - outline: none; -} -.gallery-search-enter-hint { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - font-weight: 600; - color: var(--accent, var(--red)); - pointer-events: none; - opacity: 0; - transition: opacity 0.12s; - white-space: nowrap; -} -/* Show the hint only once the user has typed something (placeholder gone). */ -.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; } -.gallery-search:focus { - border-color: var(--red); -} -.gallery-model-filter, -.gallery-sort { - padding: 4px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - position: relative; - top: 4px; -} -.gallery-selection-bar { - margin-bottom: 4px; - position: relative; - top: 2px; - padding-right: 18px; -} -.gallery-tag-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 4px; -} -.gallery-chip { - height: 28px; - box-sizing: border-box; - padding: 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - border-radius: 14px; - font-size: 12px; - line-height: 1; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: border-color 0.15s, background 0.15s; -} -.gallery-chip:hover { - border-color: var(--red); -} -.gallery-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Album chips row */ -.gallery-album-chips { - display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px; -} -.gallery-chip-fav { color: var(--red); } -.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); } -.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; } -/* Active-album indicator — appears when the Photos grid is filtered to one album. */ -.gallery-chip-active-album, -.gallery-active-filter-pill { - display: inline-flex; align-items: center; gap: 4px; - background: color-mix(in srgb, var(--red) 14%, transparent); - border-color: color-mix(in srgb, var(--red) 45%, transparent); - position: relative; - top: 6px; -} -.gallery-active-filter-pill { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - color: color-mix(in srgb, var(--fg) 86%, transparent); -} -.gallery-clear-all-filters { - height: 28px; - border-style: dashed; - opacity: 0.72; -} -.gallery-clear-all-filters:hover { - opacity: 1; - color: var(--red); -} -.gallery-chip-clear { - background: none; - border: none; - color: inherit; - opacity: 0.6; - cursor: pointer; - padding: 0; - margin-left: 1px; - width: 12px; - height: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 12px; - line-height: 1; - position: relative; - top: -4px; -} -.gallery-chip-clear:hover { opacity: 1; } - - - -/* Favorite button on card */ -/* Video cards: fills the same slot as , plus a small play badge - so it's clear the thumbnail represents a video. */ -.gallery-card video { - width: 100%; - aspect-ratio: 1 / 1; - object-fit: cover; - background: #000; - display: block; -} -.gallery-card-play { - position: absolute; - left: 50%; top: 50%; - transform: translate(-50%, -50%); - width: 36px; height: 36px; - display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.55); - color: #fff; - border-radius: 50%; - pointer-events: none; - z-index: 1; -} -.gallery-card-play svg { margin-left: 2px; } - -.gallery-fav-btn { - position: absolute; top: 0px; right: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; font-size: 14px; - color: rgba(255,255,255,0.6); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s; - padding: 0; -} -.gallery-fav-btn.gallery-fav-active { opacity: 1; color: var(--red); } - -.gallery-dl-btn { - position: absolute; top: 0px; left: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; - color: rgba(255,255,255,0.75); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s, background 0.15s; - padding: 0; -} -.gallery-dl-btn:hover { background: rgba(0,0,0,0.7); color: #fff; } -/* In select mode the per-thumbnail hover buttons (favorite + download) just - get in the way of picking — hide them so the card is a clean select target. - `body.gallery-selecting` is the authoritative signal (one class for the - whole grid) — the per-card .gallery-card-selectable is kept as a backup - in case the body class is missed. */ -body.gallery-selecting .gallery-fav-btn, -body.gallery-selecting .gallery-dl-btn, -.gallery-card-selectable .gallery-fav-btn, -.gallery-card-selectable .gallery-dl-btn { - display: none !important; -} - -/* AI tag chips in detail view */ -.gallery-ai-tags { display: flex; gap: 4px; flex-wrap: wrap; } -.gallery-ai-chip { - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); - border-radius: 10px; padding: 3px 8px; font-size: 10px; - color: var(--fg); opacity: 0.8; - font: inherit; font-size: 10px; - cursor: pointer; - transition: background 0.12s, opacity 0.12s, border-color 0.12s; -} -.gallery-ai-chip:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); -} -/* AI-generated tags: muted neutral with a tiny sparkle marker so the - user can tell at a glance these came from the model, not them. */ -.gallery-aitag-chip { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-aitag-chip:hover { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.gallery-aitag-mark { - font-size: 8px; - opacity: 0.55; - color: var(--accent, var(--red)); -} -/* User-applied tags keep the accent-colored chip — they're the personal/ - curated tags, so they get the strongest visual weight. */ -.gallery-user-chip { - font-weight: 600; -} -/* × to remove a user tag (appears inside the chip). */ -.gallery-tag-x { - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 5px; - width: 13px; - height: 13px; - border-radius: 50%; - font-size: 12px; - line-height: 1; - opacity: 0.55; - transition: opacity 0.12s, background 0.12s, color 0.12s; -} -.gallery-tag-x:hover { - opacity: 1; - background: color-mix(in srgb, var(--red, #ff5555) 22%, transparent); - color: var(--red, #ff5555); -} - -.gallery-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - align-content: start; - gap: 8px; - max-height: calc(92vh - 165px); - overflow-y: auto; - padding: 2px; - transition: border-color 0.15s, background 0.15s; -} -.gallery-grid.gallery-dragover { - border: 2px dashed var(--red); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.gallery-card { - position: relative; - aspect-ratio: 1; - border-radius: 6px; - overflow: hidden; - border: 1px solid var(--border); - cursor: pointer; - transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s; -} -.gallery-card.gallery-card-focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 36%, transparent); -} -/* Upload-affordance tile pinned to the top-left of the Photos grid. Matches - the Upload album tile in the Albums tab — dashed border, centered icon. */ -.gallery-card-upload { - border-style: dashed; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - opacity: 0.75; - transition: opacity 0.12s, border-color 0.15s, transform 0.15s; -} -/* The "Start AI tag" button turns into a Cancel control during a tag run. */ -.gallery-tag-cancelling { - color: var(--red) !important; - border-color: color-mix(in srgb, var(--red) 45%, var(--border)) !important; -} -/* Skeleton/shimmer placeholder tiles shown while the first page of photos loads. */ -.gallery-card-skeleton { - cursor: default; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.gallery-card-skeleton:hover { transform: none; box-shadow: none; border-color: var(--border); } -/* Chat attachment image: shimmer skeleton + centered whirlpool shown while a - sent photo uploads / its thumbnail loads (see chatRenderer buildAttachCards). */ -.attach-image-skeleton { - position: relative; - width: 160px; height: 120px; - border-radius: 6px; - display: flex; align-items: center; justify-content: center; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.attach-image-skeleton > .spinner-whirlpool { margin: 0 !important; } - -/* Corner "Aa" button on a chat photo thumbnail — opens the vision/OCR editor - so the user can correct what the vision model fed to the LLM. */ -.attach-image-preview { position: relative; } -.attach-vision-model, -.attach-image-name { - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.attach-vision-model { - font-size: 10px; - color: var(--accent, var(--red)); - opacity: 0.85; - margin-top: 3px; -} -.attach-image-name { - font-size: 10px; - opacity: 0.5; - margin-top: 1px; -} -.attach-ocr-btn { - position: absolute; top: 4px; right: 4px; - z-index: 5; /* sit above msg-action overlays so the click lands here */ - height: 22px; - background: rgba(0,0,0,0.55); color: #fff; - border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; - padding: 0 8px 0 6px; cursor: pointer; - display: inline-flex; align-items: center; justify-content: center; gap: 5px; - font-size: 11px; font-weight: 500; line-height: 1; - opacity: 0.75; transition: opacity 0.15s; -} -.attach-ocr-btn:hover { opacity: 1; } -.attach-ocr-btn svg { display: block; } -/* On mobile keep just the (larger) edit icon — the "Caption" label crowds - the corner of the small chat photo. */ -@media (max-width: 768px) { - .attach-ocr-btn .attach-ocr-label { display: none; } - .attach-ocr-btn { - width: 28px; height: 28px; - padding: 0; - } - .attach-ocr-btn svg { width: 16px; height: 16px; } -} - -/* Vision/OCR text editor modal — opened from the "Aa" button above. */ -.vision-editor-overlay { - position: fixed; inset: 0; z-index: 9999; - background: rgba(0,0,0,0.55); - display: flex; align-items: center; justify-content: center; - padding: 16px; -} -.vision-editor-panel { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 14px 16px; - width: min(560px, 92vw); - max-height: 88vh; - display: flex; flex-direction: column; gap: 8px; - box-shadow: 0 14px 40px rgba(0,0,0,0.4); -} -.vision-editor-title { - font-size: 13px; font-weight: 600; color: var(--fg); - display: flex; align-items: center; gap: 6px; -} -.vision-editor-desc { font-size: 11px; opacity: 0.6; line-height: 1.4; } -.vision-editor-text { - width: 100%; min-height: 180px; - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 8px 10px; - font-family: inherit; font-size: 12px; line-height: 1.5; - resize: vertical; outline: none; -} -.vision-editor-text:focus { border-color: var(--accent-primary, var(--red)); } -.vision-editor-hint { font-size: 10.5px; opacity: 0.55; line-height: 1.4; } -.vision-editor-actions { - display: flex; gap: 8px; justify-content: flex-end; - margin-top: 4px; -} -.vision-editor-btn { - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; - transition: border-color 0.15s, background 0.15s; - display: inline-flex; align-items: center; gap: 5px; -} -.vision-editor-btn svg { display: block; } -/* Optical nudge: the button text sits 1px higher than the SVG glyphs - alongside it. Drop the text down 1px so they line up. */ -.vision-editor-btn .vision-btn-label { position: relative; top: 1px; } -.vision-editor-btn:hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); } -.vision-editor-btn-primary { - background: var(--accent-primary, var(--red)); - color: #fff; border-color: transparent; -} -.vision-editor-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } - -/* Quick full-size lightbox shown when the user taps a chat photo thumbnail. */ -.attach-lightbox { - position: fixed; inset: 0; z-index: 9998; - background: rgba(0, 0, 0, 0.85); - display: flex; align-items: center; justify-content: center; - padding: 16px; cursor: zoom-out; - animation: attach-lightbox-fade 0.12s ease-out; -} -.attach-lightbox img { - max-width: 100%; max-height: 100%; - border-radius: 6px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); - object-fit: contain; -} -.attach-lightbox-err { - position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); - background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 10px; font-size: 12px; -} -@keyframes attach-lightbox-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -.gallery-card-upload:hover { - opacity: 1; - border-color: var(--red); - transform: translateY(-1px); -} -.gallery-card-upload-inner { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 6px; - color: var(--fg); -} -.gallery-card-upload-label { - font-size: 12px; - font-weight: 500; - opacity: 0.8; -} -.gallery-card:hover { - border-color: var(--red); - box-shadow: 0 2px 8px color-mix(in srgb, var(--red) 15%, transparent); - transform: translateY(-1px); -} -.gallery-card-overlay { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 8px; - background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 34%, rgba(0,0,0,0.22)); - color: #fff; - opacity: 0; - pointer-events: none; - transition: opacity 0.15s; -} -.gallery-card-badges { - display: flex; - gap: 4px; - flex-wrap: wrap; - align-items: flex-end; - justify-content: flex-end; - margin-top: auto; - margin-bottom: 34px; -} -.gallery-card-badge { - padding: 2px 6px; - border-radius: 999px; - background: rgba(0, 0, 0, 0.55); - border: 1px solid rgba(255, 255, 255, 0.14); - color: rgba(255, 255, 255, 0.88); - font-size: 9px; - font-weight: 600; - line-height: 1.1; -} -.gallery-card-badge-fav { - color: #ff5f6f; -} -.gallery-select-dot { - position: absolute; top: 6px; left: 6px; z-index: 2; - display: block; - width: 10px; height: 10px; border-radius: 50%; cursor: pointer; - background: color-mix(in srgb, var(--fg) 30%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 50%, transparent); - transition: background 0.15s; -} -.gallery-select-dot.selected { background: var(--red); border-color: var(--red); } -.gallery-select-dot:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -.gallery-select-btn { - padding: 9px 10px 11px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; transition: all 0.15s; - margin-top: -4px !important; -} -.gallery-select-btn:hover { opacity: 1; } -/* Match the library Select toggle's red tint (.memory-toolbar-btn.active). - Bumped contrast so the toggled state actually reads on mobile — the prior - 15% red on transparent was nearly invisible against the page background. */ -.gallery-select-btn.active { - background: color-mix(in srgb, var(--red) 28%, transparent); - color: var(--red); - border-color: var(--red); - font-weight: 600; - opacity: 1; -} -/* Toolbar action buttons sit 2px lower than the generic select-btn so - they baseline-align with the toolbar's selects/inputs instead of - floating above them. */ -.gallery-toolbar-action { - margin-top: 0 !important; - display: inline-flex; - align-items: center; - gap: 2px; -} -.gallery-bulk-bar { - display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 12px; - justify-content: flex-end; -} -.gallery-bulk-bar.hidden { display: none; } -.gallery-bulk-all { - display: inline-flex; align-items: center; gap: 4px; - font-size: 11px; opacity: 0.75; cursor: pointer; margin-right: auto; -} -.gallery-bulk-all input { accent-color: var(--accent, var(--red)); cursor: pointer; } -.gallery-bulk-delete { - padding: 3px 10px; background: transparent; color: var(--red); - border: 1px solid var(--red); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; -} -.gallery-bulk-delete:hover { background: var(--red); color: #fff; } -.gallery-bulk-cancel { - padding: 3px 10px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; -} -.gallery-bulk-cancel:hover { opacity: 1; } -.gallery-card img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} -.gallery-card-info { - position: absolute; - bottom: 0; - left: 0; - right: 0; - padding: 6px 8px; - background: linear-gradient(transparent, rgba(0,0,0,0.75)); - color: #fff; -} -.gallery-card-prompt { - font-size: 10px; - line-height: 1.3; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.gallery-card-meta { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - font-size: 9px; - opacity: 0.8; -} -.gallery-card-model { - padding: 1px 5px; - border-radius: 6px; - background: rgba(255,255,255,0.15); -} -.gallery-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -.gallery-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: none; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-load-more:hover { - border-color: var(--red); - color: var(--red); -} - -/* Gallery detail overlay */ -.gallery-detail { - position: absolute; - inset: 0; - background: var(--panel); - z-index: 10; - display: flex; - flex-direction: column; - overflow-y: auto; -} -.gallery-detail-header { - display: flex; - align-items: center; - gap: 6px; - padding: 2px 8px 4px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - /* Pin to top so a tall (portrait) photo can't cover the Back / ⋮ - menu when the detail body grows past the visible area. */ - position: sticky; - top: 0; - background: var(--panel); - /* Above the image's rotate/nav buttons (z-index:3) so they scroll cleanly - behind the header instead of colliding with it. */ - z-index: 5; - min-height: 0; -} -.gallery-detail-title { - max-width: min(360px, 40vw); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 12px; - font-weight: 600; - opacity: 0.82; - position: relative; - top: 1px; -} -.gallery-detail-back { - background: none; - border: none; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 12px; - padding: 4px 8px; - border-radius: 4px; - position: relative; - top: -2px; -} -/* Slightly larger header icons (back/edit/heart/⋮) — override the inline 14px. */ -.gallery-detail-header svg { width: 16px; height: 16px; } -.gallery-detail-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.gallery-detail-action { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - padding: 3px 8px; - border-radius: 4px; - transition: border-color 0.15s; -} -.gallery-detail-action:hover { - border-color: var(--fg); -} -.gallery-detail-action.danger { - color: var(--color-error, #e55); -} -/* Overflow menu — replaces the seven individual action buttons that used - to crowd the right side of the detail header. */ -.gallery-detail-menu-wrap { position: relative; } -.gallery-detail-menu-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 26px; - padding: 0; - position: relative; - top: -2px; -} -.gallery-detail-menu { - position: absolute; - top: calc(100% + 4px); - right: 0; - min-width: 150px; - padding: 3px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 12; - display: flex; - flex-direction: column; - gap: 0; -} -.gallery-detail-menu[hidden] { display: none; } -.gallery-detail-menu .dropdown-item-compact { - width: 100%; - background: none; - border: none; - text-align: left; - font: inherit; - /* Tighter than the global default — the photo-detail menu has 8 - items so every pixel of vertical padding adds up. */ - padding: 4px 8px; - font-size: 11px; - gap: 8px; - line-height: 1.2; -} -.gallery-detail-menu .dropdown-item-compact .dropdown-icon, -.gallery-detail-menu .dropdown-item-compact .dropdown-icon svg { - width: 12px; - height: 12px; -} -.gallery-detail-body { - display: flex; - gap: 16px; - padding: 12px; - flex: 1; - min-height: 0; - overflow: hidden; -} -.gallery-detail-image { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - min-width: 0; - min-height: 0; - height: 100%; - padding-inline: 56px; - box-sizing: border-box; - overflow: hidden; -} -/* Shrink-wraps the actual image so overlay children (heart, face boxes) - anchor to the IMAGE bounds, not the wider letterboxed wrapper. */ -.gallery-detail-img-frame { - position: relative; - display: inline-flex; - max-width: 100%; - max-height: 100%; -} -.gallery-detail-nav { - position: absolute; - top: 50%; - transform: translateY(-50%); - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-nav:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-nav-prev { left: 8px; } -.gallery-detail-nav-next { right: 8px; } - -/* Rotate buttons — same pattern as the prev/next arrows but pinned to the - top corners. Hover-revealed so they don't clutter the image. */ -.gallery-detail-rotate { - position: absolute; - top: 8px; - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-rotate:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-rotate-ccw { left: 8px; } -.gallery-detail-rotate-cw { right: 8px; } -/* Inline heart that lives next to the "Date" label in the sidebar. - Tiny outline icon by default; fills red when favorited. */ -.gallery-detail-date-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; -} -.gallery-detail-fav-inline { - background: none; - border: none; - padding: 0; - /* Nudge up 8px so the heart sits visually above the Date label - baseline rather than centred on it. */ - margin-top: -8px; - border-radius: 4px; - color: var(--fg-muted); - opacity: 0.6; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; - transition: opacity 0.15s, color 0.15s; -} -/* Shrink the SVG so the heart matches the height of the small uppercase - Date label sitting next to it. */ -.gallery-detail-fav-inline svg { - width: 12px; - height: 12px; - display: block; -} -.gallery-detail-fav-inline:hover { - opacity: 1; -} -.gallery-detail-fav-inline.active { - opacity: 1; - color: var(--red); -} -.gallery-detail-sidebar { - width: 290px; - flex-shrink: 0; - /* Vertical scroll for long metadata, but never horizontal — overflow:auto - alone would let a wide child (e.g. long album name in the select) push - the sidebar into horizontal-scroll mode. */ - overflow-x: hidden; - overflow-y: auto; - min-width: 0; -} -.gallery-detail-inspector-head { - padding-bottom: 10px; - margin-bottom: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.gallery-detail-eyebrow { - font-size: 9px; - font-weight: 700; - color: var(--accent, var(--red)); - text-transform: uppercase; - letter-spacing: 0.08em; - margin-bottom: 3px; -} -.gallery-detail-inspector-title { - font-size: 14px; - font-weight: 650; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-meta-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 6px; - margin: 4px 0 12px; -} -.gallery-detail-meta-card { - min-width: 0; - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-radius: 7px; - padding: 7px 8px; -} -.gallery-detail-meta-card span { - display: block; - font-size: 9px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 48%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 3px; -} -.gallery-detail-meta-card strong { - display: block; - min-width: 0; - font-size: 11px; - font-weight: 600; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-inline-link { - appearance: none; - background: none; - border: none; - color: var(--accent, var(--red)); - padding: 0; - font: inherit; - font-weight: inherit; - cursor: pointer; - text-align: left; -} -.gallery-detail-mini-actions { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 7px; -} -.gallery-detail-mini-btn { - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 8px; - font: inherit; - font-size: 10.5px; - cursor: pointer; - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-detail-mini-btn:hover { - border-color: color-mix(in srgb, var(--fg) 42%, var(--border)); -} -/* Constrain any child that could otherwise stretch the sidebar wider than - its declared width (selects with long option text, long URLs, etc.). */ -.gallery-detail-sidebar select, -.gallery-detail-sidebar input, -.gallery-detail-sidebar .gallery-detail-prompt { - max-width: 100%; - box-sizing: border-box; -} -.gallery-date-rel { - opacity: 0.55; - margin-left: 4px; - font-size: 11px; - font-style: italic; -} -.gallery-detail-section label { - display: block; - font-size: 10px; - font-weight: 600; - opacity: 0.6; - margin-bottom: 3px; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.gallery-detail-section div { - font-size: 12px; - word-break: break-word; -} -.gallery-detail-prompt { - white-space: pre-wrap; - line-height: 1.4; -} -.gallery-tag-input, -.gallery-detail-name-input { - width: 100%; - padding: 5px 8px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - font: inherit; - font-size: 11px; - margin-bottom: 4px; -} -.gallery-tag-input:focus, -.gallery-detail-name-input:focus { - border-color: var(--red); - outline: none; -} -/* ↵ enter hint inside the Add-a-tag field (accent), replacing the "press Enter" - placeholder text. */ -.gallery-tag-input-wrap { position: relative; } -.gallery-tag-enter-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; /* the input has 4px bottom margin; center on the box */ - color: var(--accent, var(--red)); - font-size: 13px; - line-height: 1; - pointer-events: none; - opacity: 0.8; -} -.gallery-detail-name-input { font-size: 13px; font-weight: 500; } -/* ↵ enter hint on the image name field — accent, shown while editing to signal - that Enter saves. */ -.gallery-name-wrap { position: relative; } -.gallery-name-enter { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; - color: var(--accent, var(--red)); - opacity: 0.85; - pointer-events: none; - display: none; -} -.gallery-tag-save { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 10px; - padding: 3px 10px; - border-radius: 4px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-tag-save:hover { - border-color: var(--red); - color: var(--red); -} - -@media (max-width: 600px) { - .gallery-grid { - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - } - /* Single-row toolbar on mobile: search + Sources + Recent (sort) all share - one row. The search bar shrinks so the dropdowns fit beside it. */ - .gallery-toolbar { - flex-wrap: nowrap; - gap: 4px; - } - .gallery-search-wrap { - flex: 1 1 0; - min-width: 0; - } - .gallery-search { - /* Drop the desktop right-padding reserved for the "enter to tag" hint — - hide the hint on mobile (below) so the input can use its full width. */ - padding-right: 8px; - } - .gallery-search-enter-hint { display: none; } - /* Disable the row break so the filters stay inline with search. */ - .gallery-toolbar-break { display: none; } - .gallery-model-filter, - .gallery-sort { - flex: 0 0 auto; - /* Cap each dropdown so search keeps a usable amount of width. */ - max-width: 100px; - font-size: 11px; - padding: 4px 6px; - /* Keep the same vertical alignment as the desktop toolbar. */ - position: relative; - top: 4px; - } - .gallery-select-btn { - padding: 6px 8px 8px; - font-size: 9px; - flex-shrink: 0; - /* Push Select to the far right of the single-row toolbar, regardless - of its DOM position (sits between search and the dropdowns). */ - order: 99; - margin-left: auto; - } - /* Tabs scroll horizontally rather than wrapping when narrow. */ - .gallery-tabs { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .gallery-tabs::-webkit-scrollbar { display: none; } - .gallery-tab { flex-shrink: 0; } - /* Album chips also scroll horizontally so they don't stack. */ - .gallery-album-chips { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .gallery-album-chips::-webkit-scrollbar { display: none; } - .gallery-chip { flex-shrink: 0; } - /* Tasks filter chips — same horizontal-swipe behaviour as the gallery - albums / library chips on mobile: one row, slide-to-see-more. */ - .tasks-activity-filters { - overflow-x: auto; - flex-wrap: nowrap !important; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .tasks-activity-filters::-webkit-scrollbar { display: none; } - .tasks-activity-filters > * { flex-shrink: 0; } - /* Detail view */ - .gallery-detail-body { - flex-direction: column; - overflow: visible; - } - .gallery-detail-image { - flex: 0 0 auto; - width: 100%; - height: clamp(280px, 58vh, 520px); - padding-inline: 48px; - } - .gallery-detail-sidebar { - width: 100%; - } - .gallery-detail-title { - display: none; - } - .gallery-detail-meta-grid { - grid-template-columns: 1fr; - } - /* Album grid: smaller tile minimum on mobile. */ - .gallery-albums-grid { - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 8px; - } -} - - -/* ── Scoreboard table ── */ -.scoreboard-table { - width: 100%; - border-collapse: collapse; - font-size: 0.88em; -} -.scoreboard-table th { - text-align: left; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 55%, transparent); - text-transform: uppercase; - letter-spacing: 0.5px; -} -.scoreboard-table th:not(:first-child) { - text-align: center; -} -.scoreboard-table td { - padding: 5px 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; -} -.scoreboard-table td.scoreboard-model { - text-align: left; - font-weight: 500; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.scoreboard-table td.scoreboard-pct { - font-weight: 600; - color: var(--red); -} -.scoreboard-table tbody tr:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} - -#scoreboard-overlay svg { - color: var(--accent, var(--red)); -} - -/* ── Compare search results ── */ -.compare-search-results { - display: flex; - flex-direction: column; - gap: 2px; -} -.compare-search-result { - padding: 6px 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); -} -.compare-search-result:last-child { - border-bottom: none; -} -.search-result-title { - color: var(--color-accent); - text-decoration: none; - font-weight: 500; - font-size: 0.9em; - display: block; - line-height: 1.3; -} -.search-result-title:hover { - color: var(--color-link-hover); - text-decoration: underline; -} -.search-result-snippet { - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.82em; - line-height: 1.4; - margin-top: 2px; -} -.search-result-url { - color: color-mix(in srgb, var(--fg) 35%, transparent); - font-size: 0.75em; - margin-top: 3px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - - -/* ── Cookbook Tool ── */ - -/* ── Cookbook ── */ -.cookbook-serve-preset { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - margin-bottom: 3px; - cursor: pointer; - transition: background 0.15s; -} -.cookbook-serve-preset:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cookbook-serve-preset-name { - font-size: 11px; - font-weight: 500; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-serve-preset-meta { - font-size: 9px; - font-family: 'Fira Code', monospace; - color: var(--fg-muted); - opacity: 0.5; -} -.cookbook-serve-preset-launch { - background: none; - border: none; - color: #50fa7b; - cursor: pointer; - font-size: 10px; - opacity: 0.6; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-launch:hover { opacity: 1; } -.cookbook-serve-preset-rm { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 10px; - opacity: 0.3; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-rm:hover { opacity: 0.8; color: var(--red); } -.cookbook-body { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - flex: 1; - min-height: 0; - scrollbar-width: thin; -} -#cookbook-modal .modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -#cookbook-modal .modal-header { - flex: 0 0 auto; -} -#cookbook-modal .cookbook-body { - min-height: 0; - overflow-y: auto; - overflow-x: hidden; -} -#cookbook-modal .cookbook-group { - min-height: 0; -} -#cookbook-modal .cookbook-group > .admin-card { - min-height: 0; - /* Let .cookbook-body be the SINGLE scroll surface. Nesting another - overflow:auto here trapped the wheel inside the cached-list when a - serve panel expanded — the page couldn't scroll past the panel's - bottom (Launch button got hidden). */ - overflow: visible !important; -} -#cookbook-modal .cookbook-section-body { - min-height: 0; -} -.cookbook-group { - display: flex; - flex-direction: column; - gap: 8px; - flex: 1; - min-height: 0; -} - -/* Cards — match admin-card */ -.cookbook-card { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-card-header { - display: flex; - align-items: center; - justify-content: space-between; -} -.cookbook-card-header-actions { display: flex; gap: 4px; } -.cookbook-card-title { font-size: 13px; font-weight: 600; } -.cookbook-card-desc { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} - -/* Fields grid */ -.cookbook-fields { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 6px; - margin-top: 4px; -} -.cookbook-field-label { - display: flex; - flex-direction: column; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - gap: 3px; -} - -/* Inputs — match admin-add-form input */ -.cookbook-field-input { - padding: 5px 8px; - font-size: 12px; - font-family: inherit; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - outline: none; -} -.cookbook-field-input:focus { border-color: var(--red); } -#hwfit-dl-server, #hwfit-usecase, #hwfit-server-select, #hwfit-cache-server, #serve-sort { height: 28px; width: 88px; } -/* Serve tab — match Documents sizing, but leave room for the active "Cancel" - label and center it vertically so the descenders don't clip. */ -#hwfit-cache-select { - min-width: 58px; - height: 28px; - position: relative; - top: -3px; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; -} - -/* Command preview */ -.cookbook-cmd-preview { - margin: 4px 0 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - color: var(--fg); -} - -/* Buttons — match admin-btn-sm */ -.cookbook-actions { display: flex; gap: 6px; margin-top: 2px; } -.cookbook-btn { - padding: 4px 10px; - min-width: 54px; - text-align: center; - font-size: 11px; - font-family: inherit; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - cursor: pointer; - transition: all 0.15s; -} -.cookbook-btn:hover { - background: var(--border); - border-color: var(--accent); -} -.cookbook-btn:disabled { opacity: 0.5; cursor: not-allowed; } -.cookbook-run-btn { - background: var(--accent); - color: var(--panel); - border-color: var(--accent); - font-weight: 600; -} -.cookbook-run-btn:hover { opacity: 0.85; } -.cookbook-stop-btn { - background: var(--color-error); - border-color: var(--color-error); -} - -/* Output */ -.cookbook-output-wrap { - position: relative; - margin: 0; -} -.cookbook-output-kill { - position: absolute; - top: 4px; - right: 34px; - width: 22px; - height: 22px; - padding: 0; - background: none; - border: 1px solid transparent; - border-radius: 4px; - color: var(--fg-muted); - font-size: 16px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity .15s, color .15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-output-kill:hover { color: var(--color-error, var(--warn)); border-color: var(--border); } -.cookbook-output-wrap .copy-code { - position: absolute; - top: 6px; - right: 6px; -} -.cookbook-output-wrap:hover .copy-code { opacity: 0.7; } -.cookbook-output-pre { - margin: 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - max-height: 180px; - overflow-y: auto; -} -.cookbook-output-error { color: var(--color-error); } - -/* Downloaded/cached models */ -.hwfit-cached-item { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 6px; - padding: 6px 8px; - margin: 2px 0; - border-radius: 6px; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 3%, transparent); - cursor: default; - font-size: 12px; -} -.hwfit-cached-item .hwfit-serve-panel { - flex-basis: 100%; - margin-top: 4px; -} -.hwfit-cached-item:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); -} -.hwfit-cached-header { - font-size: 10px; - opacity: 0.4; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - border: none !important; - background: none !important; - padding: 2px 8px; - cursor: default !important; -} -.hwfit-cached-header:hover { background: none !important; } -.hwfit-cached-name { - font-weight: 600; - flex-shrink: 0; -} -.hwfit-cached-repo { - flex: 1; - color: var(--fg-muted); - font-size: 11px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-cached-size, -.hwfit-cached-files { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; -} -.hwfit-cached-serve, -.hwfit-cached-delete { - flex-shrink: 0; -} -.hwfit-cached-delete { - opacity: 0.4; - font-size: 10px; -} -.hwfit-cached-delete:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} -.hwfit-cached-badge { - font-size: 9px; - padding: 1px 6px; - border-radius: 3px; - font-weight: 600; - white-space: nowrap; -} -.hwfit-cached-ready { - background: color-mix(in srgb, var(--color-success, #4caf50) 20%, transparent); - color: var(--color-success, #4caf50); -} -.hwfit-cached-dl { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 20%, transparent); - color: var(--color-warning, #f0ad4e); -} - -/* Cookbook notification dot */ -#tool-cookbook-btn { - position: relative; -} -#rail-cookbook { - position: relative; -} -.cookbook-open-loading { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - flex-shrink: 0; - margin-left: 2px; - color: var(--accent, var(--red)); - opacity: 0.9; -} -.cookbook-open-loading .ai-spinner, -.cookbook-open-loading .ai-spinner-whirlpool { - display: inline-flex !important; - width: 14px; - height: 14px; - margin: 0 !important; - gap: 0 !important; -} -.cookbook-open-loading canvas { - display: block; -} -.cookbook-section-loading-wp, -.cookbook-section-loading-wp canvas { - width: 22px !important; - height: 22px !important; - display: block !important; -} -#rail-cookbook .cookbook-open-loading { - position: absolute; - right: 2px; - top: -2px; - width: 12px; - height: 12px; - margin-left: 0; -} -/* The serve-list 'downloading' / 'download stalled' status text sits a hair - low against the rest of the meta line on mobile — nudge it up 2px. */ -@media (max-width: 768px) { - .cookbook-dl-status { - position: relative; - top: -2px; - } -} -.cookbook-notif-dot { - width: 6px; - height: 6px; - flex-shrink: 0; - border-radius: 50%; - background: var(--color-success, #4caf50); - /* Drives the breathing glow in the keyframes (matches email's dot). */ - --notif-glow: var(--color-success, #4caf50); - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -.cookbook-notif-dot.cookbook-notif-error { - background: var(--color-error, #f44); - --notif-glow: var(--color-error, #f44); - position: relative; - left: -2px; - top: -1px; -} -.rail-notify-error { color: var(--color-error, #f44) !important; } -.cookbook-tab-error-dot { - display: inline-block; width: 5px; height: 5px; border-radius: 50%; - background: var(--color-error, #f44); margin-left: 4px; vertical-align: middle; - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -@keyframes cookbook-notif-pulse { - 0%, 100% { - opacity: 1; - box-shadow: 0 0 0 0 color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 60%, transparent); - } - 50% { - opacity: 0.85; - box-shadow: 0 0 6px 2px color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 55%, transparent); - } -} -.cookbook-clear-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-clear-btn:hover { - color: var(--red) !important; - border-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 8%, transparent) !important; -} -.cookbook-bulk-bar { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - margin-bottom: 4px; - background: color-mix(in srgb, var(--red) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 6px; - font-size: 11px; -} -.cookbook-bulk-bar.hidden { display: none; } -.serve-select-cb { - width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; cursor: pointer; - background: var(--border); transition: background 0.15s; - align-self: center; - position: relative; - top: -2px; -} -#serve-bulk-bar { - position: relative; - top: -6px; -} -#serve-bulk-bar #serve-bulk-cancel { - top: -2px !important; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 28px; - height: 24px; - line-height: 1; -} -#serve-bulk-bar #serve-bulk-cancel svg { - top: 0; -} -.serve-select-cb.selected { background: var(--red); } -.serve-select-cb:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -#hwfit-cache-select.active { - background: var(--red); - color: #fff; - border-color: var(--red); -} -.cookbook-serve-dirs { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.cookbook-serve-dir-pill { - font-size: 10px; - font-family: 'Fira Code', monospace; - padding: 2px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); - color: var(--fg-muted); - letter-spacing: 0.2px; -} -/* Status pills shown inline in a Serve-tab card title (next to the model - name) when the model has a live serve / download task. Shared base - class so "running" and "downloading" sit on the same row with the - same chrome; only color varies. */ -.cookbook-serve-running-pill, -.cookbook-serve-downloading-pill { - display: inline-block; - margin-left: 6px; - padding: 1px 7px; - border-radius: 10px; - font-size: 9px; - font-weight: 500; - text-transform: lowercase; - letter-spacing: 0.3px; - vertical-align: 2px; - position: relative; - top: 1px; /* nudged down 2px from the old -1px so it sits - flush with the cap-height of the title text */ -} -.cookbook-serve-running-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); -} -.cookbook-serve-downloading-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); - opacity: 0.85; -} -.cookbook-serve-downloading-pill.is-stalled { - /* Stalled downloads stay visible but read as warning, not progress. */ - color: var(--fg-muted, #888); - background: color-mix(in srgb, var(--fg-muted, #888) 10%, transparent); - border-color: color-mix(in srgb, var(--fg-muted, #888) 30%, transparent); - opacity: 1; -} -.cookbook-serve-running-pill.is-clickable { - cursor: pointer; - transition: background 0.12s, border-color 0.12s; -} -.cookbook-serve-running-pill.is-clickable:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -.cookbook-serve-dir-edit { - font-size: 9px; - color: var(--fg-muted); - opacity: 0.4; - cursor: pointer; - text-decoration: underline; - text-underline-offset: 2px; -} -.cookbook-serve-dir-edit:hover { - opacity: 0.7; - color: var(--accent, var(--red)); -} -.cookbook-gpu-group { - display: flex; - gap: 3px; - margin-top: -4px; -} -.cookbook-gpu-btn { - width: 30px; height: 30px; - font-size: 12px; - font-family: 'Fira Code', monospace; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - color: var(--fg-muted); - cursor: pointer; - opacity: 0.4; - transition: all 0.15s; - padding: 0; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-gpu-btn:hover { - opacity: 0.7; - border-color: var(--fg-muted); -} -.cookbook-gpu-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 55%, transparent); - color: var(--fg); - font-weight: 600; -} -/* Probe annotation states (set by "Free?" button) */ -.cookbook-gpu-btn.gpu-free { - opacity: 1; - border-color: #4ade80; - color: #4ade80; -} -.cookbook-gpu-btn.gpu-busy { - opacity: 0.85; - border-color: color-mix(in srgb, var(--red) 70%, transparent); - color: var(--red); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-btn.gpu-missing { - opacity: 0.2; - text-decoration: line-through; - cursor: not-allowed; -} -/* "Clear Server" lives in the actions row next to "Probe GPUs". - Inherits .cookbook-btn sizing; this rule just adds the destructive - red tint on hover so the "this kills processes" cue stays. */ -.cookbook-gpu-clear:hover:not(:disabled) { - color: var(--red); - border-color: color-mix(in srgb, var(--red) 60%, var(--border)); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -/* GPU probe popup — per-GPU process list with kill buttons */ -.cookbook-gpu-popup { - /* Fixed positioning (relative to viewport) so we never get pulled into - a scrolling/transform stacking context from an ancestor. Z-index has - to clear the cookbook modal (260) and the rest of the high-z UI - layers (themed-confirm and various overlays sit around 9000-10000). */ - position: fixed; - z-index: 10010; - min-width: 280px; - max-width: 420px; - background: var(--panel, #1a1a1a); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 8px 24px rgba(0,0,0,0.4); - font-family: 'Fira Code', monospace; - font-size: 11px; - color: var(--fg); -} -.cookbook-gpu-popup-head { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; -} -.cookbook-gpu-popup-stats { - flex: 1; - font-weight: 400; - color: var(--fg-muted); - font-size: 10px; -} -.cookbook-gpu-popup-close { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 14px; - padding: 0 4px; -} -.cookbook-gpu-popup-close:hover { color: var(--fg); } -.cookbook-gpu-popup-body { - padding: 4px 0; - max-height: 320px; - overflow-y: auto; -} -.cookbook-gpu-popup-empty { - padding: 10px; - color: var(--fg-muted); - font-style: italic; -} -.cookbook-gpu-proc { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; -} -.cookbook-gpu-proc:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); } -.cookbook-gpu-proc-info { - flex: 1; - display: flex; - gap: 8px; - align-items: center; - min-width: 0; -} -.cookbook-gpu-proc-pid { - color: var(--fg-muted); - width: 56px; - flex-shrink: 0; -} -.cookbook-gpu-proc-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.cookbook-gpu-proc-mem { - color: var(--fg-muted); - flex-shrink: 0; -} -.cookbook-gpu-proc-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} -.cookbook-gpu-kill { - border: 1px solid color-mix(in srgb, var(--red) 60%, transparent); - background: none; - color: var(--red); - border-radius: 3px; - padding: 2px 8px; - cursor: pointer; - font-family: inherit; - font-size: 10px; -} -.cookbook-gpu-kill[data-sig="KILL"] { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.cookbook-gpu-kill:hover:not(:disabled) { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.cookbook-gpu-kill:disabled { opacity: 0.4; cursor: wait; } -.cookbook-serve-title { - display: flex; - align-items: center; - gap: 4px; - min-width: 0; -} -.cookbook-serve-title-name { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-hf-link { - font-size: 9px; - text-decoration: none; - color: var(--fg-muted); - opacity: 0.5; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - vertical-align: 1px; - letter-spacing: 0.3px; - font-weight: 600; - flex-shrink: 0; -} -.cookbook-hf-link:hover { - opacity: 0.8; - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -/* In the Trending models list the HF pill is the row's main affordance — - tint it accent so it stands out next to the model name. */ -#cookbook-hf-latest-list .cookbook-hf-link { - color: var(--accent, var(--red)); - opacity: 0.85; - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); -} - -/* Running tab sections */ -.cookbook-saved-section, -.cookbook-serve-section, -.cookbook-dl-section { - border: 1px solid var(--border); - border-radius: 6px; - margin-bottom: 6px; - overflow: hidden; -} -/* Divider between server-group sections in the Active tab so the - transition between e.g. local + remote-host server blocks reads - visually instead of running into each other. */ -.cookbook-group[data-backend-group="Running"] > [class*="cookbook-server-section-"] + [class*="cookbook-server-section-"] { - margin-top: 14px; - padding-top: 14px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.cookbook-section-header { - display: flex; - align-items: center; - gap: 6px; - padding: 7px 10px; - cursor: pointer; - user-select: none; - /* Persistent surface + border so it reads as a clickable bar instead of - blending into the panel background. */ - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - transition: background 0.15s, border-color 0.15s; -} -.cookbook-section-header:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: color-mix(in srgb, var(--fg) 22%, transparent); -} -.cookbook-section-header.has-server-color { - color: var(--fg); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 45%, var(--border)); - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 4%, var(--bg)); - box-shadow: inset 6px 0 0 var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color:hover { - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 7%, var(--bg)); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 60%, var(--border)); -} -.cookbook-section-header.has-server-color .cookbook-section-title, -.cookbook-section-header.has-server-color .cookbook-section-chevron { - color: var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color .cookbook-srv-status.ok { - background: var(--cookbook-server-accent, var(--cookbook-server-color)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 55%, transparent); -} -.cookbook-section-header .cookbook-clear-btn { - margin-left: 0; - position: relative; - top: -3px; -} -@media (max-width: 768px) { - .cookbook-section-header .cookbook-clear-btn { - top: -3px; - } - .cookbook-task-check { - top: 4px; - } -} -/* "Stop all" sits just left of "Clear finished"; it carries the auto margin so - the pair is pushed together to the right of the section title. */ -.cookbook-section-header .cookbook-stop-all-btn { - margin-left: auto; - margin-right: 6px; - position: relative; - top: -3px; -} -.cookbook-stop-all-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-stop-all-btn:hover { - color: var(--warn, #f0ad4e) !important; - border-color: var(--warn, #f0ad4e) !important; - background: color-mix(in srgb, var(--warn, #f0ad4e) 8%, transparent) !important; -} -.cookbook-section-chevron { - flex-shrink: 0; - opacity: 0.6; - transition: transform 0.15s ease, opacity 0.15s ease; -} -.cookbook-section-body { - padding: 0; -} -.cookbook-saved-toggle-header { - font-size: 10px; - font-weight: 600; - color: var(--fg-muted); - text-transform: uppercase; - letter-spacing: 0.3px; - margin-bottom: 4px; -} -.cookbook-saved-toggle:hover { color: var(--fg); } -.cookbook-saved-item { - margin: 2px 0; - border-radius: 4px; - font-size: 11px; -} -.cookbook-saved-header { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 6px; - border-radius: 4px; - transition: background 0.08s; -} -.cookbook-saved-header:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.cookbook-saved-item .hwfit-serve-panel { - margin: 4px 0 4px 6px; -} -.cookbook-saved-host { - font-size: 10px; - color: var(--fg-muted); - font-family: 'Fira Code', monospace; -} -.cookbook-saved-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-saved-launch { - font-size: 10px; - padding: 2px 8px; -} -.cookbook-saved-del { - background: none; - border: none; - color: var(--fg-muted); - opacity: 0.3; - cursor: pointer; - font-size: 11px; - padding: 0 2px; -} -.cookbook-saved-del:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} - -/* Serve config panel */ -.hwfit-serve-panel { - margin-top: 6px; - padding: 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex: 1; - display: flex; - flex-direction: column; - position: relative; -} -.cookbook-serve-slots { - display: flex; - gap: 3px; - justify-content: flex-end; - margin-bottom: 4px; -} -/* When the Save split sits inside Row 1 (next to GPUs), align it with the - input baseline (the row's grid cells stretch top-down; without this the - Save buttons sit above the GPU button group). */ -.hwfit-serve-row .cookbook-serve-slots { - align-self: end; - margin-bottom: 4px; -} -.cookbook-slot-btn { - min-width: 22px; height: 28px; - padding: 0 6px; - font-size: 10px; font-weight: 600; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - white-space: nowrap; - max-width: 80px; - overflow: hidden; - text-overflow: ellipsis; - color: var(--fg-muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.5; - transition: all 0.15s; -} -.cookbook-slot-btn:hover { opacity: 0.9; border-color: var(--fg-muted); } -/* Saved-configs split button: "Save" + dropdown-arrow joined into one control, - but each segment keeps its own style — Save uses the filled accent - "Done"-button look; the arrow stays a subtle outlined menu trigger. */ -.cookbook-saved-split { gap: 0; } -.cookbook-saved-save, -.cookbook-saved-arrow { max-width: none; } -.cookbook-saved-save { - padding: 0 10px; - gap: 4px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - font-weight: 600; - opacity: 1; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.cookbook-saved-save:hover { - background: var(--border); - border-color: var(--accent); - opacity: 1; -} -.cookbook-saved-arrow { - padding: 0 6px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - opacity: 1; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: none; -} -.cookbook-saved-menu .cookbook-saved-favorite { - border-left: 3px solid var(--red); - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-saved-fav-btn { - width: 20px; - height: 20px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: none; - color: var(--fg-muted); - opacity: 0.55; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -.cookbook-saved-fav-btn:hover, -.cookbook-saved-fav-btn.active { - color: var(--red); - opacity: 1; - background: color-mix(in srgb, var(--red) 10%, transparent); -} -.cookbook-saved-fav-badge { - flex-shrink: 0; - background: transparent; - padding: 0; -} -.cookbook-serve-favorite-model { - border-left-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-serve-fav-badge { - display: inline-flex; - align-items: center; - vertical-align: 1px; - margin-left: 5px; - margin-right: 2px; - background: transparent; - padding: 0; -} -.cookbook-slot-saved { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); } -.cookbook-slot-saved:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); } -.cookbook-slot-btn.active { opacity: 1; background: var(--accent); color: #fff; border-color: var(--accent); -} -.cookbook-slot-wrap { - position: relative; - display: inline-flex; -} -.cookbook-slot-del { - position: absolute; - top: -5px; - right: -5px; - width: 14px; - height: 14px; - padding: 0; - border: 1px solid var(--border); - border-radius: 50%; - background: var(--bg, #1a1a1a); - color: var(--fg-muted); - font-size: 12px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, color 0.15s, border-color 0.15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-slot-del:hover { - color: #f44; - border-color: #f44; -} -.cookbook-card-backend { display: none; } -/* Dependencies row — aligned tags */ -.cookbook-dep-row { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel); -} -/* Let the deps list fill the available window height instead of being - capped at the default .doclib-grid 400-px box. */ -[data-backend-group="Dependencies"] #cookbook-deps-list { - flex: 1; - max-height: none; -} - -/* Settings tab: stack the HF token + Servers cards as distinct blocks - (matches the Download tab's block-per-section layout). */ -.cookbook-settings-stack { - display: flex; - flex-direction: column; - gap: 10px; - flex: 1; - /* Scroll the whole settings panel so the Servers card can grow to hold every - server (it used to be a cramped internal scroll box that clipped them). */ - overflow-y: auto; - min-height: 0; -} -.cookbook-settings-stack.hidden { display: none; } -.cookbook-dep-row.cookbook-dep-blocked { opacity: 0.4; } -.cookbook-dep-info { flex: 1; min-width: 0; } -.cookbook-dep-section { - display: flex; - align-items: baseline; - gap: 8px; - margin: 12px 2px 4px; -} -.cookbook-dep-section-title { - font-size: 11px; - font-weight: 600; - letter-spacing: 0.03em; -} -.cookbook-dep-section-note { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-subgroup { - margin: 6px 0 8px 12px; - padding: 7px 8px 8px; - border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - border-radius: 0 6px 6px 0; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-dep-subgroup-title { - display: flex; - align-items: baseline; - gap: 7px; - margin: 0 1px 1px; - font-size: 10.5px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 78%, transparent); -} -.cookbook-dep-subgroup-title em { - font-size: 9.5px; - font-style: normal; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.cookbook-dep-tag { - font-size: 9px; - padding: 0 8px; - border-radius: 4px; - white-space: nowrap; - text-align: center; - min-width: 62px; - box-sizing: border-box; - line-height: 1; - /* Explicit height so the tags (Install / Installed) are the EXACT - same height as the sibling tags — Firefox gives buttons a taller - native box otherwise. Mirrors the server-row tag rule (height:24px). */ - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.cookbook-dep-target { - border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-cat { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -/* Rebuild tag — same look as the LLM category tag, sits to its left. */ -.cookbook-dep-rebuild { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 75%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent); - cursor: pointer; - font-family: inherit; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - position: relative; - top: -2px; -} -.cookbook-dep-reinstall { - top: -3px; -} -.cookbook-dep-rebuild:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); -} -.cookbook-dep-installed { - background: color-mix(in srgb, var(--green, #50fa7b) 18%, transparent); - color: var(--green, #50fa7b); - border: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); - /* Match Install + Installed ▾ so all three variants align in mixed rows. */ - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-na { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); - cursor: help; - /* Match other dep tag widths so N/A rows line up with Install / Installed. */ - min-width: 75.85px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-install { - background: var(--accent, var(--red)); - color: #fff; - border: none; - cursor: pointer; - font-family: inherit; - font-weight: 500; - position: relative; - top: -3px; - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - /* Strip the native button box so it's the same height as the sibling tags - (Firefox renders taller otherwise); height comes from .cookbook-dep-tag. */ - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-build-deps { - width: auto; - min-width: 87.7px; - max-width: 100%; - white-space: normal; - overflow-wrap: anywhere; - line-height: 1.15; - text-align: center; - height: auto; - min-height: 24px; -} -/* The install state is the terminal control in a dependency row. Keep it on - the far edge even when optional rebuild/category controls are present. */ -.cookbook-dep-row > .cookbook-dep-installed, -.cookbook-dep-row > .cookbook-dep-na, -.cookbook-dep-row > .cookbook-dep-install:not(.cookbook-dep-build-deps) { - margin-left: auto; -} -/* Center the "?" glyph inside the help chip. Without text-align it sits 0.5px - left of true center because of the character's natural baseline offset. */ -.hwfit-help-chip { - text-align: center; - padding-left: 0.5px; -} -.cookbook-dep-install:hover { opacity: 0.85; } -/* Installed split button: "Installed" label + separator + ▾ caret; clicking it - opens the actions menu (Update). Replaces the old ⋮ button. */ -.cookbook-dep-installed-btn { - padding: 0; - width: 87.7px; - min-width: 87.7px; - cursor: pointer; - font-family: inherit; - overflow: hidden; - position: relative; - top: -3px; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-installed-btn .cookbook-dep-caret { - display: inline-flex; - align-items: center; - align-self: stretch; - padding: 0 5px; - font-size: 14px; - line-height: 1; - position: relative; - top: 1px; - left: 4px; - opacity: 0.85; - border-left: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); -} -.hwfit-serve-row { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - margin-bottom: 6px; -} -/* Tighter min on the core-knobs row only so Dtype / TP / GPU Mem / Max - Seqs fit on one line without crushing Row 1's GPU button strip or - the Backend picker. */ -.hwfit-serve-row-core { - grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); -} -/* Save / saved-config split aligned to the right end of Row 1 and - nudged up 4px so it sits flush with the rest of the row. */ -.hwfit-serve-row .cookbook-serve-slots { - justify-self: end; - position: relative; - top: -4px; -} -/* The Settings label wraps the Save split — right-align its title text - so the word "Settings" sits above the right-aligned Save button - instead of floating off to the left of its grid cell. 8px right - padding now (16px → 8px) per follow-up tweak, brings the title back - over the actual Save button instead of overshooting it. */ -.hwfit-serve-row label:has(> .cookbook-serve-slots) { - grid-column: -2 / -1; - justify-self: end; - text-align: right; - padding-right: 0; -} -.hwfit-serve-row label:has(> .cookbook-serve-slots) > span { - display: inline-block; - position: relative; - left: -33px; -} -.hwfit-serve-topline { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - margin: 0 0 6px; -} -.hwfit-serve-topline .hwfit-serve-runtime-text { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-serve-preset-row { - display: flex; - justify-content: flex-end; - margin: 0; - flex: 0 0 auto; - position: relative; - top: -6px; -} -.hwfit-serve-row label { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; - letter-spacing: 0.3px; -} -.hwfit-serve-row-core .hwfit-context-label { - grid-column: span 2; - width: auto; - min-width: 0; - max-width: none; - justify-self: start; -} -.hwfit-context-control { - display: block; - position: relative; - align-items: center; - margin-top: 2px; - width: 100px; -} -.hwfit-context-control .hwfit-sf[data-field="ctx"] { - min-width: 0; - width: 100%; - padding-right: 40px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]) { - position: relative; - left: -66px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - position: relative; - left: -64px; -} -.hwfit-serve-row-core .hwfit-gpus-label { - position: relative; - left: -70px; -} -.hwfit-context-calc-btn { - position: absolute; - right: 3px; - top: -4px; - width: 34px; - height: 28px; - min-width: 34px; - padding: 0; - font-size: 10px; - line-height: 1; - display: inline-flex; - align-items: center; - justify-content: center; - text-align: center; - overflow: hidden; - white-space: nowrap; -} -.hwfit-context-calc-btn .spinner-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 auto !important; -} -.hwfit-context-calc-btn .ai-spinner-whirlpool { - width: 12px !important; - height: 12px !important; -} -@media (min-width: 769px) { - [data-llama-mode-toggle].mode-toggle.mode-toggle-three .mode-toggle-btn > span { - top: -6px !important; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { - font-size: 0; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { - content: 'Unif'; - font-size: 11px; - } -} -@media (max-width: 768px) { - .hwfit-serve-row-core .hwfit-context-label { - display: flex; - flex-direction: column; - align-items: flex-start; - } - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]), - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - left: 0; - } - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_env_preset"]), - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="extra_env"]) { - grid-column: 1 / -1 !important; - } - .hwfit-context-control { - display: inline-flex; - align-items: center; - gap: 4px; - width: auto; - } - .hwfit-context-control .hwfit-sf[data-field="ctx"] { - width: 100px; - padding-right: 6px; - } - .hwfit-context-calc-btn { - position: relative; - top: -2px; - left: 2px; - right: auto; - flex: 0 0 34px; - width: 34px; - min-width: 34px; - } -} -.hwfit-auto-ctx-note { - display: block; - margin-top: 3px; - font-size: 9px; - opacity: 0.6; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Ctx slider — ported from origin/main. Sits in the Scan/Download toolbar - next to the quant dropdown. Drives _ctxValue() in cookbook-hwfit.js. */ -.hwfit-ctx-control { - height: 28px; - min-width: 134px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - gap: 5px; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - font-size: 11px; /* slightly smaller than .cookbook-field-input so Context reads quieter next to Engine/Quant */ -} -.hwfit-ctx-control span { - /* Match Quant/Engine select label style: no uppercase, no letter-spacing. */ - text-transform: none; - letter-spacing: 0; - opacity: 0.9; -} -.hwfit-ctx-control > span:first-child { - font-size: 9.5px; -} -/* Editor-style slider (same look as the gallery editor sliders): thin pill - rail that fattens on interaction, circular red thumb that grows on hover. */ -.hwfit-ctx-control input[type="range"] { - width: 64px; - min-width: 64px; - height: 4px; - padding: 0; - border: 0; - -webkit-appearance: none; - appearance: none; - /* Hard-coded grey so the rail is GUARANTEED visible regardless of theme — - every theme-derived color we tried (--fg-muted, --border, accent-bg mix) - kept blending into the panel background on at least one theme. */ - background: rgba(150, 150, 150, 0.65); - border-radius: 999px; - accent-color: var(--red); - cursor: pointer; - transition: height 0.15s ease, background 0.15s ease; -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - background: var(--fg); -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - height: 10px; -} -.hwfit-ctx-control input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]::-moz-range-thumb { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]:hover::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:focus::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:active::-webkit-slider-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control input[type="range"]:hover::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:focus::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:active::-moz-range-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control output { - min-width: 28px; - text-align: right; - color: var(--fg); - font-weight: 600; -} -.hwfit-sf { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - font-family: inherit; - font-size: 12px; - padding: 0 6px; - height: 28px; - box-sizing: border-box; -} -.hwfit-sf[data-field="backend"], -.hwfit-sf[data-field="dtype"], -.hwfit-sf[data-field="tp"] { - height: 32px; - box-sizing: border-box; - width: 100%; -} -.hwfit-sf:focus { - border-color: var(--accent, var(--red)); - outline: none; -} -.hwfit-sf[type="text"] { width: 100%; } -.hwfit-sf.hwfit-sf-wide { width: 100%; } -.hwfit-serve-checks { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 6px; -} -.hwfit-sf-cb { - display: flex; - flex-direction: row; - align-items: center; - gap: 6px; - font-size: 11px; - color: var(--fg-muted); - cursor: pointer; - padding: 3px 0; -} diff --git a/static/index.html b/static/index.html index cba90c7c9..ad04daf89 100644 --- a/static/index.html +++ b/static/index.html @@ -246,15 +246,24 @@ real request is discarded and the font fetched a second time. --> - - - - - - - - - + + + + + + + + + + + + + + + + + +
with hljs spans) and the transparent textarea use two - different rendering paths internally — the textarea uses the browser's - native form line-breaker, the overlay uses CSS line-breaking — and they - can never be guaranteed to wrap byte-identical no matter how many CSS - properties are pinned. Pick one source of truth: make the textarea its - own visible text, hide the overlay. Trade-off: no syntax highlighting in - the live editor (rendered markdown preview / chat output still has it). - The caret is now glued to the typed text by definition since both are - rendered by the same element. */ -.doc-editor-highlight, -#doc-editor-code.email-mode .doc-editor-highlight { - display: none !important; -} -/* Find bar is open: float the highlight overlay ON TOP of the - textarea with transparent background + transparent text, so only - the solid spans are visible. pointer-events - off so clicks still go through to the textarea below. */ -body.doc-find-active .doc-editor-highlight, -body.doc-find-active #doc-editor-code.email-mode .doc-editor-highlight { - display: block !important; - z-index: 5 !important; - background: transparent !important; - pointer-events: none; -} -body.doc-find-active .doc-editor-highlight, -body.doc-find-active .doc-editor-highlight * { - color: transparent !important; - background: transparent !important; -} diff --git a/static/css/14-email.css b/static/css/14-email.css new file mode 100644 index 000000000..56d1df6df --- /dev/null +++ b/static/css/14-email.css @@ -0,0 +1,4729 @@ +/* 14-email - 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. + */ +#group-model-picker input[type="checkbox"] { + accent-color: var(--accent, var(--red)); +} +#group-model-picker input[type="radio"] { + accent-color: var(--accent, var(--red)); +} +#group-model-picker .btn-primary { + background: var(--accent, var(--red)); + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 500; +} +#group-model-picker .btn-primary:disabled { + opacity: 0.4; + cursor: not-allowed; +} +#group-model-picker .btn-primary:hover:not(:disabled) { + filter: brightness(1.1); +} + +/* ── Email document type ── */ +.doc-email-header { + display: flex; + flex-direction: column; + gap: 6px; + padding: 10px 12px; + border-bottom: 1px solid var(--border); + background: var(--bg); + flex-shrink: 0; +} +.email-field { + display: flex; + align-items: center; + gap: 8px; +} +.email-field label { + font-size: 11px; + font-weight: 600; + color: var(--fg); + opacity: 0.5; + min-width: 50px; + text-align: right; +} +.email-field input { + flex: 1; + background: transparent; + border: 1px solid var(--border); + border-radius: 4px; + padding: 5px 8px; + font-size: 13px; + font-family: inherit; + color: var(--fg); + outline: none; +} +.email-field input:focus { + border-color: var(--accent, #4a9eff); +} +.email-actions { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 2px; +} +.email-draft-btn { + background: transparent; + color: var(--fg); + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 14px; + font-size: 12px; + cursor: pointer; + font-family: inherit; +} +.email-draft-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } +.email-draft-btn:disabled { opacity: 0.5; cursor: not-allowed; } + +/* ── Email inbox list ── */ +.email-body { border-top: 1px solid var(--border); } +.email-folder-bar { padding: 4px 8px; border-bottom: 1px solid var(--border); } +.email-folder-select { + background: transparent; border: 1px solid var(--border); border-radius: 4px; + color: var(--fg); font-size: 11px; padding: 2px 6px; cursor: pointer; width: 100%; height: 26px; +} +.email-item { + display: flex; align-items: flex-start; gap: 8px; padding: 8px 12px; + cursor: pointer; border-bottom: 1px solid var(--border); position: relative; transition: background 0.1s; +} +.email-item:hover { background: var(--hover-bg, rgba(255,255,255,0.03)); } +.email-item-spam:hover { opacity: 0.75; } +.email-tag-spam { + display: inline-flex; align-items: center; gap: 4px; + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); + color: var(--accent, var(--red)); + border-radius: 10px; padding: 1px 6px; font-size: 9px; font-weight: 600; + text-transform: lowercase; letter-spacing: 0.3px; +} +.email-spam-unflag { + background: none; border: none; color: inherit; cursor: pointer; + padding: 0 2px; font-size: 10px; line-height: 1; opacity: 0.6; +} +.email-spam-unflag:hover { opacity: 1; color: var(--fg); } +/* Unread state is now a subtle icon, not full styling */ +.email-done-check { + display: inline-flex; align-items: center; justify-content: center; + width: 16px; height: 16px; border-radius: 50%; background: transparent; + border: 1.5px solid var(--border); color: transparent; + flex-shrink: 0; cursor: pointer; transition: all 0.15s; + margin-right: 4px; +} +.email-done-check:hover { border-color: var(--accent, #4a9eff); } +.email-done-check.active { background: var(--accent, #4a9eff); border-color: var(--accent, #4a9eff); color: #fff; } +.email-done-check.active svg { display: block; } +.email-done-check svg { display: none; } + + + + +.email-delete-busy > :not(.email-delete-overlay) { + opacity: 0.42; + transition: opacity 0.12s ease; +} + +.email-delete-overlay { + position: absolute; + inset: 0; + z-index: 8; + display: flex; + align-items: center; + justify-content: center; + border-radius: inherit; + background: color-mix(in srgb, var(--panel, var(--bg)) 82%, transparent); + pointer-events: none; +} + +.email-delete-overlay .spinner-whirlpool, +.email-delete-overlay .ai-spinner-whirlpool { + margin: 0 !important; +} + +.doclib-card.email-card-removing { + pointer-events: none !important; + max-height: 0 !important; + padding-top: 0 !important; + padding-bottom: 0 !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + border-color: transparent !important; + opacity: 0; + transform: translateX(-10px) scale(0.985); + transition: + opacity 0.18s ease, + transform 0.22s ease, + max-height 0.23s ease, + padding 0.23s ease, + margin 0.23s ease, + border-color 0.18s ease; +} + +@media (prefers-reduced-motion: reduce) { + .doclib-card.email-card-removing { + transition: opacity 0.08s ease; + transform: none; + } +} + +/* Library card done check — always visible, subtle icon next to title */ +.email-card-done { + display: inline-flex; align-items: center; justify-content: center; + flex-shrink: 0; cursor: pointer; + transition: opacity 0.15s, color 0.15s; + opacity: 0.15; color: var(--fg); +} +.email-card-titlerow .memory-item-title { + flex: 1 1 auto; + min-width: 0; +} +.email-card-titlerow { position: relative; min-width: 0; } +.email-card-status { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: 7px; + flex: 0 0 auto; + min-width: max-content; + margin-left: auto; + white-space: nowrap; +} +.email-card-status > * { + flex: 0 0 auto; +} +.email-card-status + .email-card-nav-arrows { + margin-left: 6px; +} +.email-card-status .email-card-nav-arrows { + margin-left: 2px; +} +.email-card-attachment { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 13px; + width: 13px; + height: 18px; +} +/* Hover preview: bright accent when unchecked so the user sees a check coming. + Once active, keep the exact same color on hover so the done state does not + visually flip while the pointer is still over it. */ +.email-card-done:not(.active):hover { + opacity: 0.75 !important; + color: var(--accent-primary, var(--red)); +} +.email-card-done.active { opacity: 0.95; color: var(--accent-primary, var(--red)); } +.email-card-done.active:hover { + opacity: 0.95 !important; + color: var(--accent-primary, var(--red)) !important; +} +.email-card-favorite { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 18px; + width: 18px; + height: 18px; + margin: 0; + padding: 0; + border: 0; + background: transparent; + color: var(--fg); + cursor: pointer; + position: relative; + top: -3px; + opacity: 0.18; + transition: opacity 0.15s, color 0.15s, transform 0.15s; +} +.email-card-favorite:hover, +.email-card-favorite:focus-visible { + opacity: 0.8; + color: var(--accent-primary, var(--red)); +} +.email-card-favorite.active { + opacity: 0.9; + color: var(--accent-primary, var(--red)); +} +@keyframes check-pop { + 0% { transform: scale(1); } + 40% { transform: scale(1.7) rotate(-10deg); } + 70% { transform: scale(1.1) rotate(4deg); } + 100% { transform: scale(1) rotate(0); } +} +@keyframes check-unpop { + 0% { transform: scale(1); opacity: 0.95; } + 35% { transform: scale(0.45) rotate(8deg); opacity: 0.05; } + 65% { transform: scale(0.9); opacity: 0.2; } + 100% { transform: scale(1); } +} + +/* Expanded email preview in library — override .memory-item row layout */ +.doclib-card.email-card-expanded.memory-item { + cursor: default !important; + background: var(--bg) !important; + border: 1px solid var(--border) !important; + display: flex !important; + flex-direction: column !important; + align-items: stretch !important; + /* Fill the available grid height instead of the old hardcoded 70vh — + the modal already caps height (90dvh on mobile, auto on desktop), so + the card just needs to flex into whatever remains. */ + flex: 1 1 auto !important; + height: 100% !important; + min-height: 0 !important; + max-height: none !important; + padding: 0 !important; + overflow: hidden !important; + gap: 0 !important; +} +.doclib-card.email-card-expanded.memory-item > div:first-child { + /* The summary content (subject, date) - acts as the title bar. Left padding + trimmed 14→6px to shift the title 8px left total. */ + padding: 2px 4px 10px !important; /* top trimmed to lift the header text up */ + margin-top: -14px !important; /* pull the whole bar up into the freed space */ + border-bottom: 1px solid var(--border); + flex: 0 0 auto !important; + width: 100%; +} +/* Keep the per-card '...' actions menu visible while the email card is + expanded — we used to hide it and surface a duplicate menu button in + the expanded header, but that was redundant. The bottom menu now + carries the actions in both states. */ +/* When expanded inline, show the subject a touch larger than the 12px card + title (close to the new-tab window header) without being oversized. */ +.doclib-card.email-card-expanded .memory-item-title { + font-size: 14px; + font-weight: 600; +} +/* The sender name is redundant once expanded (the From: row shows it just + below), so drop it and keep only the date — which then sits left-aligned + directly under the subject. Nudge the date 1px right to line up exactly with + the subject text, and up closer to it. */ +.doclib-card.email-card-expanded .email-meta-sender, +.doclib-card.email-card-expanded .email-meta-sep { + display: none; +} +.doclib-card.email-card-expanded .memory-item-meta { + margin-top: -9px !important; +} +.doclib-card.email-card-expanded .email-meta-date { + margin-left: 3px; +} +.email-card-reader { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + font-size: 12px; +} +.email-card-reader-loading { + background: color-mix(in srgb, var(--bg) 86%, #000 14%); + border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); + min-height: 240px; +} +.email-card-reader-error { + background: color-mix(in srgb, var(--bg) 86%, #000 14%); + border-top: 1px solid color-mix(in srgb, var(--border) 78%, transparent); +} +.email-reader-load-error { + display: flex; + min-height: 180px; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 9px; + padding: 24px 16px; + text-align: center; + color: var(--fg); +} +.email-reader-load-error-title { + font-size: 13px; + font-weight: 700; +} +.email-reader-load-error-msg { + max-width: min(460px, 92%); + color: color-mix(in srgb, var(--fg) 62%, transparent); + font-size: 11px; + line-height: 1.35; + overflow-wrap: anywhere; +} +.email-skeleton-line, +.email-skeleton-dot { + display: block; + border-radius: 999px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 12%, transparent) 0%, + color-mix(in srgb, var(--fg) 22%, transparent) 45%, + color-mix(in srgb, var(--fg) 12%, transparent) 90% + ); + background-size: 220% 100%; + animation: email-skeleton-shimmer 1.35s ease-in-out infinite; +} +.email-skeleton-dot { + width: 9px; + height: 9px; + flex: 0 0 auto; +} +.email-list-skeleton { + position: absolute; + inset: 0; + padding: 10px 7px; + display: flex; + flex-direction: column; + gap: 8px; + pointer-events: none; +} +.email-skeleton-row { + display: flex; + align-items: center; + gap: 10px; + min-height: 48px; + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--border) 76%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 70%, transparent); +} +.email-skeleton-lines { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 7px; +} +.email-reader-skeleton { + display: flex; + flex-direction: column; + min-height: 100%; + flex: 1; +} +.email-reader-skeleton-header { + min-height: 60px; + padding: 12px 14px; + display: flex; + align-items: flex-start; + gap: 8px; + border-bottom: 1px solid var(--border); + background: color-mix(in srgb, var(--panel) 72%, transparent); +} +.email-reader-skeleton-header .chip { width: 132px; height: 18px; } +.email-reader-skeleton-header .chip.short { width: 78px; } +.email-reader-skeleton-header .action { + width: 38px; + height: 28px; + margin-left: auto; + border-radius: 6px; +} +.email-reader-skeleton-header .action + .action { margin-left: 0; } +.email-reader-skeleton-atts { + display: flex; + gap: 7px; + padding: 8px 14px; + border-bottom: 1px solid var(--border); +} +.email-reader-skeleton-atts .attachment { width: 126px; height: 20px; border-radius: 6px; } +.email-reader-skeleton-atts .attachment.short { width: 88px; } +.email-reader-skeleton-body { + display: flex; + flex-direction: column; + gap: 11px; + padding: 16px 14px; + min-height: 340px; + flex: 1; +} +.email-reader-skeleton-body .body { width: 88%; height: 9px; border-radius: 6px; } +.email-reader-skeleton-body .body.wide { width: 100%; } +.email-reader-skeleton-body .body.medium { width: 94%; } +.email-reader-skeleton-body .body.short { width: 62%; } +.email-reader-skeleton-body .body.gap { + margin-top: 12px; + width: 78%; +} +.email-search-progress-row { + display: flex; + align-items: center; + justify-content: center; + gap: 7px; + min-height: 30px; + padding: 6px 10px; + font-size: 11px; + opacity: 0.65; +} +.email-search-progress-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--accent-primary, var(--red)); + box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); + animation: email-search-progress-pulse 1.2s ease-in-out infinite; +} +@media (prefers-reduced-motion: reduce) { + .email-skeleton-line, + .email-skeleton-dot, + .email-inline-image-skeleton, + .email-inline-image-placeholder.is-loading .email-inline-image-icon, + .email-inline-image-frame.is-loading, + .email-search-progress-dot { + animation: none; + } +} +/* Per-email unread dot in the expanded reader's title row. Background color + stays inline (per-sender hue from _senderColor), but the dot gets a soft + breathing glow + a 4px vertical nudge so it reads as centered against + the row instead of riding the baseline. */ +.email-card-unread-dot { + position: relative; + top: 0; + animation: email-card-unread-breathe 2.2s ease-in-out infinite; +} +.email-reader-header { + display: flex; + flex-direction: row; + /* Top-align so when the user expands To/Cc, the action cluster + stays on the From line above instead of drifting to the middle. */ + align-items: flex-start; + gap: 12px; + /* Taller header so the 44px-tall absolute-positioned action + cluster inside the From row has vertical breathing room. */ + padding: 8px 14px; + min-height: 60px; + border-bottom: 1px solid var(--border); + background: var(--bg); + flex-shrink: 0; + /* Containing block for the absolute action cluster. */ + position: relative; + /* Container so the action-cluster wrap breakpoints below react to + the reader's own width instead of the page viewport. */ + container-type: inline-size; + container-name: emailreader; +} +.email-reader-header > .email-reader-actions { + margin-left: auto; + flex-shrink: 0; +} +/* Meta block stacks the From / To / Cc rows; each row keeps label + tight to its chip(s) on the same line. The recipient-chips inside + scroll horizontally so long lists slide under the floating action + cluster on the right. */ +.email-reader-meta { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + /* Positioning context for the absolute action cluster. */ + position: relative; +} +.email-reader-meta .recipient-chips { + display: inline-flex; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + gap: 4px; + max-width: 100%; + scrollbar-width: none; + -ms-overflow-style: none; +} +.email-reader-meta .recipient-chips::-webkit-scrollbar { display: none; } +.email-reader-meta-row { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.email-reader-meta-row.email-reader-meta-from { + position: relative; + /* Never wrap "From:" off the chip onto its own line — the chip + span has overflow-x scroll built in, so it shrinks/scrolls + instead of jumping to a new row. */ + flex-wrap: nowrap; + min-width: 0; + /* Reserve space on the right for the 1-row cluster in wide mode. */ + padding-right: 320px; +} +.email-reader-meta > .email-reader-meta-details { + min-width: 0; + padding-right: 320px; +} +.email-reader-meta > .email-reader-actions-inline { + position: absolute; + top: 1px; + right: 0; + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + justify-content: flex-end; + gap: 4px; + z-index: 2; + /* Sit visually above the From baseline like the lifted overlay. */ + margin-top: -9px; +} +/* Recipient chip span keeps at least ~4 characters visible (60px) + before the overflow-scroll kicks in, so the sender / first + recipient name is always partly readable. */ +.email-reader-meta .recipient-chips { + min-width: 60px; +} +/* Medium reader widths: cap the cluster at 3 icons per row so it + never wraps past 2 rows. min-width forces the cluster to reserve + enough room for 3 icons in a row — without it, grid col 2 could + shrink below the cap and the cluster fanned out into 4+ short + rows. */ +@container emailreader (max-width: 450px) { + .email-reader-meta > .email-reader-actions-inline { + /* 3 icons (48px each) + 2 gaps (4px each) = 152px exact. Adding + 6px slack so subpixel rounding can't tip a row from 3 to 2 + icons and fan the cluster into a 3rd row. */ + min-width: 158px; + max-width: 158px; + /* Solid background so the email body content behind the cluster + doesn't peek through the gaps between icons or the slack on + the right edge when the cluster's wrapped to 2 rows. */ + background: var(--bg); + } + .email-reader-meta > .email-reader-actions-inline .memory-toolbar-btn.reader-icon-btn { + width: 48px; + min-width: 48px; + max-width: 48px; + height: 40px; + padding-left: 0; + padding-right: 0; + gap: 2px; + } + .email-reader-meta > .email-reader-actions-inline .reader-btn-label { + display: block; + max-width: 44px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 8px; + line-height: 1; + } + /* Tighten the right-side padding now that the cluster is 158px + wide (instead of the ~308 wide 1-row layout). */ + .email-reader-meta-row.email-reader-meta-from, + .email-reader-meta > .email-reader-meta-details { + padding-right: 170px; + } + /* Cluster wrapped to 2 rows (44px + 4 gap + 44px = 92px) sits + absolute above the From baseline. Set min-height on the meta + (a descendant of the container, more reliable than targeting + the container itself) so the header grows enough to contain + the cluster + 4px of breathing room between the cluster's + bottom and the header bottom. */ + .email-reader-meta { + min-height: 96px; + } +} +/* Very narrow reader: once the chip span has shrunk to its ~4 + character min, the cluster snaps to absolute overlay with a soft + shadow so it visually floats above the truncated chip. */ +@container emailreader (max-width: 380px) { + /* Pane too narrow even for 158px of right-side reservation — kill + the padding so chips use the full width, and let the cluster + overlay via z-index + shadow. */ + .email-reader-meta-row.email-reader-meta-from, + .email-reader-meta > .email-reader-meta-details { + padding-right: 0; + } + .email-reader-meta > .email-reader-actions-inline { + /* Keep the same -7px lift as the medium-width state so the + cluster doesn't slide ~4px DOWN when it transitions into + overlay mode. */ + /* margin-top stays inherited at -7px from the base rule. */ + /* 158px content + 22px padding-left so the inner row still fits + 3 icons cleanly. */ + min-width: 180px; + max-width: 180px; + background: linear-gradient(to right, transparent 0, var(--bg) 18px); + padding-left: 22px; + /* Soft shadow on the left edge so the overlap reads as the + cluster "floating over" the chip. */ + box-shadow: -6px 0 12px -6px rgba(0, 0, 0, 0.25); + } +} +/* Mobile: drop the box-shadow AND the left-edge gradient fade — + either can read as a shadow under the icons on phone screens. + Also lift the cluster 1px more. !important needed because the + @container query overlay rule has the same selector specificity. */ +@media (max-width: 768px) { + .memory-toolbar-btn[data-mode="ai-reply-fast"] { + height: 30px !important; + min-height: 30px !important; + } + .email-reader-meta > .email-reader-actions-inline { + box-shadow: none !important; + background: none !important; + padding-left: 0 !important; + margin-top: -10px; + } +} +/* Docked panes used to flow the cluster under the meta; now they + share the same absolute-top-right + wrap behaviour as undocked. + No docked-specific overrides — the container queries below pick + the right cluster size and overlay state from the reader width. */ +/* Gmail-style chevron — collapsed view shows only From; the chevron + reveals the To/Cc details inline below. */ +.email-reader-meta-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + margin-left: -3px; + padding: 0; + border: none; + background: transparent; + color: inherit; + opacity: 0.55; + cursor: pointer; + border-radius: 4px; + flex-shrink: 0; +} +.email-reader-meta-toggle:hover { opacity: 1; background: color-mix(in srgb, currentColor 10%, transparent); } +.email-reader-meta-toggle svg { transition: transform 0.15s ease; } +.email-reader-meta-toggle.open svg { transform: rotate(180deg); } +.email-reader-meta-toggle.email-reader-cc-toggle { + width: auto; + min-width: 18px; + gap: 3px; + margin-left: 1px; + padding: 0 5px; + color: var(--accent, var(--red)); + font-family: inherit; + font-size: 9px; + font-weight: 600; + line-height: 1; +} +.email-reader-meta-details { + display: flex; + flex-direction: column; + gap: 6px; +} +/* When expanded, To/Cc details live in the header flow. A floating popover + can overlap the folded attachment strip directly beneath the header. */ +.email-reader-meta > .email-reader-meta-details:not([hidden]) { + position: relative; + top: auto; + left: auto; + right: auto; + margin-top: 4px; + background: transparent; + border: 0; + border-radius: 0; + padding: 0; + box-shadow: none; + z-index: 1; +} +/* Reserve a fixed label width so From: / To: / Cc: all align and + their chips start at the same x. */ +.email-reader-meta-row strong { opacity: 0.5; font-weight: 600; flex-shrink: 0; min-width: 36px; } +.email-reader-meta-row > span { + white-space: nowrap; + min-width: 0; +} +/* Recipient chips */ +.recipient-chips { + display: inline-flex !important; flex-wrap: wrap; gap: 4px; + white-space: normal !important; overflow: visible !important; + text-overflow: clip !important; +} +.recipient-chip { + display: inline-flex; align-items: center; + gap: 5px; + padding: 1px 8px; font-size: 10px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid var(--border); + border-radius: 10px; + color: var(--fg); + white-space: nowrap; + cursor: pointer; + transition: background 0.15s, border-color 0.15s, max-width 0.2s; + max-width: 220px; + overflow: hidden; + text-overflow: ellipsis; +} +.recipient-chip-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} +.recipient-chip-copy { + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + top: -2px; + width: 14px; + height: 14px; + padding: 0; + border: none; + background: none; + color: inherit; + opacity: 0.55; + cursor: pointer; + flex: 0 0 auto; +} +.recipient-chip-copy:hover, +.recipient-chip-copy.copied { + opacity: 1; + color: var(--accent-primary, var(--red)); +} +.recipient-chip-copy[hidden] { + display: none !important; +} +.recipient-chip:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 12%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); +} +.recipient-chip.expanded { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); + border-color: var(--accent-primary, var(--red)); + max-width: 500px; +} +.recipient-chip.popover-open { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 15%, transparent); + border-color: var(--accent-primary, var(--red)); +} +.recipient-chip-popover { + position: fixed; + z-index: 12050; + display: flex; + align-items: center; + gap: 8px; + max-width: min(420px, calc(100vw - 20px)); + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + border-radius: 8px; + background: color-mix(in srgb, var(--bg) 96%, var(--fg) 4%); + color: var(--fg); + box-shadow: 0 14px 34px rgba(0, 0, 0, 0.42); + font-size: 12px; + line-height: 1.35; + pointer-events: auto; +} +.recipient-chip-popover-main { + min-width: 0; +} +.recipient-chip-popover-name { + font-weight: 650; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.recipient-chip-popover-detail { + overflow-wrap: anywhere; + opacity: 0.86; +} +.recipient-chip-popover-copy { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 26px; + height: 26px; + padding: 0; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 6%, transparent); + color: inherit; + cursor: pointer; +} +.recipient-chip-popover-copy:hover, +.recipient-chip-popover-copy.copied { + color: var(--accent-primary, var(--red)); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, transparent); +} +@container docpane (max-width: 460px) { + .email-reader-header { + flex-direction: column; + gap: 6px; + } + .email-reader-header > .email-reader-actions { + /* On narrow widths fall back to in-flow so it doesn't overlap + the meta entirely. */ + position: static; + align-self: flex-end; + background: none; + padding-left: 0; + } + .email-reader-meta-row:not(.email-reader-meta-from) { + padding-right: 0; + } + .email-reader-meta-row strong { + min-width: 0; + } + .recipient-chip { + max-width: 100%; + } +} +/* Mobile: long recipient lists (To/Cc with many addresses) shouldn't wrap to + N rows and push the body down. Keep them on one row, horizontally scrollable, + no scrollbar chrome. */ +@media (max-width: 768px) { + .recipient-chips { + flex-wrap: nowrap !important; + overflow-x: auto !important; + overflow-y: hidden !important; + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + } + .recipient-chips::-webkit-scrollbar { display: none; } + .recipient-chip { flex-shrink: 0; } +} +.email-reader-actions { + display: flex; flex-direction: column; gap: 4px; align-items: flex-end; + flex-shrink: 0; + margin-top: -4px; +} +/* Two stacked rows: Reply/Reply-all/Forward on the From line, with + Summary/AI-reply/More on a row below. */ +.email-reader-actions-row { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + align-items: center; + justify-content: flex-end; + gap: 4px; +} +.email-reader-atts { + display: flex; flex-wrap: wrap; gap: 6px; + padding: 8px 14px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.email-reader-body { + font-size: 12px; + line-height: 1.55; + white-space: pre-wrap; + word-wrap: break-word; + overflow-wrap: anywhere; + flex: 1; + overflow-y: auto; + padding: 14px; + min-height: 0; + background: var(--panel); + box-shadow: inset 0 1px 0 color-mix(in srgb, var(--border) 65%, transparent); + /* Use the OS colored emoji font for incoming mail content */ + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla"; + font-variant-emoji: emoji; +} +.email-reader-body.html-body img { max-width: 100%; height: auto; } +.email-reader-body.html-body [style*="max-width:600px"], +.email-reader-body.html-body [style*="max-width: 600px"], +.email-reader-body.html-body [style*="width:600px"], +.email-reader-body.html-body [style*="width: 600px"], +.email-reader-body.html-body table[width="600"] { + max-width: none !important; + width: 100% !important; + margin-left: 0 !important; + margin-right: 0 !important; +} +.email-reader-body.html-body [align="center"] { + margin-left: 0 !important; + margin-right: 0 !important; +} +.email-reader-body .email-inline-image-placeholder { + display: inline-flex; + align-items: center; + gap: 9px; + max-width: min(100%, 360px); + min-height: 58px; + margin: 6px 0; + padding: 8px 10px; + border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 72%, transparent); + color: var(--fg); + vertical-align: middle; +} +.email-inline-image-skeleton { + width: 58px; + height: 42px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 6px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 7%, transparent) 0%, + color-mix(in srgb, var(--fg) 13%, transparent) 45%, + color-mix(in srgb, var(--fg) 7%, transparent) 90% + ); + background-size: 220% 100%; + animation: email-skeleton-shimmer 1.35s ease-in-out infinite; +} +.email-inline-image-icon { + opacity: 0.52; + display: inline-flex; +} +.email-inline-image-placeholder.is-loading .email-inline-image-icon svg { + display: none; +} +.email-inline-image-info { + display: inline-flex; + flex-direction: column; + min-width: 0; + line-height: 1.25; +} +.email-inline-image-title { + font-size: 11px; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-inline-image-sub { + font-size: 10px; + opacity: 0.58; +} +.email-inline-image-actions { + display: inline-flex; + align-items: center; + gap: 5px; + margin-left: auto; + flex: 0 0 auto; +} +.email-inline-image-btn { + display: inline-flex; + align-items: center; + justify-content: center; + transform: translateY(-2px); + height: 24px; + line-height: 1; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: inherit; + font: inherit; + font-size: 10px; + text-decoration: none; + cursor: pointer; +} +.email-inline-image-btn:hover { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); + color: var(--accent-primary, var(--red)); +} +.email-inline-image-download-btn { + width: 24px; + min-width: 24px; + padding: 0; +} +.email-inline-image-loaded { + max-width: 100%; + height: auto; + border-radius: 6px; +} +.email-inline-image-frame { + display: inline-flex; + max-width: 100%; + min-width: min(360px, 100%); + min-height: 90px; + margin: 6px 0; + align-items: center; + justify-content: center; + border-radius: 8px; + background: linear-gradient( + 90deg, + color-mix(in srgb, var(--fg) 7%, transparent) 0%, + color-mix(in srgb, var(--fg) 13%, transparent) 45%, + color-mix(in srgb, var(--fg) 7%, transparent) 90% + ); + background-size: 220% 100%; +} +.email-inline-image-frame .email-inline-image-loaded { + opacity: 0; + transition: opacity 0.15s ease; +} +.email-inline-image-load-all { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + max-width: 100%; + margin: -4px 0 6px; + padding: 7px 9px; + border: 1px solid color-mix(in srgb, var(--border) 85%, transparent); + border-radius: 8px; + background: color-mix(in srgb, var(--panel) 72%, transparent); + font-size: 11px; +} +.email-inline-image-load-all span { + opacity: 0.68; + margin-right: auto; +} +.email-inline-image-load-all button { + transform: translateY(-4px); + height: 24px; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: inherit; + font: inherit; + font-size: 10px; + cursor: pointer; +} +.email-inline-image-load-all button:hover { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); + color: var(--accent-primary, var(--red)); +} +@media (max-width: 520px) { + .email-reader-body .email-inline-image-placeholder { + display: flex; + width: 100%; + } + .email-inline-image-skeleton { + width: 48px; + } + .email-inline-image-actions { + flex-direction: column; + align-items: stretch; + } +} +.email-reader-body.html-body table { max-width: 100%; border-collapse: collapse; } +/* Gmail Drive chips arrive with inline width/height/border/padding from + Gmail's composer (border:1px solid #ddd, white-ish bg assumed). On a + dark theme that reads as a near-invisible white box. Override with a + theme-aware chip surface so the filename + icon stay visible. */ +.email-reader-body .gmail_chip, +.email-reader-body .gmail_drive_chip { + width: auto !important; + max-width: 100% !important; + height: auto !important; + max-height: none !important; + display: inline-flex !important; + align-items: center !important; + gap: 6px; + padding: 6px 10px !important; + margin: 6px 0 !important; + border: 1px solid var(--border) !important; + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.email-reader-body .gmail_chip:hover, +.email-reader-body .gmail_drive_chip:hover { + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)) !important; + background: color-mix(in srgb, var(--accent, var(--red)) 6%, transparent); +} +.email-reader-body .gmail_chip a, +.email-reader-body .gmail_drive_chip a { + color: var(--accent, var(--red)) !important; + display: inline-flex !important; + align-items: center; + gap: 6px; + max-width: 100% !important; +} +.email-reader-body .gmail_chip a > span, +.email-reader-body .gmail_drive_chip a > span { + color: var(--fg) !important; + font-size: 12px; +} +.email-reader-body .gmail_chip img, +.email-reader-body .gmail_drive_chip img { + width: 16px !important; + height: 16px !important; + padding-right: 0 !important; + filter: none; +} + +/* Force any phone/sms/date/address auto-link to inherit the body color so + numbers don't render as bright-blue browser-default text inside email + content. format-detection in turns off detection on iOS, but some + mail clients ship pre-wrapped tel: links — those still need taming. */ +.email-reader-body a[href^="tel:"], +.email-reader-body a[href^="sms:"], +.email-reader-body a[x-apple-data-detectors], +.email-bubble-body a[href^="tel:"], +.email-bubble-body a[href^="sms:"], +.email-bubble-body a[x-apple-data-detectors], +.email-thread-turn-body a[href^="tel:"], +.email-thread-turn-body a[href^="sms:"], +.email-thread-turn-body a[x-apple-data-detectors] { + color: inherit !important; + text-decoration: none !important; + pointer-events: auto; +} +.email-reader-body a, +.email-bubble-body a, +.email-thread-turn-body a { + color: var(--accent-primary, var(--red)); + text-decoration: underline; + overflow-wrap: anywhere; + word-break: normal; + cursor: pointer; +} +.email-reader-body a:hover, +.email-bubble-body a:hover, +.email-thread-turn-body a:hover { opacity: 0.8; } +/* Summary block — same band chrome as Attachments / Signature / Earlier- + thread (no accent tint, no rounded corners, no leading star icon). The + only difference is the label, so all collapsible sections in the email + reader look uniform. */ +.email-summary-panel { + margin: 0 0 12px 0; + padding: 10px 12px; + background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); + border-radius: 6px; +} +.email-summary-header { + display: flex; align-items: center; gap: 5px; + font-size: 10px; font-weight: 600; text-transform: uppercase; + color: var(--accent-primary, var(--red)); opacity: 0.85; + margin-bottom: 6px; letter-spacing: 0.4px; +} +.email-summary-content { + font-size: 12px; line-height: 1.5; color: var(--fg); white-space: pre-wrap; +} +/* Click-to-fold: tap the summary header to collapse/expand. The chevron + flips when collapsed, the content hides + the panel's bottom margin + tightens so a folded summary doesn't take up vertical space. */ +.email-summary-toggle { cursor: pointer; user-select: none; } +.email-summary-panel.collapsed { padding-bottom: 6px; margin-bottom: 6px; } +.email-translation-loading { + display: flex; + align-items: center; + justify-content: center; + min-height: 26px; +} +.email-translation-busy { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; +} +.email-translation-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; +} +.email-translation-loading-text { + opacity: 0.68; + font-size: 12px; +} +.email-translation-panel .email-translation-icon { + color: var(--accent-primary, var(--red)); +} + +/* Foldable attachments — same fold-on-click UX as the summary panel. */ +.email-reader-atts-wrap { + display: flex; flex-direction: column; + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.email-reader-atts-header { + display: flex; align-items: center; gap: 5px; + padding: 6px 14px; + font-size: 10px; font-weight: 600; text-transform: uppercase; + color: var(--fg); opacity: 0.7; + cursor: pointer; user-select: none; + letter-spacing: 0.4px; +} +.email-attachments-download-all { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + height: 22px; + padding: 0 8px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, transparent); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); + color: var(--accent-primary, var(--red)); + font: inherit; + font-size: 10px; + font-weight: 700; + text-transform: none; + letter-spacing: 0; + cursor: pointer; + flex: 0 0 auto; +} +.email-attachments-download-all:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 60%, transparent); +} +.email-attachments-download-all .spinner-whirlpool, +.email-attachments-download-all .ai-spinner-whirlpool { + width: 12px !important; + height: 12px !important; + margin: 0 !important; +} +.email-reader-atts-wrap > .email-reader-atts { + border-bottom: none !important; +} +.email-reader-atts-hidden-note { + padding: 0 14px 6px; + font-size: 10px; + color: var(--fg-muted); + opacity: 0.65; +} + +/* Quote fold = neutral full-width band (matches attachments header). */ +.email-quote-fold { + margin: 0 -14px; + padding: 0; + border: 0; + border-top: 1px solid var(--border) !important; + border-radius: 0; + outline: 0 !important; + box-shadow: none !important; + /* Neutralise any rich-mail HTML that injects a yellow / colored bg + or border onto blockquote ancestors. */ + background: transparent !important; +} +/* Same neutralisation for any element nested inside the fold (the inner + blockquote, paragraphs, divs from the original message). Yellow outlines + in Outlook-style mails almost always come from inline border-color or + box-shadow on these. */ +.email-quote-fold *, +.email-quote-fold *::before, +.email-quote-fold *::after { + outline-color: transparent !important; + box-shadow: none !important; +} +.email-quote-fold blockquote, +.email-quote-fold blockquote[type="cite"], +.email-quote-fold table, +.email-quote-fold div { + border-color: var(--border) !important; + /* Override the .msg blockquote rule that sets a colored left border via + var(--hl-function) — that's where the "yellow" outline was coming from. */ + border-left-color: var(--border) !important; + border-radius: 0 !important; + background: transparent !important; +} +/* JS marks the last .email-quote-fold in each email body with this class, + so it's the only one that gets rounded bottom corners. Avoids :has() + browser-support quirks. */ +.email-quote-fold.last-fold { + border-radius: 0 0 8px 8px; + overflow: hidden; +} +/* ── Threaded reply turns (recursive parser) ── */ +.email-thread-turn-body { + padding: 8px 14px 4px; + font-size: 12px; + line-height: 1.45; +} +/* Nested turns indent slightly + get their own card outline so the + reply hierarchy is obvious at a glance. */ +.email-thread-turn-body .email-thread-turn { + margin: 8px 0 4px; + margin-left: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +/* The original blockquote's left stripe is redundant when each turn is + already a card — drop it inside thread bodies. */ +.email-thread-turn-body blockquote { + border-left: none !important; + margin: 0 !important; + padding: 0 !important; + background: transparent !important; +} +/* Flatten the inner blockquote: no rounded corners, no yellow tint + inherited from rich-mail HTML. Subtle left border + neutral bg. */ +.email-quote-fold blockquote { + margin: 0; + padding: 8px 12px; + border: 0; + border-left: 2px solid var(--border); + border-radius: 0 !important; + background: transparent !important; + color: inherit; +} +/* "From … · Date …" chip appended to the summary line */ +.email-fold-summary-meta { + margin-left: 6px; + font-size: 10px; + font-weight: 500; + text-transform: none; + letter-spacing: 0; + opacity: 0.55; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +/* Sender name is the primary affordance — slightly larger and not all-caps so + "From: Sam" feels like a click target on the person, not a section label. */ +.email-fold-summary-name { + font-size: 11px; + font-weight: 600; + text-transform: none; + letter-spacing: 0.1px; + color: var(--fg); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} +.email-quote-fold .email-fold-summary { + display: flex; align-items: center; gap: 5px; + padding: 6px 14px; + cursor: pointer; list-style: none; + font-size: 10px; font-weight: 600; text-transform: uppercase; + letter-spacing: 0.4px; + /* Force neutral page colors regardless of any rich-mail HTML wrapping us */ + color: var(--fg) !important; + background: transparent !important; + background-color: transparent !important; + text-shadow: none !important; + font-style: normal !important; + opacity: 0.7; + user-select: none; + border-radius: 0; +} +.email-quote-fold .email-fold-summary > * { + /* Prevent inline-color attrs on summary children (icon SVGs, meta span) + from picking up a forced color from ancestor email HTML. */ + color: inherit !important; + background: transparent !important; +} +.email-quote-fold .email-fold-summary { list-style: none; list-style-type: none; } +.email-quote-fold[open] > *:not(summary) { + padding: 8px 14px 12px 14px; + border-top: 1px solid var(--border); +} + +/* Signature fold = clean full-width band like the attachments header. No + accent overlay, no rounded corners — neutral chrome so it doesn't look + like the AI Summary panel. */ +.email-sig-fold { + margin: 8px 0 0; + padding: 0; + border: 0; + border-top: 1px dashed color-mix(in srgb, var(--fg) 18%, transparent); + background: transparent; + border-radius: 0; +} +.email-sig-fold .email-fold-summary { + display: flex; align-items: center; gap: 5px; + padding: 4px 0; + cursor: pointer; + list-style: none; list-style-type: none; + font-size: 9.5px; font-weight: 600; text-transform: uppercase; + letter-spacing: 0.4px; + color: var(--fg); opacity: 0.55; + user-select: none; +} +.email-sig-fold[open] > *:not(summary) { + padding: 6px 0 0; + border-top: 0; + font-size: 11px; line-height: 1.45; + color: color-mix(in srgb, var(--fg) 75%, transparent); + white-space: pre-wrap; +} + +/* ── Chat-bubble layout for email threads ── + Each parsed turn becomes a bubble. The active account's outgoing + replies align right (mine); everyone else aligns left (theirs). + Bubbles read top→bottom oldest→newest, like a chat. */ +.email-bubbles { + display: flex; + flex-direction: column; + gap: 10px; + padding: 4px 0 8px; + margin: 0 -2px; +} +.email-bubble-row { + display: flex; + align-items: flex-end; + gap: 8px; + width: 100%; + min-width: 0; +} +.email-bubble { + flex: 0 1 auto; + max-width: 90%; + min-width: 0; + padding: 8px 12px; + border: 1px solid var(--border); + background: var(--panel); + font-size: 12.5px; + line-height: 1.5; + word-wrap: break-word; + overflow-wrap: anywhere; + overflow: hidden; +} +.email-bubble-mine .email-bubble { + background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 10%, var(--bg)); + border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 35%, var(--border)); + border-radius: 14px 14px 4px 14px; +} +.email-bubble-theirs .email-bubble { + background: color-mix(in srgb, var(--bubble-accent, var(--fg)) 5%, var(--panel)); + border-color: color-mix(in srgb, var(--bubble-accent, var(--border)) 25%, var(--border)); + border-radius: 14px 14px 14px 4px; +} +.email-bubble-head { + display: flex; + align-items: baseline; + gap: 8px; + font-size: 10.5px; + margin-bottom: 4px; + opacity: 0.7; + min-width: 0; +} +.email-bubble-author { + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 50ch; +} +.email-bubble-date { + opacity: 0.7; + font-weight: 400; + white-space: nowrap; +} +.email-bubble-body { + white-space: normal; + /* Reader-controlled typography — override sender's inline styles AND + attribute-based sizing (, , , h1..h6). Weight + and italics still pass through for emphasis. */ + font-family: inherit; + font-size: var(--email-body-size, 13.5px); + line-height: 1.5; +} +/* Heading tags carry browser-default font-size multipliers that bypass + `font-size: inherit` when an inline style was set; pin them too. */ +.email-bubble-body h1, +.email-bubble-body h2, +.email-bubble-body h3, +.email-bubble-body h4, +.email-bubble-body h5, +.email-bubble-body h6, +.email-bubble-body big, +.email-bubble-body small, +.email-bubble-body font { + font-size: inherit !important; + line-height: inherit !important; +} +/* Sender HTML often wraps content in a fixed-width + or . Override so content fills the reader's available + width — otherwise resizing the email window doesn't reflow anything. */ +.email-bubble-body, +.email-bubble-body * { + max-width: 100% !important; +} +.email-bubble-body div[style*="width"], +.email-bubble-body table[width], +.email-bubble-body table { + width: 100% !important; + box-sizing: border-box !important; +} +.email-bubble-body blockquote { + border-left: 2px solid var(--border) !important; + margin: 4px 0 !important; + padding: 4px 10px !important; + background: transparent !important; + border-radius: 0 !important; + color: inherit; +} +.email-bubble-avatar { + flex-shrink: 0; + width: 26px; + height: 26px; + border-radius: 50%; + /* Per-sender color set inline (`style="background:hsl(...)"`); this + fallback only kicks in when JS hasn't assigned one. */ + background: color-mix(in srgb, var(--fg) 14%, transparent); + color: #fff; + text-shadow: 0 1px 0 rgba(0, 0, 0, 0.22); + display: flex; + align-items: center; + justify-content: center; + font-size: 9.5px; + font-weight: 600; + letter-spacing: 0.3px; + user-select: none; +} +/* Sig folds inside a bubble: tone down — no full-bleed band, since the + bubble already provides the chrome. */ +.email-bubble-body .email-sig-fold { + margin: 8px 0 0; + border-top: 1px dashed var(--border); +} +.email-bubble-body .email-sig-fold[open] > *:not(summary), +.email-bubble-body .email-quote-fold[open] > *:not(summary) { + padding: 6px 0 0; + border-top: 0; +} +@media (max-width: 600px) { + .email-bubble { max-width: 96%; } + .email-bubble-avatar { display: none; } +} + +/* ── Schedule Send modal ── */ +.schedule-send-body { + display: flex; + flex-direction: column; + gap: 6px; + padding: 16px 18px 12px; +} +.schedule-send-label { + font-size: 11px; + font-weight: 500; + letter-spacing: 0.3px; + text-transform: uppercase; + color: var(--fg); + opacity: 0.55; + margin-top: 6px; +} +.schedule-send-label:first-child { margin-top: 0; } +.schedule-send-presets { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; + margin-bottom: 4px; +} +.schedule-send-presets .memory-toolbar-btn { + padding: 5px 10px 11px; + font-size: 12px; + justify-content: center; + line-height: 1; +} +.schedule-send-confirm svg { + width: 12px; + height: 12px; + vertical-align: -1px; + margin-right: 6px; +} +.schedule-send-datetime { + font-size: 13px; + padding: 8px 10px; + background: var(--bg); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 6px; + outline: none; + font-family: inherit; + width: 100%; + box-sizing: border-box; +} +.schedule-send-datetime:focus { + border-color: var(--hl-function); +} +.schedule-send-footer { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 8px; + padding: 10px 18px 14px; + border-top: 1px solid var(--border); + margin-top: 4px; +} +.schedule-send-confirm { + background: var(--accent-primary, var(--red)) !important; + color: #fff !important; + border-color: var(--accent-primary, var(--red)) !important; +} +.schedule-send-confirm:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 85%, white) !important; +} + +/* Senders embed inline colors in their HTML that clash with the app's + theme (black text in dark mode, white backgrounds, yellow highlights, + etc.). Force theme colors on every descendant of the rendered body so + emails inherit the user's chosen palette regardless of what the + sender's mail client emits. */ +.email-reader-body *, +.email-bubble-body * { + color: var(--fg) !important; + -webkit-text-fill-color: currentColor !important; + background-color: transparent !important; + background-image: none !important; +} +.email-reader-body a, +.email-bubble-body a, +.email-thread-turn-body a { + color: var(--hl-function) !important; +} +.email-reader-body a[href^="tel:"], +.email-reader-body a[href^="sms:"], +.email-reader-body a[href^="x-apple-data-detectors:"], +.email-reader-body a[x-apple-data-detectors], +.email-bubble-body a[href^="tel:"], +.email-bubble-body a[href^="sms:"], +.email-bubble-body a[href^="x-apple-data-detectors:"], +.email-bubble-body a[x-apple-data-detectors], +.email-thread-turn-body a[href^="tel:"], +.email-thread-turn-body a[href^="sms:"], +.email-thread-turn-body a[href^="x-apple-data-detectors:"], +.email-thread-turn-body a[x-apple-data-detectors] { + color: inherit !important; + -webkit-text-fill-color: currentColor !important; + text-decoration: none !important; + background-color: transparent !important; + background-image: none !important; + -webkit-background-clip: border-box !important; + background-clip: border-box !important; +} + +/* Prefer local/system emoji fonts; avoid remote font providers. */ +.doc-editor-textarea, .doc-editor-highlight, #doc-editor-code, +.doc-md-preview, .doc-csv-preview { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', monospace; + font-variant-emoji: text; +} +/* Preview panes claim the editor's space so the action footer stays pinned + at the bottom of the pane even when the rendered content is short + (shorter table, single-paragraph markdown, etc). */ +.doc-csv-preview, +.doc-md-preview { + flex: 1 1 auto; + min-height: 0; + overflow: auto; +} + +/* Email documents override: use colored system emoji in compose. */ +.doc-editor-textarea.email-mode, +#doc-editor-code.email-mode, +#doc-editor-code.email-mode .doc-editor-highlight, +#doc-editor-code.email-mode .doc-editor-textarea { + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Twemoji Mozilla" !important; + font-variant-emoji: emoji; +} + +/* Single-layer rendering for ALL documents (not just email): the highlight + overlay ( with hljs spans) and the transparent textarea use two + different rendering paths internally — the textarea uses the browser's + native form line-breaker, the overlay uses CSS line-breaking — and they + can never be guaranteed to wrap byte-identical no matter how many CSS + properties are pinned. Pick one source of truth: make the textarea its + own visible text, hide the overlay. Trade-off: no syntax highlighting in + the live editor (rendered markdown preview / chat output still has it). + The caret is now glued to the typed text by definition since both are + rendered by the same element. */ +.doc-editor-highlight, +#doc-editor-code.email-mode .doc-editor-highlight { + display: none !important; +} +/* Find bar is open: float the highlight overlay ON TOP of the + textarea with transparent background + transparent text, so only + the solid spans are visible. pointer-events + off so clicks still go through to the textarea below. */ +body.doc-find-active .doc-editor-highlight, +body.doc-find-active #doc-editor-code.email-mode .doc-editor-highlight { + display: block !important; + z-index: 5 !important; + background: transparent !important; + pointer-events: none; +} +body.doc-find-active .doc-editor-highlight, +body.doc-find-active .doc-editor-highlight * { + color: transparent !important; + background: transparent !important; +} +.doc-editor-textarea, +#doc-editor-code.email-mode .doc-editor-textarea { + color: var(--fg) !important; + caret-color: var(--fg) !important; + z-index: 1; +} + +/* Emoji picker (monochrome icon picker) */ +.emoji-picker-btn { + background: none; border: none; color: var(--fg); opacity: 0.35; + padding: 4px 7px; cursor: pointer; min-height: 28px; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.1s; +} +.emoji-picker-btn:hover { opacity: 1; } +.emoji-picker { + width: min(300px, 92vw); max-height: min(300px, 55vh); + background: var(--panel); border: 1px solid var(--border); border-radius: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.3); + display: flex; flex-direction: column; overflow: hidden; +} +.emoji-picker-search { + padding: 8px 12px; background: var(--panel); border: none; + border-bottom: 1px solid var(--border); color: var(--fg); + font-size: 12px; font-family: inherit; outline: none; +} +.emoji-picker-groups { overflow-y: auto; padding: 4px 0; flex: 1; } +.emoji-picker-group { padding: 0 6px 4px 6px; } +.emoji-picker-group-name { + font-size: 10px; font-weight: 600; opacity: 0.5; + text-transform: uppercase; letter-spacing: 0.5px; + padding: 4px 4px 2px 4px; +} +.emoji-picker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; } +.emoji-picker-item { + background: none; border: none; cursor: pointer; + padding: 4px; border-radius: 4px; + display: flex; align-items: center; justify-content: center; + color: var(--fg); transition: background 0.1s; +} +.emoji-picker-item:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } +.emoji-picker-item svg { width: 16px; height: 16px; } + +.email-avatar { + width: 28px; height: 28px; border-radius: 50%; background: var(--accent, #4a9eff); + color: #fff; display: flex; align-items: center; justify-content: center; + font-size: 12px; font-weight: 600; flex-shrink: 0; margin-top: 1px; +} +.email-item-content { flex: 1; min-width: 0; overflow: hidden; } +.email-item-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } +.email-sender { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.email-filter-chip { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; + margin: 4px 8px 6px; + padding: 4px 8px; + border-radius: 12px; + font-size: 11px; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + color: var(--fg); +} +.email-filter-chip-clear { + background: none; + border: none; + color: inherit; + cursor: pointer; + font-size: 14px; + line-height: 1; + padding: 0 2px; + opacity: 0.6; +} +.email-date { font-size: 10px; opacity: 0.5; white-space: nowrap; flex-shrink: 0; } +.email-subject { font-size: 11px; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } +.email-sent-chip, +.email-folder-chip { + display: inline-flex; + align-items: center; + height: 14px; + padding: 0 5px; + margin-right: 5px; + border-radius: 999px; + font-size: 8px; + line-height: 1; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + vertical-align: 1px; +} +.email-folder-chip { + position: relative; + top: 4px; + color: color-mix(in srgb, var(--fg) 75%, transparent); + background: rgba(127, 127, 127, 0.13); + border-color: rgba(127, 127, 127, 0.24); +} +.email-meta-date-group { + display: inline-flex; + align-items: center; + gap: 5px; + white-space: nowrap; +} +.email-meta-date-group .email-folder-chip { + margin-right: 0; +} +.email-card-expanded .email-sent-chip, +.email-card-reader .email-sent-chip { + position: relative; + top: 4px; +} +.email-tags { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 3px; margin-left: 6px; vertical-align: middle; position: relative; top: -2px; } +.email-card-tags { + flex: 0 0 auto; + min-width: 0; + max-width: 48%; + position: relative; + top: -4px; +} +.email-card-expanded .email-tags, +.email-card-reader .email-tags { top: -4px; } +.doclib-card-expanded .email-card-tags { top: -4px; } +.email-card-expanded .email-card-titlerow .email-tag, +.email-card-expanded .email-card-titlerow .email-tags-more { + transform: translateY(-2px); +} +@media (max-width: 768px) { + .email-card-expanded .email-card-titlerow .email-tag, + .email-card-expanded .email-card-titlerow .email-tags-more { + transform: translateY(2px); + } +} +.email-card-tags.email-tags-expanded { + position: absolute; + z-index: 4; + right: calc(var(--email-status-width, 44px) + 6px); + top: -5px; + max-width: min(58%, 260px); + padding: 4px 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent); +} +.email-tags-more { + appearance: none; + border: 0; + box-sizing: border-box; + border-radius: 999px; + background: rgba(127, 127, 127, 0.16); + color: var(--fg); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 1px; + height: 13px !important; + min-height: 0 !important; + min-width: 23px; + padding: 1px 4px; + font-family: inherit; + font-size: 9px; + line-height: 1; + margin: 0; + opacity: 0.68; + position: relative; + top: 0; + vertical-align: middle; +} +.email-tags-more svg { display: block; flex: 0 0 auto; } +.email-tags-expanded .email-tags-more svg { transform: rotate(180deg); } +.email-tags-toggle-inline { + position: absolute; + right: 94px; + top: 50%; + transform: translateY(calc(-50% - 5px)); + height: 20px !important; + min-width: 34px; + padding: 2px 6px !important; + font-size: 10px !important; + line-height: 1 !important; + opacity: 0.62; + border-radius: 999px !important; +} +.email-tags-toggle-inline.active { + opacity: 1; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 36%, transparent); +} +.email-tags-toggle-inline.email-tags-empty { + color: var(--warning, #d6b35a); + background: color-mix(in srgb, var(--warning, #d6b35a) 12%, transparent); + border-color: color-mix(in srgb, var(--warning, #d6b35a) 34%, transparent); +} +.email-tag { + display: inline-block; + font-size: 9px; + line-height: 1; + padding: 2px 5px; + border-radius: 8px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.3px; + background: rgba(127, 127, 127, 0.18); + color: var(--fg); +} +.email-tag-clickable { + appearance: none; + border: 0; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + font-family: inherit; + font-size: 9px; + font-weight: 500; + height: 13px !important; + min-height: 0 !important; + line-height: 1; + margin: 0; + padding: 2px 5px !important; + text-transform: uppercase; + vertical-align: middle; + flex: 0 0 auto; +} +.email-tag-work { background: rgba(96, 165, 250, 0.22); color: #60a5fa; } +.email-tag-personal { background: rgba(74, 222, 128, 0.22); color: #4ade80; } +.email-tag-finance { background: rgba(250, 204, 21, 0.22); color: #facc15; } +.email-tag-bills { background: rgba(251, 146, 60, 0.22); color: #fb923c; } +.email-tag-receipt { background: rgba(251, 146, 60, 0.22); color: #fb923c; } +.email-tag-travel { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } +.email-tag-newsletter { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } +.email-tag-promo, +.email-tag-marketing { background: rgba(244, 114, 182, 0.22); color: #f472b6; } +.email-tag-notification { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } +.email-tag-security { background: rgba(248, 113, 113, 0.22); color: #f87171; } +.email-tag-urgent { background: color-mix(in srgb, var(--color-error, #e06c75) 25%, transparent); color: var(--color-error, #e06c75); font-weight: 600; } +.email-tag-reply-soon { background: rgba(240, 173, 78, 0.22); color: #f0ad4e; } +.email-tag-social { background: rgba(56, 189, 248, 0.22); color: #38bdf8; } +.email-tag-shopping { background: rgba(236, 72, 153, 0.22); color: #ec4899; } +.email-tag-calendar { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } +.email-menu-wrap { position: relative; flex-shrink: 0; } +.email-menu-btn { + background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; + padding: 4px; transition: opacity 0.15s; + position: relative; top: 0px; +} +.email-dropdown { + position: absolute; right: 0; top: 100%; z-index: 9999; + min-width: 120px; background: var(--bg); border: 1px solid var(--border); + border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); padding: 4px 0; +} +.email-loading { padding: 16px; text-align: center; font-size: 12px; opacity: 0.5; } +.email-load-more { padding: 8px; text-align: center; } +.email-load-more-btn { + background: transparent; border: 1px solid var(--border); border-radius: 4px; + color: var(--fg); padding: 4px 12px; font-size: 11px; cursor: pointer; +} +.email-load-more-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } +.email-spinner { + display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); + border-top-color: #fff; border-radius: 50%; animation: email-spin 0.6s linear infinite; + vertical-align: middle; margin-right: 4px; +} + +/* Email section doesn't collapse — hide the auto-injected chevron */ +#email-section .section-collapse-btn { display: none !important; } + +#email-section:hover #email-compose-btn { + opacity: 0.7 !important; +} +#email-compose-btn:hover { + opacity: 1 !important; + background: none !important; +} +#email-compose-btn svg { + width: 11px; + height: 11px; + transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); +} +#email-compose-btn .list-item-plus-label { + top: calc(50% - 0.5px); + color: var(--fg); + font-size: 10px; +} +#email-compose-btn:hover svg { + transform: rotate(180deg) scale(1.15); +} + +/* Library row "+" — identical to the email compose "+": hidden until the row + is hovered, no accent box, and the same rotate-in animation. */ +#library-new-doc-btn, +#notes-new-note-btn { + opacity: 0.4 !important; + transition: opacity 0.15s ease; +} +#tool-library-btn:hover #library-new-doc-btn, +#tool-notes-btn:hover #notes-new-note-btn { + opacity: 0.7 !important; +} +#library-new-doc-btn:hover, +#notes-new-note-btn:hover { + opacity: 1 !important; + background: none !important; +} +#library-new-doc-btn svg, +#notes-new-note-btn svg { + transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); +} +#library-new-doc-btn .list-item-plus-label, +#notes-new-note-btn .list-item-plus-label { + top: calc(50% + 1px); +} +#library-new-doc-btn:hover svg, +#notes-new-note-btn:hover svg { + transform: rotate(180deg) scale(1.15); +} + + +/* MD toolbar attach button — matches other toolbar buttons */ +.md-toolbar-attach-btn { + background: none; border: none; color: var(--fg); opacity: 0.35; + padding: 4px 7px; cursor: pointer; min-height: 28px; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.1s; +} +.md-toolbar-attach-btn:hover { opacity: 1; } + +/* Email reader icon buttons — vertical icon + label stack. */ +.memory-toolbar-btn.reader-icon-btn { + width: auto; + height: 44px; + /* padding: 4px 2px; */ + position: relative; + top: 1px; + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + flex: 0 0 auto; +} +.memory-toolbar-btn.reader-icon-btn svg { width: 18px; height: 18px; } +.memory-toolbar-btn.reader-icon-btn[data-act="reply"] svg { + color: inherit; + stroke: currentColor; +} +.memory-toolbar-btn.reader-icon-btn.active { + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + color: var(--accent, var(--red)); +} +.memory-toolbar-btn.reader-icon-btn[data-act="more"] { + color: inherit; +} +.memory-toolbar-btn.reader-icon-btn[data-act="more"] svg { fill: currentColor; } +.memory-toolbar-btn.reader-icon-btn.reader-more-active { + background: color-mix(in srgb, var(--fg) 12%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); + color: inherit; +} +/* Cookbook Serve dropdown triggers (the cached-model kebab + the saved-config + arrow) — accent-colored to signal they open a menu, with a filled tint when + their dropdown is open. Matches the email reader More button pattern. */ +.hwfit-cached-menu-btn, +.cookbook-saved-arrow { + color: var(--accent, var(--red)); + /* Base .memory-item-btn is flex with align-items but no justify-content, + so the kebab SVG sat left-aligned inside the button instead of being + horizontally centered. Pin it center. */ + justify-content: center; +} +.hwfit-cached-menu-btn.cookbook-menu-active, +.cookbook-saved-arrow.cookbook-menu-active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); + color: var(--accent, var(--red)); +} + +/* Card action menus share one hit target and baseline. Keep this late in the + cascade because modal buttons and Cookbook Serve add their own offsets. */ +.memory-item-actions > .memory-item-btn, +.memory-item-actions .hwfit-cached-menu-btn { + box-sizing: border-box; + width: 28px; + min-width: 28px; + height: 22px; + min-height: 22px; + margin: 0 !important; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-muted); + font-size: 11px; + display: inline-flex; + align-items: center; + align-self: center; + justify-content: center; + position: relative; + top: 0 !important; + transform: none !important; + line-height: 1; + transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s; +} +.memory-item-actions .hwfit-cached-menu-btn:hover { + opacity: 1 !important; + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); + color: var(--fg); +} +.memory-item-actions .hwfit-cached-menu-btn { + transform: translateY(2px) !important; +} + +@media (min-width: 769px) { + .task-card .memory-item-actions { + top: 6px; + } + .task-card .memory-item-actions > .memory-item-btn { + transform: none !important; + } +} + +/* All kebab menus use the same muted tone as the expand/collapse chevron. */ +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +) { + color: var(--color-muted, var(--fg)) !important; +} +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +) svg { + color: inherit; + fill: currentColor; +} +/* Card kebabs are lightweight menu affordances, not bordered buttons. The + old base hover rule gave only some library cards a boxed outline while + chats, tasks, and cookbook stayed borderless. */ +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +):hover { + background: none !important; + border-color: transparent !important; + color: var(--fg) !important; + opacity: 1; +} +/* Skills used a separate, larger kebab with a different hover treatment. + Match every other card action trigger. */ +.skill-kebab-btn { + box-sizing: border-box; + width: 28px; + min-width: 28px; + height: 22px; + min-height: 22px; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-muted) !important; + opacity: 0.55; + top: -2px; +} +@media (max-width: 768px) { + .memory-item-actions > .memory-item-btn, + .memory-item-actions .hwfit-cached-menu-btn { + width: 40px; + min-width: 40px; + height: 40px; + min-height: 40px; + } + /* Card menus all need the same comfortable tap target. These components + previously escaped the shared action-button sizing through more-specific + desktop selectors. */ + .memory-menu-btn, + .skill-kebab-btn, + .task-card .memory-item-actions .memory-item-btn { + width: 40px !important; + min-width: 40px !important; + height: 40px !important; + min-height: 40px !important; + } + .memory-menu-btn { opacity: 0.7; } + /* Document cards seat their actions on a slightly higher title baseline + than chats/research cards. Bring only that kebab down 4px. */ + #doclib-modal .doclib-card[data-doc-id] > .doclib-document-header .memory-item-actions .memory-item-btn { + transform: translateY(-2px) !important; + } +} +/* Mobile: center the kebab/menu dropdowns in the viewport so they don't + slide left/right as the window width changes (they were positioned via + inline `right: ...px` relative to the kebab button). Stable position is + easier to land on by touch. The inline `top:` set by the menu code is + preserved; we only override the horizontal axis. */ +@media (max-width: 768px) { + .email-card-dropdown, + .cookbook-saved-menu, + .cookbook-dep-menu { + left: 50% !important; + right: auto !important; + transform: translateX(-50%); + } +} +/* Mobile-only Cancel item appended to dropdowns (kebab/More menus). On + desktop outside-click closes them cleanly; on touch that's fiddly, so + give an explicit Cancel row. Visually separated from the action items + above by a top divider + slight muted color. */ +.dropdown-cancel-mobile { display: none; } +@media (max-width: 768px) { + .dropdown-cancel-mobile { + display: flex; + border-top: 1px solid var(--border); + margin-top: 4px; + padding-top: 8px; + opacity: 0.85; + } +} +/* Launch-command Copy button morphs into Cancel once Launch has been clicked. */ +.hwfit-serve-copy.hwfit-serve-cancel { + border-color: var(--color-danger, #e06c75) !important; + color: var(--color-danger, #e06c75) !important; +} +.hwfit-serve-copy.hwfit-serve-cancel:hover { + background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); +} + +/* One visual contract for card kebab menus. These menus are built by several + modules, and native button defaults previously made identical rows render + with different fonts, heights, and hover movement. */ +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) { + box-sizing: border-box; + padding: 4px !important; + color: var(--fg); + background: var(--panel, var(--bg)) !important; + border: 1px solid var(--border) !important; + border-radius: 8px !important; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important; + font-family: inherit !important; + font-size: 11px !important; + line-height: 1.2; +} +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) > .dropdown-item-compact { + box-sizing: border-box; + width: 100%; + min-height: 28px; + padding: 6px 8px; + gap: 10px; + border: 0; + border-radius: 6px; + color: var(--fg); + background: transparent; + font: inherit !important; + line-height: 1.2; + text-align: left; + white-space: nowrap; +} +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) > .dropdown-item-compact:hover:not(:disabled) { + color: var(--fg); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + transform: none; +} +@media (max-width: 768px) { + :is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu + ) > .dropdown-item-compact { + min-height: 44px; + padding: 12px !important; + gap: 10px !important; + font-size: 14px !important; + } +} +/* Email modal title unread badge — small accent pill next to "Email". */ +.email-lib-unread-badge { + display: inline-block; + font-size: 0.55em; + font-weight: 700; + background: var(--accent-primary, var(--red)); + color: var(--bg, #1a1a1a); + border-radius: 9px; + padding: 1px 7px; + margin-left: 8px; + vertical-align: 2px; + line-height: 1.3; + letter-spacing: 0.02em; + cursor: pointer; +} +.email-lib-unread-badge.email-lib-away-badge { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); + padding: 0 7px; +} +.email-lib-auto-reply-badge { + display: inline-block; + margin-left: 7px; + padding: 1px 7px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border)); + border-radius: 9px; + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + color: var(--accent, var(--red)); + font-size: 0.55em; + font-weight: 700; + line-height: 1.3; + letter-spacing: 0.01em; + vertical-align: 2px; + cursor: pointer; +} +.email-lib-auto-reply-status-dot { + width: 6px; + height: 6px; + margin-left: 6px; + border-radius: 50%; + background: var(--accent, var(--red)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), + 0 0 8px color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); + vertical-align: 2px; +} +.modal-header h4 > .email-lib-auto-reply-badge { + font-size: 0.65em; + white-space: nowrap; + vertical-align: 1px; +} +#email-lib-modal.email-lib-unread-active .doclib-modal-content { + box-shadow: + 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent), + 0 0 18px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), + var(--shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.35)); +} +#email-lib-modal .email-lib-header-actions .minimize-btn { + position: relative; + left: 6px; +} +/* Per-card prev/next nav — only visible on the currently expanded card, + sits at the right of the title row next to the done check. */ +.email-card-nav-arrows { + display: none; + margin-left: auto; + gap: 2px; + flex-shrink: 0; + align-items: center; + position: relative; + top: -3px; + left: 19px; +} +.doclib-card.email-card-expanded .email-card-nav-arrows { + display: inline-flex; +} +.email-card-header-menu { + display: none; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 28px; + height: 28px; + padding: 0; + border: none; + border-radius: 6px; + background: none; + color: var(--fg); + opacity: 0.65; + cursor: pointer; + position: relative; + top: -3px; + transition: opacity 0.12s, background 0.12s; +} +.doclib-card.email-card-expanded .email-card-header-menu { + display: inline-flex; +} +.email-card-header-menu:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.email-card-nav-btn { + background: none; + border: none; + color: var(--fg); + opacity: 0.65; + cursor: pointer; + padding: 6px 8px; + border-radius: 6px; + display: inline-flex; + align-items: center; + justify-content: center; + transition: opacity 0.12s, background 0.12s; +} +.email-card-nav-btn:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} + +/* ── Email document editor ── */ +.doc-email-header { + display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; + border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; +} +.doc-email-fields { + display: flex; + flex-direction: column; + gap: 6px; + min-height: 0; +} +.doc-email-collapse-btn { + width: 100%; + min-height: 24px; + display: none; + align-items: center; + gap: 7px; + padding: 2px 4px 3px; + border: none; + background: transparent; + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; + opacity: 0.72; + text-align: left; +} +.doc-email-collapse-btn:hover { opacity: 1; color: var(--accent, var(--red)); } +.doc-email-collapse-btn svg { + flex-shrink: 0; + opacity: 0.65; + transition: transform 0.14s ease; +} +.doc-email-collapse-summary { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + opacity: 0.74; +} +.doc-email-header.doc-email-header-collapsed .doc-email-collapse-btn svg { + transform: rotate(180deg); +} +.email-field { display: flex; align-items: center; gap: 8px; position: relative; } +.email-field label { font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.5; min-width: 50px; text-align: right; flex-shrink: 0; } +.email-field input { + flex: 1; width: 100%; background: transparent; border: 1px solid var(--border); border-radius: 4px; + padding: 5px 8px; font-size: 13px; font-family: inherit; color: var(--fg); outline: none; + min-width: 0; +} +.email-field input:focus { border-color: var(--accent, #4a9eff); } +@container docpane (max-width: 460px) { + .doc-email-header .email-field { + display: grid; + grid-template-columns: 1fr; + gap: 3px; + align-items: stretch; + } + .doc-email-header .email-field label { + min-width: 0; + text-align: left; + } + .doc-email-header .email-field input { + width: 100%; + } +} +/* Cc toggle and attach button are absolute so they don't steal width + from the To input. Anchored to the input's vertical center + 4px + nudge so the label sits where the user expects it. */ +.email-field .email-cc-toggle { + position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); + z-index: 2; +} +/* Accent-tinted prefix label (To / Cc / Bcc / Subject) sitting at + the left edge of each input. Replaces both the old tag + and the all-placeholder accent. */ +.email-field .email-field-prefix { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + font-size: 11px; + font-weight: 600; + color: var(--accent-primary, var(--red)); + pointer-events: none; + z-index: 1; + line-height: 1; +} +.email-field input { padding-left: 44px; } +.email-field #doc-email-subject { padding-left: 64px; } +/* X close button inside Cc / Bcc fields. */ +.email-field .email-cc-close { + position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); + background: none; border: none; color: var(--fg); + opacity: 0.4; cursor: pointer; + width: 22px; height: 22px; + display: inline-flex; align-items: center; justify-content: center; + border-radius: 4px; + padding: 0; + z-index: 2; +} +.email-field .email-cc-close:hover { + opacity: 1; + color: var(--accent, var(--red)); + background: color-mix(in srgb, currentColor 10%, transparent); +} +.email-field input { padding-right: 60px; } +.email-field #doc-email-cc, .email-field #doc-email-bcc { padding-right: 32px; } +.email-field #doc-email-subject { padding-right: 8px; } + +.doc-email-actions { + display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; + border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; + align-items: center; + position: relative; + z-index: 30; + pointer-events: auto; +} +#doc-actions-footer .email-send-split { + margin-left: auto; +} +.doc-save-state-icon { display: none; flex: 0 0 auto; } +.doc-save-button[data-save-state="dirty"] { color: var(--accent, var(--red)); } +.doc-save-button[data-save-state="saving"] { color: var(--fg-muted, var(--fg)); } +.doc-save-button[data-save-state="saved"] { color: var(--fg); } +.doc-save-button[data-save-state="error"] { color: var(--color-danger, #e06c75); } +.doc-stats-wrap { + position: relative; + margin-left: 0; + flex: 0 0 auto; +} +.doc-stats-btn { + height: 28px; + border: 0; + border-radius: 4px; + padding: 0 7px; + background: transparent; + color: var(--fg-muted, var(--fg)); + font: inherit; + font-size: 10px; + cursor: pointer; + white-space: nowrap; +} +.doc-stats-btn:hover, +.doc-stats-btn[aria-expanded="true"] { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 7%, transparent); +} +.doc-stats-btn.doc-stats-selected { + color: var(--accent, var(--red)); + font-weight: 600; +} +.doc-stats-popover { + position: fixed; + right: auto; + bottom: auto; + width: 190px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); + color: var(--fg); + font-size: 11px; +} +.doc-stats-scope { + padding-bottom: 6px; + margin-bottom: 5px; + border-bottom: 1px solid var(--border); + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + text-transform: uppercase; +} +.doc-stats-row { + min-height: 22px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; +} +/* WYSIWYG email body — what the recipient sees, edited in place. */ +.doc-email-richbody { + flex: 1; + min-height: 0; + overflow-y: auto; + padding: 12px 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.6; + color: var(--fg); + background: var(--bg); + outline: none; + white-space: pre-wrap; + text-align: left; + word-wrap: break-word; +} +.doc-email-richbody.richtext-mode { + background: var(--bg); +} +.doc-rich-empty-import { + position: absolute; + top: 90px; + right: 18px; + bottom: auto; + left: auto; + z-index: 2; + display: flex; + pointer-events: none; +} +.doc-rich-empty-import .doc-rich-empty-import-btn { + position: static; + float: none; + margin: 0; + opacity: 1; + pointer-events: auto; + transform: none; +} +.doc-email-richbody.richtext-mode ul.rich-checklist { + list-style: none; + padding-left: 1.8em; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li { + position: relative; + min-height: 1.6em; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li::before { + content: ""; + position: absolute; + left: -1.55em; + top: 0.3em; + width: 0.95em; + height: 0.95em; + box-sizing: border-box; + border: 1.5px solid color-mix(in srgb, var(--fg) 48%, transparent); + border-radius: 3px; + background: var(--sidebar-bg, var(--panel)); + cursor: pointer; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::before { + border-color: var(--accent, var(--red)); + background: var(--accent, var(--red)); +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::after { + content: ""; + position: absolute; + left: -1.27em; + top: 0.44em; + width: 0.28em; + height: 0.5em; + border: solid #fff; + border-width: 0 1.5px 1.5px 0; + transform: rotate(45deg); + pointer-events: none; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"] { + color: color-mix(in srgb, var(--fg) 62%, transparent); + text-decoration: line-through; +} +.doc-email-richbody.richtext-mode hr { + border: 0; + border-top: 1px solid var(--border); + margin: 1.25em 0; +} +.doc-email-richbody.richtext-mode hr.richtext-page-break { + height: 8px; + border-top: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); + border-bottom: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); + margin: 1.6em 0; + opacity: 0.8; +} +.doc-email-richbody.richtext-mode table { + width: 100%; + margin: 12px 0; + border-collapse: collapse; + table-layout: fixed; +} +.doc-email-richbody.richtext-mode th, +.doc-email-richbody.richtext-mode td { + min-width: 48px; + padding: 7px 8px; + border: 1px solid var(--border); + vertical-align: top; + overflow-wrap: anywhere; +} +.doc-email-richbody.richtext-mode th { + background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 85%, var(--fg) 15%); + font-weight: 600; +} +.doc-email-richbody.richtext-mode code, +.doc-email-richbody.richtext-mode span[style*="OdysseusInlineCode"] { + padding: 0.12em 0.35em; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + border-radius: 3px; + background: color-mix(in srgb, var(--fg) 8%, transparent); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + white-space: break-spaces; +} +.doc-email-richbody.richtext-mode pre { + margin: 1em 0; + padding: 12px 14px; + overflow-x: auto; + border: 1px solid var(--border); + border-radius: 4px; + background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 82%, #000 18%) !important; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + line-height: 1.5; + white-space: pre-wrap; + tab-size: 2; +} + +::highlight(doc-find-results) { + color: inherit; + background-color: color-mix(in srgb, #facc15 48%, transparent); +} + +::highlight(doc-find-current) { + color: inherit; + background-color: color-mix(in srgb, var(--accent, #ef4444) 68%, #facc15); +} + +::highlight(doc-ai-selections) { + color: inherit; + background-color: color-mix(in srgb, var(--accent, #ef4444) 30%, transparent); +} +.doc-email-richbody img.richtext-image { + display: block; + margin: 12px 0; + max-width: 100%; + height: auto; + border-radius: 4px; + cursor: pointer; +} +.doc-email-richbody figure.richtext-image { + margin: 12px 0; + max-width: 100%; +} +.doc-email-richbody figure.richtext-image:not(:has(img)) { display: none; } +.doc-email-richbody figure.richtext-image img.richtext-image { + display: block; + margin-top: 0; + margin-bottom: 0; + max-width: 100%; + height: auto; + border-radius: 4px; +} +.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-60) .richtext-image-caption { width: 60%; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-35) .richtext-image-caption { width: 35%; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-left) .richtext-image-caption { margin-left: 0; margin-right: auto; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-center) .richtext-image-caption { margin-left: auto; margin-right: auto; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-right) .richtext-image-caption { margin-left: auto; margin-right: 0; } +.doc-email-richbody img.richtext-image-size-100 { width: 100%; } +.doc-email-richbody img.richtext-image-size-60 { width: 60%; } +.doc-email-richbody img.richtext-image-size-35 { width: 35%; } +.doc-email-richbody img.richtext-image-align-left { margin-left: 0; margin-right: auto; } +.doc-email-richbody img.richtext-image-align-center { margin-left: auto; margin-right: auto; } +.doc-email-richbody img.richtext-image-align-right { margin-left: auto; margin-right: 0; } +.doc-email-richbody img.richtext-image[data-editor-image-selected] { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 3px; +} +.doc-rich-image-toolbar { + position: fixed; + display: inline-flex; + align-items: center; + gap: 2px; + padding: 2px; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); + z-index: 10032; +} +.doc-rich-image-toolbar button { + width: 24px; + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + border: 0; + border-radius: 3px; + background: transparent; + color: inherit; + cursor: pointer; +} +.doc-rich-image-toolbar button:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--accent, var(--red)); +} +.doc-rich-image-toolbar .doc-rich-image-remove:hover { + color: var(--accent, var(--red)); +} +.doc-rich-image-source-menu { + position: fixed; + width: 260px; + min-width: 260px; + padding: 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 4px 16px color-mix(in srgb, var(--fg) 20%, transparent); + z-index: 10033; + font-size: 11px; +} +.doc-rich-image-source-title { + padding: 5px 7px 4px; + color: var(--fg); + font-weight: 700; + opacity: .75; +} +.doc-rich-image-source-menu > button, +.doc-rich-gallery-item { + width: 100%; + display: flex; + align-items: center; + gap: 7px; + padding: 7px 8px; + border: 0; + border-radius: 4px; + background: transparent; + color: var(--fg); + font: inherit; + text-align: left; + cursor: pointer; +} +.doc-rich-image-source-menu > button[data-image-source="computer"] { + padding-left: 9px; +} +.doc-rich-image-source-menu > button:hover, +.doc-rich-gallery-item:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--accent, var(--red)); +} +.doc-rich-gallery-list { + display: grid; + gap: 2px; + max-height: 300px; + overflow-y: auto; +} +.doc-rich-gallery-item { + min-height: 44px; + min-width: 0; +} +.doc-rich-gallery-item > span:last-child { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.doc-rich-gallery-thumb { + width: 40px; + height: 40px; + flex: 0 0 40px; + border: 1px solid var(--border); + border-radius: 4px; + background-position: center; + background-size: cover; + background-repeat: no-repeat; +} +.rich-color-letter { + border-bottom: 2px solid var(--accent, var(--red)); + font-weight: 700; + line-height: 14px; +} +.rich-color-swatch { + width: 13px !important; + height: 13px !important; + border: 1px solid color-mix(in srgb, var(--fg) 35%, transparent); + border-radius: 50%; +} +.rich-highlight-swatch { border-radius: 50%; } +.rich-color-swatch.is-transparent { + background: + linear-gradient(135deg, transparent 43%, var(--red) 44%, var(--red) 56%, transparent 57%), + var(--bg); +} +.rich-color-palette-menu { + min-width: min(292px, calc(100vw - 16px)); +} +.rich-color-palette-menu .rich-color-reset { + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + margin-bottom: 3px; + padding-bottom: 7px; +} +.rich-color-palette-grid { + display: grid; + grid-template-columns: repeat(2, minmax(126px, 1fr)); + gap: 0 4px; + padding: 0 4px; +} +.rich-color-palette-group { + min-width: 0; +} +.rich-color-palette-label { + padding: 4px 8px 3px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 9px; + font-weight: 600; + text-transform: uppercase; +} +.rich-color-palette-group .doc-overflow-item { + min-width: 0; + padding-inline: 8px; +} +.rich-toolbar-color-custom { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-top: 3px; + padding: 7px 9px 4px 10px; + border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + color: var(--fg); + font-size: 11px; +} +.rich-toolbar-color-input.cp-swatch-input { + width: 22px; + height: 22px; + flex: 0 0 22px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 28%, var(--border)); + border-radius: 50%; + cursor: pointer; +} +.rich-toolbar-color-input.cp-swatch-input:focus-visible { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 2px; +} + +.rich-toolbar-slider-row { + display: grid; + grid-template-columns: auto minmax(104px, 1fr) 42px; + align-items: center; + gap: 9px; + min-width: 220px; + padding: 10px; + color: var(--fg); + font-size: 11px; +} +.rich-toolbar-range { + width: 100%; + min-width: 0; + height: 8px; + margin: 0; + border: 0; + border-radius: 999px; + background: color-mix(in srgb, var(--fg) 25%, transparent); + accent-color: var(--red); + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} +.rich-toolbar-range::-webkit-slider-thumb { + width: 9px; + height: 9px; + border: 0; + border-radius: 50%; + background: var(--red); + cursor: pointer; + -webkit-appearance: none; + appearance: none; + transition: width 0.12s ease, height 0.12s ease; +} +.rich-toolbar-range:hover::-webkit-slider-thumb, +.rich-toolbar-range:focus::-webkit-slider-thumb, +.rich-toolbar-range:active::-webkit-slider-thumb { + width: 16px; + height: 16px; +} +.rich-toolbar-range::-moz-range-thumb { + width: 9px; + height: 9px; + border: 0; + border-radius: 50%; + background: var(--red); + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.rich-toolbar-range:hover::-moz-range-thumb, +.rich-toolbar-range:focus::-moz-range-thumb, +.rich-toolbar-range:active::-moz-range-thumb { + width: 16px; + height: 16px; +} +.rich-toolbar-range:focus-visible { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 4px; +} +.rich-toolbar-slider-value { + width: 42px; + box-sizing: border-box; + padding: 2px 3px; + border: 1px solid transparent; + border-radius: 3px; + background: transparent; + text-align: right; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-variant-numeric: tabular-nums; + -webkit-appearance: textfield; + appearance: textfield; +} +.rich-toolbar-slider-value::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +.rich-toolbar-slider-value:focus { + border-color: var(--accent, var(--red)); + outline: none; + color: var(--fg); +} + +.rich-script-icon, +.rich-font-icon, +.rich-font-size-icon { + display: inline-block; + line-height: 1; + font-size: 11px; +} +.rich-highlight-icon { + display: block; + flex-shrink: 0; + color: var(--accent-primary, var(--red)); +} + +.rich-script-icon sup, +.rich-script-icon sub { + font-size: 7px; + line-height: 0; +} + +.rich-font-icon { + font-family: Georgia, serif; + font-size: 12px; +} + +.rich-font-size-icon { + min-width: 16px; + font-size: 10px; + font-weight: 600; + font-variant-numeric: tabular-nums; + text-align: center; +} +.rich-align-center { text-align: center; } +.rich-align-right { text-align: right; } +.doc-email-richbody .email-quoted-history { + margin-top: 14px; + padding: 10px 0 10px 12px; + border-left: 3px solid var(--border); + color: color-mix(in srgb, var(--fg) 70%, transparent); + font-size: 0.92em; + line-height: 1.45; + white-space: pre-wrap; + user-select: text; + pointer-events: none; +} +.doc-email-richbody h1, .doc-email-richbody h2, .doc-email-richbody h3, +.doc-email-richbody h4, .doc-email-richbody h5, .doc-email-richbody h6 { margin: 0.4em 0; } +.email-more-menu { + position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; + min-width: 160px; background: var(--bg); border: 1px solid var(--border); + border-radius: 6px; box-shadow: 0 -4px 12px rgba(0,0,0,0.2); padding: 4px; + z-index: 100; +} +.email-send-btn { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 20%, transparent); + color: var(--accent-primary, var(--red)); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); + border-radius: 6px; + padding: 0 14px; height: 28px; font-size: 11px; font-weight: 600; cursor: pointer; + font-family: inherit; transition: all 0.15s; white-space: nowrap; + display: inline-flex; align-items: center; gap: 6px; +} +/* Split send button: [ Send │ ▾ ] — the caret drops the send-options menu UP. */ +.email-send-split { position: relative; display: inline-flex; } +.email-send-split .email-send-main { + border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; +} +.email-send-split .email-send-caret { + border-top-left-radius: 0; border-bottom-left-radius: 0; + padding: 0 8px; gap: 0; + /* its left border is the single divider between the two halves */ + border-left: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); +} +.email-send-split .email-send-caret svg { transition: transform 0.15s ease; } +.email-send-split .email-send-caret[aria-expanded="true"] svg { transform: none; } +.email-send-btn svg { flex-shrink: 0; } +.email-send-btn:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); + border-color: var(--accent-primary, var(--red)); +} +.email-draft-btn { + background: none; border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border-radius: 6px; + padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; + font-family: inherit; transition: all 0.15s; white-space: nowrap; +} +.email-draft-btn:hover { border-color: var(--fg); color: var(--fg); } +.email-discard-btn { + background: transparent; + /* Dim the label text via color-mix instead of `opacity:0.6` on the whole + button — opacity multiplies through to the SVG and washes out the accent + X glyph. Per-element color dimming keeps the X at full accent strength. */ + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 14px; font-size: 12px; cursor: pointer; font-family: inherit; +} +/* The ✕ glyph itself reads in accent. */ +.email-discard-btn svg { color: var(--accent-primary, var(--red)); opacity: 1; } +.email-discard-btn:hover { opacity: 1; color: var(--red, #e55); border-color: var(--red, #e55); } + +/* Compose email "more" menu button — icon only, matches draft btn height */ +#doc-email-more-btn { + background: none; border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border-radius: 6px; + width: 28px; height: 28px; padding: 0; + cursor: pointer; font-family: inherit; transition: all 0.15s; + display: inline-flex; align-items: center; justify-content: center; +} +#doc-email-more-btn:hover { + border-color: var(--fg); color: var(--fg); +} +.email-more-wrap { display: inline-flex; } +.email-attachments { + display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 0 58px; +} +.email-attachment-chip { + display: inline-flex; align-items: center; gap: 4px; + padding: 4px 8px; font-size: 11px; background: var(--hover-bg, rgba(255,255,255,0.05)); + border: 1px solid var(--border); border-radius: 12px; color: var(--fg); + text-decoration: none; cursor: pointer; transition: background 0.15s, border-color 0.15s; + max-width: 200px; min-width: 0; +} +.email-attachment-chip:hover { + background: color-mix(in srgb, var(--accent) 15%, transparent); + border-color: var(--accent); +} +.email-attachment-chip.is-expanded { + max-width: 90vw; +} +.email-attachment-chip > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + flex: 1 1 auto; min-width: 0; +} +.email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-attachment-chip-related { + border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); +} +/* "Open in editor" launch icon — same prominent style on desktop AND mobile + (was 24px / dim / no border on desktop, easy to miss). Accent-tinted + background + border makes it read as a real action. */ +.email-attachment-open, +.email-attachment-download, +.email-attachment-calendar-open, +.cal-event-email-source { + display: inline-flex; align-items: center; gap: 4px; + height: 22px; padding: 0 9px; border-radius: 999px; + margin-left: 6px; flex: 0 0 auto; + min-width: 52px; justify-content: center; + font-size: 10px; font-weight: 500; letter-spacing: 0.02em; + color: var(--accent-primary, var(--red)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); + cursor: pointer; transition: background 0.1s, border-color 0.1s, color 0.1s; +} +.email-attachment-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-open svg, +.email-attachment-open > svg, +.email-attachment-download svg, +.email-attachment-download > svg, +.email-attachment-calendar-open svg, +.email-attachment-calendar-open > svg, +.cal-event-email-source svg, +.cal-event-email-source > svg { + width: 12px; height: 12px; + opacity: 0.9; +} +.email-attachment-open:hover, +.email-attachment-download:hover, +.email-attachment-calendar-open:hover, +.cal-event-email-source:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 70%, transparent); +} +.email-attachment-download { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-calendar-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.cal-event-email-source { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-open-label { line-height: 1; } +.email-attachment-chip.is-expanded .email-attachment-open, +.email-attachment-chip.is-expanded .email-attachment-download, +.email-attachment-chip.is-expanded .email-attachment-calendar-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-chip.is-expanded .email-attachment-open-label { + display: none; +} +@media (max-width: 768px) { + .doc-email-collapse-btn { + background: inherit; + } + /* Mobile: keep the pill but ensure a comfortable touch target. */ + .email-attachment-open { + height: 26px; padding: 0 10px; + min-height: 26px !important; + min-width: 58px; + } + .email-attachments, + .email-compose-atts { + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-left: 0; + padding-bottom: 2px; + } + .email-attachments::-webkit-scrollbar, + .email-compose-atts::-webkit-scrollbar { + display: none; + } + /* Attachment chip body — modest minimum height so the open icon sits + neatly without dominating. */ + .email-attachment-chip, + .email-compose-chip { + flex: 0 0 auto; + padding: 6px 8px !important; + min-height: 36px !important; + } + .email-attachment-chip.is-expanded { + flex-wrap: nowrap; + align-items: center; + max-width: min(92vw, 360px); + } + .email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + flex: 1 1 auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.25; + min-width: 0; + } + .email-attachment-chip.is-expanded .att-size { + margin-left: 0; + } + .email-attachment-chip.is-expanded .email-attachment-open, + .email-attachment-chip.is-expanded .email-attachment-download, + .email-attachment-chip.is-expanded .email-attachment-calendar-open { + width: 28px; + min-width: 28px; + padding: 0; + margin-left: 4px; + } + .email-compose-chip .compose-chip-name { + max-width: 190px; + } +} + +/* Compose attachment chips (when sending new email) */ +.email-compose-atts { + display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; + padding: 6px 0 0 0; +} +.email-compose-chip { + display: inline-flex; align-items: center; gap: 4px; + flex: 0 1 auto; + min-width: 0; + max-width: min(240px, 100%); + height: 24px; min-height: 24px; box-sizing: border-box; + padding: 2px 3px 2px 7px; font-size: 11px; + background: var(--hover-bg, rgba(255,255,255,0.05)); + border: 1px solid var(--border); border-radius: 12px; color: var(--fg); +} +.email-compose-chip .compose-chip-name { max-width: 180px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.email-compose-chip .compose-chip-remove { + background: none; border: none; color: var(--fg); opacity: 0.5; + height: 18px; font-size: 14px; line-height: 18px; padding: 0 3px; cursor: pointer; + flex-shrink: 0; +} +.email-compose-chip .compose-chip-remove:hover { opacity: 1; color: var(--red, #e55); } + +.pdf-annotation-line-value { + width: 48px; + height: 22px; + box-sizing: border-box; + padding: 0 5px; + border: 1px solid var(--border); + border-radius: 4px; + outline: none; + background: var(--bg-elev, var(--panel)); + color: var(--fg); + font-family: inherit; + font-size: 10px; + font-weight: 500; + line-height: 1; + text-align: right; +} +.pdf-annotation-line-value:hover { border-color: color-mix(in srgb, var(--fg) 38%, var(--border)); } +.pdf-annotation-line-value:focus { border-color: var(--accent, var(--red)); } + +.email-odysseus-attach-menu { + position: fixed; + z-index: 10050; + width: 300px; + max-width: calc(100vw - 16px); + max-height: min(420px, calc(100vh - 16px)); + padding: 7px; + border: 1px solid var(--border); + border-radius: 7px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 12px 30px rgba(0,0,0,0.28); + overflow: hidden; +} +.email-odysseus-attach-local { + width: 100%; + height: 30px; + display: flex; + align-items: center; + gap: 7px; + padding: 0 9px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, var(--border)); + border-radius: 5px; + background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-local:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); +} +.email-odysseus-attach-tabs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 5px; + margin: 7px 0; +} +.email-odysseus-attach-tabs button { + height: 26px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border: 1px solid var(--border); + border-radius: 5px; + background: transparent; + color: var(--fg); + opacity: 0.72; + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-tabs button.active { + opacity: 1; + color: var(--accent-primary, var(--red)); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); +} +.email-odysseus-attach-tabs button svg { + flex-shrink: 0; +} +.email-odysseus-attach-search-wrap { + height: 28px; + display: flex; + align-items: center; + gap: 6px; + margin: -1px 0 7px; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 5px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + color: color-mix(in srgb, var(--fg) 48%, transparent); +} +.email-odysseus-attach-search-wrap:focus-within { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + color: var(--accent-primary, var(--red)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, transparent); +} +.email-odysseus-attach-search { + min-width: 0; + flex: 1; + border: 0; + outline: 0; + background: transparent; + color: var(--fg); + font: inherit; + font-size: 11px; +} +.email-odysseus-attach-search::placeholder { + color: color-mix(in srgb, var(--fg) 38%, transparent); +} +.email-odysseus-attach-list { + max-height: 320px; + overflow: auto; + display: flex; + flex-direction: column; + gap: 4px; +} +.email-odysseus-attach-row { + width: 100%; + min-height: 38px; + display: flex; + align-items: center; + gap: 8px; + padding: 6px 7px; + border: 1px solid transparent; + border-radius: 5px; + background: transparent; + color: var(--fg); + text-align: left; + font: inherit; + cursor: pointer; +} +.email-odysseus-attach-row:hover { + background: var(--hover-bg, rgba(255,255,255,0.06)); + border-color: var(--border); +} +.email-odysseus-attach-row.is-selected { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); +} +.email-odysseus-attach-dot { + width: 8px; + height: 8px; + flex: 0 0 auto; + display: inline-block; + border: 1px solid var(--border); + border-radius: 50%; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.email-odysseus-attach-row.is-selected .email-odysseus-attach-dot { + border-color: var(--accent-primary, var(--red)); + background: var(--accent-primary, var(--red)); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); +} +.email-odysseus-attach-icon, +.email-odysseus-attach-thumb { + width: 28px; + height: 28px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 7%, transparent); + overflow: hidden; +} +.email-odysseus-attach-main { + min-width: 0; + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} +.email-odysseus-attach-title, +.email-odysseus-attach-meta { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-odysseus-attach-title { + font-size: 11px; + line-height: 1.2; +} +.email-odysseus-attach-meta { + font-size: 10px; + line-height: 1.15; + opacity: 0.52; +} +.email-odysseus-attach-empty { + padding: 14px 8px; + color: var(--muted, #888); + font-size: 11px; + text-align: center; +} +.email-odysseus-attach-actions { + display: flex; + align-items: center; + gap: 6px; + margin-top: 2px; + padding-top: 5px; + border-top: 1px solid color-mix(in srgb, var(--border) 75%, transparent); + background: var(--bg); + position: sticky; + bottom: 0; +} +.email-odysseus-attach-count { + flex: 1; + min-width: 0; + color: var(--muted, #888); + font-size: 11px; +} +.email-odysseus-attach-selected { + height: 28px; + min-width: 72px; + padding: 0 12px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + background: var(--accent-primary, var(--red)); + color: #fff; + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-selected svg { + flex-shrink: 0; +} + +.email-cc-toggle { + background: none; border: none; color: var(--fg); + opacity: 0.4; font-size: 11px; cursor: pointer; + padding: 4px 8px; font-family: inherit; +} +.email-cc-toggle:hover { + opacity: 1; + color: var(--accent, #4a9eff); + background: none !important; +} + +@media (max-width: 768px) { + .doc-email-collapse-btn { + display: flex; + } +} + +.email-autocomplete { + position: absolute; top: 100%; left: 58px; right: 0; z-index: 1000; + background: var(--bg); border: 1px solid var(--border); border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 240px; overflow-y: auto; + margin-top: 2px; +} +.contact-suggestion { + display: flex; justify-content: space-between; gap: 8px; + padding: 6px 10px; font-size: 12px; cursor: pointer; + border-bottom: 1px solid var(--border); +} +.contact-suggestion:last-child { border-bottom: none; } +.contact-suggestion:hover, +.contact-suggestion.active, +.contact-suggestion[aria-selected="true"] { + background: color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 15%, transparent); + outline: 1px solid color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 40%, transparent); + outline-offset: -1px; +} +.contact-suggestion .contact-name { font-weight: 600; color: var(--fg); } +.contact-suggestion .contact-email { opacity: 0.6; font-size: 11px; } +.contact-suggestion-status { + padding: 7px 10px; + color: color-mix(in srgb, var(--fg) 65%, transparent); + font-size: 11px; + cursor: default; +} +.contact-suggestion-status.is-loading { opacity: 0.72; } +.contact-suggestion-status.is-unavailable { + color: color-mix(in srgb, var(--red) 82%, var(--fg)); +} +.email-ai-reply-btn { + background: none; + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; + font-family: inherit; transition: all 0.15s; + display: inline-flex; align-items: center; white-space: nowrap; +} +.email-ai-reply-btn:hover { + border-color: var(--accent, #4a9eff); + color: var(--accent, #4a9eff); +} +/* Align select/new buttons in popup library toolbar */ +#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn, #email-lib-compose-btn { position: relative; top: -4px; } +/* Select + Refresh sit slightly lower than Compose on desktop. */ +#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -2px; } + +#email-lib-modal.email-settings-mode .email-accounts-row, +#email-lib-modal.email-settings-mode .admin-card > .memory-toolbar, +#email-lib-modal.email-settings-mode #email-lib-bulk, +#email-lib-modal.email-settings-mode #email-lib-grid, +#email-lib-modal.email-settings-mode #email-lib-sync-status, +#email-lib-modal.email-settings-mode #email-lib-fab { + display: none !important; +} +#email-lib-settings-page[hidden] { + display: none !important; +} +#email-lib-modal.email-settings-mode #email-lib-settings-page { + display: flex; +} +.email-settings-page { + display: flex; + flex: 1; + min-height: 0; + min-width: 0; + flex-direction: column; + gap: 6px; + overflow: hidden; + padding: 2px 0 8px; + width: 100%; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; +} +.settings-email-preferences-card { + display: contents; +} +.settings-email-preferences-card .email-settings-section { + margin: 0 0 10px; + padding: 14px 12px 12px; + background: var(--panel); +} +.settings-email-preferences-card .email-settings-section:last-child { + margin-bottom: 0; +} +.settings-email-preferences-card .email-settings-style-section { + margin-bottom: 4px; +} +.settings-email-default-card #settings-email-default-host { + display: flex; + justify-content: flex-end; + min-width: 0; + margin-left: auto; +} +.settings-email-default-card .settings-email-default-title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} +.settings-email-default-card .settings-email-default-title > span { + min-width: 0; +} +.settings-email-default-card .settings-email-default-title > span > svg { + color: var(--accent, var(--red)); +} +.settings-email-default-card .email-settings-account-picker { + margin-left: 0 !important; +} +.settings-email-default-card .email-settings-support-copy { + position: relative; + top: 6px; +} +.settings-email-default-spacer { + height: 12px; +} +.settings-email-default-card .cookbook-srv-default-label { + position: relative; + top: 2px; +} +.settings-email-default-card .cookbook-srv-default-icon > svg { + position: relative; + top: 7px; +} +.settings-email-settings-legacy { + display: none; +} + +/* Keep the standalone Email Settings cards aligned with the expandable + preference cards below them. */ +[data-settings-panel="email"] > .admin-card { + padding-top: 14px; +} +.email-settings-page-host { + display: flex; + flex: 0 0 auto; + min-height: 0; + padding: 0; + overflow: visible; +} +.settings-email-preferences-card > .email-settings-page-host { width: 100%; } +.email-settings-page-head { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + min-height: 32px; + padding-bottom: 2px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); +} +.email-settings-account-picker { + min-width: 0; + max-width: 100%; +} +.email-settings-account-picker select { + min-width: 0; + max-width: 100%; +} +.email-settings-back { + position: relative; + top: -2px; +} +.email-settings-header-back { + background: transparent; + color: color-mix(in srgb, var(--fg) 58%, transparent); + border: 1px solid transparent; + width: auto; + height: 24px; + padding: 0 8px 0 6px; + align-items: center; + justify-content: center; + gap: 4px; + cursor: pointer; + border-radius: 999px; + flex-shrink: 0; + position: relative; + font-size: 11px; + font-weight: 600; + line-height: 1; +} +.email-settings-header-back:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); + border-color: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--fg); +} +.email-settings-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; +} +.email-settings-title { + display: inline-flex; + align-items: center; + gap: 6px; + flex: 1 1 auto; + width: 100%; + min-width: 0; + font-size: 12px; + font-weight: 700; +} +.email-settings-title svg { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-settings-close { + width: 24px; + height: 24px; + border: 1px solid var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 70%, transparent); + cursor: pointer; + font-size: 14px; + line-height: 1; +} +.email-settings-close:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)); +} +.email-settings-body { + display: flex; + flex-direction: column; + flex: 1 1 auto; + gap: 6px; + padding-top: 4px; + box-sizing: border-box; + min-width: 0; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + overscroll-behavior: contain; +} +.email-settings-loading, +.email-settings-error { + font-size: 12px; + color: color-mix(in srgb, var(--fg) 65%, transparent); + padding: 8px 2px; +} +.email-settings-loading { + display: inline-flex; + align-items: center; + gap: 7px; +} +.email-settings-loading-css-whirlpool { + display: inline-block; + width: 14px; + height: 14px; + box-sizing: border-box; + border: 1.5px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + border-top-color: var(--accent, var(--red)); + border-right-color: color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); + border-radius: 50%; + animation: whirlpool-spin 0.72s linear infinite; + flex: 0 0 auto; +} +.email-settings-loading-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: var(--accent, var(--red)); +} +.email-settings-section { + display: flex; + flex-direction: column; + flex: 0 0 auto; + gap: 7px; + margin: 0; + padding: 14px 8px 8px; + min-width: 0; + box-sizing: border-box; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + transition: opacity 0.15s ease, background 0.15s ease; +} +.email-settings-section.is-disabled { + opacity: 0.58; + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +.email-settings-section-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.email-settings-section-head > div:first-child { + min-width: 0; + flex: 1 1 auto; +} +.email-settings-section-head > .email-settings-enabled-control, +.email-settings-section-head > button { + margin-left: auto; +} +.email-settings-section-toggle { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + width: 100%; + min-width: 0; + padding: 0; + border: 0; + background: transparent; + color: inherit; + text-align: left; + cursor: pointer; +} +.email-settings-section-toggle:hover, +.email-settings-section-toggle:active { + background: transparent; +} +.email-settings-section-toggle-copy { + display: flex; + flex-direction: column; + align-items: flex-start; + min-width: 0; + flex: 1 1 auto; +} +.email-settings-section-toggle > .email-settings-enabled-control { + margin-left: auto; + flex: 0 0 auto; +} +.email-settings-section-chevron { + flex: 0 0 auto; + color: color-mix(in srgb, var(--fg) 62%, transparent); + transition: transform .14s ease, color .14s ease; +} +.email-settings-section-toggle:hover .email-settings-section-chevron, +.email-settings-section-toggle:focus-visible .email-settings-section-chevron { + color: var(--accent, var(--red)); +} +.email-settings-section-title { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 14px; + line-height: 1; + font-weight: 600; + letter-spacing: 0; + margin: 0; + padding: 0; +} +.email-settings-section-title svg { + color: var(--accent, var(--red)); + opacity: 0.6; + flex-shrink: 0; +} +.email-settings-account-scope { + margin-left: 4px; + font-size: 11px; + font-weight: 500; + color: color-mix(in srgb, var(--fg) 60%, transparent); +} +.email-settings-section-body { + display: flex; + flex-direction: column; + gap: 7px; + min-width: 0; + width: 100%; +} +.email-settings-section[open] > .email-settings-section-body { + min-height: 0; + flex: 0 0 auto; + height: auto; + max-height: none; + overflow: visible; + overscroll-behavior: contain; + padding: 0 4px 4px 0; + scrollbar-width: thin; +} +.email-settings-section[open] { + min-height: 0; + flex: 0 0 auto; +} +.email-settings-auto-reply-section[open], +.email-settings-cleanup-section[open]:has(.email-unsub-card) { + flex: 1 1 auto; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + align-content: stretch; +} +.email-settings-auto-reply-section[open] > .email-settings-section-body, +.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body { + flex: 0 0 auto; + height: 100%; + max-height: 100%; + overflow-y: auto; +} +.email-settings-section[open] > .email-settings-section-body > * { + flex: 0 0 auto; +} +.email-settings-section-body-head { + display: flex; + justify-content: flex-end; +} +.email-settings-display-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.email-settings-display-title { + display: inline-flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.email-settings-display-title > svg { + flex: 0 0 auto; + color: var(--accent, var(--red)); +} +.email-settings-display-title-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + position: relative; + top: 10px; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-display-title-row .email-settings-section-title { + flex: 1 1 auto; + width: auto; + padding-bottom: 0; + border-bottom: 0; +} +.email-settings-style-section .email-settings-section-title { + display: flex; + width: 100%; + position: relative; + top: 0; + padding-bottom: 8px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-style-section .email-settings-section-head, +.email-settings-style-section .email-settings-section-body { + position: static; +} +.email-settings-style-section .email-settings-section-head { margin-top: 8px; } +.email-settings-style-section .email-settings-section-body { margin-top: 12px; } +.email-settings-style-section .email-settings-section-desc { + margin-top: 16px; + top: 0; +} +.email-settings-auto-reply-section .email-settings-section-title, +.email-settings-cleanup-section .email-settings-section-title { + display: flex; + width: 100%; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-cleanup-section .email-settings-section-title { + position: relative; + top: 10px; +} +.email-settings-auto-reply-section .email-settings-section-desc, +.email-settings-cleanup-section .email-settings-section-desc { + margin-top: 10px; +} +.email-settings-display-section .email-settings-section-desc, +.email-settings-cleanup-section .email-settings-section-desc, +.email-settings-inline-images-section .email-settings-section-desc { + margin-top: 12px; + top: 12px; +} +.email-settings-auto-reply-title-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + position: relative; + top: 10px; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-auto-reply-section .email-settings-section-head { + margin-top: 8px; +} +.email-settings-auto-reply-section .email-settings-section-body { + margin-top: 8px; +} +.email-settings-auto-reply-section .email-settings-section-desc { + margin-top: 12px; + top: 0; +} +.email-settings-display-section .email-settings-section-desc { + margin-top: 10px; + top: 10px; +} +.email-settings-auto-reply-section .email-settings-section-body { + margin-top: 14px; +} +.email-settings-auto-reply-title-row .email-settings-section-title { + flex: 1 1 auto; + width: auto; + padding-bottom: 0; + border-bottom: 0; +} +.email-settings-support-copy { + color: color-mix(in srgb, var(--fg) 65%, transparent); + font-size: 11px; + line-height: 1.3; +} +.email-settings-clean-btn > svg { + color: var(--accent, var(--red)); +} +.email-settings-clean-btn { + height: 32px; +} +.email-unsub-rescan-btn { + height: 32px; +} +.email-settings-clean-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 7px; + min-width: 0; + margin-left: auto; +} +.email-settings-clean-status { + display: none; + font-size: 10.5px !important; + flex: 0 1 auto; + width: auto !important; + max-width: min(52vw, 360px); + min-width: 0; + overflow: hidden; + text-align: right; + white-space: nowrap; + text-overflow: ellipsis; + position: relative; + top: 2px; +} +.email-settings-clean-status.is-busy { + order: 0; + align-self: center; + width: auto !important; + max-width: 100%; + margin-right: auto; + text-align: left; +} +.email-settings-clean-btn { + flex: 0 0 auto; +} +.email-settings-default .cookbook-srv-default-icon > svg { + position: relative; + top: 3px; +} +.email-unsub-auto-safe-btn > svg { + position: relative; + top: 2px; +} +.email-unsub-auto-safe-btn { + height: 29px; +} +.email-unsub-status, +.email-unsub-panel-status { + position: relative; + top: 2px; +} +.email-unsub-panel-status { + flex: 1 1 220px; + min-width: 0; + margin-right: auto; + overflow: hidden; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-size: 11px; + line-height: 1.3; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-unsub-panel-status.is-error { + color: var(--red, #e57373); +} +.email-unsub-card .email-tag { + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); +} +.email-unsub-duplicate-badge { + position: relative; + top: 2px; +} +.email-unsub-accent-icon { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-unsub-card { + position: relative; + transition: opacity .16s ease; +} +.email-unsub-card-details { + display: flex; + flex-direction: column; + gap: 7px; +} +.email-unsub-card-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 24px; + height: 24px; + padding: 0; + border: 0; + background: transparent; + color: color-mix(in srgb, var(--fg) 62%, transparent); + cursor: pointer; +} +.email-unsub-card-toggle:hover, +.email-unsub-card-toggle:focus-visible { + color: var(--accent, var(--red)); +} +.email-unsub-card-toggle > svg { + transition: transform .14s ease; +} +.email-unsub-done-badge { + display: inline-flex; + align-items: center; + gap: 3px; +} +.email-unsub-done-btn { + display: inline-flex; + align-items: center; + gap: 3px; + flex: 0 0 auto; + padding: 3px 6px; + position: relative; + top: -6px; +} +.email-unsub-card-details .email-unsub-done-btn { + top: 0; +} +.email-unsub-done-btn > svg { + color: var(--accent, var(--red)); +} +.email-unsub-ignore-btn { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 22px; + height: 22px; + padding: 0; + position: absolute; + top: 5px; + right: 6px; + color: color-mix(in srgb, var(--fg) 58%, transparent); +} +.email-unsub-card-details .email-unsub-ignore-btn { + position: static; +} +.email-unsub-ignore-btn:hover { + color: var(--accent, var(--red)); +} +.email-unsub-remaining-actions { + display: inline-flex; + align-items: center; + gap: 7px; + flex-wrap: wrap; +} +.email-unsub-remaining-actions button { + display: inline-flex; + align-items: center; + gap: 4px; + position: relative; + top: -2px; +} +.email-unsub-remaining-actions svg { + flex: 0 0 auto; +} +.email-unsub-link-btn { + position: relative; + top: 2px; +} +.email-unsub-status.is-error { + color: var(--accent, var(--red)) !important; +} +.email-settings-section-desc { + position: relative; + top: 8px; + margin-top: 4px; + font-size: 10.5px; + line-height: 1.25; + color: color-mix(in srgb, var(--fg) 55%, transparent); +} +.email-settings-inline-images-section .email-settings-section-desc { + margin-bottom: 4px; +} +.email-settings-inline-images-spacer { + height: 12px; +} +.email-settings-display-spacer { + height: 10px; +} +.email-settings-display-section .email-settings-section-desc { + margin-top: 10px; + top: 10px; +} +.email-settings-display-section, +.email-settings-inline-images-section { + padding-bottom: 18px; +} +.email-settings-cleanup-section .email-settings-section-desc { + margin-top: 14px; + top: 14px; +} +.email-settings-auto-reply-section .email-settings-section-desc { + margin-top: 12px; + top: 10px; +} +.email-settings-toggle, +.email-settings-check { + display: flex; + align-items: center; + gap: 7px; + font-size: 12px; + line-height: 1.25; +} +.email-settings-toggle { + flex-shrink: 0; +} +.email-settings-enabled-control { + display: inline-flex; + align-items: center; + gap: 7px; + flex-shrink: 0; +} +.email-settings-enabled-state { + font-size: 10px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.email-settings-section.is-enabled .email-settings-enabled-state { + color: var(--accent, var(--red)); +} +.email-settings-section.is-disabled .email-settings-enabled-state { + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.email-settings-display-section.is-enabled .email-settings-enabled-state { + color: var(--accent, var(--red)); +} +.email-settings-check { + min-height: 24px; + padding: 3px 0; +} +.email-settings-check > span:first-child { + min-width: 0; + margin-right: auto; +} +.email-settings-check-spacer { + flex: 1; +} +.email-settings-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 7px; + min-width: 0; +} +.email-settings-grid label, +.email-settings-field { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; +} +.email-settings-grid span, +.email-settings-field > span { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0; + color: color-mix(in srgb, var(--fg) 56%, transparent); +} +.email-settings-page input[type="date"], +.email-settings-page input[type="datetime-local"], +.email-settings-page select, +.email-settings-page textarea { + width: 100%; + min-width: 0; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + color: var(--fg); + font: inherit; + font-size: 12px; +} +.email-settings-page input[type="date"], +.email-settings-page input[type="datetime-local"], +.email-settings-page select { + height: 28px; + padding: 0 7px; +} +.email-settings-page textarea { + resize: vertical; + min-height: 72px; + padding: 7px; + line-height: 1.35; +} +.email-settings-static-value { + min-height: 28px; + display: flex; + align-items: center; + padding: 0 7px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--panel, var(--bg)) 74%, transparent); + color: color-mix(in srgb, var(--fg) 72%, transparent); + font-size: 12px; +} +.email-settings-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 8px; + margin-top: 2px; +} + +/* Email Settings actions use the same quiet outlined treatment everywhere: + the Settings links, the modal settings page, and unsubscribe actions. */ +[data-settings-panel="email"] #set-email-open-integrations, +[data-settings-panel="email"] #set-email-open-tasks, +[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn, +#email-lib-settings-page .email-settings-section button.memory-toolbar-btn { + background: transparent; + border: 1px solid var(--border); + color: var(--fg); + font-family: inherit; + font-weight: 400; +} +[data-settings-panel="email"] #set-email-open-integrations:hover, +[data-settings-panel="email"] #set-email-open-tasks:hover, +[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn:hover, +#email-lib-settings-page .email-settings-section button.memory-toolbar-btn:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); + color: var(--fg); +} +[data-settings-panel="email"] #set-email-open-integrations > svg, +[data-settings-panel="email"] #set-email-open-tasks > svg { + color: var(--accent, var(--red)); + opacity: 1 !important; +} +[data-settings-panel="email"] .email-style-settings-extract, +[data-settings-panel="email"] .email-style-settings-save, +#email-lib-settings-page .email-style-settings-extract, +#email-lib-settings-page .email-style-settings-save { + background: transparent; + border: 1px solid var(--border); + color: var(--fg); + font-weight: 400; +} +[data-settings-panel="email"] .email-style-settings-extract:hover, +[data-settings-panel="email"] .email-style-settings-save:hover, +#email-lib-settings-page .email-style-settings-extract:hover, +#email-lib-settings-page .email-style-settings-save:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); + color: var(--fg); +} +.email-settings-status, +.email-style-settings-status { + margin-right: auto; + font-size: 11px; + color: color-mix(in srgb, var(--fg) 62%, transparent); +} +.email-settings-status.is-success, +.email-style-settings-status.is-success { + color: var(--color-success, #4caf50); + position: relative; + top: 2px; +} +.email-settings-status.is-error, +.email-style-settings-status.is-error { + color: var(--accent, var(--red)); + position: relative; + top: 2px; +} +.email-settings-save, +.email-style-settings-save { + height: 28px; + min-width: 64px; + justify-content: center; + display: inline-flex; + align-items: center; + gap: 5px; +} +.email-settings-save { + height: 32px; +} +.email-style-settings-extract { + height: 32px; +} +.email-style-settings-save { + height: 32px; +} +.email-settings-save svg, +.email-style-settings-save svg { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-style-settings-extract svg { + color: var(--accent, var(--red)); + position: relative; + top: 2px; +} +.email-settings-task-setting { + color: color-mix(in srgb, var(--fg) 72%, transparent); +} +.email-settings-inline-link { + appearance: none; + border: 0; + padding: 0; + background: transparent; + color: var(--accent, var(--red)); + font: inherit; + text-decoration: underline; + cursor: pointer; +} +.email-style-extract-help { + width: 18px; + height: 18px; + min-width: 18px; + padding: 0; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: color-mix(in srgb, var(--fg) 68%, transparent); + display: inline-flex; + align-items: center; + justify-content: center; + font: inherit; + font-size: 11px; + font-weight: 700; + line-height: 1; + cursor: help; +} +.email-style-extract-help:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); +} +@media (max-width: 420px) { + .email-settings-grid { + grid-template-columns: 1fr; + } +} + + +/* ── Notes ── */ +/* Notes panel — body-level flex sibling of chat-container */ +body.notes-view .chat-container { flex: 1; min-width: 0; } + +/* Mobile: notes panel takes over full screen */ +@media (max-width: 768px) { + body.notes-view .notes-pane { + position: fixed; + inset: 0; + max-width: 100%; + width: 100% !important; + /* Desktop sets height: min(80vh, 820px) which wins over inset:0's bottom:0 + and leaves a gap at the bottom of the viewport. Force full height here. */ + height: 100dvh !important; + max-height: 100dvh !important; + z-index: 170; + /* Bottom-sheet treatment like the document editor: stroked, rounded top, + slides up from the bottom. */ + border: 1px solid var(--border); + border-bottom: none; + border-radius: 14px 14px 0 0; + /* Pane itself never scrolls — its inner .notes-pane-body is the scroller. + Two nested scrollers caused the body's flex:1 to lose its height bound + on Firefox mobile and the last row got clipped off-screen. */ + overflow: hidden; + animation: sheet-enter 0.25s cubic-bezier(0.2, 0.8, 0.2, 1) both; + transform-origin: bottom center; + } + /* Required for the flex:1 body to actually constrain to remaining pane + height instead of expanding to its content (default min-height:auto on + flex items). Without this both grid and list views overflow the viewport + bottom on phones. */ + body.notes-view .notes-pane-body { + min-height: 0; + -webkit-overflow-scrolling: touch; + } + body.notes-view #notes-divider { + display: none; + } + body.notes-view .notes-mobile-grabber { + display: block; + flex-shrink: 0; + height: 18px; + position: relative; + background: var(--bg); + touch-action: none; + cursor: grab; + } + body.notes-view .notes-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; + } + body.notes-view .chat-container { + display: none; + } + body.notes-view .mobile-new-chat-btn, + body.notes-view .hamburger-btn { + display: none !important; + } +} +/* ── Mobile notes UX ──────────────────────────────────── + Tiles become read-only previews on touch ≤768px wide. + Tap opens a fullscreen overlay where editing happens; long-press + toggles drag-to-reorder. Wired up in static/js/notes.js. */ +body.notes-mobile-mode .note-card-corner, +body.notes-mobile-mode .note-card-corner-edit, +body.notes-mobile-mode .note-card-corner-archive, +body.notes-mobile-mode .note-card-corner-copy, +body.notes-mobile-mode .note-card-corner-trash, +body.notes-mobile-mode .note-card-corner-unarchive, +body.notes-mobile-mode .note-card-corner-done, +body.notes-mobile-mode .note-card-edit-corner, +body.notes-mobile-mode .note-card-done, +body.notes-mobile-mode .note-card-actions, +body.notes-mobile-mode .note-cl-quickadd, +body.notes-mobile-mode .note-card .note-checkbox-rm, +/* Panel-fullscreen toggle is redundant on mobile — the panel always + fills the viewport already. */ +body.notes-mobile-mode #notes-fullscreen-toggle { + display: none !important; +} + +/* Header toggle icons (archive view, list/grid switch) — defaults are + tuned for the dense desktop header; on mobile bump them slightly so + they read as tap targets without dominating the header. */ +body.notes-mobile-mode #notes-archive-toggle, +body.notes-mobile-mode #notes-view-toggle { + width: 30px; + height: 30px; + padding: 6px; +} +body.notes-mobile-mode #notes-archive-toggle svg, +body.notes-mobile-mode #notes-view-toggle svg { + width: 17px; + height: 17px; +} +/* The whole card is the tap target on mobile — bigger feedback */ +body.notes-mobile-mode .note-card { + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform 0.12s ease, box-shadow 0.12s ease; +} + +/* Drag-to-reorder mode — desaturate the grid and sweep a subtle + shimmer across every tile so it's obvious you're in rearrange mode + (the Google Keep "active edit" cue). Also disable scroll inside the + panel so the drag works smoothly. */ +body.notes-drag-mode .notes-pane-body { + overflow: hidden; + touch-action: none; +} diff --git a/static/css/style-part-07.css b/static/css/15-notes.css similarity index 55% rename from static/css/style-part-07.css rename to static/css/15-notes.css index 0c6fc8fee..3c4103273 100644 --- a/static/css/style-part-07.css +++ b/static/css/15-notes.css @@ -1,3002 +1,19 @@ -/* Fragment 8/9 of the former static/style.css. +/* 15-notes - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -.doc-editor-textarea, -#doc-editor-code.email-mode .doc-editor-textarea { - color: var(--fg) !important; - caret-color: var(--fg) !important; - z-index: 1; -} - -/* Emoji picker (monochrome icon picker) */ -.emoji-picker-btn { - background: none; border: none; color: var(--fg); opacity: 0.35; - padding: 4px 7px; cursor: pointer; min-height: 28px; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.1s; -} -.emoji-picker-btn:hover { opacity: 1; } -.emoji-picker { - width: min(300px, 92vw); max-height: min(300px, 55vh); - background: var(--panel); border: 1px solid var(--border); border-radius: 8px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3); - display: flex; flex-direction: column; overflow: hidden; -} -.emoji-picker-search { - padding: 8px 12px; background: var(--panel); border: none; - border-bottom: 1px solid var(--border); color: var(--fg); - font-size: 12px; font-family: inherit; outline: none; -} -.emoji-picker-groups { overflow-y: auto; padding: 4px 0; flex: 1; } -.emoji-picker-group { padding: 0 6px 4px 6px; } -.emoji-picker-group-name { - font-size: 10px; font-weight: 600; opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.5px; - padding: 4px 4px 2px 4px; -} -.emoji-picker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; } -.emoji-picker-item { - background: none; border: none; cursor: pointer; - padding: 4px; border-radius: 4px; - display: flex; align-items: center; justify-content: center; - color: var(--fg); transition: background 0.1s; -} -.emoji-picker-item:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } -.emoji-picker-item svg { width: 16px; height: 16px; } - -.email-avatar { - width: 28px; height: 28px; border-radius: 50%; background: var(--accent, #4a9eff); - color: #fff; display: flex; align-items: center; justify-content: center; - font-size: 12px; font-weight: 600; flex-shrink: 0; margin-top: 1px; -} -.email-item-content { flex: 1; min-width: 0; overflow: hidden; } -.email-item-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } -.email-sender { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.email-filter-chip { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; - margin: 4px 8px 6px; - padding: 4px 8px; - border-radius: 12px; - font-size: 11px; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - color: var(--fg); -} -.email-filter-chip-clear { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 14px; - line-height: 1; - padding: 0 2px; - opacity: 0.6; -} -.email-date { font-size: 10px; opacity: 0.5; white-space: nowrap; flex-shrink: 0; } -.email-subject { font-size: 11px; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } -.email-sent-chip, -.email-folder-chip { - display: inline-flex; - align-items: center; - height: 14px; - padding: 0 5px; - margin-right: 5px; - border-radius: 999px; - font-size: 8px; - line-height: 1; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - vertical-align: 1px; -} -.email-folder-chip { - position: relative; - top: 4px; - color: color-mix(in srgb, var(--fg) 75%, transparent); - background: rgba(127, 127, 127, 0.13); - border-color: rgba(127, 127, 127, 0.24); -} -.email-meta-date-group { - display: inline-flex; - align-items: center; - gap: 5px; - white-space: nowrap; -} -.email-meta-date-group .email-folder-chip { - margin-right: 0; -} -.email-card-expanded .email-sent-chip, -.email-card-reader .email-sent-chip { - position: relative; - top: 4px; -} -.email-tags { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 3px; margin-left: 6px; vertical-align: middle; position: relative; top: -2px; } -.email-card-tags { - flex: 0 0 auto; - min-width: 0; - max-width: 48%; - position: relative; - top: -4px; -} -.email-card-expanded .email-tags, -.email-card-reader .email-tags { top: -4px; } -.doclib-card-expanded .email-card-tags { top: -4px; } -.email-card-expanded .email-card-titlerow .email-tag, -.email-card-expanded .email-card-titlerow .email-tags-more { - transform: translateY(-2px); -} -@media (max-width: 768px) { - .email-card-expanded .email-card-titlerow .email-tag, - .email-card-expanded .email-card-titlerow .email-tags-more { - transform: translateY(2px); - } -} -.email-card-tags.email-tags-expanded { - position: absolute; - z-index: 4; - right: calc(var(--email-status-width, 44px) + 6px); - top: -5px; - max-width: min(58%, 260px); - padding: 4px 5px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent); -} -.email-tags-more { - appearance: none; - border: 0; - box-sizing: border-box; - border-radius: 999px; - background: rgba(127, 127, 127, 0.16); - color: var(--fg); - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 1px; - height: 13px !important; - min-height: 0 !important; - min-width: 23px; - padding: 1px 4px; - font-family: inherit; - font-size: 9px; - line-height: 1; - margin: 0; - opacity: 0.68; - position: relative; - top: 0; - vertical-align: middle; -} -.email-tags-more svg { display: block; flex: 0 0 auto; } -.email-tags-expanded .email-tags-more svg { transform: rotate(180deg); } -.email-tags-toggle-inline { - position: absolute; - right: 94px; - top: 50%; - transform: translateY(calc(-50% - 5px)); - height: 20px !important; - min-width: 34px; - padding: 2px 6px !important; - font-size: 10px !important; - line-height: 1 !important; - opacity: 0.62; - border-radius: 999px !important; -} -.email-tags-toggle-inline.active { - opacity: 1; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 36%, transparent); -} -.email-tags-toggle-inline.email-tags-empty { - color: var(--warning, #d6b35a); - background: color-mix(in srgb, var(--warning, #d6b35a) 12%, transparent); - border-color: color-mix(in srgb, var(--warning, #d6b35a) 34%, transparent); -} -.email-tag { - display: inline-block; - font-size: 9px; - line-height: 1; - padding: 2px 5px; - border-radius: 8px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.3px; - background: rgba(127, 127, 127, 0.18); - color: var(--fg); -} -.email-tag-clickable { - appearance: none; - border: 0; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - font-family: inherit; - font-size: 9px; - font-weight: 500; - height: 13px !important; - min-height: 0 !important; - line-height: 1; - margin: 0; - padding: 2px 5px !important; - text-transform: uppercase; - vertical-align: middle; - flex: 0 0 auto; -} -.email-tag-work { background: rgba(96, 165, 250, 0.22); color: #60a5fa; } -.email-tag-personal { background: rgba(74, 222, 128, 0.22); color: #4ade80; } -.email-tag-finance { background: rgba(250, 204, 21, 0.22); color: #facc15; } -.email-tag-bills { background: rgba(251, 146, 60, 0.22); color: #fb923c; } -.email-tag-receipt { background: rgba(251, 146, 60, 0.22); color: #fb923c; } -.email-tag-travel { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } -.email-tag-newsletter { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } -.email-tag-promo, -.email-tag-marketing { background: rgba(244, 114, 182, 0.22); color: #f472b6; } -.email-tag-notification { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } -.email-tag-security { background: rgba(248, 113, 113, 0.22); color: #f87171; } -.email-tag-urgent { background: color-mix(in srgb, var(--color-error, #e06c75) 25%, transparent); color: var(--color-error, #e06c75); font-weight: 600; } -.email-tag-reply-soon { background: rgba(240, 173, 78, 0.22); color: #f0ad4e; } -.email-tag-social { background: rgba(56, 189, 248, 0.22); color: #38bdf8; } -.email-tag-shopping { background: rgba(236, 72, 153, 0.22); color: #ec4899; } -.email-tag-calendar { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } -.email-menu-wrap { position: relative; flex-shrink: 0; } -.email-menu-btn { - background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; - padding: 4px; transition: opacity 0.15s; - position: relative; top: 0px; -} -.email-dropdown { - position: absolute; right: 0; top: 100%; z-index: 9999; - min-width: 120px; background: var(--bg); border: 1px solid var(--border); - border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); padding: 4px 0; -} -.email-loading { padding: 16px; text-align: center; font-size: 12px; opacity: 0.5; } -.email-load-more { padding: 8px; text-align: center; } -.email-load-more-btn { - background: transparent; border: 1px solid var(--border); border-radius: 4px; - color: var(--fg); padding: 4px 12px; font-size: 11px; cursor: pointer; -} -.email-load-more-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } -.email-spinner { - display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); - border-top-color: #fff; border-radius: 50%; animation: email-spin 0.6s linear infinite; - vertical-align: middle; margin-right: 4px; -} - -/* Email section doesn't collapse — hide the auto-injected chevron */ -#email-section .section-collapse-btn { display: none !important; } - -#email-section:hover #email-compose-btn { - opacity: 0.7 !important; -} -#email-compose-btn:hover { - opacity: 1 !important; - background: none !important; -} -#email-compose-btn svg { - width: 11px; - height: 11px; - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); -} -#email-compose-btn .list-item-plus-label { - top: calc(50% - 0.5px); - color: var(--fg); - font-size: 10px; -} -#email-compose-btn:hover svg { - transform: rotate(180deg) scale(1.15); -} - -/* Library row "+" — identical to the email compose "+": hidden until the row - is hovered, no accent box, and the same rotate-in animation. */ -#library-new-doc-btn, -#notes-new-note-btn { - opacity: 0.4 !important; - transition: opacity 0.15s ease; -} -#tool-library-btn:hover #library-new-doc-btn, -#tool-notes-btn:hover #notes-new-note-btn { - opacity: 0.7 !important; -} -#library-new-doc-btn:hover, -#notes-new-note-btn:hover { - opacity: 1 !important; - background: none !important; -} -#library-new-doc-btn svg, -#notes-new-note-btn svg { - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); -} -#library-new-doc-btn .list-item-plus-label, -#notes-new-note-btn .list-item-plus-label { - top: calc(50% + 1px); -} -#library-new-doc-btn:hover svg, -#notes-new-note-btn:hover svg { - transform: rotate(180deg) scale(1.15); -} - - -/* MD toolbar attach button — matches other toolbar buttons */ -.md-toolbar-attach-btn { - background: none; border: none; color: var(--fg); opacity: 0.35; - padding: 4px 7px; cursor: pointer; min-height: 28px; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.1s; -} -.md-toolbar-attach-btn:hover { opacity: 1; } - -/* Email reader icon buttons — vertical icon + label stack. */ -.memory-toolbar-btn.reader-icon-btn { - width: auto; - height: 44px; - /* padding: 4px 2px; */ - position: relative; - top: 1px; - display: inline-flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 3px; - flex: 0 0 auto; -} -.memory-toolbar-btn.reader-icon-btn svg { width: 18px; height: 18px; } -.memory-toolbar-btn.reader-icon-btn[data-act="reply"] svg { - color: inherit; - stroke: currentColor; -} -.memory-toolbar-btn.reader-icon-btn.active { - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - color: var(--accent, var(--red)); -} -.memory-toolbar-btn.reader-icon-btn[data-act="more"] { - color: inherit; -} -.memory-toolbar-btn.reader-icon-btn[data-act="more"] svg { fill: currentColor; } -.memory-toolbar-btn.reader-icon-btn.reader-more-active { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); - color: inherit; -} -/* Cookbook Serve dropdown triggers (the cached-model kebab + the saved-config - arrow) — accent-colored to signal they open a menu, with a filled tint when - their dropdown is open. Matches the email reader More button pattern. */ -.hwfit-cached-menu-btn, -.cookbook-saved-arrow { - color: var(--accent, var(--red)); - /* Base .memory-item-btn is flex with align-items but no justify-content, - so the kebab SVG sat left-aligned inside the button instead of being - horizontally centered. Pin it center. */ - justify-content: center; -} -.hwfit-cached-menu-btn.cookbook-menu-active, -.cookbook-saved-arrow.cookbook-menu-active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - color: var(--accent, var(--red)); -} - -/* Card action menus share one hit target and baseline. Keep this late in the - cascade because modal buttons and Cookbook Serve add their own offsets. */ -.memory-item-actions > .memory-item-btn, -.memory-item-actions .hwfit-cached-menu-btn { - box-sizing: border-box; - width: 28px; - min-width: 28px; - height: 22px; - min-height: 22px; - margin: 0 !important; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - color: var(--color-muted); - font-size: 11px; - display: inline-flex; - align-items: center; - align-self: center; - justify-content: center; - position: relative; - top: 0 !important; - transform: none !important; - line-height: 1; - transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s; -} -.memory-item-actions .hwfit-cached-menu-btn:hover { - opacity: 1 !important; - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); - color: var(--fg); -} -.memory-item-actions .hwfit-cached-menu-btn { - transform: translateY(2px) !important; -} - -@media (min-width: 769px) { - .task-card .memory-item-actions { - top: 6px; - } - .task-card .memory-item-actions > .memory-item-btn { - transform: none !important; - } -} - -/* All kebab menus use the same muted tone as the expand/collapse chevron. */ -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -) { - color: var(--color-muted, var(--fg)) !important; -} -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -) svg { - color: inherit; - fill: currentColor; -} -/* Card kebabs are lightweight menu affordances, not bordered buttons. The - old base hover rule gave only some library cards a boxed outline while - chats, tasks, and cookbook stayed borderless. */ -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -):hover { - background: none !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1; -} -/* Skills used a separate, larger kebab with a different hover treatment. - Match every other card action trigger. */ -.skill-kebab-btn { - box-sizing: border-box; - width: 28px; - min-width: 28px; - height: 22px; - min-height: 22px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - color: var(--color-muted) !important; - opacity: 0.55; - top: -2px; -} -@media (max-width: 768px) { - .memory-item-actions > .memory-item-btn, - .memory-item-actions .hwfit-cached-menu-btn { - width: 40px; - min-width: 40px; - height: 40px; - min-height: 40px; - } - /* Card menus all need the same comfortable tap target. These components - previously escaped the shared action-button sizing through more-specific - desktop selectors. */ - .memory-menu-btn, - .skill-kebab-btn, - .task-card .memory-item-actions .memory-item-btn { - width: 40px !important; - min-width: 40px !important; - height: 40px !important; - min-height: 40px !important; - } - .memory-menu-btn { opacity: 0.7; } - /* Document cards seat their actions on a slightly higher title baseline - than chats/research cards. Bring only that kebab down 4px. */ - #doclib-modal .doclib-card[data-doc-id] > .doclib-document-header .memory-item-actions .memory-item-btn { - transform: translateY(-2px) !important; - } -} -/* Mobile: center the kebab/menu dropdowns in the viewport so they don't - slide left/right as the window width changes (they were positioned via - inline `right: ...px` relative to the kebab button). Stable position is - easier to land on by touch. The inline `top:` set by the menu code is - preserved; we only override the horizontal axis. */ -@media (max-width: 768px) { - .email-card-dropdown, - .cookbook-saved-menu, - .cookbook-dep-menu { - left: 50% !important; - right: auto !important; - transform: translateX(-50%); - } -} -/* Mobile-only Cancel item appended to dropdowns (kebab/More menus). On - desktop outside-click closes them cleanly; on touch that's fiddly, so - give an explicit Cancel row. Visually separated from the action items - above by a top divider + slight muted color. */ -.dropdown-cancel-mobile { display: none; } -@media (max-width: 768px) { - .dropdown-cancel-mobile { - display: flex; - border-top: 1px solid var(--border); - margin-top: 4px; - padding-top: 8px; - opacity: 0.85; - } -} -/* Launch-command Copy button morphs into Cancel once Launch has been clicked. */ -.hwfit-serve-copy.hwfit-serve-cancel { - border-color: var(--color-danger, #e06c75) !important; - color: var(--color-danger, #e06c75) !important; -} -.hwfit-serve-copy.hwfit-serve-cancel:hover { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); -} - -/* One visual contract for card kebab menus. These menus are built by several - modules, and native button defaults previously made identical rows render - with different fonts, heights, and hover movement. */ -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) { - box-sizing: border-box; - padding: 4px !important; - color: var(--fg); - background: var(--panel, var(--bg)) !important; - border: 1px solid var(--border) !important; - border-radius: 8px !important; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important; - font-family: inherit !important; - font-size: 11px !important; - line-height: 1.2; -} -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) > .dropdown-item-compact { - box-sizing: border-box; - width: 100%; - min-height: 28px; - padding: 6px 8px; - gap: 10px; - border: 0; - border-radius: 6px; - color: var(--fg); - background: transparent; - font: inherit !important; - line-height: 1.2; - text-align: left; - white-space: nowrap; -} -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) > .dropdown-item-compact:hover:not(:disabled) { - color: var(--fg); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - transform: none; -} -@media (max-width: 768px) { - :is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu - ) > .dropdown-item-compact { - min-height: 44px; - padding: 12px !important; - gap: 10px !important; - font-size: 14px !important; - } -} -/* Email modal title unread badge — small accent pill next to "Email". */ -.email-lib-unread-badge { - display: inline-block; - font-size: 0.55em; - font-weight: 700; - background: var(--accent-primary, var(--red)); - color: var(--bg, #1a1a1a); - border-radius: 9px; - padding: 1px 7px; - margin-left: 8px; - vertical-align: 2px; - line-height: 1.3; - letter-spacing: 0.02em; - cursor: pointer; -} -.email-lib-unread-badge.email-lib-away-badge { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); - padding: 0 7px; -} -.email-lib-auto-reply-badge { - display: inline-block; - margin-left: 7px; - padding: 1px 7px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border)); - border-radius: 9px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - color: var(--accent, var(--red)); - font-size: 0.55em; - font-weight: 700; - line-height: 1.3; - letter-spacing: 0.01em; - vertical-align: 2px; - cursor: pointer; -} -.email-lib-auto-reply-status-dot { - width: 6px; - height: 6px; - margin-left: 6px; - border-radius: 50%; - background: var(--accent, var(--red)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); - vertical-align: 2px; -} -.modal-header h4 > .email-lib-auto-reply-badge { - font-size: 0.65em; - white-space: nowrap; - vertical-align: 1px; -} -#email-lib-modal.email-lib-unread-active .doclib-modal-content { - box-shadow: - 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent), - 0 0 18px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), - var(--shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.35)); -} -#email-lib-modal .email-lib-header-actions .minimize-btn { - position: relative; - left: 6px; -} -/* Per-card prev/next nav — only visible on the currently expanded card, - sits at the right of the title row next to the done check. */ -.email-card-nav-arrows { - display: none; - margin-left: auto; - gap: 2px; - flex-shrink: 0; - align-items: center; - position: relative; - top: -3px; - left: 19px; -} -.doclib-card.email-card-expanded .email-card-nav-arrows { - display: inline-flex; -} -.email-card-header-menu { - display: none; - align-items: center; - justify-content: center; - flex-shrink: 0; - width: 28px; - height: 28px; - padding: 0; - border: none; - border-radius: 6px; - background: none; - color: var(--fg); - opacity: 0.65; - cursor: pointer; - position: relative; - top: -3px; - transition: opacity 0.12s, background 0.12s; -} -.doclib-card.email-card-expanded .email-card-header-menu { - display: inline-flex; -} -.email-card-header-menu:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.email-card-nav-btn { - background: none; - border: none; - color: var(--fg); - opacity: 0.65; - cursor: pointer; - padding: 6px 8px; - border-radius: 6px; - display: inline-flex; - align-items: center; - justify-content: center; - transition: opacity 0.12s, background 0.12s; -} -.email-card-nav-btn:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} - -/* ── Email document editor ── */ -.doc-email-header { - display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; - border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; -} -.doc-email-fields { - display: flex; - flex-direction: column; - gap: 6px; - min-height: 0; -} -.doc-email-collapse-btn { - width: 100%; - min-height: 24px; - display: none; - align-items: center; - gap: 7px; - padding: 2px 4px 3px; - border: none; - background: transparent; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - opacity: 0.72; - text-align: left; -} -.doc-email-collapse-btn:hover { opacity: 1; color: var(--accent, var(--red)); } -.doc-email-collapse-btn svg { - flex-shrink: 0; - opacity: 0.65; - transition: transform 0.14s ease; -} -.doc-email-collapse-summary { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - opacity: 0.74; -} -.doc-email-header.doc-email-header-collapsed .doc-email-collapse-btn svg { - transform: rotate(180deg); -} -.email-field { display: flex; align-items: center; gap: 8px; position: relative; } -.email-field label { font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.5; min-width: 50px; text-align: right; flex-shrink: 0; } -.email-field input { - flex: 1; width: 100%; background: transparent; border: 1px solid var(--border); border-radius: 4px; - padding: 5px 8px; font-size: 13px; font-family: inherit; color: var(--fg); outline: none; - min-width: 0; -} -.email-field input:focus { border-color: var(--accent, #4a9eff); } -@container docpane (max-width: 460px) { - .doc-email-header .email-field { - display: grid; - grid-template-columns: 1fr; - gap: 3px; - align-items: stretch; - } - .doc-email-header .email-field label { - min-width: 0; - text-align: left; - } - .doc-email-header .email-field input { - width: 100%; - } -} -/* Cc toggle and attach button are absolute so they don't steal width - from the To input. Anchored to the input's vertical center + 4px - nudge so the label sits where the user expects it. */ -.email-field .email-cc-toggle { - position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); - z-index: 2; -} -/* Accent-tinted prefix label (To / Cc / Bcc / Subject) sitting at - the left edge of each input. Replaces both the old tag - and the all-placeholder accent. */ -.email-field .email-field-prefix { - position: absolute; - left: 10px; - top: 50%; - transform: translateY(-50%); - font-size: 11px; - font-weight: 600; - color: var(--accent-primary, var(--red)); - pointer-events: none; - z-index: 1; - line-height: 1; -} -.email-field input { padding-left: 44px; } -.email-field #doc-email-subject { padding-left: 64px; } -/* X close button inside Cc / Bcc fields. */ -.email-field .email-cc-close { - position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); - background: none; border: none; color: var(--fg); - opacity: 0.4; cursor: pointer; - width: 22px; height: 22px; - display: inline-flex; align-items: center; justify-content: center; - border-radius: 4px; - padding: 0; - z-index: 2; -} -.email-field .email-cc-close:hover { - opacity: 1; - color: var(--accent, var(--red)); - background: color-mix(in srgb, currentColor 10%, transparent); -} -.email-field input { padding-right: 60px; } -.email-field #doc-email-cc, .email-field #doc-email-bcc { padding-right: 32px; } -.email-field #doc-email-subject { padding-right: 8px; } - -.doc-email-actions { - display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; - border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; - align-items: center; - position: relative; - z-index: 30; - pointer-events: auto; -} -#doc-actions-footer .email-send-split { - margin-left: auto; -} -.doc-save-state-icon { display: none; flex: 0 0 auto; } -.doc-save-button[data-save-state="dirty"] { color: var(--accent, var(--red)); } -.doc-save-button[data-save-state="saving"] { color: var(--fg-muted, var(--fg)); } -.doc-save-button[data-save-state="saved"] { color: var(--fg); } -.doc-save-button[data-save-state="error"] { color: var(--color-danger, #e06c75); } -.doc-stats-wrap { - position: relative; - margin-left: 0; - flex: 0 0 auto; -} -.doc-stats-btn { - height: 28px; - border: 0; - border-radius: 4px; - padding: 0 7px; - background: transparent; - color: var(--fg-muted, var(--fg)); - font: inherit; - font-size: 10px; - cursor: pointer; - white-space: nowrap; -} -.doc-stats-btn:hover, -.doc-stats-btn[aria-expanded="true"] { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 7%, transparent); -} -.doc-stats-btn.doc-stats-selected { - color: var(--accent, var(--red)); - font-weight: 600; -} -.doc-stats-popover { - position: fixed; - right: auto; - bottom: auto; - width: 190px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); - color: var(--fg); - font-size: 11px; -} -.doc-stats-scope { - padding-bottom: 6px; - margin-bottom: 5px; - border-bottom: 1px solid var(--border); - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - text-transform: uppercase; -} -.doc-stats-row { - min-height: 22px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 14px; -} -/* WYSIWYG email body — what the recipient sees, edited in place. */ -.doc-email-richbody { - flex: 1; - min-height: 0; - overflow-y: auto; - padding: 12px 16px; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.6; - color: var(--fg); - background: var(--bg); - outline: none; - white-space: pre-wrap; - text-align: left; - word-wrap: break-word; -} -.doc-email-richbody.richtext-mode { - background: var(--bg); -} -.doc-rich-empty-import { - position: absolute; - top: 90px; - right: 18px; - bottom: auto; - left: auto; - z-index: 2; - display: flex; - pointer-events: none; -} -.doc-rich-empty-import .doc-rich-empty-import-btn { - position: static; - float: none; - margin: 0; - opacity: 1; - pointer-events: auto; - transform: none; -} -.doc-email-richbody.richtext-mode ul.rich-checklist { - list-style: none; - padding-left: 1.8em; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li { - position: relative; - min-height: 1.6em; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li::before { - content: ""; - position: absolute; - left: -1.55em; - top: 0.3em; - width: 0.95em; - height: 0.95em; - box-sizing: border-box; - border: 1.5px solid color-mix(in srgb, var(--fg) 48%, transparent); - border-radius: 3px; - background: var(--sidebar-bg, var(--panel)); - cursor: pointer; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::before { - border-color: var(--accent, var(--red)); - background: var(--accent, var(--red)); -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::after { - content: ""; - position: absolute; - left: -1.27em; - top: 0.44em; - width: 0.28em; - height: 0.5em; - border: solid #fff; - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); - pointer-events: none; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"] { - color: color-mix(in srgb, var(--fg) 62%, transparent); - text-decoration: line-through; -} -.doc-email-richbody.richtext-mode hr { - border: 0; - border-top: 1px solid var(--border); - margin: 1.25em 0; -} -.doc-email-richbody.richtext-mode hr.richtext-page-break { - height: 8px; - border-top: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); - border-bottom: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); - margin: 1.6em 0; - opacity: 0.8; -} -.doc-email-richbody.richtext-mode table { - width: 100%; - margin: 12px 0; - border-collapse: collapse; - table-layout: fixed; -} -.doc-email-richbody.richtext-mode th, -.doc-email-richbody.richtext-mode td { - min-width: 48px; - padding: 7px 8px; - border: 1px solid var(--border); - vertical-align: top; - overflow-wrap: anywhere; -} -.doc-email-richbody.richtext-mode th { - background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 85%, var(--fg) 15%); - font-weight: 600; -} -.doc-email-richbody.richtext-mode code, -.doc-email-richbody.richtext-mode span[style*="OdysseusInlineCode"] { - padding: 0.12em 0.35em; - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 0.9em; - white-space: break-spaces; -} -.doc-email-richbody.richtext-mode pre { - margin: 1em 0; - padding: 12px 14px; - overflow-x: auto; - border: 1px solid var(--border); - border-radius: 4px; - background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 82%, #000 18%) !important; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 0.9em; - line-height: 1.5; - white-space: pre-wrap; - tab-size: 2; -} - -::highlight(doc-find-results) { - color: inherit; - background-color: color-mix(in srgb, #facc15 48%, transparent); -} - -::highlight(doc-find-current) { - color: inherit; - background-color: color-mix(in srgb, var(--accent, #ef4444) 68%, #facc15); -} - -::highlight(doc-ai-selections) { - color: inherit; - background-color: color-mix(in srgb, var(--accent, #ef4444) 30%, transparent); -} -.doc-email-richbody img.richtext-image { - display: block; - margin: 12px 0; - max-width: 100%; - height: auto; - border-radius: 4px; - cursor: pointer; -} -.doc-email-richbody figure.richtext-image { - margin: 12px 0; - max-width: 100%; -} -.doc-email-richbody figure.richtext-image:not(:has(img)) { display: none; } -.doc-email-richbody figure.richtext-image img.richtext-image { - display: block; - margin-top: 0; - margin-bottom: 0; - max-width: 100%; - height: auto; - border-radius: 4px; -} -.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-60) .richtext-image-caption { width: 60%; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-35) .richtext-image-caption { width: 35%; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-left) .richtext-image-caption { margin-left: 0; margin-right: auto; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-center) .richtext-image-caption { margin-left: auto; margin-right: auto; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-right) .richtext-image-caption { margin-left: auto; margin-right: 0; } -.doc-email-richbody img.richtext-image-size-100 { width: 100%; } -.doc-email-richbody img.richtext-image-size-60 { width: 60%; } -.doc-email-richbody img.richtext-image-size-35 { width: 35%; } -.doc-email-richbody img.richtext-image-align-left { margin-left: 0; margin-right: auto; } -.doc-email-richbody img.richtext-image-align-center { margin-left: auto; margin-right: auto; } -.doc-email-richbody img.richtext-image-align-right { margin-left: auto; margin-right: 0; } -.doc-email-richbody img.richtext-image[data-editor-image-selected] { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 3px; -} -.doc-rich-image-toolbar { - position: fixed; - display: inline-flex; - align-items: center; - gap: 2px; - padding: 2px; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 10032; -} -.doc-rich-image-toolbar button { - width: 24px; - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - border: 0; - border-radius: 3px; - background: transparent; - color: inherit; - cursor: pointer; -} -.doc-rich-image-toolbar button:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--accent, var(--red)); -} -.doc-rich-image-toolbar .doc-rich-image-remove:hover { - color: var(--accent, var(--red)); -} -.doc-rich-image-source-menu { - position: fixed; - width: 260px; - min-width: 260px; - padding: 5px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 4px 16px color-mix(in srgb, var(--fg) 20%, transparent); - z-index: 10033; - font-size: 11px; -} -.doc-rich-image-source-title { - padding: 5px 7px 4px; - color: var(--fg); - font-weight: 700; - opacity: .75; -} -.doc-rich-image-source-menu > button, -.doc-rich-gallery-item { - width: 100%; - display: flex; - align-items: center; - gap: 7px; - padding: 7px 8px; - border: 0; - border-radius: 4px; - background: transparent; - color: var(--fg); - font: inherit; - text-align: left; - cursor: pointer; -} -.doc-rich-image-source-menu > button[data-image-source="computer"] { - padding-left: 9px; -} -.doc-rich-image-source-menu > button:hover, -.doc-rich-gallery-item:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--accent, var(--red)); -} -.doc-rich-gallery-list { - display: grid; - gap: 2px; - max-height: 300px; - overflow-y: auto; -} -.doc-rich-gallery-item { - min-height: 44px; - min-width: 0; -} -.doc-rich-gallery-item > span:last-child { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.doc-rich-gallery-thumb { - width: 40px; - height: 40px; - flex: 0 0 40px; - border: 1px solid var(--border); - border-radius: 4px; - background-position: center; - background-size: cover; - background-repeat: no-repeat; -} -.rich-color-letter { - border-bottom: 2px solid var(--accent, var(--red)); - font-weight: 700; - line-height: 14px; -} -.rich-color-swatch { - width: 13px !important; - height: 13px !important; - border: 1px solid color-mix(in srgb, var(--fg) 35%, transparent); - border-radius: 50%; -} -.rich-highlight-swatch { border-radius: 50%; } -.rich-color-swatch.is-transparent { - background: - linear-gradient(135deg, transparent 43%, var(--red) 44%, var(--red) 56%, transparent 57%), - var(--bg); -} -.rich-color-palette-menu { - min-width: min(292px, calc(100vw - 16px)); -} -.rich-color-palette-menu .rich-color-reset { - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - margin-bottom: 3px; - padding-bottom: 7px; -} -.rich-color-palette-grid { - display: grid; - grid-template-columns: repeat(2, minmax(126px, 1fr)); - gap: 0 4px; - padding: 0 4px; -} -.rich-color-palette-group { - min-width: 0; -} -.rich-color-palette-label { - padding: 4px 8px 3px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 9px; - font-weight: 600; - text-transform: uppercase; -} -.rich-color-palette-group .doc-overflow-item { - min-width: 0; - padding-inline: 8px; -} -.rich-toolbar-color-custom { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-top: 3px; - padding: 7px 9px 4px 10px; - border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - color: var(--fg); - font-size: 11px; -} -.rich-toolbar-color-input.cp-swatch-input { - width: 22px; - height: 22px; - flex: 0 0 22px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 28%, var(--border)); - border-radius: 50%; - cursor: pointer; -} -.rich-toolbar-color-input.cp-swatch-input:focus-visible { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 2px; -} - -.rich-toolbar-slider-row { - display: grid; - grid-template-columns: auto minmax(104px, 1fr) 42px; - align-items: center; - gap: 9px; - min-width: 220px; - padding: 10px; - color: var(--fg); - font-size: 11px; -} -.rich-toolbar-range { - width: 100%; - min-width: 0; - height: 8px; - margin: 0; - border: 0; - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 25%, transparent); - accent-color: var(--red); - cursor: pointer; - -webkit-appearance: none; - appearance: none; -} -.rich-toolbar-range::-webkit-slider-thumb { - width: 9px; - height: 9px; - border: 0; - border-radius: 50%; - background: var(--red); - cursor: pointer; - -webkit-appearance: none; - appearance: none; - transition: width 0.12s ease, height 0.12s ease; -} -.rich-toolbar-range:hover::-webkit-slider-thumb, -.rich-toolbar-range:focus::-webkit-slider-thumb, -.rich-toolbar-range:active::-webkit-slider-thumb { - width: 16px; - height: 16px; -} -.rich-toolbar-range::-moz-range-thumb { - width: 9px; - height: 9px; - border: 0; - border-radius: 50%; - background: var(--red); - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.rich-toolbar-range:hover::-moz-range-thumb, -.rich-toolbar-range:focus::-moz-range-thumb, -.rich-toolbar-range:active::-moz-range-thumb { - width: 16px; - height: 16px; -} -.rich-toolbar-range:focus-visible { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 4px; -} -.rich-toolbar-slider-value { - width: 42px; - box-sizing: border-box; - padding: 2px 3px; - border: 1px solid transparent; - border-radius: 3px; - background: transparent; - text-align: right; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-variant-numeric: tabular-nums; - -webkit-appearance: textfield; - appearance: textfield; -} -.rich-toolbar-slider-value::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -.rich-toolbar-slider-value:focus { - border-color: var(--accent, var(--red)); - outline: none; - color: var(--fg); -} - -.rich-script-icon, -.rich-font-icon, -.rich-font-size-icon { - display: inline-block; - line-height: 1; - font-size: 11px; -} -.rich-highlight-icon { - display: block; - flex-shrink: 0; - color: var(--accent-primary, var(--red)); -} - -.rich-script-icon sup, -.rich-script-icon sub { - font-size: 7px; - line-height: 0; -} - -.rich-font-icon { - font-family: Georgia, serif; - font-size: 12px; -} - -.rich-font-size-icon { - min-width: 16px; - font-size: 10px; - font-weight: 600; - font-variant-numeric: tabular-nums; - text-align: center; -} -.rich-align-center { text-align: center; } -.rich-align-right { text-align: right; } -.doc-email-richbody .email-quoted-history { - margin-top: 14px; - padding: 10px 0 10px 12px; - border-left: 3px solid var(--border); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.92em; - line-height: 1.45; - white-space: pre-wrap; - user-select: text; - pointer-events: none; -} -.doc-email-richbody h1, .doc-email-richbody h2, .doc-email-richbody h3, -.doc-email-richbody h4, .doc-email-richbody h5, .doc-email-richbody h6 { margin: 0.4em 0; } -.email-more-menu { - position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; - min-width: 160px; background: var(--bg); border: 1px solid var(--border); - border-radius: 6px; box-shadow: 0 -4px 12px rgba(0,0,0,0.2); padding: 4px; - z-index: 100; -} -.email-send-btn { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 20%, transparent); - color: var(--accent-primary, var(--red)); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); - border-radius: 6px; - padding: 0 14px; height: 28px; font-size: 11px; font-weight: 600; cursor: pointer; - font-family: inherit; transition: all 0.15s; white-space: nowrap; - display: inline-flex; align-items: center; gap: 6px; -} -/* Split send button: [ Send │ ▾ ] — the caret drops the send-options menu UP. */ -.email-send-split { position: relative; display: inline-flex; } -.email-send-split .email-send-main { - border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; -} -.email-send-split .email-send-caret { - border-top-left-radius: 0; border-bottom-left-radius: 0; - padding: 0 8px; gap: 0; - /* its left border is the single divider between the two halves */ - border-left: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); -} -.email-send-split .email-send-caret svg { transition: transform 0.15s ease; } -.email-send-split .email-send-caret[aria-expanded="true"] svg { transform: none; } -.email-send-btn svg { flex-shrink: 0; } -.email-send-btn:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); - border-color: var(--accent-primary, var(--red)); -} -.email-draft-btn { - background: none; border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border-radius: 6px; - padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; - font-family: inherit; transition: all 0.15s; white-space: nowrap; -} -.email-draft-btn:hover { border-color: var(--fg); color: var(--fg); } -.email-discard-btn { - background: transparent; - /* Dim the label text via color-mix instead of `opacity:0.6` on the whole - button — opacity multiplies through to the SVG and washes out the accent - X glyph. Per-element color dimming keeps the X at full accent strength. */ - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; font-family: inherit; -} -/* The ✕ glyph itself reads in accent. */ -.email-discard-btn svg { color: var(--accent-primary, var(--red)); opacity: 1; } -.email-discard-btn:hover { opacity: 1; color: var(--red, #e55); border-color: var(--red, #e55); } - -/* Compose email "more" menu button — icon only, matches draft btn height */ -#doc-email-more-btn { - background: none; border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border-radius: 6px; - width: 28px; height: 28px; padding: 0; - cursor: pointer; font-family: inherit; transition: all 0.15s; - display: inline-flex; align-items: center; justify-content: center; -} -#doc-email-more-btn:hover { - border-color: var(--fg); color: var(--fg); -} -.email-more-wrap { display: inline-flex; } -.email-attachments { - display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 0 58px; -} -.email-attachment-chip { - display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px; font-size: 11px; background: var(--hover-bg, rgba(255,255,255,0.05)); - border: 1px solid var(--border); border-radius: 12px; color: var(--fg); - text-decoration: none; cursor: pointer; transition: background 0.15s, border-color 0.15s; - max-width: 200px; min-width: 0; -} -.email-attachment-chip:hover { - background: color-mix(in srgb, var(--accent) 15%, transparent); - border-color: var(--accent); -} -.email-attachment-chip.is-expanded { - max-width: 90vw; -} -.email-attachment-chip > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - flex: 1 1 auto; min-width: 0; -} -.email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-attachment-chip-related { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); -} -/* "Open in editor" launch icon — same prominent style on desktop AND mobile - (was 24px / dim / no border on desktop, easy to miss). Accent-tinted - background + border makes it read as a real action. */ -.email-attachment-open, -.email-attachment-download, -.email-attachment-calendar-open, -.cal-event-email-source { - display: inline-flex; align-items: center; gap: 4px; - height: 22px; padding: 0 9px; border-radius: 999px; - margin-left: 6px; flex: 0 0 auto; - min-width: 52px; justify-content: center; - font-size: 10px; font-weight: 500; letter-spacing: 0.02em; - color: var(--accent-primary, var(--red)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); - cursor: pointer; transition: background 0.1s, border-color 0.1s, color 0.1s; -} -.email-attachment-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-open svg, -.email-attachment-open > svg, -.email-attachment-download svg, -.email-attachment-download > svg, -.email-attachment-calendar-open svg, -.email-attachment-calendar-open > svg, -.cal-event-email-source svg, -.cal-event-email-source > svg { - width: 12px; height: 12px; - opacity: 0.9; -} -.email-attachment-open:hover, -.email-attachment-download:hover, -.email-attachment-calendar-open:hover, -.cal-event-email-source:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 70%, transparent); -} -.email-attachment-download { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-calendar-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.cal-event-email-source { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-open-label { line-height: 1; } -.email-attachment-chip.is-expanded .email-attachment-open, -.email-attachment-chip.is-expanded .email-attachment-download, -.email-attachment-chip.is-expanded .email-attachment-calendar-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-chip.is-expanded .email-attachment-open-label { - display: none; -} -@media (max-width: 768px) { - .doc-email-collapse-btn { - background: inherit; - } - /* Mobile: keep the pill but ensure a comfortable touch target. */ - .email-attachment-open { - height: 26px; padding: 0 10px; - min-height: 26px !important; - min-width: 58px; - } - .email-attachments, - .email-compose-atts { - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-left: 0; - padding-bottom: 2px; - } - .email-attachments::-webkit-scrollbar, - .email-compose-atts::-webkit-scrollbar { - display: none; - } - /* Attachment chip body — modest minimum height so the open icon sits - neatly without dominating. */ - .email-attachment-chip, - .email-compose-chip { - flex: 0 0 auto; - padding: 6px 8px !important; - min-height: 36px !important; - } - .email-attachment-chip.is-expanded { - flex-wrap: nowrap; - align-items: center; - max-width: min(92vw, 360px); - } - .email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - flex: 1 1 auto; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.25; - min-width: 0; - } - .email-attachment-chip.is-expanded .att-size { - margin-left: 0; - } - .email-attachment-chip.is-expanded .email-attachment-open, - .email-attachment-chip.is-expanded .email-attachment-download, - .email-attachment-chip.is-expanded .email-attachment-calendar-open { - width: 28px; - min-width: 28px; - padding: 0; - margin-left: 4px; - } - .email-compose-chip .compose-chip-name { - max-width: 190px; - } -} - -/* Compose attachment chips (when sending new email) */ -.email-compose-atts { - display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; - padding: 6px 0 0 0; -} -.email-compose-chip { - display: inline-flex; align-items: center; gap: 4px; - flex: 0 1 auto; - min-width: 0; - max-width: min(240px, 100%); - height: 24px; min-height: 24px; box-sizing: border-box; - padding: 2px 3px 2px 7px; font-size: 11px; - background: var(--hover-bg, rgba(255,255,255,0.05)); - border: 1px solid var(--border); border-radius: 12px; color: var(--fg); -} -.email-compose-chip .compose-chip-name { max-width: 180px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.email-compose-chip .compose-chip-remove { - background: none; border: none; color: var(--fg); opacity: 0.5; - height: 18px; font-size: 14px; line-height: 18px; padding: 0 3px; cursor: pointer; - flex-shrink: 0; -} -.email-compose-chip .compose-chip-remove:hover { opacity: 1; color: var(--red, #e55); } - -.pdf-annotation-line-value { - width: 48px; - height: 22px; - box-sizing: border-box; - padding: 0 5px; - border: 1px solid var(--border); - border-radius: 4px; - outline: none; - background: var(--bg-elev, var(--panel)); - color: var(--fg); - font-family: inherit; - font-size: 10px; - font-weight: 500; - line-height: 1; - text-align: right; -} -.pdf-annotation-line-value:hover { border-color: color-mix(in srgb, var(--fg) 38%, var(--border)); } -.pdf-annotation-line-value:focus { border-color: var(--accent, var(--red)); } - -.email-odysseus-attach-menu { - position: fixed; - z-index: 10050; - width: 300px; - max-width: calc(100vw - 16px); - max-height: min(420px, calc(100vh - 16px)); - padding: 7px; - border: 1px solid var(--border); - border-radius: 7px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 12px 30px rgba(0,0,0,0.28); - overflow: hidden; -} -.email-odysseus-attach-local { - width: 100%; - height: 30px; - display: flex; - align-items: center; - gap: 7px; - padding: 0 9px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, var(--border)); - border-radius: 5px; - background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-local:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); -} -.email-odysseus-attach-tabs { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 5px; - margin: 7px 0; -} -.email-odysseus-attach-tabs button { - height: 26px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border: 1px solid var(--border); - border-radius: 5px; - background: transparent; - color: var(--fg); - opacity: 0.72; - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-tabs button.active { - opacity: 1; - color: var(--accent-primary, var(--red)); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); -} -.email-odysseus-attach-tabs button svg { - flex-shrink: 0; -} -.email-odysseus-attach-search-wrap { - height: 28px; - display: flex; - align-items: center; - gap: 6px; - margin: -1px 0 7px; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 5px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.email-odysseus-attach-search-wrap:focus-within { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - color: var(--accent-primary, var(--red)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, transparent); -} -.email-odysseus-attach-search { - min-width: 0; - flex: 1; - border: 0; - outline: 0; - background: transparent; - color: var(--fg); - font: inherit; - font-size: 11px; -} -.email-odysseus-attach-search::placeholder { - color: color-mix(in srgb, var(--fg) 38%, transparent); -} -.email-odysseus-attach-list { - max-height: 320px; - overflow: auto; - display: flex; - flex-direction: column; - gap: 4px; -} -.email-odysseus-attach-row { - width: 100%; - min-height: 38px; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 7px; - border: 1px solid transparent; - border-radius: 5px; - background: transparent; - color: var(--fg); - text-align: left; - font: inherit; - cursor: pointer; -} -.email-odysseus-attach-row:hover { - background: var(--hover-bg, rgba(255,255,255,0.06)); - border-color: var(--border); -} -.email-odysseus-attach-row.is-selected { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); -} -.email-odysseus-attach-dot { - width: 8px; - height: 8px; - flex: 0 0 auto; - display: inline-block; - border: 1px solid var(--border); - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.email-odysseus-attach-row.is-selected .email-odysseus-attach-dot { - border-color: var(--accent-primary, var(--red)); - background: var(--accent-primary, var(--red)); - box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); -} -.email-odysseus-attach-icon, -.email-odysseus-attach-thumb { - width: 28px; - height: 28px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 7%, transparent); - overflow: hidden; -} -.email-odysseus-attach-main { - min-width: 0; - flex: 1; - display: flex; - flex-direction: column; - gap: 2px; -} -.email-odysseus-attach-title, -.email-odysseus-attach-meta { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-odysseus-attach-title { - font-size: 11px; - line-height: 1.2; -} -.email-odysseus-attach-meta { - font-size: 10px; - line-height: 1.15; - opacity: 0.52; -} -.email-odysseus-attach-empty { - padding: 14px 8px; - color: var(--muted, #888); - font-size: 11px; - text-align: center; -} -.email-odysseus-attach-actions { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - padding-top: 5px; - border-top: 1px solid color-mix(in srgb, var(--border) 75%, transparent); - background: var(--bg); - position: sticky; - bottom: 0; -} -.email-odysseus-attach-count { - flex: 1; - min-width: 0; - color: var(--muted, #888); - font-size: 11px; -} -.email-odysseus-attach-selected { - height: 28px; - min-width: 72px; - padding: 0 12px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - background: var(--accent-primary, var(--red)); - color: #fff; - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-selected svg { - flex-shrink: 0; -} - -.email-cc-toggle { - background: none; border: none; color: var(--fg); - opacity: 0.4; font-size: 11px; cursor: pointer; - padding: 4px 8px; font-family: inherit; -} -.email-cc-toggle:hover { - opacity: 1; - color: var(--accent, #4a9eff); - background: none !important; -} - -@media (max-width: 768px) { - .doc-email-collapse-btn { - display: flex; - } -} - -.email-autocomplete { - position: absolute; top: 100%; left: 58px; right: 0; z-index: 1000; - background: var(--bg); border: 1px solid var(--border); border-radius: 6px; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 240px; overflow-y: auto; - margin-top: 2px; -} -.contact-suggestion { - display: flex; justify-content: space-between; gap: 8px; - padding: 6px 10px; font-size: 12px; cursor: pointer; - border-bottom: 1px solid var(--border); -} -.contact-suggestion:last-child { border-bottom: none; } -.contact-suggestion:hover, -.contact-suggestion.active, -.contact-suggestion[aria-selected="true"] { - background: color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 15%, transparent); - outline: 1px solid color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 40%, transparent); - outline-offset: -1px; -} -.contact-suggestion .contact-name { font-weight: 600; color: var(--fg); } -.contact-suggestion .contact-email { opacity: 0.6; font-size: 11px; } -.contact-suggestion-status { - padding: 7px 10px; - color: color-mix(in srgb, var(--fg) 65%, transparent); - font-size: 11px; - cursor: default; -} -.contact-suggestion-status.is-loading { opacity: 0.72; } -.contact-suggestion-status.is-unavailable { - color: color-mix(in srgb, var(--red) 82%, var(--fg)); -} -.email-ai-reply-btn { - background: none; - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; - font-family: inherit; transition: all 0.15s; - display: inline-flex; align-items: center; white-space: nowrap; -} -.email-ai-reply-btn:hover { - border-color: var(--accent, #4a9eff); - color: var(--accent, #4a9eff); -} -/* Align select/new buttons in popup library toolbar */ -#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn, #email-lib-compose-btn { position: relative; top: -4px; } -/* Select + Refresh sit slightly lower than Compose on desktop. */ -#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -2px; } - -#email-lib-modal.email-settings-mode .email-accounts-row, -#email-lib-modal.email-settings-mode .admin-card > .memory-toolbar, -#email-lib-modal.email-settings-mode #email-lib-bulk, -#email-lib-modal.email-settings-mode #email-lib-grid, -#email-lib-modal.email-settings-mode #email-lib-sync-status, -#email-lib-modal.email-settings-mode #email-lib-fab { - display: none !important; -} -#email-lib-settings-page[hidden] { - display: none !important; -} -#email-lib-modal.email-settings-mode #email-lib-settings-page { - display: flex; -} -.email-settings-page { - display: flex; - flex: 1; - min-height: 0; - min-width: 0; - flex-direction: column; - gap: 6px; - overflow: hidden; - padding: 2px 0 8px; - width: 100%; - box-sizing: border-box; - -webkit-overflow-scrolling: touch; -} -.settings-email-preferences-card { - display: contents; -} -.settings-email-preferences-card .email-settings-section { - margin: 0 0 10px; - padding: 14px 12px 12px; - background: var(--panel); -} -.settings-email-preferences-card .email-settings-section:last-child { - margin-bottom: 0; -} -.settings-email-preferences-card .email-settings-style-section { - margin-bottom: 4px; -} -.settings-email-default-card #settings-email-default-host { - display: flex; - justify-content: flex-end; - min-width: 0; - margin-left: auto; -} -.settings-email-default-card .settings-email-default-title { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} -.settings-email-default-card .settings-email-default-title > span { - min-width: 0; -} -.settings-email-default-card .settings-email-default-title > span > svg { - color: var(--accent, var(--red)); -} -.settings-email-default-card .email-settings-account-picker { - margin-left: 0 !important; -} -.settings-email-default-card .email-settings-support-copy { - position: relative; - top: 6px; -} -.settings-email-default-spacer { - height: 12px; -} -.settings-email-default-card .cookbook-srv-default-label { - position: relative; - top: 2px; -} -.settings-email-default-card .cookbook-srv-default-icon > svg { - position: relative; - top: 7px; -} -.settings-email-settings-legacy { - display: none; -} - -/* Keep the standalone Email Settings cards aligned with the expandable - preference cards below them. */ -[data-settings-panel="email"] > .admin-card { - padding-top: 14px; -} -.email-settings-page-host { - display: flex; - flex: 0 0 auto; - min-height: 0; - padding: 0; - overflow: visible; -} -.settings-email-preferences-card > .email-settings-page-host { width: 100%; } -.email-settings-page-head { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; - min-height: 32px; - padding-bottom: 2px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.email-settings-account-picker { - min-width: 0; - max-width: 100%; -} -.email-settings-account-picker select { - min-width: 0; - max-width: 100%; -} -.email-settings-back { - position: relative; - top: -2px; -} -.email-settings-header-back { - background: transparent; - color: color-mix(in srgb, var(--fg) 58%, transparent); - border: 1px solid transparent; - width: auto; - height: 24px; - padding: 0 8px 0 6px; - align-items: center; - justify-content: center; - gap: 4px; - cursor: pointer; - border-radius: 999px; - flex-shrink: 0; - position: relative; - font-size: 11px; - font-weight: 600; - line-height: 1; -} -.email-settings-header-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); -} -.email-settings-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 8px; -} -.email-settings-title { - display: inline-flex; - align-items: center; - gap: 6px; - flex: 1 1 auto; - width: 100%; - min-width: 0; - font-size: 12px; - font-weight: 700; -} -.email-settings-title svg { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-settings-close { - width: 24px; - height: 24px; - border: 1px solid var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 70%, transparent); - cursor: pointer; - font-size: 14px; - line-height: 1; -} -.email-settings-close:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)); -} -.email-settings-body { - display: flex; - flex-direction: column; - flex: 1 1 auto; - gap: 6px; - padding-top: 4px; - box-sizing: border-box; - min-width: 0; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; - overscroll-behavior: contain; -} -.email-settings-loading, -.email-settings-error { - font-size: 12px; - color: color-mix(in srgb, var(--fg) 65%, transparent); - padding: 8px 2px; -} -.email-settings-loading { - display: inline-flex; - align-items: center; - gap: 7px; -} -.email-settings-loading-css-whirlpool { - display: inline-block; - width: 14px; - height: 14px; - box-sizing: border-box; - border: 1.5px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - border-top-color: var(--accent, var(--red)); - border-right-color: color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); - border-radius: 50%; - animation: whirlpool-spin 0.72s linear infinite; - flex: 0 0 auto; -} -.email-settings-loading-spinner { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - color: var(--accent, var(--red)); -} -.email-settings-section { - display: flex; - flex-direction: column; - flex: 0 0 auto; - gap: 7px; - margin: 0; - padding: 14px 8px 8px; - min-width: 0; - box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - transition: opacity 0.15s ease, background 0.15s ease; -} -.email-settings-section.is-disabled { - opacity: 0.58; - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -.email-settings-section-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.email-settings-section-head > div:first-child { - min-width: 0; - flex: 1 1 auto; -} -.email-settings-section-head > .email-settings-enabled-control, -.email-settings-section-head > button { - margin-left: auto; -} -.email-settings-section-toggle { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - width: 100%; - min-width: 0; - padding: 0; - border: 0; - background: transparent; - color: inherit; - text-align: left; - cursor: pointer; -} -.email-settings-section-toggle:hover, -.email-settings-section-toggle:active { - background: transparent; -} -.email-settings-section-toggle-copy { - display: flex; - flex-direction: column; - align-items: flex-start; - min-width: 0; - flex: 1 1 auto; -} -.email-settings-section-toggle > .email-settings-enabled-control { - margin-left: auto; - flex: 0 0 auto; -} -.email-settings-section-chevron { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); - transition: transform .14s ease, color .14s ease; -} -.email-settings-section-toggle:hover .email-settings-section-chevron, -.email-settings-section-toggle:focus-visible .email-settings-section-chevron { - color: var(--accent, var(--red)); -} -.email-settings-section-title { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 14px; - line-height: 1; - font-weight: 600; - letter-spacing: 0; - margin: 0; - padding: 0; -} -.email-settings-section-title svg { - color: var(--accent, var(--red)); - opacity: 0.6; - flex-shrink: 0; -} -.email-settings-account-scope { - margin-left: 4px; - font-size: 11px; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -.email-settings-section-body { - display: flex; - flex-direction: column; - gap: 7px; - min-width: 0; - width: 100%; -} -.email-settings-section[open] > .email-settings-section-body { - min-height: 0; - flex: 0 0 auto; - height: auto; - max-height: none; - overflow: visible; - overscroll-behavior: contain; - padding: 0 4px 4px 0; - scrollbar-width: thin; -} -.email-settings-section[open] { - min-height: 0; - flex: 0 0 auto; -} -.email-settings-auto-reply-section[open], -.email-settings-cleanup-section[open]:has(.email-unsub-card) { - flex: 1 1 auto; - display: grid; - grid-template-rows: auto minmax(0, 1fr); - align-content: stretch; -} -.email-settings-auto-reply-section[open] > .email-settings-section-body, -.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body { - flex: 0 0 auto; - height: 100%; - max-height: 100%; - overflow-y: auto; -} -.email-settings-section[open] > .email-settings-section-body > * { - flex: 0 0 auto; -} -.email-settings-section-body-head { - display: flex; - justify-content: flex-end; -} -.email-settings-display-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.email-settings-display-title { - display: inline-flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.email-settings-display-title > svg { - flex: 0 0 auto; - color: var(--accent, var(--red)); -} -.email-settings-display-title-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - position: relative; - top: 10px; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-display-title-row .email-settings-section-title { - flex: 1 1 auto; - width: auto; - padding-bottom: 0; - border-bottom: 0; -} -.email-settings-style-section .email-settings-section-title { - display: flex; - width: 100%; - position: relative; - top: 0; - padding-bottom: 8px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-style-section .email-settings-section-head, -.email-settings-style-section .email-settings-section-body { - position: static; -} -.email-settings-style-section .email-settings-section-head { margin-top: 8px; } -.email-settings-style-section .email-settings-section-body { margin-top: 12px; } -.email-settings-style-section .email-settings-section-desc { - margin-top: 16px; - top: 0; -} -.email-settings-auto-reply-section .email-settings-section-title, -.email-settings-cleanup-section .email-settings-section-title { - display: flex; - width: 100%; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-cleanup-section .email-settings-section-title { - position: relative; - top: 10px; -} -.email-settings-auto-reply-section .email-settings-section-desc, -.email-settings-cleanup-section .email-settings-section-desc { - margin-top: 10px; -} -.email-settings-display-section .email-settings-section-desc, -.email-settings-cleanup-section .email-settings-section-desc, -.email-settings-inline-images-section .email-settings-section-desc { - margin-top: 12px; - top: 12px; -} -.email-settings-auto-reply-title-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - position: relative; - top: 10px; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-auto-reply-section .email-settings-section-head { - margin-top: 8px; -} -.email-settings-auto-reply-section .email-settings-section-body { - margin-top: 8px; -} -.email-settings-auto-reply-section .email-settings-section-desc { - margin-top: 12px; - top: 0; -} -.email-settings-display-section .email-settings-section-desc { - margin-top: 10px; - top: 10px; -} -.email-settings-auto-reply-section .email-settings-section-body { - margin-top: 14px; -} -.email-settings-auto-reply-title-row .email-settings-section-title { - flex: 1 1 auto; - width: auto; - padding-bottom: 0; - border-bottom: 0; -} -.email-settings-support-copy { - color: color-mix(in srgb, var(--fg) 65%, transparent); - font-size: 11px; - line-height: 1.3; -} -.email-settings-clean-btn > svg { - color: var(--accent, var(--red)); -} -.email-settings-clean-btn { - height: 32px; -} -.email-unsub-rescan-btn { - height: 32px; -} -.email-settings-clean-actions { - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; - gap: 7px; - min-width: 0; - margin-left: auto; -} -.email-settings-clean-status { - display: none; - font-size: 10.5px !important; - flex: 0 1 auto; - width: auto !important; - max-width: min(52vw, 360px); - min-width: 0; - overflow: hidden; - text-align: right; - white-space: nowrap; - text-overflow: ellipsis; - position: relative; - top: 2px; -} -.email-settings-clean-status.is-busy { - order: 0; - align-self: center; - width: auto !important; - max-width: 100%; - margin-right: auto; - text-align: left; -} -.email-settings-clean-btn { - flex: 0 0 auto; -} -.email-settings-default .cookbook-srv-default-icon > svg { - position: relative; - top: 3px; -} -.email-unsub-auto-safe-btn > svg { - position: relative; - top: 2px; -} -.email-unsub-auto-safe-btn { - height: 29px; -} -.email-unsub-status, -.email-unsub-panel-status { - position: relative; - top: 2px; -} -.email-unsub-panel-status { - flex: 1 1 220px; - min-width: 0; - margin-right: auto; - overflow: hidden; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-size: 11px; - line-height: 1.3; - text-align: left; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-unsub-panel-status.is-error { - color: var(--red, #e57373); -} -.email-unsub-card .email-tag { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); -} -.email-unsub-duplicate-badge { - position: relative; - top: 2px; -} -.email-unsub-accent-icon { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-unsub-card { - position: relative; - transition: opacity .16s ease; -} -.email-unsub-card-details { - display: flex; - flex-direction: column; - gap: 7px; -} -.email-unsub-card-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 24px; - height: 24px; - padding: 0; - border: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 62%, transparent); - cursor: pointer; -} -.email-unsub-card-toggle:hover, -.email-unsub-card-toggle:focus-visible { - color: var(--accent, var(--red)); -} -.email-unsub-card-toggle > svg { - transition: transform .14s ease; -} -.email-unsub-done-badge { - display: inline-flex; - align-items: center; - gap: 3px; -} -.email-unsub-done-btn { - display: inline-flex; - align-items: center; - gap: 3px; - flex: 0 0 auto; - padding: 3px 6px; - position: relative; - top: -6px; -} -.email-unsub-card-details .email-unsub-done-btn { - top: 0; -} -.email-unsub-done-btn > svg { - color: var(--accent, var(--red)); -} -.email-unsub-ignore-btn { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 22px; - height: 22px; - padding: 0; - position: absolute; - top: 5px; - right: 6px; - color: color-mix(in srgb, var(--fg) 58%, transparent); -} -.email-unsub-card-details .email-unsub-ignore-btn { - position: static; -} -.email-unsub-ignore-btn:hover { - color: var(--accent, var(--red)); -} -.email-unsub-remaining-actions { - display: inline-flex; - align-items: center; - gap: 7px; - flex-wrap: wrap; -} -.email-unsub-remaining-actions button { - display: inline-flex; - align-items: center; - gap: 4px; - position: relative; - top: -2px; -} -.email-unsub-remaining-actions svg { - flex: 0 0 auto; -} -.email-unsub-link-btn { - position: relative; - top: 2px; -} -.email-unsub-status.is-error { - color: var(--accent, var(--red)) !important; -} -.email-settings-section-desc { - position: relative; - top: 8px; - margin-top: 4px; - font-size: 10.5px; - line-height: 1.25; - color: color-mix(in srgb, var(--fg) 55%, transparent); -} -.email-settings-inline-images-section .email-settings-section-desc { - margin-bottom: 4px; -} -.email-settings-inline-images-spacer { - height: 12px; -} -.email-settings-display-spacer { - height: 10px; -} -.email-settings-display-section .email-settings-section-desc { - margin-top: 10px; - top: 10px; -} -.email-settings-display-section, -.email-settings-inline-images-section { - padding-bottom: 18px; -} -.email-settings-cleanup-section .email-settings-section-desc { - margin-top: 14px; - top: 14px; -} -.email-settings-auto-reply-section .email-settings-section-desc { - margin-top: 12px; - top: 10px; -} -.email-settings-toggle, -.email-settings-check { - display: flex; - align-items: center; - gap: 7px; - font-size: 12px; - line-height: 1.25; -} -.email-settings-toggle { - flex-shrink: 0; -} -.email-settings-enabled-control { - display: inline-flex; - align-items: center; - gap: 7px; - flex-shrink: 0; -} -.email-settings-enabled-state { - font-size: 10px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.email-settings-section.is-enabled .email-settings-enabled-state { - color: var(--accent, var(--red)); -} -.email-settings-section.is-disabled .email-settings-enabled-state { - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.email-settings-display-section.is-enabled .email-settings-enabled-state { - color: var(--accent, var(--red)); -} -.email-settings-check { - min-height: 24px; - padding: 3px 0; -} -.email-settings-check > span:first-child { - min-width: 0; - margin-right: auto; -} -.email-settings-check-spacer { - flex: 1; -} -.email-settings-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 7px; - min-width: 0; -} -.email-settings-grid label, -.email-settings-field { - display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; -} -.email-settings-grid span, -.email-settings-field > span { - font-size: 10px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0; - color: color-mix(in srgb, var(--fg) 56%, transparent); -} -.email-settings-page input[type="date"], -.email-settings-page input[type="datetime-local"], -.email-settings-page select, -.email-settings-page textarea { - width: 100%; - min-width: 0; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.email-settings-page input[type="date"], -.email-settings-page input[type="datetime-local"], -.email-settings-page select { - height: 28px; - padding: 0 7px; -} -.email-settings-page textarea { - resize: vertical; - min-height: 72px; - padding: 7px; - line-height: 1.35; -} -.email-settings-static-value { - min-height: 28px; - display: flex; - align-items: center; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--panel, var(--bg)) 74%, transparent); - color: color-mix(in srgb, var(--fg) 72%, transparent); - font-size: 12px; -} -.email-settings-actions { - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; - gap: 8px; - margin-top: 2px; -} - -/* Email Settings actions use the same quiet outlined treatment everywhere: - the Settings links, the modal settings page, and unsubscribe actions. */ -[data-settings-panel="email"] #set-email-open-integrations, -[data-settings-panel="email"] #set-email-open-tasks, -[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn, -#email-lib-settings-page .email-settings-section button.memory-toolbar-btn { - background: transparent; - border: 1px solid var(--border); - color: var(--fg); - font-family: inherit; - font-weight: 400; -} -[data-settings-panel="email"] #set-email-open-integrations:hover, -[data-settings-panel="email"] #set-email-open-tasks:hover, -[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn:hover, -#email-lib-settings-page .email-settings-section button.memory-toolbar-btn:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); - color: var(--fg); -} -[data-settings-panel="email"] #set-email-open-integrations > svg, -[data-settings-panel="email"] #set-email-open-tasks > svg { - color: var(--accent, var(--red)); - opacity: 1 !important; -} -[data-settings-panel="email"] .email-style-settings-extract, -[data-settings-panel="email"] .email-style-settings-save, -#email-lib-settings-page .email-style-settings-extract, -#email-lib-settings-page .email-style-settings-save { - background: transparent; - border: 1px solid var(--border); - color: var(--fg); - font-weight: 400; -} -[data-settings-panel="email"] .email-style-settings-extract:hover, -[data-settings-panel="email"] .email-style-settings-save:hover, -#email-lib-settings-page .email-style-settings-extract:hover, -#email-lib-settings-page .email-style-settings-save:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); - color: var(--fg); -} -.email-settings-status, -.email-style-settings-status { - margin-right: auto; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.email-settings-status.is-success, -.email-style-settings-status.is-success { - color: var(--color-success, #4caf50); - position: relative; - top: 2px; -} -.email-settings-status.is-error, -.email-style-settings-status.is-error { - color: var(--accent, var(--red)); - position: relative; - top: 2px; -} -.email-settings-save, -.email-style-settings-save { - height: 28px; - min-width: 64px; - justify-content: center; - display: inline-flex; - align-items: center; - gap: 5px; -} -.email-settings-save { - height: 32px; -} -.email-style-settings-extract { - height: 32px; -} -.email-style-settings-save { - height: 32px; -} -.email-settings-save svg, -.email-style-settings-save svg { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-style-settings-extract svg { - color: var(--accent, var(--red)); - position: relative; - top: 2px; -} -.email-settings-task-setting { - color: color-mix(in srgb, var(--fg) 72%, transparent); -} -.email-settings-inline-link { - appearance: none; - border: 0; - padding: 0; - background: transparent; - color: var(--accent, var(--red)); - font: inherit; - text-decoration: underline; - cursor: pointer; -} -.email-style-extract-help { - width: 18px; - height: 18px; - min-width: 18px; - padding: 0; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: color-mix(in srgb, var(--fg) 68%, transparent); - display: inline-flex; - align-items: center; - justify-content: center; - font: inherit; - font-size: 11px; - font-weight: 700; - line-height: 1; - cursor: help; -} -.email-style-extract-help:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); -} -@media (max-width: 420px) { - .email-settings-grid { - grid-template-columns: 1fr; - } -} - - -/* ── Notes ── */ -/* Notes panel — body-level flex sibling of chat-container */ -body.notes-view .chat-container { flex: 1; min-width: 0; } - -/* Mobile: notes panel takes over full screen */ -@media (max-width: 768px) { - body.notes-view .notes-pane { - position: fixed; - inset: 0; - max-width: 100%; - width: 100% !important; - /* Desktop sets height: min(80vh, 820px) which wins over inset:0's bottom:0 - and leaves a gap at the bottom of the viewport. Force full height here. */ - height: 100dvh !important; - max-height: 100dvh !important; - z-index: 170; - /* Bottom-sheet treatment like the document editor: stroked, rounded top, - slides up from the bottom. */ - border: 1px solid var(--border); - border-bottom: none; - border-radius: 14px 14px 0 0; - /* Pane itself never scrolls — its inner .notes-pane-body is the scroller. - Two nested scrollers caused the body's flex:1 to lose its height bound - on Firefox mobile and the last row got clipped off-screen. */ - overflow: hidden; - animation: sheet-enter 0.25s cubic-bezier(0.2, 0.8, 0.2, 1) both; - transform-origin: bottom center; - } - /* Required for the flex:1 body to actually constrain to remaining pane - height instead of expanding to its content (default min-height:auto on - flex items). Without this both grid and list views overflow the viewport - bottom on phones. */ - body.notes-view .notes-pane-body { - min-height: 0; - -webkit-overflow-scrolling: touch; - } - body.notes-view #notes-divider { - display: none; - } - body.notes-view .notes-mobile-grabber { - display: block; - flex-shrink: 0; - height: 18px; - position: relative; - background: var(--bg); - touch-action: none; - cursor: grab; - } - body.notes-view .notes-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; - } - body.notes-view .chat-container { - display: none; - } - body.notes-view .mobile-new-chat-btn, - body.notes-view .hamburger-btn { - display: none !important; - } -} -/* ── Mobile notes UX ──────────────────────────────────── - Tiles become read-only previews on touch ≤768px wide. - Tap opens a fullscreen overlay where editing happens; long-press - toggles drag-to-reorder. Wired up in static/js/notes.js. */ -body.notes-mobile-mode .note-card-corner, -body.notes-mobile-mode .note-card-corner-edit, -body.notes-mobile-mode .note-card-corner-archive, -body.notes-mobile-mode .note-card-corner-copy, -body.notes-mobile-mode .note-card-corner-trash, -body.notes-mobile-mode .note-card-corner-unarchive, -body.notes-mobile-mode .note-card-corner-done, -body.notes-mobile-mode .note-card-edit-corner, -body.notes-mobile-mode .note-card-done, -body.notes-mobile-mode .note-card-actions, -body.notes-mobile-mode .note-cl-quickadd, -body.notes-mobile-mode .note-card .note-checkbox-rm, -/* Panel-fullscreen toggle is redundant on mobile — the panel always - fills the viewport already. */ -body.notes-mobile-mode #notes-fullscreen-toggle { - display: none !important; -} - -/* Header toggle icons (archive view, list/grid switch) — defaults are - tuned for the dense desktop header; on mobile bump them slightly so - they read as tap targets without dominating the header. */ -body.notes-mobile-mode #notes-archive-toggle, -body.notes-mobile-mode #notes-view-toggle { - width: 30px; - height: 30px; - padding: 6px; -} -body.notes-mobile-mode #notes-archive-toggle svg, -body.notes-mobile-mode #notes-view-toggle svg { - width: 17px; - height: 17px; -} -/* The whole card is the tap target on mobile — bigger feedback */ -body.notes-mobile-mode .note-card { - cursor: pointer; - -webkit-tap-highlight-color: transparent; - transition: transform 0.12s ease, box-shadow 0.12s ease; -} - -/* Drag-to-reorder mode — desaturate the grid and sweep a subtle - shimmer across every tile so it's obvious you're in rearrange mode - (the Google Keep "active edit" cue). Also disable scroll inside the - panel so the drag works smoothly. */ -body.notes-drag-mode .notes-pane-body { - overflow: hidden; - touch-action: none; -} body.notes-drag-mode .note-card { cursor: grab; transform: scale(0.985); @@ -6010,3 +3027,1036 @@ body.notes-drag-mode .note-card { align-items: center; gap: 6px; } +.note-cl-dot { + width: 8px; + height: 8px; + border-radius: 50%; + border: 1.5px solid color-mix(in srgb, var(--fg) 30%, transparent); + flex-shrink: 0; + cursor: pointer; + transition: all 0.15s; +} +.note-cl-dot:hover { border-color: var(--fg); transform: scale(1.2); } +.note-cl-row.done .note-cl-dot { + background: var(--accent); + border-color: var(--accent); +} +.note-cl-row.done .note-cl-text { + opacity: 0.4; + background: linear-gradient(currentColor, currentColor) no-repeat; + background-size: 0 1px; + background-position: 0 calc(50% - 1px); + animation: cl-strike 0.32s ease-out forwards; + transition: opacity 0.2s ease; +} +/* Draws the strikethrough line left-to-right when the row is marked + done, instead of snapping it in full-width on the same frame. */ +@keyframes cl-strike { + to { background-size: 100% 1px; } +} +.note-cl-text { + background: transparent; + border: none; + border-bottom: 1px solid var(--border); + color: var(--fg); + font-size: 12px; + padding: 3px 0 3px 3px; + flex: 1 1 auto; + width: auto; + min-width: 0; + outline: none; +} +.note-cl-text:focus { border-color: var(--fg); } +.note-cl-rm { + background: none; + border: none; + color: var(--fg); + opacity: 0.3; + cursor: pointer; + font-size: 14px; + padding: 0; + width: 24px; + height: 14px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: flex-end; + line-height: 1; +} +.note-cl-rm:hover { opacity: 0.8; color: var(--red); } +.note-cl-add { + background: none; + border: none; + color: var(--fg); + opacity: 0.4; + cursor: pointer; + font-size: 11px; + padding: 4px 0; + text-align: left; +} + +/* ── Calendar ── */ +.cal-modal-content { width:min(680px, 94vw); max-height:88vh; overflow-x:hidden; position:relative; } +#calendar-modal .cal-modal-content { animation: none; } +/* Was overflow-y:auto with grid + day-detail flowing naturally. Now uses a + true splitter layout: the body is a flex column that doesn't scroll + itself; the grid takes the remaining space and scrolls if needed; the + day-detail has an explicit height (driven by the splitter drag via + --cal-detail-h) and shrinks the grid visually as it grows. */ +#cal-body { display:flex; flex-direction:column; gap:8px; overflow:hidden; padding:0; min-height:0; } +#cal-body > .cal-grid { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overflow-x: hidden; } +@media (max-width: 768px) { + /* Let the calendar pane shrink to nothing on mobile so the day-detail + splitter can be dragged all the way to the top, hiding the calendar + entirely. Without min-height:0 the grid (or the week-wrap below) + refuses to shrink past its built-in floor. */ + #cal-body > .cal-grid, + #cal-body > .cal-wk-wrap { + min-height: 0; + max-height: none; + } + /* Let the week grid fill the calendar pane and scroll its hours internally + instead of overflowing #cal-body (which clips it and feels cramped). */ + #cal-body > .cal-wk-wrap { + flex: 1 1 auto; + overflow: auto; + } +} +.cal-toolbar { display:flex; align-items:center; gap:6px; margin-bottom:7px; flex-wrap:wrap; line-height:1; max-width:100%; row-gap:6px; } + +/* Quick-add bar: natural-language event creation. Sits directly under + the toolbar; focused with `Q`; opens the bespoke form pre-filled. */ +.cal-quickadd-row { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 8px; + padding: 1px 10px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + transition: border-color 0.15s, background 0.15s; + position: relative; +} +/* Two-tone placeholder hint: "Quick add" in accent, the example in grey, both + at one low opacity. Overlays the (empty) input; hidden once the user types. */ +.cal-quickadd-hint { + position: absolute; + left: 10px; + right: 10px; + top: 50%; + transform: translateY(-50%); + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--fg); + opacity: 0.5; + pointer-events: none; + white-space: nowrap; + overflow: hidden; +} +.cal-quickadd-hint .qa-hint-accent { color: var(--accent, var(--red)); } +/* ↵ enter glyph in the hint — makes it obvious the field submits on Enter. */ +.cal-quickadd-hint .qa-hint-enter { margin-left: auto; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.8; } +/* Cycling example text fades out before swap, then fades back in. */ +.cal-quickadd-hint .qa-hint-example { + display: block; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-style: italic; + transition: opacity 0.18s ease; +} +.cal-quickadd-hint .qa-hint-example.qa-hint-fade { opacity: 0; } +.cal-title-hint { + position: absolute; + left: 0; + top: 50%; + transform: translateY(calc(-50% - 2px)); + display: none; + align-items: center; + gap: 7px; + font-size: 18px; + line-height: 1.2; + white-space: nowrap; + pointer-events: none; + color: color-mix(in srgb, var(--fg) 42%, transparent); +} +.cal-hero-title:placeholder-shown ~ .cal-title-hint { display: inline-flex; } +.cal-title-hint .cal-title-enter-ico { color: var(--accent, var(--red)); flex-shrink: 0; } +/* Pure-CSS toggle: hide the hint as soon as the field has real text (no + per-keystroke JS, so typing never triggers a re-render / focus loss). */ +.cal-quickadd-input:not(:placeholder-shown) ~ .cal-quickadd-hint { display: none; } +.cal-quickadd-row:focus-within { + border-color: color-mix(in srgb, var(--accent, var(--fg)) 55%, var(--border)); + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); +} +.cal-quickadd-icon { + color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent); + display: inline-flex; + align-items: center; + flex-shrink: 0; + opacity: 0.7; +} +.cal-quickadd-input { + flex: 1; + min-width: 0; + background: transparent; + border: none; + outline: none; + color: var(--fg); + font-family: inherit; + font-size: 12px; + height: 22px; + padding: 0; +} +.cal-quickadd-status { + font-size: 10px; + opacity: 0.55; + font-variant-numeric: tabular-nums; +} +.cal-toolbar > *, .cal-toolbar-nav > *, .cal-toolbar-right > * { vertical-align:middle; } +.cal-toolbar-nav { display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; } +.cal-toolbar-right { + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 6px; + flex: 1 1 100%; + width: 100%; + margin-left: 0; + margin-top: 6px; + flex-wrap: wrap; +} +.cal-title { font-size:13px; font-weight:600; white-space:nowrap; height:24px; line-height:30px; padding:0 6px; display:inline-block; vertical-align:middle; box-sizing:border-box; } +.cal-toolbar-clock { + margin-left: 8px; + color: var(--accent, var(--red)); + font-size: 11px; + font-weight: 600; + font-variant-numeric: tabular-nums; +} +button.cal-nav { background:color-mix(in srgb, var(--fg) 6%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 8px; height:24px; line-height:22px; cursor:pointer; font-size:11px; font-family:inherit; box-sizing:border-box; vertical-align:middle; } +button.cal-nav:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); } +button.cal-nav.cal-toolbar-arrow { + display: none; + align-items: center; + justify-content: center; + width: 24px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + border-radius: 50%; + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + font-size: 18px; + line-height: 1; + opacity: 0.84; +} +.cal-toolbar-arrow-glyph { + position: relative; + top: -1px; + line-height: 1; +} +@media (max-width: 768px) { + button.cal-nav.cal-toolbar-arrow { display: inline-flex !important; } + .cal-toolbar-nav { + flex: 1 1 100%; + width: 100%; + min-width: 0; + } + #cal-next { margin-left: auto; } +} +.cal-week-start-toggle { + display: inline-flex; + align-items: center; + gap: 0; + width: max-content; + padding: 0; + height: 24px; + box-sizing: border-box; + border: 1px solid var(--border); + border-radius: 5px; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.cal-week-start-btn { + box-sizing: border-box; + height: 22px !important; + min-height: 22px !important; + margin-top: 0 !important; + padding: 0 7px; + border: 1px solid transparent; + border-radius: 4px; + background: transparent; + color: color-mix(in srgb, var(--fg) 68%, transparent); + cursor: pointer; + font-family: inherit; + font-size: 10px; + line-height: 20px; + transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; +} +.cal-settings-actions { + display: flex; + justify-content: flex-end; + width: 100%; +} +#cal-settings-panel .cal-week-start-toggle { + display: flex; + margin-left: auto; +} +.cal-settings-sync-status { + position: relative; + top: 2px; + font-size: 11px; + opacity: 0.6; +} +.cal-settings-sync-status.is-error { + color: var(--red); + opacity: 1; +} +.cal-week-start-btn:hover { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.cal-week-start-btn.active, +.cal-week-start-btn.active:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); +} +.cal-side-nav { + position: absolute; + top: var(--cal-side-nav-y, 50%); + transform: translateY(-50%); + width: 32px; + height: 32px; + border-radius: 50%; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + cursor: pointer; + z-index: 6; + opacity: 0; + pointer-events: none; + font-family: inherit; + font-size: 24px; + line-height: 28px; + padding: 0 0 3px; + transition: opacity 0.12s ease, background 0.12s ease, transform 0.12s ease; +} +.cal-modal-content:hover .cal-side-nav, +.cal-side-nav:focus-visible { + opacity: 0.84; + pointer-events: auto; +} +.cal-side-nav:hover { + opacity: 1; + background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); + transform: translateY(-50%) scale(1.04); +} +button.cal-today-btn { font-size:10px; opacity:0.5; } +button.cal-add-btn { background:var(--accent); color:#fff; border:none; border-radius:50%; width:24px; height:24px; line-height:22px; font-size:18px; cursor:pointer; flex-shrink:0; padding:0; box-sizing:border-box; font-family:inherit; vertical-align:middle; text-align:center; } +button.cal-add-btn.cal-add-btn-text { + width:auto; min-width:0; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 12px; + padding: 0 10px 0 6px; + display: inline-flex; + align-items: center; + gap: 4px; + height: 24px; + line-height: 1; + margin-top: 0; + transition: background 0.15s, border-color 0.15s; +} +button.cal-add-btn.cal-add-btn-text:hover { + background: color-mix(in srgb, var(--fg) 14%, transparent); + border-color: var(--accent); + opacity: 1; +} +.cal-add-plus { + font-size: 16px; + line-height: 1; + color: var(--accent, var(--red)); + font-weight: 500; + display: inline-block; + transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + padding-bottom: 2px; +} +button.cal-add-btn.cal-add-btn-text:hover .cal-add-plus { + transform: rotate(180deg); +} +.cal-add-label { font-size:11px; font-weight:600; line-height:1; opacity:0.85; } +/* Small variant of the toolbar's +New button. Inherits the .cal-add-btn-text + styles (pill with "+" + "New" + spring-rotate on the +), so the + animation is identical. Just shrunk a bit. */ +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { + height: 20px; + border-radius: 10px; + /* At rest: tight, equal padding around the "+" so it sits centred + with no trailing gap. On hover the right pad and the flex gap grow + to make room for the revealed "New". */ + padding: 0 5px; + gap: 0; + transition: padding 0.25s ease, gap 0.25s ease, background 0.15s, border-color 0.15s; +} +/* Don't touch .cal-add-plus on the small variant — the toolbar +New is + the reference and we want the same rotation centre / motion. The label + is shrunk and hidden until hover so the pill is compact at rest. */ +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { + font-size: 10px; + max-width: 0; + margin-left: 0; + opacity: 0; + overflow: hidden; + white-space: nowrap; + transition: max-width 0.25s ease, opacity 0.18s ease, margin-left 0.25s ease; +} +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover .cal-add-label { + max-width: 40px; + margin-left: 3px; + opacity: 0.85; +} +.cal-add-btn:hover { opacity:0.85; } + +/* Mobile: bump the +New pill and the +/- zoom buttons up to a tap-friendly + size so they're easy to hit with a thumb. */ +@media (max-width: 768px) { + /* Toolbar +New (relocated by JS into the quickadd row on mobile) */ + button.cal-add-btn.cal-add-btn-text { + height: 36px; + border-radius: 18px; + padding: 0 14px 0 12px; + gap: 6px; + flex-shrink: 0; + } + /* Day-detail +New: just a "+" on mobile — drop the label entirely. */ + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { + width: 28px; + height: 28px; + border-radius: 50%; + padding: 0; + gap: 0; + justify-content: center; + } + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { + display: none; + } + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { + font-size: 18px; + padding-bottom: 0; + } + .cal-add-plus { font-size: 22px; padding-bottom: 1px; } + .cal-add-label { font-size: 13px; } + .cal-wk-zoom { + width: 36px; + height: 36px; + font-size: 20px; + border-radius: 8px; + opacity: 0.85; + } + /* The +New button sits as a sibling of the quickadd row inside a flex + wrapper, so it's clearly outside the input's bordered box. The row + itself keeps its own styling. */ + .cal-quickadd-wrap { + display: flex; + align-items: stretch; + gap: 8px; + margin: 0 0 8px; + } + .cal-quickadd-wrap > #cal-quickadd-row { + flex: 1; + min-width: 0; + margin: 0; + } + .cal-quickadd-wrap > .cal-add-btn-text { + align-self: stretch; + flex-shrink: 0; + } + /* Nudge the "+" glyph up one pixel on the round day-detail button so it + sits visually centred inside the circle. */ + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { + transform: translateY(-1px); + } + /* Event form: bigger Create / Cancel buttons so they're easy to tap. */ + .cal-form-actions button.cal-btn { + height: 44px; + padding: 0 20px; + font-size: 14px; + border-radius: 8px; + min-width: 96px; + } + /* Left-align the day/month + tags row in the day-detail panel and + the calendar/category chip row so they sit flush against the + left edge on a narrow screen. */ + .cal-detail-header { + justify-content: flex-start; + gap: 12px; + } + .cal-filters { + justify-content: flex-start; + } +} + +/* Refresh button spin — driven by JS toggling .cal-syncing on the button. + transform-box + transform-origin keep the rotation pivoted at the SVG's + own centre so it spins in place instead of orbiting / drifting up. */ +#cal-sync svg, +#email-lib-refresh-btn svg { display: block; transform-box: fill-box; transform-origin: 50% 50%; } +/* Brief checkmark flash after a refresh completes. Lasts ~900ms in JS; + the small bounce + accent color give a clear "done" cue. */ +#cal-sync.cal-sync-done svg, +#email-lib-refresh-btn.email-lib-refresh-done svg { + color: var(--accent, #2ea043); + animation: refresh-check-pop 0.32s ease-out; +} +@keyframes refresh-check-pop { + 0% { transform: scale(0.55); opacity: 0.4; } + 60% { transform: scale(1.18); opacity: 1; } + 100% { transform: scale(1); opacity: 1; } +} +#cal-sync.cal-syncing svg, +#email-lib-refresh-btn.email-lib-refreshing svg { animation: spin 0.6s linear infinite; } +.email-undone-toggle.active, +.email-attach-toggle.active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); + color: var(--accent, var(--red)); + font-weight: 600; +} +/* Hide the gallery-style reader-btn labels on desktop — only the mobile + @media block re-displays them under each icon. Desktop keeps compact + icon-only reader buttons. */ +.reader-btn-label { + display: inline-block; + font-size: 9px; + font-weight: 500; + line-height: 1; + letter-spacing: 0.02em; + white-space: nowrap; + opacity: 0.85; +} + +/* Inline attachment-filter toggle nested inside the search input. The + input has padding-right:34px set inline so its text doesn't slide + under the button. */ +.email-search-wrap { display: flex; align-items: center; } +/* Keep the email search input and the adjacent "Select" button identical in + height regardless of base-vs-mobile overrides — pin both explicitly. */ +.email-search-row .memory-search-input { + height: 32px; + min-height: 32px; + box-sizing: border-box; +} +.email-search-row .email-search-select-btn { + height: 32px; + min-height: 32px; + box-sizing: border-box; + margin-top: 6px; + flex-shrink: 0; +} +/* Select moved into the dropdown row — dock it to the right and match the + selects' vertical nudge (.memory-sort-select has top:3px). */ +.memory-category-filters .email-filter-select-btn, +.memory-category-filters .email-filter-refresh-btn { + position: relative; + top: 3px; + flex-shrink: 0; +} +.memory-category-filters .email-filter-refresh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 8px; +} +.memory-category-filters .email-reminders-clear-btn { + position: relative; + top: -2px; + flex-shrink: 0; +} +.email-attach-toggle-inline { + position: absolute !important; + right: 4px !important; + top: 50%; + transform: translateY(calc(-50% - 5px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-attach-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-attach-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +/* Inline undone-toggle nested inside the search input, sits LEFT of the + attach toggle. Same visual treatment. */ +.email-undone-toggle-inline { + position: absolute !important; + right: 34px !important; + top: 50%; + transform: translateY(calc(-50% - 3px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-reminder-toggle-inline { + position: absolute !important; + right: 64px !important; + top: 50%; + transform: translateY(calc(-50% - 3px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-reminder-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-reminder-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +.email-undone-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-undone-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +/* Round (circular) toggles in the search bar, matching the app's default icon + buttons. Always fully visible (not dimmed until hover). */ +.email-attach-toggle-inline, +.email-undone-toggle-inline, +.email-reminder-toggle-inline { border-radius: 50% !important; opacity: 1 !important; } +.email-search-options-btn { + position: absolute !important; + right: 5px !important; + top: 50% !important; + transform: translateY(calc(-50% - 5px)); + width: 26px !important; + height: 26px !important; + min-width: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 999px !important; + background: transparent !important; + border: 0 !important; + color: inherit !important; + opacity: 0.7; + z-index: 2; +} +.email-search-options-btn:hover, +.email-search-options-btn.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +.email-search-options-menu { + position: absolute; + top: calc(100% + 2px); + right: 0; + z-index: 75; + min-width: 140px; + padding: 4px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--panel, var(--bg)); + box-shadow: 0 8px 24px rgba(0,0,0,0.3); +} +.email-search-options-title { + padding: 5px 9px 4px; + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + opacity: 0.65; + white-space: nowrap; +} +.email-search-options-menu button { + width: 100%; + border: 0; + background: transparent; + color: var(--fg); + display: flex; + align-items: center; + gap: 10px; + height: auto; + min-height: 0; + padding: 6px 8px; + border-radius: 8px; + font-family: inherit; + font-size: 11px; + font-weight: 400; + line-height: normal; + letter-spacing: normal; + text-align: left; + cursor: pointer; +} +.email-search-options-menu button:hover, +.email-search-options-menu button.active { + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + color: var(--accent, var(--red)); +} +.email-search-option-icon { + width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + opacity: 0.5; +} +.email-search-option-icon svg { width: 14px; height: 14px; } +.email-search-date-panel { + width: 220px; + padding: 6px 7px 5px; + border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); +} +.email-search-date-panel input { + width: 100%; + height: 26px; + box-sizing: border-box; + padding: 2px 6px; + border: 1px solid var(--border); + border-radius: 5px; + outline: none; + background: var(--bg); + color: var(--fg); + font: inherit; +} +.email-search-date-panel input:focus { border-color: var(--accent, var(--red)); } +.email-search-date-actions { display: flex; justify-content: flex-end; gap: 5px; } +.email-search-options-menu .email-search-date-actions [data-email-date-apply] { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +@media (max-width: 768px) { + .email-search-options-menu button { + min-height: 44px; + padding: 12px 12px !important; + font-size: 14px !important; + gap: 10px !important; + } + .email-search-options-menu .email-search-option-icon { + width: 18px; + height: 18px; + } + .email-search-options-menu .email-search-option-icon svg { + width: 16px; + height: 16px; + } +} +/* Mobile: enlarge the icons inside the inline search-bar toggles + (done / attachment / reminders) — buttons themselves stay the same, + only the SVG glyph scales up so it's tappable + visible. */ +@media (max-width: 768px) { + .email-filter-btn { + height: 30px; + min-height: 30px; + font-size: 12px; + top: -6px; + padding: 0 9px; + } + .email-folder-btn { + top: -4px; + } + + #email-lib-folder, + .memory-category-filters .email-filter-select-btn, + .memory-category-filters .email-filter-refresh-btn, + .memory-category-filters .email-filter-settings-btn { + height: 30px !important; + min-height: 30px !important; + box-sizing: border-box; + } + + .memory-category-filters .email-filter-refresh-btn, + .memory-category-filters .email-filter-settings-btn { + width: 30px; + padding: 0 !important; + } + .memory-category-filters .email-filter-settings-btn > svg { + width: 15px; + height: 15px; + } + + .email-search-row .memory-search-input { + height: 38px; + min-height: 38px; + } + + #email-lib-chip-bar { + padding-right: 42px !important; + padding-left: 30px !important; + gap: 5px !important; + } + + .email-lib-chip-bar-icon { + width: 14px !important; + height: 14px !important; + left: 9px !important; + } + + #email-lib-search { + font-size: 14px !important; + min-width: 88px !important; + top: 0 !important; + } + + #email-lib-search::placeholder { + font-size: 14px !important; + } + + #email-lib-chip-bar.has-email-lib-pills #email-lib-search { + min-width: 24px !important; + flex: 1 1 24px !important; + } + + .email-lib-pill { + min-height: 24px !important; + height: 24px !important; + line-height: 24px !important; + font-size: 12px !important; + padding: 0 6px 0 8px !important; + max-width: 180px !important; + } + + .email-lib-pill svg { + width: 14px !important; + height: 14px !important; + } + + .email-lib-pill-x { + font-size: 13px !important; + top: -4.5px !important; + } + + .email-lib-filter-pill { + min-height: 24px !important; + height: 24px !important; + line-height: 24px !important; + gap: 4px !important; + padding: 0 6px 0 8px !important; + } + + .email-lib-filter-pill .email-lib-pill-icon { + width: 17px !important; + height: 17px !important; + } + + .email-lib-filter-pill svg { + width: 18px !important; + height: 18px !important; + } + + .email-lib-filter-pill .email-lib-pill-x { + font-size: 13px !important; + top: -4.5px !important; + } + + .email-tags-toggle-inline { + right: 6px; + height: 28px !important; + min-width: 42px; + padding: 0 8px !important; + font-size: 11px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-attach-toggle-inline { + right: 56px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-undone-toggle-inline { + right: 92px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-reminder-toggle-inline { + right: 128px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-attach-toggle-inline svg, + .email-undone-toggle-inline svg, + .email-reminder-toggle-inline svg, + .email-filter-refresh-btn svg { + width: 16px !important; + height: 16px !important; + } +} +.email-accounts-row { + display: flex; + align-items: center; + gap: 10px; + padding: 0 0 6px; +} +.email-accounts-row .doclib-desc { flex-shrink: 0; } +/* Only the direct-child compose button gets pushed right; nested chips + inside #email-lib-accounts pack to the left as normal flex items. */ +.email-accounts-row > .memory-toolbar-btn { flex-shrink: 0; margin-left: auto; } +.email-account-chip { + display: inline-flex; + align-items: center; + gap: 5px; +} +.email-account-chip.email-account-has-unread { + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + color: color-mix(in srgb, var(--accent, var(--red)) 86%, var(--fg)); +} +.email-account-unread-dot { + width: 6px; + height: 6px; + border-radius: 999px; + background: var(--accent, var(--red)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + flex: 0 0 auto; +} +.email-account-chip-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} +.email-account-unread-count { + font-size: 9px; + line-height: 1; + font-weight: 700; + color: var(--accent, var(--red)); + opacity: 0.9; + position: relative; + top: 0; +} +.email-account-away-label { + color: var(--accent, var(--red)); + font-size: 9px; + font-weight: 700; + line-height: 1; + flex: 0 0 auto; +} +.email-accounts-loading-whirlpool { + width: 14px; + height: 14px; + margin: 3px 4px 0 1px; + display: inline-flex; + flex: 0 0 auto; +} +.email-accounts-loading-label { + font-size: 10px; + opacity: 0.55; + position: relative; + top: 2px; + white-space: nowrap; +} +.email-loading-with-label { + /* Fill the email list grid so the spinner lands in the middle of the + email window rather than the top of the empty list section. */ + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + text-align: center; + pointer-events: none; +} +.date-section-header { + display: flex; + align-items: center; + gap: 7px; + padding: 8px 9px 4px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 10px; + font-weight: 700; + letter-spacing: 0; + line-height: 1.2; + text-transform: uppercase; + user-select: none; +} +.date-section-header::after { + content: ''; + flex: 1 1 auto; + height: 1px; + background: color-mix(in srgb, var(--border) 72%, transparent); +} +#session-list .date-section-header { + padding-left: 10px; + padding-right: 8px; + cursor: pointer; +} +#session-list .date-section-header.collapsed { + color: color-mix(in srgb, var(--fg) 36%, transparent); +} +#session-list .date-section-header.collapsed::after { + opacity: 0.45; +} +#session-list .date-section-header:focus-visible { + outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); + outline-offset: 2px; + border-radius: 5px; +} +/* Bootstrap row shown while the session list hydrates, and on load failure. + Reads as a normal list row but is not selectable. */ +.session-list-bootstrap { + cursor: default; + pointer-events: none; +} +#email-lib-grid .date-section-header { + padding: 10px 5px 3px; +} +#email-lib-modal .email-lib-sync-status { + flex: 0 0 auto; + min-height: 14px; + padding: 1px 4px 0; + font-size: 10.5px; + line-height: 1.2; + text-align: center; + opacity: 0.58; + color: var(--muted-text, currentColor); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + top: 4px; +} diff --git a/static/css/16-calendar.css b/static/css/16-calendar.css new file mode 100644 index 000000000..d4112f705 --- /dev/null +++ b/static/css/16-calendar.css @@ -0,0 +1,1175 @@ +/* 16-calendar - 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. + */ +@media (max-width: 768px) { + #email-lib-modal .email-lib-sync-status { + min-height: 13px; + padding-right: 5px; + font-size: 10px; + } +} +/* Nudge the subject + sender/date meta line 4px right inside each + email card so they don't sit flush against the read/unread/done + markers on the left. Includes #email-lib-modal selector to + override the expanded card's `padding: 4px 0 6px` shorthand + which was zeroing out the title's padding-left. */ +#email-lib-grid .email-card-titlerow, +#email-lib-grid .memory-item-meta, +#email-lib-modal .doclib-card-expanded .email-card-titlerow { + padding-left: 4px; +} +.email-loading-label { + font-size: 11px; + opacity: 0.6; +} + +/* Refresh button now lives top-right in the modal header next to the close X. + Borderless (matches the close X), and a fixed square box so the spin and the + refresh→checkmark icon swap never change its size or nudge neighbours. */ +.email-lib-header-actions #email-lib-refresh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + border: none; + background: none; + padding: 0; + width: 24px; + height: 24px; +} +/* Bump the icon up on mobile for a bigger visual — but keep the button BOX at + 24px so it never exceeds the header's natural height (the title/close set + that). A taller box would grow the sticky header and push the list down. */ +@media (max-width: 768px) { + .email-lib-header-actions #email-lib-refresh-btn svg { + width: 17px; + height: 17px; + } +} +.cal-view-toggle { + display: inline-flex; + align-items: stretch; + border: 1px solid var(--border); + border-radius: 5px; + overflow: hidden; + vertical-align: middle; + box-sizing: border-box; + padding: 0; + margin: 0; + line-height: 1; +} +.cal-view-btn + .cal-view-btn { border-left: 1px solid var(--border); } +.cal-view-btn:hover { opacity: 0.75; } +.cal-view-btn.active { + background: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--accent, var(--red)); + opacity: 1; + font-weight: 600; +} +/* Toolbar holds only the toggle button; the chip row renders below the + toolbar on its own line and wraps freely. */ +.cal-filters { + display:flex; + gap:6px; + margin:6px 0 8px; + flex-wrap:wrap; + padding:6px 8px; + border:1px solid color-mix(in srgb, var(--fg) 8%, transparent); + border-radius:8px; + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 4%, transparent), transparent), + color-mix(in srgb, var(--fg) 2.5%, var(--bg)); + box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 4%, transparent); +} +/* Round the native color-input swatch in calendar settings. The outer + wraps a colored fill drawn by the browser; we need to round both + the wrapper and the fill so it actually appears circular. */ +.cal-s-color { border-radius: 50%; overflow: hidden; } +.cal-filter-item { display:inline-flex; align-items:center; gap:4px; cursor:pointer; font-size:10px; padding:2px 8px; line-height:1.4; border-radius:10px; background:color-mix(in srgb, var(--bg) 72%, transparent); border:1px solid color-mix(in srgb, var(--fg) 12%, transparent); transition:opacity 0.15s, background 0.15s, border-color 0.15s, transform 0.15s; } +.cal-filter-item:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); } +.cal-filter-item.cal-filter-important { font-weight:700; color:#e5a33a; border-color:color-mix(in srgb, #e5a33a 40%, transparent); } +/* Match the toolbar's other buttons (cal-nav, cal-view-btn, cal-add-btn-text): + 24px tall, 11px font, 5px corners. */ +.cal-filter-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font-size: 11px; + font-family: inherit; + font-weight: 500; + padding: 0 10px; + height: 24px; + line-height: 1; + border-radius: 5px; + box-sizing: border-box; + vertical-align: middle; + opacity: 0.65; + position: relative; + top: -3px; +} +.cal-filter-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } +.cal-filter-toggle-glyph { + display: inline-block; + position: relative; + top: 0; + margin-right: 3px; + color: var(--accent, var(--red)); +} +.cal-filter-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; } +/* The grid is now a flex column: one row of weekday headers + six + .cal-week-row rows. Each row holds the 7 day cells in its own + internal grid plus the absolute-positioned multi-day overlays. + --bars on a week-row is the number of multi-day events overlapping + it; cells in that row use it to reserve top padding so the bar + overlays don't cover the day numbers or single-event rows. */ +.cal-grid { display:flex; flex-direction:column; gap:1px; background:color-mix(in srgb, var(--fg) 8%, transparent); border-radius:6px; overflow:hidden; } +.cal-week-headers { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } +.cal-week-row { position:relative; display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } +.cal-weekday { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); text-align:center; font-size:10px; font-weight:600; opacity:0.4; padding:5px 0; } +.cal-day { + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 34px), + var(--bg); + min-height:78px; + padding:3px; + cursor:pointer; + position:relative; + transition:background 0.12s, box-shadow 0.12s; + overflow:hidden; +} +.cal-day:hover { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); } +.cal-day.cal-today { + box-shadow: inset 0 0 0 2px var(--accent, var(--red)); + background: var(--bg); + border-radius: 8px; +} +.cal-day.cal-selected:not(.cal-today) .cal-day-num { + color: var(--accent, var(--fg)); + opacity: 1; + font-weight: 700; +} +.cal-day.cal-drag-over { background:color-mix(in srgb, var(--accent) 18%, var(--bg)); } +.cal-day.cal-range-select { background:color-mix(in srgb, var(--accent, var(--red)) 25%, var(--bg)); box-shadow:inset 0 0 0 1px var(--accent, var(--red)); } +.cal-day-num { font-size:10px; font-weight:600; display:block; margin-bottom:1px; opacity:0.7; line-height:1.2; } +.cal-dots { display:flex; flex-direction:row; align-items:center; gap:2px; flex-wrap:nowrap; margin-bottom:1px; height:6px; } +.cal-dot { width:5px; height:5px; border-radius:50%; display:inline-block; cursor:grab; flex-shrink:0; } +.cal-dot-more { font-size:7px; opacity:0.4; } +.cal-event-row { + display:flex; + align-items:center; + gap:4px; + padding:2px 4px; + border-radius:4px; + cursor:grab; + line-height:1.2; + margin-bottom:2px; + min-height:17px; + background:color-mix(in srgb, var(--fg) 5%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); + animation:cal-event-enter 0.16s ease-out both; + transition:background 0.15s, box-shadow 0.15s, transform 0.15s; +} +.cal-event-row:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); transform:translateY(-1px); } +.cal-event-row-dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } +.cal-event-row-time { font-size:10px; opacity:0.72; font-variant-numeric:tabular-nums; flex-shrink:0; font-weight:600; } +.cal-event-row-name { font-size:10px; opacity:0.96; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; font-weight:600; } +.cal-event-reminder { + display: inline-flex; + width: 12px; + height: 12px; + margin-left: 5px; + vertical-align: -2px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + opacity: 0.9; + flex: 0 0 auto; +} +.cal-event-reminder svg { + width: 100%; + height: 100%; + display: block; +} +.cal-event-row .cal-event-reminder { + width: 10px; + height: 10px; + margin-left: 4px; +} +.cal-event-source { + display:inline-flex; + align-items:center; + justify-content:center; + width:12px; + height:12px; + margin-left:5px; + border-radius:50%; + font-size:7px; + font-weight:800; + line-height:1; + color:var(--cal-event-fg, var(--fg)); + background:color-mix(in srgb, currentColor 16%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); + vertical-align:1px; +} +.cal-event-source svg { + width: 10px; + height: 10px; + display: block; +} +.cal-event-row .cal-event-source { + width:11px; + height:11px; + font-size:7px; + margin-left:4px; + color:color-mix(in srgb, var(--fg) 72%, transparent); +} +.cal-multiday .cal-event-reminder, +.cal-wk-allday-event .cal-event-reminder { + color: var(--cal-event-fg, currentColor); + opacity: 0.88; +} +.cal-multiday .cal-event-source, +.cal-wk-allday-event .cal-event-source { + color: var(--cal-event-fg, currentColor); +} +.cal-event-more { font-size:9px; opacity:0.55; padding-left:6px; } +/* Multi-day bar — positioned as an absolute overlay inside its + .cal-week-row so it spans uninterrupted across every column it + covers. --col is the starting column (0–6), --span is the number + of days it covers within the row, --slot stacks parallel bars. */ +.cal-multiday { + position: absolute; + /* Fractional offsets let timed events that cross midnight render at + true proportional width. start-frac shifts the left edge into the + first day; end-frac trims the right edge inside the last day. + All-day events default to (0, 1) and still fill whole columns. */ + left: calc((var(--col, 0) + var(--start-frac, 0)) * (100% / 7)); + width: calc( + (var(--span, 1) - var(--start-frac, 0) - (1 - var(--end-frac, 1))) + * (100% / 7) + ); + top: calc(2px + var(--slot, 0) * 12px); + height: 12px; + font-size: 9px; + line-height: 12px; + padding: 0 5px; + border-radius: 4px; + color: var(--cal-event-fg, #fff); + font-weight: 700; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: grab; + opacity: 0.98; + z-index: 2; + pointer-events: auto; + box-shadow: 0 1px 2px color-mix(in srgb, #000 18%, transparent); +} +/* Splitter between the month/week grid and the day-tasks panel — drag + vertically to give the day panel more room. Height clamped by the + `--cal-detail-h` variable set on #cal-body by the splitter drag JS. */ +.cal-splitter { + height: 8px; + margin: 6px -10px 0; + cursor: row-resize; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + user-select: none; + touch-action: none; +} +/* Mobile: fat hitbox so the splitter is easy to grab with a thumb, + without changing how the grip itself looks. */ +@media (max-width: 768px) { + .cal-splitter { + height: 24px; + margin-top: 0; + margin-bottom: 0; + } + .cal-splitter-grip { + width: 56px; + height: 4px; + } +} +.cal-splitter-grip { + width: 42px; + height: 3px; + border-radius: 2px; + background: color-mix(in srgb, var(--fg) 25%, transparent); + transition: background 0.12s; +} +.cal-splitter:hover .cal-splitter-grip, +.cal-splitter-dragging .cal-splitter-grip { + background: var(--accent, var(--red)); +} +.cal-day-detail { + margin-top: 4px; + border-top: 1px solid var(--border); + padding-top: 8px; + height: var(--cal-detail-h, 240px); + overflow-y: auto; + overscroll-behavior: contain; + flex-shrink: 0; +} +.cal-detail-header { display:flex; justify-content:space-between; align-items:center; font-size:12px; font-weight:600; margin-bottom:6px; padding-right:8px; } +.cal-empty { font-size:11px; opacity:0.3; padding:4px 0; } +.cal-event-item { display:flex; gap:9px; padding:8px 9px; border-radius:6px; cursor:pointer; align-items:flex-start; background:color-mix(in srgb, var(--fg) 3.5%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); margin-bottom:4px; } +.cal-event-item:hover { background:color-mix(in srgb, var(--fg) 8%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); } +.cal-event-dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:4px; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } +.cal-event-info { flex:1; min-width:0; } +.cal-event-name { font-size:13px; font-weight:700; line-height:1.25; color:color-mix(in srgb, var(--fg) 96%, transparent); overflow-wrap:anywhere; } +.cal-event-tag { + display: inline-block; + font-size: 10px; + font-weight: 500; + padding: 1px 6px; + border-radius: 8px; + border: 1px solid; + margin-left: 6px; + vertical-align: 1px; + opacity: 0.85; + white-space: nowrap; +} +.cal-event-time { font-size:11px; opacity:0.68; line-height:1.35; margin-top:2px; font-weight:500; } +.cal-event-loc { font-size:11px; opacity:0.58; line-height:1.35; margin-top:1px; } +/* ── Week view: hour grid ────────────────────────────────────────── + Vertical hour rail on the left, 7 day columns on the right with their + own all-day strip and an absolute-positioned hour grid below it. */ +.cal-wk-wrap { + display: flex; + flex-direction: row; + gap: 0; + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 3%, transparent), transparent 120px), + var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + /* The wrap itself scrolls so the hour rail and day columns move + together; column headers and the rail spacer are sticky-pinned. */ + overflow: auto; + max-height: calc(100vh - 220px); + min-height: 360px; + position: relative; +} +.cal-wk-now-below-hint { + position: absolute; + height: 2px; + border-radius: 2px 2px 0 0; + background: var(--accent, var(--red)); + pointer-events: none; + z-index: 38; +} +@media (max-width: 768px) { + .cal-wk-wrap { + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + } + .cal-wk-wrap > .cal-wk-rail, + .cal-wk-wrap > .cal-wk-cols { + transform: translateY(var(--wk-edge-pull, 0)); + } + .cal-wk-wrap.cal-wk-edge-release > .cal-wk-rail, + .cal-wk-wrap.cal-wk-edge-release > .cal-wk-cols { + transition: transform 0.14s cubic-bezier(0.2, 0.75, 0.25, 1); + } + .cal-wk-wrap.cal-wk-pull-top { + border-top-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); + } + .cal-wk-wrap.cal-wk-pull-bottom { + border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); + } +} +.cal-wk-rail { + flex: 0 0 56px; + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + background: color-mix(in srgb, var(--fg) 2%, var(--bg)); + position: sticky; + left: 0; + z-index: 5; +} +.cal-wk-rail-spacer { + /* Match the 32px day header. The 24px all-day row gets its own spacer + below, so the zoom controls sit high without shifting the hour grid. */ + height: 32px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 0; + background: color-mix(in srgb, var(--fg) 2%, var(--bg)); + z-index: 9; + display: flex; + align-items: center; + justify-content: center; + gap: 2px; + padding: 0 4px; +} +.cal-wk-rail-allday-spacer { + height: 24px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 32px; + z-index: 8; + background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); +} +.cal-wk-zoom { + position: relative; + top: -3px; + width: 22px; + height: 22px; + border: 1px solid var(--border); + background: var(--bg); + color: var(--fg); + border-radius: 50%; + font-family: inherit; + font-size: 14px; + font-weight: 600; + line-height: 1; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + opacity: 0.55; + transition: opacity 0.15s, background 0.15s, border-color 0.15s; +} +.cal-wk-zoom:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--fg)) 50%, var(--border)); +} +.cal-wk-rail-cell { + font-size: 11px; + color: color-mix(in srgb, var(--fg) 55%, transparent); + padding: 4px 8px 0; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); + flex-shrink: 0; + display: flex; + align-items: flex-start; + justify-content: flex-end; + font-variant-numeric: tabular-nums; +} +.cal-wk-cols { + flex: 1; + display: grid; + grid-template-columns: repeat(7, 1fr); + position: relative; +} +.cal-wk-col { + border-left: 1px solid var(--border); + display: flex; + flex-direction: column; + min-width: 0; +} +.cal-wk-col:first-child { border-left: none; } +.cal-wk-col-head { + height: 32px; + padding: 0 6px; + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + flex-shrink: 0; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); + position: sticky; + top: 0; + z-index: 40; +} +/* Today on a Sunday still wins the accent treatment. */ +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); } +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dn, +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dt { color: var(--accent, var(--fg)); } +.cal-wk-dn { font-weight: 600; opacity: 0.6; } +.cal-wk-allday { + height: 24px; + padding: 2px 4px; + display: flex; + flex-direction: row; + gap: 2px; + align-items: center; + background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); + border-bottom: 1px solid var(--border); + flex-shrink: 0; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; + position: sticky; + top: 32px; + z-index: 39; +} +.cal-wk-allday-event { + font-size: 10.5px; + font-weight: 700; + padding: 3px 6px; + border-radius: 4px; + color: var(--cal-event-fg, var(--fg)); + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* Hour grid body */ +.cal-wk-grid { + position: relative; + flex: 0 0 auto; + cursor: cell; + user-select: none; + background: + repeating-linear-gradient( + to bottom, + transparent 0, + transparent calc(var(--wk-hour-px, 52px) / 2 - 1px), + color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--wk-hour-px, 52px) / 2), + transparent calc(var(--wk-hour-px, 52px) / 2 + 1px) + ); +} +.cal-wk-cell { + border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); + box-sizing: border-box; +} +.cal-wk-cell:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); } +/* "Now" line */ +.cal-wk-now { + position: absolute; + left: 0; right: 0; + height: 0; + border-top: 2px solid color-mix(in srgb, var(--accent, var(--red, #f87171)) 90%, transparent); + pointer-events: none; + z-index: 7; + filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent)); +} +/* Event blocks */ +.cal-wk-block { + position: absolute; + left: calc((var(--lane, 0) * (100% / var(--lane-count, 1))) + 2px); + width: calc((100% / var(--lane-count, 1)) - 4px); + right:auto; + border-left: 4px solid var(--fg); + border-radius: 5px; + padding: 5px 7px 7px; + font-size: 12px; + line-height: 1.3; + cursor: pointer; + /* `clip` preserves the rounded-card boundary without creating an overflow + container, allowing the label inside to stick to the week scroller. */ + overflow: clip; + z-index: 2; + box-shadow: 0 2px 6px color-mix(in srgb, #000 14%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); + animation:cal-event-enter 0.18s ease-out both; + transition: width 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), height 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), filter 0.12s, transform 0.12s, box-shadow 0.16s; + box-sizing: border-box; +} +.cal-wk-block:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); } +.cal-wk-block-label { + position: sticky; + top: 37px; + z-index: 1; + min-width: 0; + pointer-events: none; +} +.cal-wk-has-allday .cal-wk-block-label { + top: 61px; +} +.cal-wk-block-name { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color:color-mix(in srgb, var(--fg) 96%, transparent); } +.cal-wk-block-time { font-size: 10.5px; opacity: 0.82; font-variant-numeric: tabular-nums; margin-top: 2px; font-weight:600; } +@media (hover: hover) and (pointer: fine) { + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left { + left: auto; + right: calc(100% - var(--lane-right) + 2px); + } + .cal-wk-block.cal-wk-block-crowded:hover { + width: var(--hover-width, 170px); + height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; + overflow: clip; + z-index: 30; + filter: brightness(1.08); + transform: translateY(var(--hover-shift-y, -2px)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-hover-width-only:hover { + height: var(--event-height) !important; + transform: translateY(-1px); + } + .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-label { + position: absolute; + top: 5px; + width: calc(var(--hover-width, 170px) - 18px); + animation: cal-wk-hover-label-reveal 0.2s ease-out both; + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left:hover .cal-wk-block-label { + left: auto; + right: 7px; + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-right:hover .cal-wk-block-label { + left: 7px; + right: auto; + } + .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-name { + display: block; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + } +} +@media (max-width: 768px) { + /* Mobile week view: the hour rail already shows the time, so drop it from the + card and let the title wrap to fill the freed space (more readable text). */ + .cal-wk-block-time { display: none; } + .cal-wk-block-name { + white-space: normal; + line-height: 1.15; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left { + left: auto; + right: calc(100% - var(--lane-right) + 2px); + } + .cal-wk-block.cal-wk-mobile-expanded { + width: var(--hover-width, 170px); + height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; + overflow: clip; + z-index: 30; + filter: brightness(1.08); + transform: translateY(var(--hover-shift-y, 0)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); + } + .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-label { + position: absolute; + top: 5px; + width: calc(var(--hover-width, 170px) - 18px); + animation: cal-wk-hover-label-reveal 0.2s ease-out both; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left .cal-wk-block-label { + left: auto; + right: 7px; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-right .cal-wk-block-label { + left: 7px; + right: auto; + } + .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-name { + display: block; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + -webkit-line-clamp: unset; + } +} +/* Resize handle: a 6 px hit zone along the block's bottom edge. The + visible 2-px stripe only shows on hover so blocks at rest stay clean. */ +.cal-wk-block-resize { + position: absolute; + left: 4px; + right: 4px; + bottom: 0; + height: 6px; + cursor: ns-resize; + z-index: 3; +} +/* Drag-to-create ghost */ +.cal-wk-ghost { + position: absolute; + left: 2px; + right: 2px; + background: color-mix(in srgb, var(--accent, var(--fg)) 28%, transparent); + border: 1px dashed color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent); + border-radius: 4px; + pointer-events: none; + z-index: 3; + font-size: 10px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + color: var(--accent, var(--fg)); + font-variant-numeric: tabular-nums; +} +.cal-wk-slot-selected { + background: color-mix(in srgb, var(--accent, var(--fg)) 20%, var(--bg)); + border-style: solid; + border-color: var(--accent, var(--fg)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--fg)) 20%, transparent); + overflow: visible; + pointer-events: auto; + cursor: pointer; +} +.cal-wk-slot-resize { + position: absolute; + left: 14%; + right: 14%; + bottom: -7px; + height: 14px; + z-index: 4; + cursor: ns-resize; + touch-action: none; + pointer-events: auto; +} +.cal-year { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; } +.cal-year-month { background:color-mix(in srgb, var(--fg) 3%, var(--bg)); border:1px solid var(--border); border-radius:6px; padding:6px; cursor:pointer; transition:background 0.15s, border-color 0.15s, transform 0.15s; } +.cal-year-month:hover { background:color-mix(in srgb, var(--fg) 8%, var(--bg)); border-color:var(--accent); transform:translateY(-1px); } +.cal-year-month-title { font-size:11px; font-weight:600; text-align:center; margin-bottom:4px; opacity:0.7; } +.cal-year-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; } +.cal-year-wd { font-size:7px; text-align:center; opacity:0.3; padding:1px 0; } +.cal-year-cell { font-size:8px; text-align:center; padding:2px 0; min-height:14px; border-radius:2px; transition:transform 0.12s, background 0.12s; } +.cal-year-day:hover { background:var(--accent, var(--red)); color:#fff; opacity:1; transform:scale(1.15); font-weight:700; z-index:2; position:relative; } +.cal-year-today { color:var(--accent, var(--red)); font-weight:700; opacity:1; } +.cal-year-has { opacity:1; background:color-mix(in srgb, var(--accent) 15%, transparent); } +.cal-year-has.cal-year-today { background:color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } +.cal-loading { display:flex; justify-content:center; padding:40px 0; } +.cal-badge { background:var(--accent); border-radius:50%; width:6px; height:6px; margin-left:auto; display:inline-block; flex-shrink:0; } +/* Search input — in-panel variant lives inside the day-detail panel and + spans its width; no width animation since growing/shrinking on every + keystroke would jitter beside the events list. */ +.cal-search-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:0 8px; height:24px; color:var(--fg); font-size:11px; font-family:inherit; width:120px; box-sizing:border-box; transition:width 0.15s; } +.cal-search-input:focus { outline:none; border-color:var(--accent); width:180px; } +.cal-search-input.cal-day-search { width:100%; margin-bottom:0; transition:none; padding-left: 28px; } +/* Wrap for the search input so the magnifying-glass icon can be absolute- + positioned inside the field. */ +.cal-search-wrap { + position: relative; + display: block; + margin-bottom: 6px; +} +.cal-search-wrap .cal-search-icon { + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + color: var(--accent, var(--red)); +} +.cal-day-search-meta { font-size:10px; opacity:0.5; margin:0 2px 6px; } +.cal-search-results { display:flex; flex-direction:column; gap:4px; } +.cal-search-count { font-size:10px; opacity:0.5; padding:4px 2px 8px; } +/* Agenda view */ +.cal-agenda { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-left:4px; } +.cal-agenda-day { display:flex; flex-direction:column; gap:5px; position:relative; padding-left:14px; } +.cal-agenda-date { font-size:12px; font-weight:800; opacity:0.72; padding:5px 2px 3px; border-bottom:1px solid var(--border); } +.cal-agenda-today-badge { + display: inline-block; + margin-left: 6px; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + padding: 1px 6px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + vertical-align: 1px; +} +.cal-agenda-empty { font-size:11px; opacity:0.4; padding:8px; font-style:italic; } +.cal-agenda-event { + display:flex; + gap:9px; + padding:9px 10px; + border-radius:6px; + cursor:pointer; + align-items:center; + position:relative; + background:color-mix(in srgb, var(--fg) 3.5%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); + animation:cal-event-enter 0.18s ease-out both; + transition:background 0.15s, box-shadow 0.15s, transform 0.15s; +} +.cal-agenda-event:hover { + background:color-mix(in srgb, var(--fg) 8%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); + transform:translateX(2px); +} +.cal-agenda-event .cal-event-more, +.cal-event-item .cal-event-more { opacity:0; transition:opacity 0.15s; } +.cal-event-item { position:relative; } + +/* ── Personal Assistant ── */ +.sidebar-assistant-entry { gap: 6px; min-height: 29px; box-sizing: border-box; align-items: center; } +.sidebar-assistant-entry .sidebar-action-icon { position: relative; left: -2px; } +#sidebar-assistant-gear:hover { opacity: 0.8 !important; } +.assistant-settings-form { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; } +.assistant-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: color-mix(in srgb, var(--fg) 70%, transparent); } +.assistant-field > span { font-size: 11px; opacity: 0.6; } +.assistant-field input[type="text"], +.assistant-field input[type="time"], +.assistant-field select, +.assistant-field textarea { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 5px; + padding: 7px 10px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + box-sizing: border-box; +} +.assistant-field textarea { resize: vertical; min-height: 60px; } +.assistant-field input:focus, +.assistant-field select:focus, +.assistant-field textarea:focus { outline: none; border-color: var(--accent, var(--red)); } +.assistant-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } +.assistant-field-row > .assistant-field { flex: 1 1 180px; } +.assistant-field-check { flex-direction: row !important; align-items: center; gap: 6px; padding-bottom: 7px; } +.assistant-checkins { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); } +.assistant-checkins h5 { margin: 0 0 2px; font-size: 12px; font-weight: 600; opacity: 0.8; } +.assistant-checkin-row { + display: flex; + flex-direction: column; + gap: 4px; + padding: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); +} +.assistant-checkin-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } +.assistant-checkin-head input[type="text"], +.assistant-checkin-head input[type="time"] { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 4px; + padding: 4px 8px; + color: var(--fg); + font-size: 12px; + font-family: inherit; +} +.assistant-checkin-head input[type="text"] { flex: 1; min-width: 120px; } +.assistant-checkin-head input[type="time"] { width: 90px; } +.assistant-checkin-run { + background: none; + border: 1px solid var(--border); + color: var(--fg); + font-family: inherit; + font-size: 11px; + padding: 4px 8px; + border-radius: 4px; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s; +} +.assistant-checkin-run:hover { opacity: 1; } +.assistant-checkin-prompt { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 8px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + resize: vertical; + min-height: 48px; +} +.assistant-checkin-prompt:focus { outline: none; border-color: var(--accent, var(--red)); } +.assistant-checkin-meta { font-size: 10px; opacity: 0.45; } +.assistant-settings-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--border); +} +.assistant-tools-grid { + display: flex; + flex-direction: column; + gap: 6px; + max-height: 240px; + overflow-y: auto; + padding: 6px; + border: 1px solid var(--border); + border-radius: 6px; + margin-top: 4px; +} +.assistant-tool-group { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} +.assistant-tool-group-label { + font-size: 10px; + font-weight: 600; + opacity: 0.5; + text-transform: uppercase; + letter-spacing: 0.03em; + width: 100%; + margin-bottom: 1px; +} +.assistant-tool-item { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 11px; + padding: 2px 6px; + border: 1px solid var(--border); + border-radius: 4px; + cursor: pointer; + transition: background 0.1s, border-color 0.1s; + user-select: none; +} +.assistant-tool-item:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.assistant-tool-item:has(input:checked) { + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 10%, transparent); +} +.assistant-tool-item input[type="checkbox"] { + width: 12px; + height: 12px; + margin: 0; +} + +/* Empty state */ +.cal-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px 20px; text-align:center; gap:10px; } +.cal-empty-title { font-size:14px; font-weight:600; opacity:0.8; } +.cal-empty-msg { font-size:12px; opacity:0.5; max-width:320px; line-height:1.5; margin-bottom:8px; } +.cal-allday-switch { margin:0; flex-shrink:0; } +/* Stack the "All day" label above its toggle so it doesn't get squeezed / + truncated next to the date inputs in the row. */ +.cal-allday-ctrl { display:flex; flex-direction:column; align-items:center; gap:3px; flex-shrink:0; } +.cal-allday-label { font-size:10px; opacity:0.5; white-space:nowrap; line-height:1; } +.cal-form { display:flex; flex-direction:column; gap:8px; } +.cal-form-title { font-size:13px; font-weight:600; margin-bottom:2px; } +.cal-title-wrap { position: relative; } +/* Calendar-picker select sits 2px higher; its border-left/background tint is + set in JS to the chosen calendar's colour. */ +.cal-f-cal-select { position: relative; top: -4px; } + +/* ── Bespoke event form ──────────────────────────────────────────── + Big clock-face date/time hero with the title under it. Details panel + stays collapsed until the title gets focus or "Add details" is clicked. +*/ +.cal-form-bespoke { + position: relative; + width: min(720px, calc(100% - 24px)); + max-width: none; + margin: 12px auto; + padding: 18px 28px 14px; + box-sizing: border-box; + background: var(--bg); + /* Optional per-event tint set by JS via --ev-color. When unset, falls + back to the theme's neutral border so the card looks like normal. */ + border: 1px solid var(--border); + border-radius: 14px; + box-shadow: 0 8px 24px color-mix(in srgb, var(--fg) 6%, transparent); + transition: border-color 0.18s, box-shadow 0.18s; +} +/* When a colour is picked, lift the card with a soft tinted border + glow + so the choice is unmistakable. */ +.cal-form-bespoke[style*="--ev-color"] { + border-color: color-mix(in srgb, var(--ev-color) 55%, var(--border)); + box-shadow: 0 8px 28px color-mix(in srgb, var(--ev-color) 22%, transparent); +} +.cal-form-bespoke[style*="--ev-color"] .cal-btn-primary { + background: var(--ev-color); + border-color: var(--ev-color); +} +.cal-form-bespoke.cal-form-bg-image .cal-btn-primary { + background: var(--accent, var(--red)); + border-color: var(--accent, var(--red)); + color: var(--bg); +} +.cal-form-close { + position: absolute; top: 10px; right: 12px; + background: none; border: none; color: var(--fg); + opacity: 0.35; cursor: pointer; font-size: 20px; line-height: 1; + padding: 4px 8px; border-radius: 6px; +} +.cal-form-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 8%, transparent); } +.cal-form-mobile-cancel { display: none; } + +/* "Today is …" header pinned at the top of the form. Keeps you oriented + even when the event date you're picking is way off in the future. */ +.cal-form-today { + text-align: center; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + opacity: 0.45; + margin-bottom: 0; +} + +/* Hero — clock face + date label. Spacing between the two lives entirely + on the clock's bottom margin so it's predictable. */ +.cal-hero { + display: flex; flex-direction: column; align-items: center; + gap: 0; margin-bottom: 12px; +} +.cal-hero-time { + display: inline-flex; align-items: baseline; gap: 8px; + font-family: ui-monospace, SFMono-Regular, "Menlo", "Consolas", monospace; + font-variant-numeric: tabular-nums; + font-size: 56px; + letter-spacing: 0.02em; + font-weight: 200; + color: var(--fg); + line-height: 1; + /* Behaves like a button — strip native chrome but keep it tappable. */ + background: none; + border: none; + /* Pill that reads as a real hit target for a 56-px clock face. + Top padding kept tight so the clock sits close to the "Today is" line. */ + padding: 6px 38px 18px; + margin: 0 0 24px; + border-radius: 18px; + cursor: pointer; + line-height: 1; + transition: background 0.15s, color 0.15s; +} +/* The clock is a `` and a global `button:hover` rule paints + every button's background on hover. Suppress it explicitly. */ +.cal-hero-time:hover { background: transparent; border-color: transparent; } +.cal-hero-clock { font-feature-settings: "tnum"; display: inline-flex; align-items: baseline; } +/* Per-segment hover so it's clear hh vs mm are individually clickable. */ +.cal-hero-clock-hh, .cal-hero-clock-mm { + display: inline-block; + border-radius: 6px; + padding: 0 2px; + transition: background 0.12s; +} +.cal-hero-clock-hh:hover, .cal-hero-clock-mm:hover { + background: color-mix(in srgb, var(--fg) 9%, transparent); +} +.cal-hero-sep { padding: 0 2px; opacity: 0.55; pointer-events: none; } +.cal-hero-ampm { + font-size: 14px; + letter-spacing: 0.12em; + opacity: 0.55; + font-weight: 500; + text-transform: uppercase; +} +.cal-hero-date { + font-size: 16px; + opacity: 0.6; + letter-spacing: 0.04em; + background: none; + border: none; + color: inherit; + font-family: inherit; + padding: 4px 10px; + border-radius: 6px; + cursor: pointer; + margin: 7px 0 0; /* extra nudge below the clock */ + transition: background 0.15s, opacity 0.15s; +} +.cal-hero-date:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); opacity: 0.85; } + +/* Title input — flat, large, no chrome until focused */ +.cal-input.cal-hero-title { + font-size: 18px; + text-align: left; + padding: 10px 12px 10px 0; + background: transparent; + border: 1px solid transparent; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); + border-radius: 0; + margin-bottom: 4px; +} + +/* Details panel — animated reveal via max-height + opacity. */ +.cal-form-bespoke .cal-form-details { + display: block; + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 240ms ease, opacity 180ms ease, margin 220ms ease; + margin-top: 0; +} +/* The detail children themselves keep the existing flex/gap rules from + the regular .cal-form, so we don't restyle inputs/rows here. */ +.cal-form-bespoke .cal-form-details > * + * { margin-top: 8px; } + + +/* Location row: input + Apple Maps pin link. Pin only lights up when the + field has content; clicking opens maps.apple.com (native Maps app on + Apple devices, web fallback elsewhere). All theme vars. */ +.cal-loc-row { + display: flex; + align-items: stretch; + gap: 6px; +} +.cal-loc-input-wrap { + position: relative; + flex: 1; + min-width: 0; +} +.cal-loc-hint { + position: absolute; + inset: 0 auto 0 10px; + display: none; + align-items: center; + gap: 5px; + pointer-events: none; + color: color-mix(in srgb, var(--fg) 42%, transparent); + font-size: 12px; +} +.cal-loc-map { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + flex-shrink: 0; + border: 1px solid var(--border); + border-radius: 5px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + text-decoration: none; + transition: background 0.15s, border-color 0.15s, opacity 0.15s; +} +.cal-loc-map:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); border-color: var(--accent, var(--red)); } + +@media (max-width: 520px) { + .cal-hero-time { font-size: 44px; } + .cal-form-bespoke { margin: 16px 12px; padding: 32px 16px 14px; } + .cal-form-mobile-cancel { + display: inline-flex; + position: absolute; + top: 8px; + right: 8px; + width: 30px; + height: 30px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + border-radius: 999px; + background: color-mix(in srgb, var(--bg) 82%, transparent); + color: var(--fg); + opacity: 0.72; + cursor: pointer; + z-index: 3; + -webkit-tap-highlight-color: transparent; + } + .cal-form-mobile-cancel:active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); + } +} +.cal-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:7px 10px; color:var(--fg); font-size:12px; width:100%; box-sizing:border-box; } +.cal-input:focus { outline:none; border-color:var(--accent); } +.cal-input-time { width:auto; flex:1; } +.cal-form-row { display:flex; gap:8px; align-items:center; } +.cal-form-actions { display:flex; gap:8px; justify-content:flex-end; margin-top:4px; } diff --git a/static/css/style-part-08.css b/static/css/17-research.css similarity index 57% rename from static/css/style-part-08.css rename to static/css/17-research.css index df8418a28..d2fa2b1d1 100644 --- a/static/css/style-part-08.css +++ b/static/css/17-research.css @@ -1,2206 +1,19 @@ -/* Fragment 9/9 of the former static/style.css. +/* 17-research - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -.note-cl-dot { - width: 8px; - height: 8px; - border-radius: 50%; - border: 1.5px solid color-mix(in srgb, var(--fg) 30%, transparent); - flex-shrink: 0; - cursor: pointer; - transition: all 0.15s; -} -.note-cl-dot:hover { border-color: var(--fg); transform: scale(1.2); } -.note-cl-row.done .note-cl-dot { - background: var(--accent); - border-color: var(--accent); -} -.note-cl-row.done .note-cl-text { - opacity: 0.4; - background: linear-gradient(currentColor, currentColor) no-repeat; - background-size: 0 1px; - background-position: 0 calc(50% - 1px); - animation: cl-strike 0.32s ease-out forwards; - transition: opacity 0.2s ease; -} -/* Draws the strikethrough line left-to-right when the row is marked - done, instead of snapping it in full-width on the same frame. */ -@keyframes cl-strike { - to { background-size: 100% 1px; } -} -.note-cl-text { - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - color: var(--fg); - font-size: 12px; - padding: 3px 0 3px 3px; - flex: 1 1 auto; - width: auto; - min-width: 0; - outline: none; -} -.note-cl-text:focus { border-color: var(--fg); } -.note-cl-rm { - background: none; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - font-size: 14px; - padding: 0; - width: 24px; - height: 14px; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: flex-end; - line-height: 1; -} -.note-cl-rm:hover { opacity: 0.8; color: var(--red); } -.note-cl-add { - background: none; - border: none; - color: var(--fg); - opacity: 0.4; - cursor: pointer; - font-size: 11px; - padding: 4px 0; - text-align: left; -} - -/* ── Calendar ── */ -.cal-modal-content { width:min(680px, 94vw); max-height:88vh; overflow-x:hidden; position:relative; } -#calendar-modal .cal-modal-content { animation: none; } -/* Was overflow-y:auto with grid + day-detail flowing naturally. Now uses a - true splitter layout: the body is a flex column that doesn't scroll - itself; the grid takes the remaining space and scrolls if needed; the - day-detail has an explicit height (driven by the splitter drag via - --cal-detail-h) and shrinks the grid visually as it grows. */ -#cal-body { display:flex; flex-direction:column; gap:8px; overflow:hidden; padding:0; min-height:0; } -#cal-body > .cal-grid { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overflow-x: hidden; } -@media (max-width: 768px) { - /* Let the calendar pane shrink to nothing on mobile so the day-detail - splitter can be dragged all the way to the top, hiding the calendar - entirely. Without min-height:0 the grid (or the week-wrap below) - refuses to shrink past its built-in floor. */ - #cal-body > .cal-grid, - #cal-body > .cal-wk-wrap { - min-height: 0; - max-height: none; - } - /* Let the week grid fill the calendar pane and scroll its hours internally - instead of overflowing #cal-body (which clips it and feels cramped). */ - #cal-body > .cal-wk-wrap { - flex: 1 1 auto; - overflow: auto; - } -} -.cal-toolbar { display:flex; align-items:center; gap:6px; margin-bottom:7px; flex-wrap:wrap; line-height:1; max-width:100%; row-gap:6px; } - -/* Quick-add bar: natural-language event creation. Sits directly under - the toolbar; focused with `Q`; opens the bespoke form pre-filled. */ -.cal-quickadd-row { - display: flex; - align-items: center; - gap: 8px; - margin: 0 0 8px; - padding: 1px 10px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - transition: border-color 0.15s, background 0.15s; - position: relative; -} -/* Two-tone placeholder hint: "Quick add" in accent, the example in grey, both - at one low opacity. Overlays the (empty) input; hidden once the user types. */ -.cal-quickadd-hint { - position: absolute; - left: 10px; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: flex; - align-items: center; - gap: 4px; - font-size: 12px; - color: var(--fg); - opacity: 0.5; - pointer-events: none; - white-space: nowrap; - overflow: hidden; -} -.cal-quickadd-hint .qa-hint-accent { color: var(--accent, var(--red)); } -/* ↵ enter glyph in the hint — makes it obvious the field submits on Enter. */ -.cal-quickadd-hint .qa-hint-enter { margin-left: auto; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.8; } -/* Cycling example text fades out before swap, then fades back in. */ -.cal-quickadd-hint .qa-hint-example { - display: block; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-style: italic; - transition: opacity 0.18s ease; -} -.cal-quickadd-hint .qa-hint-example.qa-hint-fade { opacity: 0; } -.cal-title-hint { - position: absolute; - left: 0; - top: 50%; - transform: translateY(calc(-50% - 2px)); - display: none; - align-items: center; - gap: 7px; - font-size: 18px; - line-height: 1.2; - white-space: nowrap; - pointer-events: none; - color: color-mix(in srgb, var(--fg) 42%, transparent); -} -.cal-hero-title:placeholder-shown ~ .cal-title-hint { display: inline-flex; } -.cal-title-hint .cal-title-enter-ico { color: var(--accent, var(--red)); flex-shrink: 0; } -/* Pure-CSS toggle: hide the hint as soon as the field has real text (no - per-keystroke JS, so typing never triggers a re-render / focus loss). */ -.cal-quickadd-input:not(:placeholder-shown) ~ .cal-quickadd-hint { display: none; } -.cal-quickadd-row:focus-within { - border-color: color-mix(in srgb, var(--accent, var(--fg)) 55%, var(--border)); - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); -} -.cal-quickadd-icon { - color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent); - display: inline-flex; - align-items: center; - flex-shrink: 0; - opacity: 0.7; -} -.cal-quickadd-input { - flex: 1; - min-width: 0; - background: transparent; - border: none; - outline: none; - color: var(--fg); - font-family: inherit; - font-size: 12px; - height: 22px; - padding: 0; -} -.cal-quickadd-status { - font-size: 10px; - opacity: 0.55; - font-variant-numeric: tabular-nums; -} -.cal-toolbar > *, .cal-toolbar-nav > *, .cal-toolbar-right > * { vertical-align:middle; } -.cal-toolbar-nav { display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; } -.cal-toolbar-right { - display: inline-flex; - align-items: center; - justify-content: flex-start; - gap: 6px; - flex: 1 1 100%; - width: 100%; - margin-left: 0; - margin-top: 6px; - flex-wrap: wrap; -} -.cal-title { font-size:13px; font-weight:600; white-space:nowrap; height:24px; line-height:30px; padding:0 6px; display:inline-block; vertical-align:middle; box-sizing:border-box; } -.cal-toolbar-clock { - margin-left: 8px; - color: var(--accent, var(--red)); - font-size: 11px; - font-weight: 600; - font-variant-numeric: tabular-nums; -} -button.cal-nav { background:color-mix(in srgb, var(--fg) 6%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 8px; height:24px; line-height:22px; cursor:pointer; font-size:11px; font-family:inherit; box-sizing:border-box; vertical-align:middle; } -button.cal-nav:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); } -button.cal-nav.cal-toolbar-arrow { - display: none; - align-items: center; - justify-content: center; - width: 24px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 1; - opacity: 0.84; -} -.cal-toolbar-arrow-glyph { - position: relative; - top: -1px; - line-height: 1; -} -@media (max-width: 768px) { - button.cal-nav.cal-toolbar-arrow { display: inline-flex !important; } - .cal-toolbar-nav { - flex: 1 1 100%; - width: 100%; - min-width: 0; - } - #cal-next { margin-left: auto; } -} -.cal-week-start-toggle { - display: inline-flex; - align-items: center; - gap: 0; - width: max-content; - padding: 0; - height: 24px; - box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 5px; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.cal-week-start-btn { - box-sizing: border-box; - height: 22px !important; - min-height: 22px !important; - margin-top: 0 !important; - padding: 0 7px; - border: 1px solid transparent; - border-radius: 4px; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - cursor: pointer; - font-family: inherit; - font-size: 10px; - line-height: 20px; - transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; -} -.cal-settings-actions { - display: flex; - justify-content: flex-end; - width: 100%; -} -#cal-settings-panel .cal-week-start-toggle { - display: flex; - margin-left: auto; -} -.cal-settings-sync-status { - position: relative; - top: 2px; - font-size: 11px; - opacity: 0.6; -} -.cal-settings-sync-status.is-error { - color: var(--red); - opacity: 1; -} -.cal-week-start-btn:hover { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.cal-week-start-btn.active, -.cal-week-start-btn.active:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); -} -.cal-side-nav { - position: absolute; - top: var(--cal-side-nav-y, 50%); - transform: translateY(-50%); - width: 32px; - height: 32px; - border-radius: 50%; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - cursor: pointer; - z-index: 6; - opacity: 0; - pointer-events: none; - font-family: inherit; - font-size: 24px; - line-height: 28px; - padding: 0 0 3px; - transition: opacity 0.12s ease, background 0.12s ease, transform 0.12s ease; -} -.cal-modal-content:hover .cal-side-nav, -.cal-side-nav:focus-visible { - opacity: 0.84; - pointer-events: auto; -} -.cal-side-nav:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); - transform: translateY(-50%) scale(1.04); -} -button.cal-today-btn { font-size:10px; opacity:0.5; } -button.cal-add-btn { background:var(--accent); color:#fff; border:none; border-radius:50%; width:24px; height:24px; line-height:22px; font-size:18px; cursor:pointer; flex-shrink:0; padding:0; box-sizing:border-box; font-family:inherit; vertical-align:middle; text-align:center; } -button.cal-add-btn.cal-add-btn-text { - width:auto; min-width:0; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 12px; - padding: 0 10px 0 6px; - display: inline-flex; - align-items: center; - gap: 4px; - height: 24px; - line-height: 1; - margin-top: 0; - transition: background 0.15s, border-color 0.15s; -} -button.cal-add-btn.cal-add-btn-text:hover { - background: color-mix(in srgb, var(--fg) 14%, transparent); - border-color: var(--accent); - opacity: 1; -} -.cal-add-plus { - font-size: 16px; - line-height: 1; - color: var(--accent, var(--red)); - font-weight: 500; - display: inline-block; - transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); - padding-bottom: 2px; -} -button.cal-add-btn.cal-add-btn-text:hover .cal-add-plus { - transform: rotate(180deg); -} -.cal-add-label { font-size:11px; font-weight:600; line-height:1; opacity:0.85; } -/* Small variant of the toolbar's +New button. Inherits the .cal-add-btn-text - styles (pill with "+" + "New" + spring-rotate on the +), so the - animation is identical. Just shrunk a bit. */ -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { - height: 20px; - border-radius: 10px; - /* At rest: tight, equal padding around the "+" so it sits centred - with no trailing gap. On hover the right pad and the flex gap grow - to make room for the revealed "New". */ - padding: 0 5px; - gap: 0; - transition: padding 0.25s ease, gap 0.25s ease, background 0.15s, border-color 0.15s; -} -/* Don't touch .cal-add-plus on the small variant — the toolbar +New is - the reference and we want the same rotation centre / motion. The label - is shrunk and hidden until hover so the pill is compact at rest. */ -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { - font-size: 10px; - max-width: 0; - margin-left: 0; - opacity: 0; - overflow: hidden; - white-space: nowrap; - transition: max-width 0.25s ease, opacity 0.18s ease, margin-left 0.25s ease; -} -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover .cal-add-label { - max-width: 40px; - margin-left: 3px; - opacity: 0.85; -} -.cal-add-btn:hover { opacity:0.85; } - -/* Mobile: bump the +New pill and the +/- zoom buttons up to a tap-friendly - size so they're easy to hit with a thumb. */ -@media (max-width: 768px) { - /* Toolbar +New (relocated by JS into the quickadd row on mobile) */ - button.cal-add-btn.cal-add-btn-text { - height: 36px; - border-radius: 18px; - padding: 0 14px 0 12px; - gap: 6px; - flex-shrink: 0; - } - /* Day-detail +New: just a "+" on mobile — drop the label entirely. */ - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { - width: 28px; - height: 28px; - border-radius: 50%; - padding: 0; - gap: 0; - justify-content: center; - } - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { - display: none; - } - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { - font-size: 18px; - padding-bottom: 0; - } - .cal-add-plus { font-size: 22px; padding-bottom: 1px; } - .cal-add-label { font-size: 13px; } - .cal-wk-zoom { - width: 36px; - height: 36px; - font-size: 20px; - border-radius: 8px; - opacity: 0.85; - } - /* The +New button sits as a sibling of the quickadd row inside a flex - wrapper, so it's clearly outside the input's bordered box. The row - itself keeps its own styling. */ - .cal-quickadd-wrap { - display: flex; - align-items: stretch; - gap: 8px; - margin: 0 0 8px; - } - .cal-quickadd-wrap > #cal-quickadd-row { - flex: 1; - min-width: 0; - margin: 0; - } - .cal-quickadd-wrap > .cal-add-btn-text { - align-self: stretch; - flex-shrink: 0; - } - /* Nudge the "+" glyph up one pixel on the round day-detail button so it - sits visually centred inside the circle. */ - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { - transform: translateY(-1px); - } - /* Event form: bigger Create / Cancel buttons so they're easy to tap. */ - .cal-form-actions button.cal-btn { - height: 44px; - padding: 0 20px; - font-size: 14px; - border-radius: 8px; - min-width: 96px; - } - /* Left-align the day/month + tags row in the day-detail panel and - the calendar/category chip row so they sit flush against the - left edge on a narrow screen. */ - .cal-detail-header { - justify-content: flex-start; - gap: 12px; - } - .cal-filters { - justify-content: flex-start; - } -} - -/* Refresh button spin — driven by JS toggling .cal-syncing on the button. - transform-box + transform-origin keep the rotation pivoted at the SVG's - own centre so it spins in place instead of orbiting / drifting up. */ -#cal-sync svg, -#email-lib-refresh-btn svg { display: block; transform-box: fill-box; transform-origin: 50% 50%; } -/* Brief checkmark flash after a refresh completes. Lasts ~900ms in JS; - the small bounce + accent color give a clear "done" cue. */ -#cal-sync.cal-sync-done svg, -#email-lib-refresh-btn.email-lib-refresh-done svg { - color: var(--accent, #2ea043); - animation: refresh-check-pop 0.32s ease-out; -} -@keyframes refresh-check-pop { - 0% { transform: scale(0.55); opacity: 0.4; } - 60% { transform: scale(1.18); opacity: 1; } - 100% { transform: scale(1); opacity: 1; } -} -#cal-sync.cal-syncing svg, -#email-lib-refresh-btn.email-lib-refreshing svg { animation: spin 0.6s linear infinite; } -.email-undone-toggle.active, -.email-attach-toggle.active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); - color: var(--accent, var(--red)); - font-weight: 600; -} -/* Hide the gallery-style reader-btn labels on desktop — only the mobile - @media block re-displays them under each icon. Desktop keeps compact - icon-only reader buttons. */ -.reader-btn-label { - display: inline-block; - font-size: 9px; - font-weight: 500; - line-height: 1; - letter-spacing: 0.02em; - white-space: nowrap; - opacity: 0.85; -} - -/* Inline attachment-filter toggle nested inside the search input. The - input has padding-right:34px set inline so its text doesn't slide - under the button. */ -.email-search-wrap { display: flex; align-items: center; } -/* Keep the email search input and the adjacent "Select" button identical in - height regardless of base-vs-mobile overrides — pin both explicitly. */ -.email-search-row .memory-search-input { - height: 32px; - min-height: 32px; - box-sizing: border-box; -} -.email-search-row .email-search-select-btn { - height: 32px; - min-height: 32px; - box-sizing: border-box; - margin-top: 6px; - flex-shrink: 0; -} -/* Select moved into the dropdown row — dock it to the right and match the - selects' vertical nudge (.memory-sort-select has top:3px). */ -.memory-category-filters .email-filter-select-btn, -.memory-category-filters .email-filter-refresh-btn { - position: relative; - top: 3px; - flex-shrink: 0; -} -.memory-category-filters .email-filter-refresh-btn { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0 8px; -} -.memory-category-filters .email-reminders-clear-btn { - position: relative; - top: -2px; - flex-shrink: 0; -} -.email-attach-toggle-inline { - position: absolute !important; - right: 4px !important; - top: 50%; - transform: translateY(calc(-50% - 5px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-attach-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-attach-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -/* Inline undone-toggle nested inside the search input, sits LEFT of the - attach toggle. Same visual treatment. */ -.email-undone-toggle-inline { - position: absolute !important; - right: 34px !important; - top: 50%; - transform: translateY(calc(-50% - 3px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-reminder-toggle-inline { - position: absolute !important; - right: 64px !important; - top: 50%; - transform: translateY(calc(-50% - 3px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-reminder-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-reminder-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -.email-undone-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-undone-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -/* Round (circular) toggles in the search bar, matching the app's default icon - buttons. Always fully visible (not dimmed until hover). */ -.email-attach-toggle-inline, -.email-undone-toggle-inline, -.email-reminder-toggle-inline { border-radius: 50% !important; opacity: 1 !important; } -.email-search-options-btn { - position: absolute !important; - right: 5px !important; - top: 50% !important; - transform: translateY(calc(-50% - 5px)); - width: 26px !important; - height: 26px !important; - min-width: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 999px !important; - background: transparent !important; - border: 0 !important; - color: inherit !important; - opacity: 0.7; - z-index: 2; -} -.email-search-options-btn:hover, -.email-search-options-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -.email-search-options-menu { - position: absolute; - top: calc(100% + 2px); - right: 0; - z-index: 75; - min-width: 140px; - padding: 4px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--panel, var(--bg)); - box-shadow: 0 8px 24px rgba(0,0,0,0.3); -} -.email-search-options-title { - padding: 5px 9px 4px; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - opacity: 0.65; - white-space: nowrap; -} -.email-search-options-menu button { - width: 100%; - border: 0; - background: transparent; - color: var(--fg); - display: flex; - align-items: center; - gap: 10px; - height: auto; - min-height: 0; - padding: 6px 8px; - border-radius: 8px; - font-family: inherit; - font-size: 11px; - font-weight: 400; - line-height: normal; - letter-spacing: normal; - text-align: left; - cursor: pointer; -} -.email-search-options-menu button:hover, -.email-search-options-menu button.active { - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - color: var(--accent, var(--red)); -} -.email-search-option-icon { - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - opacity: 0.5; -} -.email-search-option-icon svg { width: 14px; height: 14px; } -.email-search-date-panel { - width: 220px; - padding: 6px 7px 5px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.email-search-date-panel input { - width: 100%; - height: 26px; - box-sizing: border-box; - padding: 2px 6px; - border: 1px solid var(--border); - border-radius: 5px; - outline: none; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.email-search-date-panel input:focus { border-color: var(--accent, var(--red)); } -.email-search-date-actions { display: flex; justify-content: flex-end; gap: 5px; } -.email-search-options-menu .email-search-date-actions [data-email-date-apply] { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -@media (max-width: 768px) { - .email-search-options-menu button { - min-height: 44px; - padding: 12px 12px !important; - font-size: 14px !important; - gap: 10px !important; - } - .email-search-options-menu .email-search-option-icon { - width: 18px; - height: 18px; - } - .email-search-options-menu .email-search-option-icon svg { - width: 16px; - height: 16px; - } -} -/* Mobile: enlarge the icons inside the inline search-bar toggles - (done / attachment / reminders) — buttons themselves stay the same, - only the SVG glyph scales up so it's tappable + visible. */ -@media (max-width: 768px) { - .email-filter-btn { - height: 30px; - min-height: 30px; - font-size: 12px; - top: -6px; - padding: 0 9px; - } - .email-folder-btn { - top: -4px; - } - - #email-lib-folder, - .memory-category-filters .email-filter-select-btn, - .memory-category-filters .email-filter-refresh-btn, - .memory-category-filters .email-filter-settings-btn { - height: 30px !important; - min-height: 30px !important; - box-sizing: border-box; - } - - .memory-category-filters .email-filter-refresh-btn, - .memory-category-filters .email-filter-settings-btn { - width: 30px; - padding: 0 !important; - } - .memory-category-filters .email-filter-settings-btn > svg { - width: 15px; - height: 15px; - } - - .email-search-row .memory-search-input { - height: 38px; - min-height: 38px; - } - - #email-lib-chip-bar { - padding-right: 42px !important; - padding-left: 30px !important; - gap: 5px !important; - } - - .email-lib-chip-bar-icon { - width: 14px !important; - height: 14px !important; - left: 9px !important; - } - - #email-lib-search { - font-size: 14px !important; - min-width: 88px !important; - top: 0 !important; - } - - #email-lib-search::placeholder { - font-size: 14px !important; - } - - #email-lib-chip-bar.has-email-lib-pills #email-lib-search { - min-width: 24px !important; - flex: 1 1 24px !important; - } - - .email-lib-pill { - min-height: 24px !important; - height: 24px !important; - line-height: 24px !important; - font-size: 12px !important; - padding: 0 6px 0 8px !important; - max-width: 180px !important; - } - - .email-lib-pill svg { - width: 14px !important; - height: 14px !important; - } - - .email-lib-pill-x { - font-size: 13px !important; - top: -4.5px !important; - } - - .email-lib-filter-pill { - min-height: 24px !important; - height: 24px !important; - line-height: 24px !important; - gap: 4px !important; - padding: 0 6px 0 8px !important; - } - - .email-lib-filter-pill .email-lib-pill-icon { - width: 17px !important; - height: 17px !important; - } - - .email-lib-filter-pill svg { - width: 18px !important; - height: 18px !important; - } - - .email-lib-filter-pill .email-lib-pill-x { - font-size: 13px !important; - top: -4.5px !important; - } - - .email-tags-toggle-inline { - right: 6px; - height: 28px !important; - min-width: 42px; - padding: 0 8px !important; - font-size: 11px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-attach-toggle-inline { - right: 56px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-undone-toggle-inline { - right: 92px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-reminder-toggle-inline { - right: 128px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-attach-toggle-inline svg, - .email-undone-toggle-inline svg, - .email-reminder-toggle-inline svg, - .email-filter-refresh-btn svg { - width: 16px !important; - height: 16px !important; - } -} -.email-accounts-row { - display: flex; - align-items: center; - gap: 10px; - padding: 0 0 6px; -} -.email-accounts-row .doclib-desc { flex-shrink: 0; } -/* Only the direct-child compose button gets pushed right; nested chips - inside #email-lib-accounts pack to the left as normal flex items. */ -.email-accounts-row > .memory-toolbar-btn { flex-shrink: 0; margin-left: auto; } -.email-account-chip { - display: inline-flex; - align-items: center; - gap: 5px; -} -.email-account-chip.email-account-has-unread { - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - color: color-mix(in srgb, var(--accent, var(--red)) 86%, var(--fg)); -} -.email-account-unread-dot { - width: 6px; - height: 6px; - border-radius: 999px; - background: var(--accent, var(--red)); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - flex: 0 0 auto; -} -.email-account-chip-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} -.email-account-unread-count { - font-size: 9px; - line-height: 1; - font-weight: 700; - color: var(--accent, var(--red)); - opacity: 0.9; - position: relative; - top: 0; -} -.email-account-away-label { - color: var(--accent, var(--red)); - font-size: 9px; - font-weight: 700; - line-height: 1; - flex: 0 0 auto; -} -.email-accounts-loading-whirlpool { - width: 14px; - height: 14px; - margin: 3px 4px 0 1px; - display: inline-flex; - flex: 0 0 auto; -} -.email-accounts-loading-label { - font-size: 10px; - opacity: 0.55; - position: relative; - top: 2px; - white-space: nowrap; -} -.email-loading-with-label { - /* Fill the email list grid so the spinner lands in the middle of the - email window rather than the top of the empty list section. */ - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - text-align: center; - pointer-events: none; -} -.date-section-header { - display: flex; - align-items: center; - gap: 7px; - padding: 8px 9px 4px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 10px; - font-weight: 700; - letter-spacing: 0; - line-height: 1.2; - text-transform: uppercase; - user-select: none; -} -.date-section-header::after { - content: ''; - flex: 1 1 auto; - height: 1px; - background: color-mix(in srgb, var(--border) 72%, transparent); -} -#session-list .date-section-header { - padding-left: 10px; - padding-right: 8px; - cursor: pointer; -} -#session-list .date-section-header.collapsed { - color: color-mix(in srgb, var(--fg) 36%, transparent); -} -#session-list .date-section-header.collapsed::after { - opacity: 0.45; -} -#session-list .date-section-header:focus-visible { - outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); - outline-offset: 2px; - border-radius: 5px; -} -/* Bootstrap row shown while the session list hydrates, and on load failure. - Reads as a normal list row but is not selectable. */ -.session-list-bootstrap { - cursor: default; - pointer-events: none; -} -#email-lib-grid .date-section-header { - padding: 10px 5px 3px; -} -#email-lib-modal .email-lib-sync-status { - flex: 0 0 auto; - min-height: 14px; - padding: 1px 4px 0; - font-size: 10.5px; - line-height: 1.2; - text-align: center; - opacity: 0.58; - color: var(--muted-text, currentColor); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - position: relative; - top: 4px; -} -@media (max-width: 768px) { - #email-lib-modal .email-lib-sync-status { - min-height: 13px; - padding-right: 5px; - font-size: 10px; - } -} -/* Nudge the subject + sender/date meta line 4px right inside each - email card so they don't sit flush against the read/unread/done - markers on the left. Includes #email-lib-modal selector to - override the expanded card's `padding: 4px 0 6px` shorthand - which was zeroing out the title's padding-left. */ -#email-lib-grid .email-card-titlerow, -#email-lib-grid .memory-item-meta, -#email-lib-modal .doclib-card-expanded .email-card-titlerow { - padding-left: 4px; -} -.email-loading-label { - font-size: 11px; - opacity: 0.6; -} - -/* Refresh button now lives top-right in the modal header next to the close X. - Borderless (matches the close X), and a fixed square box so the spin and the - refresh→checkmark icon swap never change its size or nudge neighbours. */ -.email-lib-header-actions #email-lib-refresh-btn { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - border: none; - background: none; - padding: 0; - width: 24px; - height: 24px; -} -/* Bump the icon up on mobile for a bigger visual — but keep the button BOX at - 24px so it never exceeds the header's natural height (the title/close set - that). A taller box would grow the sticky header and push the list down. */ -@media (max-width: 768px) { - .email-lib-header-actions #email-lib-refresh-btn svg { - width: 17px; - height: 17px; - } -} -.cal-view-toggle { - display: inline-flex; - align-items: stretch; - border: 1px solid var(--border); - border-radius: 5px; - overflow: hidden; - vertical-align: middle; - box-sizing: border-box; - padding: 0; - margin: 0; - line-height: 1; -} -.cal-view-btn + .cal-view-btn { border-left: 1px solid var(--border); } -.cal-view-btn:hover { opacity: 0.75; } -.cal-view-btn.active { - background: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--accent, var(--red)); - opacity: 1; - font-weight: 600; -} -/* Toolbar holds only the toggle button; the chip row renders below the - toolbar on its own line and wraps freely. */ -.cal-filters { - display:flex; - gap:6px; - margin:6px 0 8px; - flex-wrap:wrap; - padding:6px 8px; - border:1px solid color-mix(in srgb, var(--fg) 8%, transparent); - border-radius:8px; - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 4%, transparent), transparent), - color-mix(in srgb, var(--fg) 2.5%, var(--bg)); - box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 4%, transparent); -} -/* Round the native color-input swatch in calendar settings. The outer - wraps a colored fill drawn by the browser; we need to round both - the wrapper and the fill so it actually appears circular. */ -.cal-s-color { border-radius: 50%; overflow: hidden; } -.cal-filter-item { display:inline-flex; align-items:center; gap:4px; cursor:pointer; font-size:10px; padding:2px 8px; line-height:1.4; border-radius:10px; background:color-mix(in srgb, var(--bg) 72%, transparent); border:1px solid color-mix(in srgb, var(--fg) 12%, transparent); transition:opacity 0.15s, background 0.15s, border-color 0.15s, transform 0.15s; } -.cal-filter-item:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); } -.cal-filter-item.cal-filter-important { font-weight:700; color:#e5a33a; border-color:color-mix(in srgb, #e5a33a 40%, transparent); } -/* Match the toolbar's other buttons (cal-nav, cal-view-btn, cal-add-btn-text): - 24px tall, 11px font, 5px corners. */ -.cal-filter-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font-size: 11px; - font-family: inherit; - font-weight: 500; - padding: 0 10px; - height: 24px; - line-height: 1; - border-radius: 5px; - box-sizing: border-box; - vertical-align: middle; - opacity: 0.65; - position: relative; - top: -3px; -} -.cal-filter-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } -.cal-filter-toggle-glyph { - display: inline-block; - position: relative; - top: 0; - margin-right: 3px; - color: var(--accent, var(--red)); -} -.cal-filter-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; } -/* The grid is now a flex column: one row of weekday headers + six - .cal-week-row rows. Each row holds the 7 day cells in its own - internal grid plus the absolute-positioned multi-day overlays. - --bars on a week-row is the number of multi-day events overlapping - it; cells in that row use it to reserve top padding so the bar - overlays don't cover the day numbers or single-event rows. */ -.cal-grid { display:flex; flex-direction:column; gap:1px; background:color-mix(in srgb, var(--fg) 8%, transparent); border-radius:6px; overflow:hidden; } -.cal-week-headers { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } -.cal-week-row { position:relative; display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } -.cal-weekday { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); text-align:center; font-size:10px; font-weight:600; opacity:0.4; padding:5px 0; } -.cal-day { - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 34px), - var(--bg); - min-height:78px; - padding:3px; - cursor:pointer; - position:relative; - transition:background 0.12s, box-shadow 0.12s; - overflow:hidden; -} -.cal-day:hover { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); } -.cal-day.cal-today { - box-shadow: inset 0 0 0 2px var(--accent, var(--red)); - background: var(--bg); - border-radius: 8px; -} -.cal-day.cal-selected:not(.cal-today) .cal-day-num { - color: var(--accent, var(--fg)); - opacity: 1; - font-weight: 700; -} -.cal-day.cal-drag-over { background:color-mix(in srgb, var(--accent) 18%, var(--bg)); } -.cal-day.cal-range-select { background:color-mix(in srgb, var(--accent, var(--red)) 25%, var(--bg)); box-shadow:inset 0 0 0 1px var(--accent, var(--red)); } -.cal-day-num { font-size:10px; font-weight:600; display:block; margin-bottom:1px; opacity:0.7; line-height:1.2; } -.cal-dots { display:flex; flex-direction:row; align-items:center; gap:2px; flex-wrap:nowrap; margin-bottom:1px; height:6px; } -.cal-dot { width:5px; height:5px; border-radius:50%; display:inline-block; cursor:grab; flex-shrink:0; } -.cal-dot-more { font-size:7px; opacity:0.4; } -.cal-event-row { - display:flex; - align-items:center; - gap:4px; - padding:2px 4px; - border-radius:4px; - cursor:grab; - line-height:1.2; - margin-bottom:2px; - min-height:17px; - background:color-mix(in srgb, var(--fg) 5%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); - animation:cal-event-enter 0.16s ease-out both; - transition:background 0.15s, box-shadow 0.15s, transform 0.15s; -} -.cal-event-row:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); transform:translateY(-1px); } -.cal-event-row-dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } -.cal-event-row-time { font-size:10px; opacity:0.72; font-variant-numeric:tabular-nums; flex-shrink:0; font-weight:600; } -.cal-event-row-name { font-size:10px; opacity:0.96; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; font-weight:600; } -.cal-event-reminder { - display: inline-flex; - width: 12px; - height: 12px; - margin-left: 5px; - vertical-align: -2px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - opacity: 0.9; - flex: 0 0 auto; -} -.cal-event-reminder svg { - width: 100%; - height: 100%; - display: block; -} -.cal-event-row .cal-event-reminder { - width: 10px; - height: 10px; - margin-left: 4px; -} -.cal-event-source { - display:inline-flex; - align-items:center; - justify-content:center; - width:12px; - height:12px; - margin-left:5px; - border-radius:50%; - font-size:7px; - font-weight:800; - line-height:1; - color:var(--cal-event-fg, var(--fg)); - background:color-mix(in srgb, currentColor 16%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); - vertical-align:1px; -} -.cal-event-source svg { - width: 10px; - height: 10px; - display: block; -} -.cal-event-row .cal-event-source { - width:11px; - height:11px; - font-size:7px; - margin-left:4px; - color:color-mix(in srgb, var(--fg) 72%, transparent); -} -.cal-multiday .cal-event-reminder, -.cal-wk-allday-event .cal-event-reminder { - color: var(--cal-event-fg, currentColor); - opacity: 0.88; -} -.cal-multiday .cal-event-source, -.cal-wk-allday-event .cal-event-source { - color: var(--cal-event-fg, currentColor); -} -.cal-event-more { font-size:9px; opacity:0.55; padding-left:6px; } -/* Multi-day bar — positioned as an absolute overlay inside its - .cal-week-row so it spans uninterrupted across every column it - covers. --col is the starting column (0–6), --span is the number - of days it covers within the row, --slot stacks parallel bars. */ -.cal-multiday { - position: absolute; - /* Fractional offsets let timed events that cross midnight render at - true proportional width. start-frac shifts the left edge into the - first day; end-frac trims the right edge inside the last day. - All-day events default to (0, 1) and still fill whole columns. */ - left: calc((var(--col, 0) + var(--start-frac, 0)) * (100% / 7)); - width: calc( - (var(--span, 1) - var(--start-frac, 0) - (1 - var(--end-frac, 1))) - * (100% / 7) - ); - top: calc(2px + var(--slot, 0) * 12px); - height: 12px; - font-size: 9px; - line-height: 12px; - padding: 0 5px; - border-radius: 4px; - color: var(--cal-event-fg, #fff); - font-weight: 700; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: grab; - opacity: 0.98; - z-index: 2; - pointer-events: auto; - box-shadow: 0 1px 2px color-mix(in srgb, #000 18%, transparent); -} -/* Splitter between the month/week grid and the day-tasks panel — drag - vertically to give the day panel more room. Height clamped by the - `--cal-detail-h` variable set on #cal-body by the splitter drag JS. */ -.cal-splitter { - height: 8px; - margin: 6px -10px 0; - cursor: row-resize; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - user-select: none; - touch-action: none; -} -/* Mobile: fat hitbox so the splitter is easy to grab with a thumb, - without changing how the grip itself looks. */ -@media (max-width: 768px) { - .cal-splitter { - height: 24px; - margin-top: 0; - margin-bottom: 0; - } - .cal-splitter-grip { - width: 56px; - height: 4px; - } -} -.cal-splitter-grip { - width: 42px; - height: 3px; - border-radius: 2px; - background: color-mix(in srgb, var(--fg) 25%, transparent); - transition: background 0.12s; -} -.cal-splitter:hover .cal-splitter-grip, -.cal-splitter-dragging .cal-splitter-grip { - background: var(--accent, var(--red)); -} -.cal-day-detail { - margin-top: 4px; - border-top: 1px solid var(--border); - padding-top: 8px; - height: var(--cal-detail-h, 240px); - overflow-y: auto; - overscroll-behavior: contain; - flex-shrink: 0; -} -.cal-detail-header { display:flex; justify-content:space-between; align-items:center; font-size:12px; font-weight:600; margin-bottom:6px; padding-right:8px; } -.cal-empty { font-size:11px; opacity:0.3; padding:4px 0; } -.cal-event-item { display:flex; gap:9px; padding:8px 9px; border-radius:6px; cursor:pointer; align-items:flex-start; background:color-mix(in srgb, var(--fg) 3.5%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); margin-bottom:4px; } -.cal-event-item:hover { background:color-mix(in srgb, var(--fg) 8%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); } -.cal-event-dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:4px; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } -.cal-event-info { flex:1; min-width:0; } -.cal-event-name { font-size:13px; font-weight:700; line-height:1.25; color:color-mix(in srgb, var(--fg) 96%, transparent); overflow-wrap:anywhere; } -.cal-event-tag { - display: inline-block; - font-size: 10px; - font-weight: 500; - padding: 1px 6px; - border-radius: 8px; - border: 1px solid; - margin-left: 6px; - vertical-align: 1px; - opacity: 0.85; - white-space: nowrap; -} -.cal-event-time { font-size:11px; opacity:0.68; line-height:1.35; margin-top:2px; font-weight:500; } -.cal-event-loc { font-size:11px; opacity:0.58; line-height:1.35; margin-top:1px; } -/* ── Week view: hour grid ────────────────────────────────────────── - Vertical hour rail on the left, 7 day columns on the right with their - own all-day strip and an absolute-positioned hour grid below it. */ -.cal-wk-wrap { - display: flex; - flex-direction: row; - gap: 0; - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 3%, transparent), transparent 120px), - var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - /* The wrap itself scrolls so the hour rail and day columns move - together; column headers and the rail spacer are sticky-pinned. */ - overflow: auto; - max-height: calc(100vh - 220px); - min-height: 360px; - position: relative; -} -.cal-wk-now-below-hint { - position: absolute; - height: 2px; - border-radius: 2px 2px 0 0; - background: var(--accent, var(--red)); - pointer-events: none; - z-index: 38; -} -@media (max-width: 768px) { - .cal-wk-wrap { - -webkit-overflow-scrolling: touch; - overscroll-behavior-y: contain; - } - .cal-wk-wrap > .cal-wk-rail, - .cal-wk-wrap > .cal-wk-cols { - transform: translateY(var(--wk-edge-pull, 0)); - } - .cal-wk-wrap.cal-wk-edge-release > .cal-wk-rail, - .cal-wk-wrap.cal-wk-edge-release > .cal-wk-cols { - transition: transform 0.14s cubic-bezier(0.2, 0.75, 0.25, 1); - } - .cal-wk-wrap.cal-wk-pull-top { - border-top-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); - } - .cal-wk-wrap.cal-wk-pull-bottom { - border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); - } -} -.cal-wk-rail { - flex: 0 0 56px; - border-right: 1px solid var(--border); - display: flex; - flex-direction: column; - background: color-mix(in srgb, var(--fg) 2%, var(--bg)); - position: sticky; - left: 0; - z-index: 5; -} -.cal-wk-rail-spacer { - /* Match the 32px day header. The 24px all-day row gets its own spacer - below, so the zoom controls sit high without shifting the hour grid. */ - height: 32px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 0; - background: color-mix(in srgb, var(--fg) 2%, var(--bg)); - z-index: 9; - display: flex; - align-items: center; - justify-content: center; - gap: 2px; - padding: 0 4px; -} -.cal-wk-rail-allday-spacer { - height: 24px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 32px; - z-index: 8; - background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); -} -.cal-wk-zoom { - position: relative; - top: -3px; - width: 22px; - height: 22px; - border: 1px solid var(--border); - background: var(--bg); - color: var(--fg); - border-radius: 50%; - font-family: inherit; - font-size: 14px; - font-weight: 600; - line-height: 1; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - opacity: 0.55; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.cal-wk-zoom:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--fg)) 50%, var(--border)); -} -.cal-wk-rail-cell { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 55%, transparent); - padding: 4px 8px 0; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); - flex-shrink: 0; - display: flex; - align-items: flex-start; - justify-content: flex-end; - font-variant-numeric: tabular-nums; -} -.cal-wk-cols { - flex: 1; - display: grid; - grid-template-columns: repeat(7, 1fr); - position: relative; -} -.cal-wk-col { - border-left: 1px solid var(--border); - display: flex; - flex-direction: column; - min-width: 0; -} -.cal-wk-col:first-child { border-left: none; } -.cal-wk-col-head { - height: 32px; - padding: 0 6px; - display: flex; - align-items: center; - gap: 6px; - font-size: 11px; - flex-shrink: 0; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); - position: sticky; - top: 0; - z-index: 40; -} -/* Today on a Sunday still wins the accent treatment. */ -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); } -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dn, -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dt { color: var(--accent, var(--fg)); } -.cal-wk-dn { font-weight: 600; opacity: 0.6; } -.cal-wk-allday { - height: 24px; - padding: 2px 4px; - display: flex; - flex-direction: row; - gap: 2px; - align-items: center; - background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); - border-bottom: 1px solid var(--border); - flex-shrink: 0; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: none; - position: sticky; - top: 32px; - z-index: 39; -} -.cal-wk-allday-event { - font-size: 10.5px; - font-weight: 700; - padding: 3px 6px; - border-radius: 4px; - color: var(--cal-event-fg, var(--fg)); - cursor: pointer; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Hour grid body */ -.cal-wk-grid { - position: relative; - flex: 0 0 auto; - cursor: cell; - user-select: none; - background: - repeating-linear-gradient( - to bottom, - transparent 0, - transparent calc(var(--wk-hour-px, 52px) / 2 - 1px), - color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--wk-hour-px, 52px) / 2), - transparent calc(var(--wk-hour-px, 52px) / 2 + 1px) - ); -} -.cal-wk-cell { - border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); - box-sizing: border-box; -} -.cal-wk-cell:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); } -/* "Now" line */ -.cal-wk-now { - position: absolute; - left: 0; right: 0; - height: 0; - border-top: 2px solid color-mix(in srgb, var(--accent, var(--red, #f87171)) 90%, transparent); - pointer-events: none; - z-index: 7; - filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent)); -} -/* Event blocks */ -.cal-wk-block { - position: absolute; - left: calc((var(--lane, 0) * (100% / var(--lane-count, 1))) + 2px); - width: calc((100% / var(--lane-count, 1)) - 4px); - right:auto; - border-left: 4px solid var(--fg); - border-radius: 5px; - padding: 5px 7px 7px; - font-size: 12px; - line-height: 1.3; - cursor: pointer; - /* `clip` preserves the rounded-card boundary without creating an overflow - container, allowing the label inside to stick to the week scroller. */ - overflow: clip; - z-index: 2; - box-shadow: 0 2px 6px color-mix(in srgb, #000 14%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); - animation:cal-event-enter 0.18s ease-out both; - transition: width 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), height 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), filter 0.12s, transform 0.12s, box-shadow 0.16s; - box-sizing: border-box; -} -.cal-wk-block:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); } -.cal-wk-block-label { - position: sticky; - top: 37px; - z-index: 1; - min-width: 0; - pointer-events: none; -} -.cal-wk-has-allday .cal-wk-block-label { - top: 61px; -} -.cal-wk-block-name { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color:color-mix(in srgb, var(--fg) 96%, transparent); } -.cal-wk-block-time { font-size: 10.5px; opacity: 0.82; font-variant-numeric: tabular-nums; margin-top: 2px; font-weight:600; } -@media (hover: hover) and (pointer: fine) { - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left { - left: auto; - right: calc(100% - var(--lane-right) + 2px); - } - .cal-wk-block.cal-wk-block-crowded:hover { - width: var(--hover-width, 170px); - height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; - overflow: clip; - z-index: 30; - filter: brightness(1.08); - transform: translateY(var(--hover-shift-y, -2px)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-hover-width-only:hover { - height: var(--event-height) !important; - transform: translateY(-1px); - } - .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-label { - position: absolute; - top: 5px; - width: calc(var(--hover-width, 170px) - 18px); - animation: cal-wk-hover-label-reveal 0.2s ease-out both; - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left:hover .cal-wk-block-label { - left: auto; - right: 7px; - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-right:hover .cal-wk-block-label { - left: 7px; - right: auto; - } - .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-name { - display: block; - white-space: normal; - overflow: visible; - text-overflow: clip; - overflow-wrap: anywhere; - } -} -@media (max-width: 768px) { - /* Mobile week view: the hour rail already shows the time, so drop it from the - card and let the title wrap to fill the freed space (more readable text). */ - .cal-wk-block-time { display: none; } - .cal-wk-block-name { - white-space: normal; - line-height: 1.15; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left { - left: auto; - right: calc(100% - var(--lane-right) + 2px); - } - .cal-wk-block.cal-wk-mobile-expanded { - width: var(--hover-width, 170px); - height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; - overflow: clip; - z-index: 30; - filter: brightness(1.08); - transform: translateY(var(--hover-shift-y, 0)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); - } - .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-label { - position: absolute; - top: 5px; - width: calc(var(--hover-width, 170px) - 18px); - animation: cal-wk-hover-label-reveal 0.2s ease-out both; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left .cal-wk-block-label { - left: auto; - right: 7px; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-right .cal-wk-block-label { - left: 7px; - right: auto; - } - .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-name { - display: block; - white-space: normal; - overflow: visible; - text-overflow: clip; - overflow-wrap: anywhere; - -webkit-line-clamp: unset; - } -} -/* Resize handle: a 6 px hit zone along the block's bottom edge. The - visible 2-px stripe only shows on hover so blocks at rest stay clean. */ -.cal-wk-block-resize { - position: absolute; - left: 4px; - right: 4px; - bottom: 0; - height: 6px; - cursor: ns-resize; - z-index: 3; -} -/* Drag-to-create ghost */ -.cal-wk-ghost { - position: absolute; - left: 2px; - right: 2px; - background: color-mix(in srgb, var(--accent, var(--fg)) 28%, transparent); - border: 1px dashed color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent); - border-radius: 4px; - pointer-events: none; - z-index: 3; - font-size: 10px; - font-weight: 600; - display: flex; - align-items: center; - justify-content: center; - color: var(--accent, var(--fg)); - font-variant-numeric: tabular-nums; -} -.cal-wk-slot-selected { - background: color-mix(in srgb, var(--accent, var(--fg)) 20%, var(--bg)); - border-style: solid; - border-color: var(--accent, var(--fg)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--fg)) 20%, transparent); - overflow: visible; - pointer-events: auto; - cursor: pointer; -} -.cal-wk-slot-resize { - position: absolute; - left: 14%; - right: 14%; - bottom: -7px; - height: 14px; - z-index: 4; - cursor: ns-resize; - touch-action: none; - pointer-events: auto; -} -.cal-year { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; } -.cal-year-month { background:color-mix(in srgb, var(--fg) 3%, var(--bg)); border:1px solid var(--border); border-radius:6px; padding:6px; cursor:pointer; transition:background 0.15s, border-color 0.15s, transform 0.15s; } -.cal-year-month:hover { background:color-mix(in srgb, var(--fg) 8%, var(--bg)); border-color:var(--accent); transform:translateY(-1px); } -.cal-year-month-title { font-size:11px; font-weight:600; text-align:center; margin-bottom:4px; opacity:0.7; } -.cal-year-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; } -.cal-year-wd { font-size:7px; text-align:center; opacity:0.3; padding:1px 0; } -.cal-year-cell { font-size:8px; text-align:center; padding:2px 0; min-height:14px; border-radius:2px; transition:transform 0.12s, background 0.12s; } -.cal-year-day:hover { background:var(--accent, var(--red)); color:#fff; opacity:1; transform:scale(1.15); font-weight:700; z-index:2; position:relative; } -.cal-year-today { color:var(--accent, var(--red)); font-weight:700; opacity:1; } -.cal-year-has { opacity:1; background:color-mix(in srgb, var(--accent) 15%, transparent); } -.cal-year-has.cal-year-today { background:color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } -.cal-loading { display:flex; justify-content:center; padding:40px 0; } -.cal-badge { background:var(--accent); border-radius:50%; width:6px; height:6px; margin-left:auto; display:inline-block; flex-shrink:0; } -/* Search input — in-panel variant lives inside the day-detail panel and - spans its width; no width animation since growing/shrinking on every - keystroke would jitter beside the events list. */ -.cal-search-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:0 8px; height:24px; color:var(--fg); font-size:11px; font-family:inherit; width:120px; box-sizing:border-box; transition:width 0.15s; } -.cal-search-input:focus { outline:none; border-color:var(--accent); width:180px; } -.cal-search-input.cal-day-search { width:100%; margin-bottom:0; transition:none; padding-left: 28px; } -/* Wrap for the search input so the magnifying-glass icon can be absolute- - positioned inside the field. */ -.cal-search-wrap { - position: relative; - display: block; - margin-bottom: 6px; -} -.cal-search-wrap .cal-search-icon { - position: absolute; - left: 8px; - top: 50%; - transform: translateY(-50%); - pointer-events: none; - color: var(--accent, var(--red)); -} -.cal-day-search-meta { font-size:10px; opacity:0.5; margin:0 2px 6px; } -.cal-search-results { display:flex; flex-direction:column; gap:4px; } -.cal-search-count { font-size:10px; opacity:0.5; padding:4px 2px 8px; } -/* Agenda view */ -.cal-agenda { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-left:4px; } -.cal-agenda-day { display:flex; flex-direction:column; gap:5px; position:relative; padding-left:14px; } -.cal-agenda-date { font-size:12px; font-weight:800; opacity:0.72; padding:5px 2px 3px; border-bottom:1px solid var(--border); } -.cal-agenda-today-badge { - display: inline-block; - margin-left: 6px; - font-size: 9px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - vertical-align: 1px; -} -.cal-agenda-empty { font-size:11px; opacity:0.4; padding:8px; font-style:italic; } -.cal-agenda-event { - display:flex; - gap:9px; - padding:9px 10px; - border-radius:6px; - cursor:pointer; - align-items:center; - position:relative; - background:color-mix(in srgb, var(--fg) 3.5%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); - animation:cal-event-enter 0.18s ease-out both; - transition:background 0.15s, box-shadow 0.15s, transform 0.15s; -} -.cal-agenda-event:hover { - background:color-mix(in srgb, var(--fg) 8%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); - transform:translateX(2px); -} -.cal-agenda-event .cal-event-more, -.cal-event-item .cal-event-more { opacity:0; transition:opacity 0.15s; } -.cal-event-item { position:relative; } - -/* ── Personal Assistant ── */ -.sidebar-assistant-entry { gap: 6px; min-height: 29px; box-sizing: border-box; align-items: center; } -.sidebar-assistant-entry .sidebar-action-icon { position: relative; left: -2px; } -#sidebar-assistant-gear:hover { opacity: 0.8 !important; } -.assistant-settings-form { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; } -.assistant-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: color-mix(in srgb, var(--fg) 70%, transparent); } -.assistant-field > span { font-size: 11px; opacity: 0.6; } -.assistant-field input[type="text"], -.assistant-field input[type="time"], -.assistant-field select, -.assistant-field textarea { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 5px; - padding: 7px 10px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - box-sizing: border-box; -} -.assistant-field textarea { resize: vertical; min-height: 60px; } -.assistant-field input:focus, -.assistant-field select:focus, -.assistant-field textarea:focus { outline: none; border-color: var(--accent, var(--red)); } -.assistant-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } -.assistant-field-row > .assistant-field { flex: 1 1 180px; } -.assistant-field-check { flex-direction: row !important; align-items: center; gap: 6px; padding-bottom: 7px; } -.assistant-checkins { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); } -.assistant-checkins h5 { margin: 0 0 2px; font-size: 12px; font-weight: 600; opacity: 0.8; } -.assistant-checkin-row { - display: flex; - flex-direction: column; - gap: 4px; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.assistant-checkin-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } -.assistant-checkin-head input[type="text"], -.assistant-checkin-head input[type="time"] { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 4px; - padding: 4px 8px; - color: var(--fg); - font-size: 12px; - font-family: inherit; -} -.assistant-checkin-head input[type="text"] { flex: 1; min-width: 120px; } -.assistant-checkin-head input[type="time"] { width: 90px; } -.assistant-checkin-run { - background: none; - border: 1px solid var(--border); - color: var(--fg); - font-family: inherit; - font-size: 11px; - padding: 4px 8px; - border-radius: 4px; - cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s; -} -.assistant-checkin-run:hover { opacity: 1; } -.assistant-checkin-prompt { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 4px; - padding: 6px 8px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - resize: vertical; - min-height: 48px; -} -.assistant-checkin-prompt:focus { outline: none; border-color: var(--accent, var(--red)); } -.assistant-checkin-meta { font-size: 10px; opacity: 0.45; } -.assistant-settings-actions { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 8px; - padding-top: 8px; - border-top: 1px solid var(--border); -} -.assistant-tools-grid { - display: flex; - flex-direction: column; - gap: 6px; - max-height: 240px; - overflow-y: auto; - padding: 6px; - border: 1px solid var(--border); - border-radius: 6px; - margin-top: 4px; -} -.assistant-tool-group { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.assistant-tool-group-label { - font-size: 10px; - font-weight: 600; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.03em; - width: 100%; - margin-bottom: 1px; -} -.assistant-tool-item { - display: inline-flex; - align-items: center; - gap: 3px; - font-size: 11px; - padding: 2px 6px; - border: 1px solid var(--border); - border-radius: 4px; - cursor: pointer; - transition: background 0.1s, border-color 0.1s; - user-select: none; -} -.assistant-tool-item:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.assistant-tool-item:has(input:checked) { - border-color: var(--accent); - background: color-mix(in srgb, var(--accent) 10%, transparent); -} -.assistant-tool-item input[type="checkbox"] { - width: 12px; - height: 12px; - margin: 0; -} - -/* Empty state */ -.cal-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px 20px; text-align:center; gap:10px; } -.cal-empty-title { font-size:14px; font-weight:600; opacity:0.8; } -.cal-empty-msg { font-size:12px; opacity:0.5; max-width:320px; line-height:1.5; margin-bottom:8px; } -.cal-allday-switch { margin:0; flex-shrink:0; } -/* Stack the "All day" label above its toggle so it doesn't get squeezed / - truncated next to the date inputs in the row. */ -.cal-allday-ctrl { display:flex; flex-direction:column; align-items:center; gap:3px; flex-shrink:0; } -.cal-allday-label { font-size:10px; opacity:0.5; white-space:nowrap; line-height:1; } -.cal-form { display:flex; flex-direction:column; gap:8px; } -.cal-form-title { font-size:13px; font-weight:600; margin-bottom:2px; } -.cal-title-wrap { position: relative; } -/* Calendar-picker select sits 2px higher; its border-left/background tint is - set in JS to the chosen calendar's colour. */ -.cal-f-cal-select { position: relative; top: -4px; } - -/* ── Bespoke event form ──────────────────────────────────────────── - Big clock-face date/time hero with the title under it. Details panel - stays collapsed until the title gets focus or "Add details" is clicked. -*/ -.cal-form-bespoke { - position: relative; - width: min(720px, calc(100% - 24px)); - max-width: none; - margin: 12px auto; - padding: 18px 28px 14px; - box-sizing: border-box; - background: var(--bg); - /* Optional per-event tint set by JS via --ev-color. When unset, falls - back to the theme's neutral border so the card looks like normal. */ - border: 1px solid var(--border); - border-radius: 14px; - box-shadow: 0 8px 24px color-mix(in srgb, var(--fg) 6%, transparent); - transition: border-color 0.18s, box-shadow 0.18s; -} -/* When a colour is picked, lift the card with a soft tinted border + glow - so the choice is unmistakable. */ -.cal-form-bespoke[style*="--ev-color"] { - border-color: color-mix(in srgb, var(--ev-color) 55%, var(--border)); - box-shadow: 0 8px 28px color-mix(in srgb, var(--ev-color) 22%, transparent); -} -.cal-form-bespoke[style*="--ev-color"] .cal-btn-primary { - background: var(--ev-color); - border-color: var(--ev-color); -} -.cal-form-bespoke.cal-form-bg-image .cal-btn-primary { - background: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); - color: var(--bg); -} -.cal-form-close { - position: absolute; top: 10px; right: 12px; - background: none; border: none; color: var(--fg); - opacity: 0.35; cursor: pointer; font-size: 20px; line-height: 1; - padding: 4px 8px; border-radius: 6px; -} -.cal-form-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cal-form-mobile-cancel { display: none; } - -/* "Today is …" header pinned at the top of the form. Keeps you oriented - even when the event date you're picking is way off in the future. */ -.cal-form-today { - text-align: center; - font-size: 11px; - letter-spacing: 0.08em; - text-transform: uppercase; - opacity: 0.45; - margin-bottom: 0; -} - -/* Hero — clock face + date label. Spacing between the two lives entirely - on the clock's bottom margin so it's predictable. */ -.cal-hero { - display: flex; flex-direction: column; align-items: center; - gap: 0; margin-bottom: 12px; -} -.cal-hero-time { - display: inline-flex; align-items: baseline; gap: 8px; - font-family: ui-monospace, SFMono-Regular, "Menlo", "Consolas", monospace; - font-variant-numeric: tabular-nums; - font-size: 56px; - letter-spacing: 0.02em; - font-weight: 200; - color: var(--fg); - line-height: 1; - /* Behaves like a button — strip native chrome but keep it tappable. */ - background: none; - border: none; - /* Pill that reads as a real hit target for a 56-px clock face. - Top padding kept tight so the clock sits close to the "Today is" line. */ - padding: 6px 38px 18px; - margin: 0 0 24px; - border-radius: 18px; - cursor: pointer; - line-height: 1; - transition: background 0.15s, color 0.15s; -} -/* The clock is a `` and a global `button:hover` rule paints - every button's background on hover. Suppress it explicitly. */ -.cal-hero-time:hover { background: transparent; border-color: transparent; } -.cal-hero-clock { font-feature-settings: "tnum"; display: inline-flex; align-items: baseline; } -/* Per-segment hover so it's clear hh vs mm are individually clickable. */ -.cal-hero-clock-hh, .cal-hero-clock-mm { - display: inline-block; - border-radius: 6px; - padding: 0 2px; - transition: background 0.12s; -} -.cal-hero-clock-hh:hover, .cal-hero-clock-mm:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); -} -.cal-hero-sep { padding: 0 2px; opacity: 0.55; pointer-events: none; } -.cal-hero-ampm { - font-size: 14px; - letter-spacing: 0.12em; - opacity: 0.55; - font-weight: 500; - text-transform: uppercase; -} -.cal-hero-date { - font-size: 16px; - opacity: 0.6; - letter-spacing: 0.04em; - background: none; - border: none; - color: inherit; - font-family: inherit; - padding: 4px 10px; - border-radius: 6px; - cursor: pointer; - margin: 7px 0 0; /* extra nudge below the clock */ - transition: background 0.15s, opacity 0.15s; -} -.cal-hero-date:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); opacity: 0.85; } - -/* Title input — flat, large, no chrome until focused */ -.cal-input.cal-hero-title { - font-size: 18px; - text-align: left; - padding: 10px 12px 10px 0; - background: transparent; - border: 1px solid transparent; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); - border-radius: 0; - margin-bottom: 4px; -} - -/* Details panel — animated reveal via max-height + opacity. */ -.cal-form-bespoke .cal-form-details { - display: block; - max-height: 0; - opacity: 0; - overflow: hidden; - transition: max-height 240ms ease, opacity 180ms ease, margin 220ms ease; - margin-top: 0; -} -/* The detail children themselves keep the existing flex/gap rules from - the regular .cal-form, so we don't restyle inputs/rows here. */ -.cal-form-bespoke .cal-form-details > * + * { margin-top: 8px; } - - -/* Location row: input + Apple Maps pin link. Pin only lights up when the - field has content; clicking opens maps.apple.com (native Maps app on - Apple devices, web fallback elsewhere). All theme vars. */ -.cal-loc-row { - display: flex; - align-items: stretch; - gap: 6px; -} -.cal-loc-input-wrap { - position: relative; - flex: 1; - min-width: 0; -} -.cal-loc-hint { - position: absolute; - inset: 0 auto 0 10px; - display: none; - align-items: center; - gap: 5px; - pointer-events: none; - color: color-mix(in srgb, var(--fg) 42%, transparent); - font-size: 12px; -} -.cal-loc-map { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - flex-shrink: 0; - border: 1px solid var(--border); - border-radius: 5px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - text-decoration: none; - transition: background 0.15s, border-color 0.15s, opacity 0.15s; -} -.cal-loc-map:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); border-color: var(--accent, var(--red)); } - -@media (max-width: 520px) { - .cal-hero-time { font-size: 44px; } - .cal-form-bespoke { margin: 16px 12px; padding: 32px 16px 14px; } - .cal-form-mobile-cancel { - display: inline-flex; - position: absolute; - top: 8px; - right: 8px; - width: 30px; - height: 30px; - align-items: center; - justify-content: center; - padding: 0; - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--bg) 82%, transparent); - color: var(--fg); - opacity: 0.72; - cursor: pointer; - z-index: 3; - -webkit-tap-highlight-color: transparent; - } - .cal-form-mobile-cancel:active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); - } -} -.cal-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:7px 10px; color:var(--fg); font-size:12px; width:100%; box-sizing:border-box; } -.cal-input:focus { outline:none; border-color:var(--accent); } -.cal-input-time { width:auto; flex:1; } -.cal-form-row { display:flex; gap:8px; align-items:center; } -.cal-form-actions { display:flex; gap:8px; justify-content:flex-end; margin-top:4px; } button.cal-btn { background:color-mix(in srgb, var(--fg) 8%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 14px; height:28px; font-size:11px; font-weight:500; cursor:pointer; font-family:inherit; display:inline-flex; align-items:center; justify-content:center; box-sizing:border-box; transition:background 0.15s, border-color 0.15s; } button.cal-btn:hover { background:color-mix(in srgb, var(--fg) 14%, transparent); } button.cal-btn.cal-btn-primary { background:var(--accent, var(--red)); color:var(--bg); border-color:var(--accent, var(--red)); } diff --git a/static/css/style-part-02.css b/static/css/style-part-02.css deleted file mode 100644 index a3ceff612..000000000 --- a/static/css/style-part-02.css +++ /dev/null @@ -1,6011 +0,0 @@ -/* Fragment 3/9 of the former static/style.css. - * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. - * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. - */ - .loading-dot:nth-child(1) { - animation: loading-bounce 1.4s infinite ease-in-out both; - } - .loading-dot:nth-child(2) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.32s; - } - .loading-dot:nth-child(3) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.64s; - } - .session-loading-state { - min-height: 100%; - display: flex; - flex-direction: column; - justify-content: flex-start; - gap: 12px; - color: var(--fg); - font-size: 12px; - pointer-events: none; - position: relative; - padding: 16px 14px 24px; - box-sizing: border-box; - overflow: hidden; - } - .session-loading-state::before { content: none; } - .session-skeleton-bubble { - width: min(78%, 680px); - border: 1px solid color-mix(in srgb, var(--border) 74%, transparent); - border-radius: 10px; - background: color-mix(in srgb, var(--panel, var(--bg)) 72%, transparent); - padding: 10px 12px 12px; - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.14); - } - .session-skeleton-bubble.is-user { - align-self: flex-end; - width: min(64%, 520px); - } - .session-skeleton-bubble.is-ai { - align-self: flex-start; - } - .session-skeleton-line { - position: relative; - overflow: hidden; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - border-radius: 999px; - } - .session-skeleton-line { - height: 10px; - margin-top: 8px; - opacity: 0.62; - } - .session-skeleton-line::after { - content: ''; - position: absolute; - inset: -1px; - transform: translateX(-120%); - background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--fg) 14%, transparent), transparent); - animation: session-skeleton-shimmer 1.25s ease-in-out infinite; - } - .session-skeleton-bubble:nth-child(2) .session-skeleton-line::after { animation-delay: 0.12s; } - .session-skeleton-bubble:nth-child(3) .session-skeleton-line::after { animation-delay: 0.24s; } - .session-skeleton-bubble:nth-child(4) .session-skeleton-line::after { animation-delay: 0.36s; } - @keyframes session-skeleton-shimmer { - 100% { transform: translateX(120%); } - } - @media (max-width: 768px) { - .session-loading-state { padding: 12px 10px 18px; gap: 10px; } - .session-skeleton-bubble { width: 86%; padding: 9px 10px 11px; border-radius: 9px; } - .session-skeleton-bubble.is-user { width: 72%; } - .session-skeleton-line { height: 9px; margin-top: 7px; } - } - @keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } - } - /* Modal styling */ - .modal { - position:fixed; - top:0; left:0; width:100%; height:100%; - background:none; - display:flex; align-items:center; justify-content:center; - z-index:250; - backdrop-filter:none; - pointer-events:none; - } - /* Cookbook always sits above Gallery so the "Serve a model in - Cookbook…" flow (and any other "open Cookbook from inside another - modal") is visible no matter which modal was opened first. */ - #cookbook-modal { z-index: 260; } - .modal.hidden { display:none; } - - /* Tool windows open centered in the CHAT AREA (the space right of the - sidebar + icon rail), rather than the full viewport. - We narrow the overlay to the chat area so its flex-centering lands the - window there; fullscreen / docked states use position:fixed so they - escape this narrowed overlay and still fill the screen. The - --sidebar-w / --icon-rail-w vars track collapse state live, so when a - window (e.g. Cookbook) hides the sidebar this naturally re-centers. - Desktop only — on mobile these are full-screen sheets. */ - @media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal { - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)); - width: calc(100% - (var(--icon-rail-w, 48px) + var(--sidebar-w, 0px))); - box-sizing: border-box; - /* Slide in sync with the sidebar's 0.25s collapse/expand so the - centered window glides instead of jumping when the nav toggles. */ - transition: left 0.25s ease, width 0.25s ease; - } - } - .modal-content { - background:var(--panel); - border:1px solid var(--border); - width:min(520px, 92vw); max-height:85vh; padding:10px; - box-sizing:border-box; font-size:14px; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - letter-spacing: -0.015em; - display:flex; flex-direction:column; - position:relative; - overflow-y:auto; - border-radius:10px; - box-shadow:0 8px 32px rgba(0,0,0,0.45); - pointer-events:auto; - animation: modal-enter 0.25s ease-out both; - } - .memory-modal-content, - .tasks-modal-content, - .preset-modal-content, - #cookbook-modal .modal-content, - #theme-popup, - .doclib-modal-content, - .gallery-modal-content { - container-type: inline-size; - } - .modal-header { - display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; - cursor:grab; user-select:none; - /* Pin the header (with its close button) to the top of the - scrollable modal-content so users can always dismiss the modal - even after scrolling far down — especially important on mobile - where the modal can be taller than the viewport. */ - position: sticky; - top: 0; - z-index: 5; - /* Inherit the modal-content's background so the header matches the - panel body. Many tool modals set their content to var(--bg) inline - while this header was hard-coded to var(--panel) — making the title - strip a different shade in every theme. `inherit` tracks whatever - the content uses (var(--bg) or the default var(--panel)) and stays - opaque, so the sticky header still masks scrolled content. */ - background-color: inherit; - } - .modal-header:active { cursor:grabbing; } - /* Edge/corner window resize (windowResize.js). While a resize is in - progress, suppress text selection and force the active resize cursor - across the whole document so it does not flicker as the pointer passes - over child elements mid-drag. */ - body.window-resizing-active { user-select:none !important; } - body.window-resizing-active * { cursor:inherit !important; } - /* Suppress only TRANSITIONS while resizing so the edge tracks the cursor - crisply. We deliberately do NOT toggle `animation` here: toggling - animation off→on re-triggers the modal open-animation (a scale-in) on - mouseup, which both mis-measures the final size and visibly "pops" the - window. windowResize.js instead kills the one-shot open animation inline - once, in begin(). */ - .window-resizing { - transition:none !important; - } - /* Cookbook opts out of the sticky header on mobile; the title bar scrolls - away with the content instead of following. Background is inherited from - the shared .modal-header rule so frosted/glass themes stay consistent. */ - #cookbook-modal .modal-header { - position: static; - top: auto; - } - .modal-header h4 { - margin:0; - /* Push every header control (opacity slider, minimize, close) into one - group on the right — works whether or not optional controls like the - opacity slider are present, so the minimize button never floats to - the centre when a sibling is hidden. */ - margin-right:auto; - font-size:1rem; - font-weight:600; - letter-spacing:-0.03em; - color:var(--red); - } - /* Headers with an optional Peek control already have the title's - auto-margin grouping all right-side controls. A second auto-margin on - the injected minimize button splits the group and makes it look - different from the other modal headers. */ - .modal-header .theme-opacity-wrap + .modal-minimize-btn { - margin-left: 0 !important; - } - .close-btn, - .modal-close { - background:transparent; - color:color-mix(in srgb, var(--fg) 58%, transparent); - border:1px solid transparent; - font-size:0; - width:24px; - height:24px; - padding:0; - display:inline-flex; - align-items:center; - justify-content:center; - line-height:1; - text-indent:0; - cursor:pointer; - border-radius:999px; - line-height:1; - flex-shrink:0; - position:relative; - } - .close-btn > svg, - .modal-close > svg { - display:none; - } - .close-btn::before, - .modal-close::before { - content:""; - width:10px; - height:10px; - display:block; - background:currentColor; - -webkit-mask: - url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") - center / contain no-repeat; - mask: - url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") - center / contain no-repeat; - } - .close-btn:hover, - .modal-close:hover { - background:color-mix(in srgb, var(--fg) 10%, transparent); - border-color:color-mix(in srgb, var(--fg) 12%, transparent); - color:var(--fg); - } - /* Minimize button — sits beside the close button on every modal */ - .minimize-btn { - background:transparent; - color:color-mix(in srgb, var(--fg) 58%, transparent); - border:1px solid transparent; - font-size:0; - font-weight:700; - width:24px; - height:24px; - padding:0; - display:inline-flex; - align-items:center; - justify-content:center; - line-height:1; - cursor:pointer; - border-radius:999px; - flex-shrink:0; - margin-left:auto; /* push to the right so it docks next to .close-btn */ - margin-right:4px; - } - .minimize-btn::before { - content:""; - width:9px; - height:2px; - border-radius:999px; - background:currentColor; - display:block; - transform:translateY(2px); - } - .modal-minimize-btn.minimize-btn::before { - content:none; - display:none; - } - .minimize-btn:hover { - background:color-mix(in srgb, var(--fg) 10%, transparent); - border-color:color-mix(in srgb, var(--fg) 12%, transparent); - color:var(--fg); - } - @media (max-width: 768px) { - .minimize-btn { display: none !important; } - #modal-dock { display: none !important; } - } - /* Minimized modals are hidden but stay in the DOM with their state intact */ - .modal.minimized { display:none !important; } - /* Bottom dock for minimized modals */ - #modal-dock { - position:fixed; - bottom:var(--composer-clearance, 0px); - left:0; - right:0; - display:flex; - flex-wrap:wrap; - gap:4px; - padding:4px 8px; - z-index:240; - pointer-events:none; - justify-content:flex-start; - } - #modal-dock:empty { display:none; } - .modal-dock-item { - background:var(--panel); - border:1px solid var(--border); - border-bottom:none; - border-radius:6px 6px 0 0; - padding:4px 4px 4px 10px; - display:inline-flex; - align-items:center; - gap:6px; - cursor:pointer; - pointer-events:auto; - font-size:12px; - color:var(--fg); - max-width:220px; - box-shadow:0 -2px 8px rgba(0,0,0,0.25); - transition:background 0.15s; - } - .modal-dock-item:hover { background:var(--bg); } - .modal-dock-label { - white-space:nowrap; - overflow:hidden; - text-overflow:ellipsis; - max-width:180px; - } - .modal-dock-close { - background:transparent; - border:none; - color:var(--fg); - cursor:pointer; - font-size:14px; - padding:0 4px; - line-height:1; - opacity:0.6; - } - .modal-dock-close:hover { color:var(--red); opacity:1; } - .modal-body { flex:1; overflow-y:auto; } - .modal-body button { margin-top:6px; } - /* Styled confirm dialog — keeps backdrop */ - #styled-confirm-overlay { - background:rgba(0,0,0,0.5); - backdrop-filter:blur(4px); - pointer-events:auto !important; - z-index: 99999 !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - top: 0; left: 0; width: 100%; height: 100%; - } - #styled-confirm-overlay .modal-content { - position: relative; - z-index: 10001; - } - .email-attachment-warning-modal { - z-index: 99999 !important; - } - .email-attachment-warning-modal .modal-content { - position: relative; - z-index: 100001; - } - .styled-confirm-box { - width:360px; max-width:90vw; - max-height:none; /* override modal-content's 85vh */ - padding:14px 18px; - } - .styled-confirm-box .modal-header { margin-bottom:4px; } - .styled-confirm-box .modal-body p { - margin:8px 0 12px; color:var(--fg); font-size:0.92rem; line-height:1.45; - white-space:pre-line; - } - .styled-confirm-box .modal-footer { - display:flex; justify-content:flex-end; gap:8px; padding-top:6px; - border-top:1px solid var(--border); margin-top:4px; - } - @media (max-width:768px) { - .styled-confirm-box { - width: 85vw; - padding: 12px 16px; - font-size: 0.88rem; - border-radius: 12px; - } - .styled-confirm-box .modal-header h4 { font-size: 0.9rem; } - .styled-confirm-box .modal-body p { font-size: 0.85rem; margin: 6px 0 10px; } - .styled-confirm-box .modal-footer { gap: 10px; } - .styled-confirm-box .confirm-btn { - flex: 1; - /* More bottom than top padding nudges the label up ~2px so it isn't - sitting low in the taller mobile buttons. */ - padding: 8px 12px 12px; - font-size: 0.85rem; - border-radius: 8px; - text-align: center; - } - } - .confirm-btn { - /* Asymmetric padding nudges the label UP ~2px from where it was (more - bottom than top padding), so the confirm-dialog text isn't sitting low. */ - padding:3px 16px 5px; border-radius:4px; font-size:0.85rem; - cursor:pointer; border:1px solid var(--border); - font-family:inherit; - } - @media (max-width: 820px) { - /* Mobile: flip the asymmetry to shift the text 1 px UP from - centre instead (the bigger touch targets on mobile read better - with the label sitting slightly high). */ - .confirm-btn { padding:2px 16px 4px; } - } - .confirm-btn-secondary { background:var(--bg); color:var(--fg); } - .confirm-btn-secondary:hover { background:var(--border); } - #styled-confirm-cancel .styled-confirm-cancel-label { - position:relative; - top:2px; - } - .confirm-btn-primary { background:var(--accent-primary, var(--red, #4a9eff)); color:#fff; border-color:transparent; } - .confirm-btn-primary:hover { filter:brightness(1.15); } - .confirm-btn-danger { background:var(--color-danger); color:#fff; border-color:transparent; } - .confirm-btn-danger:hover { background:var(--color-error); } - .cookbook-gguf-delete-box { - width:560px; - max-width:92vw; - } - .cookbook-gguf-delete-list { - display:flex; - flex-direction:column; - gap:6px; - max-height:42vh; - overflow:auto; - padding:2px 2px 4px; - } - .cookbook-gguf-delete-row { - display:grid; - grid-template-columns:18px minmax(0,1fr); - gap:7px 8px; - align-items:start; - padding:7px 8px; - border:1px solid var(--border); - border-radius:7px; - background:color-mix(in srgb, var(--panel, var(--bg)) 92%, var(--fg) 8%); - cursor:pointer; - } - .cookbook-gguf-delete-row:hover { - border-color:color-mix(in srgb, var(--accent-primary, var(--fg)) 45%, var(--border)); - } - .cookbook-gguf-delete-cb { - -webkit-appearance:none; - appearance:none; - width:8px !important; - height:8px !important; - min-width:8px; - min-height:8px; - padding:0; - margin:4px 0 0; - border:1px solid var(--border); - border-radius:50%; - background:transparent; - box-sizing:content-box; - cursor:pointer; - transition:background 0.15s, border-color 0.15s, transform 0.12s; - } - .cookbook-gguf-delete-cb:hover { - border-color:var(--accent, var(--red)); - transform:scale(1.12); - } - .cookbook-gguf-delete-cb:checked { - background:var(--accent, var(--red)); - border-color:var(--accent, var(--red)); - } - .cookbook-gguf-delete-main, - .cookbook-gguf-delete-path { - min-width:0; - overflow:hidden; - text-overflow:ellipsis; - white-space:nowrap; - } - .cookbook-gguf-delete-main { - font-size:0.86rem; - color:var(--fg); - } - .cookbook-gguf-delete-path { - grid-column:2; - margin-top:-2px; - font-size:0.74rem; - opacity:0.58; - } - /* Styled prompt — text-input dialog (used in place of window.prompt) */ - #styled-prompt-overlay { - background:rgba(0,0,0,0.5); - backdrop-filter:blur(4px); - pointer-events:auto !important; - z-index: 99999 !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - } - #styled-prompt-overlay .modal-content { - position: relative; - z-index: 10001; - } - .styled-prompt-box { width: min(400px, 92vw); max-width: 100%; box-sizing: border-box; } - .styled-prompt-box .modal-body { padding-top: 4px; } - .styled-prompt-input { - width:100%; - box-sizing:border-box; - margin-top:8px; - padding:9px 12px; - border:1px solid var(--border); - border-radius:6px; - background:var(--bg); - color:var(--fg); - font:inherit; - font-size:0.95rem; - outline:none; - transition: border-color 0.15s, box-shadow 0.15s; - } - .styled-prompt-input:focus { - border-color: var(--accent-primary, var(--red, #4a9eff)); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-primary, var(--red, #4a9eff)) 25%, transparent); - } - @media (max-width:768px) { - .styled-prompt-box { width: 85vw; } - .styled-prompt-input { font-size: 0.9rem; padding: 10px 12px; } - } - /* Scroll navigation buttons */ - .scroll-nav-btn { - position:fixed; - background:var(--panel); - color: var(--accent); - border:none; - border-radius:10px; - width:38px; height:38px; - padding:0; - display:flex; align-items:center; justify-content:center; - font-size:14px; - line-height:1; - font-family:inherit; - cursor:pointer; - opacity:0; - pointer-events:none; - transition: opacity .2s, transform .3s cubic-bezier(0.25, 1, 0.5, 1), background .15s; - z-index:100; - transform: translateY(0); - } - .scroll-nav-btn::before { - content: ''; - position: absolute; - inset: 0; - border-radius: 10px; - padding: 1px; - background: linear-gradient(to bottom, var(--border), color-mix(in srgb, var(--border) 30%, transparent)); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - pointer-events: none; - } - .scroll-nav-btn.agent-working::after, - body.chat-agent-working #scroll-bottom-btn.show::after { - content: ''; - position: absolute; - inset: 0; - border-radius: 10px; - padding: 1px; - background: conic-gradient( - from 0deg, - transparent 0deg, - transparent 245deg, - color-mix(in srgb, var(--accent, var(--red)) 78%, transparent) 315deg, - transparent 360deg - ); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - opacity: 0.65; - pointer-events: none; - animation: scrollAgentWorkingRing 1.05s linear infinite; - } - @keyframes scrollAgentWorkingRing { - to { transform: rotate(360deg); } - } - #scroll-bottom-btn.show { opacity:1; pointer-events:auto; } - .scroll-nav-caret { - display: inline-block; - font-family: Arial, Helvetica, sans-serif; - font-size: 16px; - line-height: 1; - transform: translateY(-1px); - pointer-events: none; - } - @media (hover: hover) and (pointer: fine) { - #scroll-bottom-btn.show:hover { - border-color: color-mix(in srgb, var(--fg) 25%, transparent); - } - } - #scroll-bottom-btn.slide-out { - transform: translateY(20px); - opacity: 0 !important; - pointer-events: none; - } - /* Focus outline for accessibility */ - :focus-visible { - outline: 2px solid var(--red); - outline-offset: 2px; - } - /* Hamburger menu button */ - .hamburger { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - background: none; - border: 1px solid transparent; - border-radius: 6px; - padding: 0; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; - } - .hamburger:hover { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: var(--border); - } - .hamburger span { - display: block; - width: 16px; - height: 2px; - background: var(--fg); - border-radius: 1px; - transition: transform 0.2s, opacity 0.2s; - } - .hamburger span + span { margin-top: 3px; } - /* Agent indicator */ - #agent-indicator { - position: fixed; - top: 20px; - right: 20px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - display: none; - z-index: 100; - cursor: pointer; - transition: all 0.2s ease; - } - #agent-indicator.active { - display: block; - border-color: var(--color-agent-active); - box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); - } - #agent-indicator:hover { - border-color: var(--color-agent-active); - background: var(--panel); - } - #research-toggle-btn:disabled { - opacity: 0.6; - cursor: not-allowed; - } - -/* ── Drag & Drop ── */ - -/* ---------- Color palette (dark / light) ---------- */ - - /* Drag and drop styling */ - .section[dnd-active="true"] { - background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: var(--red) !important; - } - - .section[dnd-over="true"] { - background: color-mix(in srgb, var(--red) 20%, transparent) !important; - border-color: var(--red) !important; - transform: scale(1.02); - } - - .drag-handle { - cursor: grab; - opacity: 0.5; - padding: 0 6px; - user-select: none; - } - - .drag-handle:hover { - opacity: 0.8; - } - - .drag-handle:active { - cursor: grabbing; - } - -/* ── UI Controls (Radio, Presets, Toolbar, Settings) ── */ - - - /* Custom radio button styling */ - .radio-option { - display: flex; - align-items: center; - gap: 10px; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - cursor: pointer; - transition: all 0.2s ease; - } - - .radio-option:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: var(--fg); - } - - .radio-option input[type="radio"] { - appearance: none; - -webkit-appearance: none; - width: 18px; - height: 18px; - border: 2px solid var(--border); - border-radius: 50%; - outline: none; - margin: 0; - background: var(--bg); - transition: all 0.2s ease; - position: relative; - flex-shrink: 0; - } - - .radio-option input[type="radio"]:checked { - border-color: var(--red); - background: var(--red); - } - - .radio-option input[type="radio"]:focus { - box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 30%, transparent); - } - - .radio-label { - color: var(--fg); - font-size: 14px; - user-select: none; - } - - /* Preset buttons */ - .preset-btn { - height: 27.2px; /* 15% smaller than 32px */ - padding: 0 8.5px; /* 15% smaller than 10px */ - margin-left: 4px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font-family: 'Fira Code', monospace; - font-size: 10.2px; /* 15% smaller than 12px */ - cursor: pointer; - transition: all 0.2s ease; - } - - .preset-btn:hover { - background: var(--panel); - border-color: var(--fg); - } - - .preset-btn.active { - background: var(--panel); - border-color: var(--fg); - box-shadow: 0 0 0 1px var(--fg), 0 0 8px color-mix(in srgb, var(--fg) 16%, transparent); - font-weight: 600; - } - - /* All preset buttons use the same blue color */ - .preset-btn { - border-color: var(--red); /* Blue color */ - } - - .preset-btn.active { - border-color: var(--red); /* Blue color when active */ - box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); - } - - /* Custom preset modal: the base .preset-modal-content sets overflow:hidden - (for desktop rounded-corner clipping), which on the mobile sheet clipped - the footer (Start/Cancel) off the bottom with no way to reach it. Let the - whole sheet scroll — same as every other mobile modal (.modal-content is - overflow-y:auto on mobile) — so the footer is always reachable. No flex - changes, so the body can't collapse. */ - #custom-preset-modal .preset-modal-content { - overflow-y: auto !important; - } - #custom-preset-modal .modal-body label { - font-size: 13px; - font-weight: 500; - color: var(--fg); - margin-top: 8px; - margin-bottom: 4px; - display: block; - } - - #custom-preset-modal .modal-body input, - #custom-preset-modal .modal-body textarea { - width: 100%; - margin-bottom: 8px; - box-sizing: border-box; - } - #custom-preset-modal .modal-body textarea, - #custom-preset-modal .modal-body input[type="text"] { - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - padding: 8px 10px; - font-size: 13px; - font-family: inherit; - transition: border-color 0.15s; - } - #custom-preset-modal .modal-body textarea { - resize: vertical; - } - #custom-preset-modal .modal-body textarea:focus, - #custom-preset-modal .modal-body input[type="text"]:focus { - outline: none; - border-color: var(--red); - } - - #custom-preset-modal .modal-footer { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 12px; - padding-top: 10px; - border-top: 1px solid var(--border); - } - - #custom-preset-modal .modal-footer button { - padding: 7px 14px; - border-radius: 6px; - font-size: 12px; - font-weight: 500; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: all 0.15s; - } - #custom-preset-modal .modal-footer button:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - } - - #custom-preset-modal .modal-footer button#save-custom-preset { - /* The theme's accent is stored in --red (theme.js sets --red = accentHex) - and --accent is undefined, so the canonical accent expression is - var(--accent, var(--red)). The old bare var(--accent) resolved to nothing - which, with color:var(--bg), made the button invisible. */ - background: var(--accent, var(--red)); - color: var(--bg); - border-color: var(--accent, var(--red)); - } - #custom-preset-modal .modal-footer button#save-custom-preset:hover { - opacity: 0.85; - } - /* Toolbar visibility tab */ - .toolbar-hint { - font-size: 12px; - color: color-mix(in srgb, var(--fg) 55%, transparent); - margin-bottom: 12px; - } - /* ── Appearance visibility toggles ── */ - .vis-toggles { - display: flex; - flex-direction: column; - } - .vis-row { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - padding: 5px 6px; - border-radius: 4px; - transition: background 0.12s; - } - .vis-row:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); - } - .vis-row input[type="checkbox"] { - display: none; - } - .vis-icon { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - color: color-mix(in srgb, var(--fg) 40%, transparent); - } - .vis-icon-text { - font-size: 10px; - font-weight: 700; - font-family: inherit; - letter-spacing: -0.5px; - } - .vis-label { - flex: 1; - font-size: 12px; - color: var(--fg); - user-select: none; - } - .vis-switch { - position: relative; - width: 30px; - height: 16px; - background: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 8px; - transition: background 0.2s; - flex-shrink: 0; - } - .vis-switch::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 12px; - height: 12px; - background: var(--panel); - border-radius: 50%; - transition: transform 0.2s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); - } - .vis-row input:checked + .vis-switch { - background: var(--red); - } - .vis-row input:checked + .vis-switch::after { - transform: translateX(14px); - } - .vis-row input:checked ~ .vis-icon, - .vis-row:has(input:checked) .vis-icon { - color: var(--fg); - } - .persona-display-name-row .vis-label { - position: relative; - top: -4px; - } - .persona-display-name-row .vis-switch { - position: relative; - left: 4px; - } - .md-attach-paperclip-icon { - position: relative; - top: 1px; - } - /* Compare model selector — match the calendar modal's clean header - (no border underline). */ - #compare-model-overlay .modal-header h4 { - pointer-events: none; - } - /* Compare model selector: keep manually-resized/tiny windows contained. - Picker dropdowns are appended to document.body, so the card itself can - clip and scroll without cropping the dropdown list. */ - #compare-model-overlay .modal-content { - display: flex; - flex-direction: column; - max-height: min(720px, calc(100dvh - 48px)); - overflow: hidden; - min-height: 180px; - } - #compare-model-overlay .modal-body { - overflow: auto; - flex: 1 1 auto; - min-height: 0; - } - .vis-hint { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 30%, transparent); - font-weight: 400; - margin-left: 2px; - } - /* Settings toggle — admin-only lock indicator */ - .ui-vis-lock { - display: none; - } - /* (legacy toolbar-toggle styles removed — now using .vis-* classes) */ - - /* Demo highlight pulse */ - .odysseus-highlight { - outline: 2px solid var(--accent, var(--red)) !important; - outline-offset: 1px; - box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - animation: ody-pulse 1.5s ease-in-out infinite; - z-index: 100; - position: relative; - } - @keyframes ody-pulse { - 0%, 100% { outline-color: var(--accent, var(--red)); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); } - 50% { outline-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); } - } - /* Floating breathing halo. Rendered as a body-level div positioned over - the target, so we don't fight the target's own outline / box-shadow / - overflow chain. JS keeps it in sync with the target's bounding rect. */ - .tour-halo { - position: fixed; - pointer-events: none; - border: 3px solid var(--accent, var(--red)); - border-radius: 10px; - z-index: 10000; - animation: ody-breathe 1.4s ease-in-out infinite; - opacity: 0; - transform: scale(0.94); - transition: opacity 0.35s ease-out, transform 0.35s ease-out; - } - .tour-halo.tour-fade-in { - opacity: 1; - transform: scale(1); - } - @keyframes ody-breathe { - 0%, 100% { - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent), - 0 0 22px 4px color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - border-color: var(--accent, var(--red)); - } - 50% { - box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 30%, transparent), - 0 0 40px 14px color-mix(in srgb, var(--accent, var(--red)) 70%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 80%, transparent); - } - } - /* While the tour is active, lift overflow:hidden on common clipping - ancestors so the halo around a highlighted child isn't cropped. */ - body.tour-active .sidebar, - body.tour-active .sidebar-inner, - body.tour-active .chat-input-bar, - body.tour-active .chat-input-top, - body.tour-active .chat-input-wrap, - body.tour-active .chat-input-right, - body.tour-active .mode-toggle, - body.tour-active .model-picker-wrap { - overflow: visible !important; - } - - /* ── Secret tour hint (drag-to-snap on first modal open) ── */ - .tour-hint { - position: fixed; - z-index: 10002; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 12px 14px 10px; - width: 240px; - font-size: 0.78rem; - line-height: 1.5; - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.32); - opacity: 0; - transform: translateY(-4px); - transition: opacity 0.28s ease-out, transform 0.28s ease-out; - pointer-events: auto; - } - .tour-hint.tour-hint-in { opacity: 1; transform: translateY(0); } - .tour-hint.tour-hint-out { opacity: 0; transform: translateY(-4px); } - .tour-hint-visual { - display: flex; - justify-content: center; - margin-bottom: 8px; - color: var(--accent, var(--red)); - } - .tour-hint-visual svg { display: block; } - .tour-hint-text { margin-bottom: 10px; opacity: 0.92; } - .tour-hint-text b { color: var(--accent, var(--red)); font-weight: 600; } - .tour-hint-dismiss { - display: block; - margin: 0 0 0 auto; - background: none; - border: 1px solid var(--border); - color: var(--fg); - border-radius: 6px; - padding: 3px 12px; - cursor: pointer; - font-family: inherit; - font-size: 0.72rem; - opacity: 0.85; - transition: opacity 0.15s, background 0.15s; - } - .tour-hint-dismiss:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, transparent); } - - /* SVG dance: cursor approaches title bar, drags right, modal snaps to a - right-half zone, holds, returns. 3.2s loop. */ - .th-cursor { animation: th-cursor 3.2s ease-in-out infinite; transform-origin: 0 0; } - .th-modal-group { animation: th-modal-slide 3.2s ease-in-out infinite; transform-origin: 39px 31px; } - .th-zone { animation: th-zone-flash 3.2s ease-in-out infinite; } - @keyframes th-cursor { - 0%, 5% { transform: translate(35px, 32px); } - 18% { transform: translate(35px, 22px); } - 48% { transform: translate(80px, 22px); } - 72% { transform: translate(80px, 22px); } - 90%, 100% { transform: translate(35px, 32px); } - } - @keyframes th-modal-slide { - 0%, 18% { transform: translate(0, 0) scale(1, 1); } - 48% { transform: translate(45px, 0) scale(1, 1); } - 58% { transform: translate(30px, -19px) scale(1.4, 2.4); } - 72% { transform: translate(30px, -19px) scale(1.4, 2.4); } - 90%, 100% { transform: translate(0, 0) scale(1, 1); } - } - @keyframes th-zone-flash { - 0%, 38% { opacity: 0; } - 48% { opacity: 0.22; } - 58%, 72% { opacity: 0; } - 100% { opacity: 0; } - } - @media (prefers-reduced-motion: reduce) { - .th-cursor, .th-modal-group, .th-zone { animation: none !important; } - } - .odysseus-hl-label { - position: absolute; - top: -22px; - left: 8px; - background: var(--red); - color: var(--bg); - font-size: 0.7rem; - padding: 2px 8px; - border-radius: 4px; - white-space: nowrap; - z-index: 101; - pointer-events: none; - } - - /* Generated images inside chat bubbles */ - .msg.generated-image-wrap .body { text-align: center; } - .generated-image { - max-width: 100%; - max-height: 512px; - border-radius: 8px; - cursor: pointer; - transition: transform 0.2s; - display: inline-block; - margin: 0 auto; - } - .generated-image:hover { transform: scale(1.02); } - .generated-image-caption { - font-size: 0.8rem; - opacity: 0.5; - margin-top: 6px; - font-style: italic; - text-align: center; - } - - /* Setup wizard */ - .setup-wizard { padding: 16px; max-width: 500px; } - .setup-wizard .setup-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 12px; color: var(--fg); } - .setup-wizard .setup-label { font-size: 0.85rem; color: var(--fg); opacity: 0.7; margin-bottom: 8px; } - .setup-wizard .setup-presets { display: flex; flex-wrap: wrap; gap: 8px; } - .setup-wizard .setup-preset-btn { - padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; - font-size: 0.85rem; transition: all 0.2s; - } - .setup-wizard .setup-preset-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .setup-wizard .setup-input { - display: block; width: 100%; padding: 8px 12px; margin-bottom: 8px; - background: var(--panel); border: 1px solid var(--border); - border-radius: 6px; color: var(--fg); font-size: 0.85rem; box-sizing: border-box; - } - .setup-wizard .setup-input:focus { border-color: var(--red); outline: none; } - .setup-wizard .setup-connect-btn { - padding: 8px 20px; background: var(--red); color: var(--bg); - border: none; border-radius: 6px; cursor: pointer; font-weight: 600; margin-top: 4px; - } - .setup-wizard .setup-connect-btn:hover { opacity: 0.9; } - .setup-wizard .setup-status { font-size: 0.8rem; margin-top: 8px; color: var(--fg); opacity: 0.7; } - .setup-wizard .setup-model-list { display: flex; flex-wrap: wrap; gap: 8px; } - .setup-wizard .setup-model-btn { - padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; - font-size: 0.85rem; transition: all 0.2s; - } - .setup-wizard .setup-model-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .setup-wizard .setup-step.hidden { display: none; } - - /* Dropdown menu styles */ - .dropdown { - position: absolute; - top: calc(100% + 6px); - right: 0; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 10px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); - z-index: 1000; - display: none; - min-width: 220px; - padding: 6px; - backdrop-filter: blur(12px); - } - - .dropdown.show { - display: block; - animation: dropdown-in 0.15s ease-out; - } - @keyframes dropdown-in { - from { opacity:0; transform:translateY(-6px) scale(0.97); } - to { opacity:1; transform:translateY(0) scale(1); } - } - - .dropdown-item { - padding: 8px 10px; - border-radius: 6px; - cursor: pointer; - transition: background 0.12s ease; - display: flex; - align-items: center; - gap: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - } - - .dropdown-item:last-child { - border-bottom: none; - } - - .dropdown-item .menu-icon { - width: 28px; - height: 28px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - flex-shrink: 0; - } - - .dropdown-item .menu-text h4 { - margin: 0; - font-size: 12.5px; - font-weight: 500; - color: var(--fg); - line-height: 1.3; - } - - .dropdown-item .menu-text p { - margin: 0; - font-size: 10.5px; - color: var(--color-subheader); - line-height: 1.3; - } - - .dropdown-item:hover { - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .dropdown-item:hover .menu-icon { - background: color-mix(in srgb, var(--red) 15%, transparent); - } - - /* Compact dropdown items (session/model context menus) */ - .dropdown-item-compact { - cursor: pointer; - padding: 6px 8px; - font-size: 11px; - border-radius: 8px; - display: flex; - align-items: center; - gap: 10px; - color: var(--fg); - transition: background 0.1s; - } - .dropdown-item-compact:hover { - background: color-mix(in srgb, var(--accent) 10%, transparent); - } - .dropdown-item-compact .dropdown-icon { - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - opacity: 0.5; - } - .dropdown-item-compact .dropdown-icon svg { - width: 14px; - height: 14px; - } - .dropdown-item-compact .dropdown-shortcut { - margin-left: auto; - font-size: 9.5px; - opacity: 0.35; - font-weight: 400; - } - /* Keyboard shortcut hints (⌘+Alt+D etc.) are meaningless on touch — - hide them in the per-chat actions menu on mobile. */ - @media (max-width: 768px) { - .session-dropdown .dropdown-shortcut { display: none; } - } - .dropdown-item-danger { - color: var(--red) !important; - } - .dropdown-item-danger .dropdown-icon { - opacity: 0.7; - } - - /* Inline rename input for sessions */ - .session-rename-input { - width: 100%; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--accent, var(--accent-primary)); - border-radius: 4px; - padding: 2px 6px; - font-family: inherit; - font-size: 12px; - line-height: 1.3; - outline: none; - } - - /* Session dropdown container */ - .session-dropdown-menu { - position: fixed; - z-index: 1000; - display: none; - min-width: auto; - width: max-content; - padding: 4px; - animation: dropdown-in 0.15s ease-out; - } - - /* Folder move submenu */ - .session-folder-submenu { - position: fixed; - z-index: 1001; - display: none; - min-width: auto; - width: max-content; - padding: 4px; - } - - .dropdown-divider { - height: 1px; - background: var(--border); - margin: 4px 8px; - } - - /* Search toggle styles */ - .search-toggle { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 0; - border-bottom: 1px solid var(--border); - margin-bottom: 8px; - } - - .search-provider-label { - font-size: 14px; - color: var(--fg); - } - -/* ── Voice, Search, Themes, Comparison, Censor, Print ── */ - - /* Voice recording styles */ - #mic-btn { - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - transition: all 0.2s ease; - } - - #mic-btn:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: var(--fg); - } - - #mic-btn.recording { - background: var(--color-recording); - border-color: var(--color-recording); - animation: pulse 1.5s infinite; - } - - @keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } - } - - #recording-indicator { - position: fixed; - top: 10px; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin: 10px; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - } - - #recording-indicator.hidden { - display: none !important; - } - - .recording-content { - display: flex; - align-items: center; - gap: 12px; - color: white; - } - - .recording-icon { - color: var(--color-recording); - font-size: 20px; - animation: pulse 1.5s infinite; - } - - .recording-text { - font-size: 16px; - font-weight: 500; - } - - #stop-recording { - background: var(--color-recording); - color: white; - border: none; - border-radius: 6px; - padding: 6px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s ease; - } - - #stop-recording:hover { - background: var(--color-recording-hover); - } - - /* Error state for recording */ - #recording-indicator.error { - background: rgba(173, 26, 26, 0.9); - } - - .recording-error { - color: var(--color-recording); - font-size: 14px; - margin-top: 4px; - } - - @media (max-width: 768px) { - #recording-indicator { - margin: 8px; - padding: 10px; - } - - .recording-text { - font-size: 14px; - } - - #stop-recording { - padding: 6px 10px; - font-size: 12px; - } - } -/* SYNTAX HIGHLIGHTING — uses theme vars from style.css, no hardcoded overrides */ -.hljs { color: var(--hl-fg, #9cdef2); background: none !important; } -pre { background: var(--code-bg, var(--hl-bg, #282c34)) !important; } - - /* ---------- Search overlay (Ctrl+K command palette) ---------- */ - .search-overlay { - position: fixed; - top: 0; left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: flex-start; - justify-content: center; - padding-top: var(--search-overlay-top, 15vh); - z-index: 300; - backdrop-filter: blur(6px); - } - .search-overlay.hidden { display: none; } - .search-popup { - width: 520px; - max-width: 90vw; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 12px; - box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5), 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); - overflow: hidden; - display: flex; - flex-direction: column; - max-height: 60vh; - } - .search-popup input#search-input { - width: 100%; - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - outline: none; - color: var(--fg); - font-size: 16px; - font-family: 'Fira Code', monospace; - padding: 14px 16px; - box-sizing: border-box; - } - .search-popup input#search-input::placeholder { - color: color-mix(in srgb, var(--fg) 30%, transparent); - } - .search-results { - overflow-y: auto; - flex: 1; - padding: 4px; - } - .search-results:empty { - display: none; - } - .search-group-header { - font-size: 10px; - font-weight: 600; - color: var(--color-subheader); - text-transform: uppercase; - letter-spacing: 0.5px; - padding: 10px 12px 4px; - } - .search-result-item { - display: flex; - align-items: baseline; - gap: 8px; - padding: 8px 12px; - border-radius: 6px; - cursor: pointer; - transition: background 0.1s; - } - .search-result-item:hover, - .search-result-item.selected { - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .search-result-role { - font-size: 10px; - font-weight: 600; - color: var(--color-subheader); - flex-shrink: 0; - min-width: 24px; - } - .search-result-snippet { - flex: 1; - font-size: 12px; - color: var(--fg); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .search-result-time { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 35%, transparent); - flex-shrink: 0; - white-space: nowrap; - } - mark.search-highlight { - /* Was orange-on-orange (0.35 alpha bg + orange text) — too low-contrast - to read. Solid accent fill with bg-colored text reads clearly. */ - background: var(--accent, #e8a830); - color: var(--bg, #1a1a1a); - border-radius: 2px; - padding: 0 2px; - font-weight: 600; - } - .search-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 24px; - font-size: 13px; - } - - /* ---------- Theme popup (uses .modal > .modal-content frame) ---------- */ - #theme-modal { z-index: 260; } - #theme-popup .theme-popup-sub { color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 11px; margin-bottom: 10px; } - /* `max-height` instead of a fixed height so the popup shrinks to fit its - content (avoids the leftover whitespace after the time-based switching - card was removed). The inner .theme-tab-panel keeps overflow:auto, so - any taller content still scrolls inside. */ - #theme-popup { overflow-y: hidden; max-height: min(85vh, 600px); } - #theme-popup .modal-header { flex-shrink: 0; } - #theme-popup .admin-tabs { margin: 0 -10px 8px; padding: 0 10px; flex-shrink: 0; } - .theme-tab-panel { overflow-y: auto; min-height: 0; flex: 1; padding-bottom: 10px; } - - .theme-grid { - display: grid; grid-template-columns: repeat(auto-fill, minmax(66px, 1fr)); - gap: 6px; margin-bottom: 12px; - } - .theme-swatch { - border: 2px solid var(--border); border-radius: 8px; cursor: pointer; - padding: 5px; text-align: center; font-size: 0.65rem; color: var(--fg); - transition: border-color 0.15s, transform 0.15s; - } - .theme-swatch:hover { transform: scale(1.06); } - .theme-swatch.active { border-color: var(--red); box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 33%, transparent); } - .theme-swatch-colors { - display: flex; justify-content: center; margin-bottom: 3px; - } - .theme-swatch-colors span { - width: 15px; height: 15px; border-radius: 50%; - margin-left: -5px; - border: 1.5px solid color-mix(in srgb, var(--fg) 12%, transparent); - } - .theme-swatch-colors span:first-child { margin-left: 0; } - .theme-custom-label { font-size: 11px; font-weight: 600; color: color-mix(in srgb, var(--fg) 50%, transparent); text-transform: uppercase; letter-spacing: 0.06em; margin: 10px 0 6px; } - .theme-custom { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; } - .color-row { display: flex; align-items: center; gap: 4px; position: relative; } - .color-row label { font-size: 13px; font-weight: 500; color: var(--fg); opacity: 0.7; flex: 1; } - .color-row input[type="color"], - .color-row input.cp-swatch-input { - width: 24px; height: 24px; border: 1px solid var(--border); border-radius: 50%; - background: none; cursor: pointer; padding: 0; flex-shrink: 0; - overflow: hidden; - -webkit-appearance: none; - appearance: none; - } - .color-row input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } - .color-row input[type="color"]::-webkit-color-swatch { border: none; border-radius: 50%; } - .color-row input[type="color"]::-moz-color-swatch { border: none; border-radius: 50%; } - #theme-bg-effect-color { position: relative; top: 2px; } - .color-row input.cp-swatch-input { - color: transparent; - text-shadow: none; - caret-color: transparent; - font-size: 0; - user-select: none; - } - .color-row input.cp-swatch-input::selection { background: transparent; } - .color-row input.cp-swatch-input:focus { outline: 1px solid var(--red); outline-offset: 1px; } - .color-reset-btn { - width: 24px; height: 24px; border: none; background: none; cursor: pointer; - color: var(--fg); opacity: 0; font-size: 1.15rem; padding: 0; line-height: 1; - transition: opacity 0.15s, color 0.15s; flex-shrink: 0; pointer-events: none; - position: absolute; right: 6px; top: 50%; transform: translateY(-50%); - } - .color-row input[type="color"], - .color-row input.cp-swatch-input { margin-left: auto; } - /* Keep the harmony hex readout on the left, with the swatch dot anchored - to the right like the other color controls. */ - #theme-harmony-card #harmony-accent-hex { - order: -1; - margin-right: auto; - } - .color-row:has(.color-reset-btn.changed) input[type="color"], - .color-row:has(.color-reset-btn.changed) input.cp-swatch-input { margin-right: 28px; } - .color-reset-btn.changed { opacity: 0.4; pointer-events: auto; } - .color-reset-btn.changed:hover { opacity: 1; color: var(--red); } - .theme-custom-divider { - grid-column: 1 / -1; font-size: 11px; color: var(--fg); opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.04em; margin: 6px 0 2px; - border-top: 1px solid var(--border); padding-top: 6px; - } - .theme-swatch[data-custom] { position: relative; overflow: visible; } - /* Accent-coloured circular X — always visible (mobile-friendly), sits - INSIDE the swatch's top-right corner so it never crops into a - neighbouring swatch. */ - .theme-delete-btn { - position: absolute; - top: -2px; - right: -2px; - width: 20px; - height: 20px; - padding: 0; - border: none; - border-radius: 50%; - background: var(--accent, var(--red, #d92534)); - color: #fff; - cursor: pointer; - z-index: 2; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); - transition: transform 0.12s, background 0.12s; - } - .theme-delete-btn:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 80%, white); - transform: scale(1.12); - } - .theme-delete-btn:active { transform: scale(0.95); } - .theme-delete-btn svg { - display: block; - /* Optical nudge — the X reads off-center inside the circle without it. */ - position: relative; - left: 1px; - top: -1px; - } - .theme-save-row { - display: flex; gap: 6px; margin-top: 8px; - } - .theme-save-row input { - flex: 1; padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 12px; font-family: inherit; - } - .theme-save-row input:focus { outline: none; border-color: var(--red); } - .theme-save-row input::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } - .theme-save-row button { - padding: 6px 12px; border: 1px solid var(--red); border-radius: 6px; - background: transparent; color: var(--red); cursor: pointer; - font-size: 12px; font-family: inherit; white-space: nowrap; transition: all 0.15s; - } - .theme-save-row button:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } - .theme-save-error { - font-size: 11px; color: var(--red); margin-top: 2px; display: none; - } - /* Import/Export */ - .theme-io-row { display: flex; gap: 6px; margin-top: 6px; } - .theme-io-btn { - flex: 1; padding: 5px 10px; border: 1px solid var(--border); border-radius: 6px; - background: transparent; color: var(--fg); cursor: pointer; - font-size: 12px; opacity: 0.7; transition: all 0.15s; font-family: inherit; - } - .theme-io-btn:hover { opacity: 1; border-color: var(--fg); background: color-mix(in srgb, var(--fg) 5%, transparent); } - - #memory-import-btn svg, - #memory-export-btn svg { - color: var(--accent, var(--red)); - } - .theme-import-area { - width: 100%; margin-top: 6px; padding: 6px 8px; - border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 0.7rem; - font-family: inherit; resize: vertical; min-height: 48px; - } - .theme-import-area:focus { outline: none; border-color: var(--red); } - .theme-import-area.hidden { display: none; } - .theme-import-actions { display: flex; gap: 6px; margin-top: 4px; } - .theme-import-actions.hidden { display: none; } - /* Font & Density */ - .theme-fd-row { display: flex; gap: 8px; margin-bottom: 8px; } - .theme-fd-group { flex: 1; display: flex; flex-direction: column; gap: 3px; } - .theme-fd-label { font-size: 12px; font-weight: 500; color: var(--fg); opacity: 0.6; } - .theme-fd-select { - padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 12px; - font-family: inherit; cursor: pointer; - } - .theme-fd-select:focus { outline: none; border-color: var(--red); } - @media (max-width: 768px) { - /* The switch is too cramped beside the Font and Density selects on - narrow screens; give Frosted a dedicated row instead. */ - .theme-fd-row:first-of-type { - flex-wrap: wrap; - } - .theme-fd-row:first-of-type #theme-frosted-group { - flex: 1 1 100%; - flex-direction: row; - align-items: center; - justify-content: space-between; - min-width: 0; - } - } - .theme-fd-range { - width: 100%; - max-width: 100%; - box-sizing: border-box; - margin: 0; - padding: 0; - height: 24px; - background: transparent; - cursor: pointer; - -webkit-appearance: none; - appearance: none; - accent-color: var(--red); - } - .theme-fd-range::-webkit-slider-runnable-track { - height: 4px; background: var(--border); border-radius: 2px; - } - .theme-fd-range::-moz-range-track { - height: 4px; background: var(--border); border-radius: 2px; - } - .theme-fd-range::-webkit-slider-thumb { - -webkit-appearance: none; appearance: none; - width: 14px; height: 14px; border-radius: 50%; - background: var(--red); border: none; margin-top: -5px; cursor: pointer; - } - .theme-fd-range::-moz-range-thumb { - width: 14px; height: 14px; border-radius: 50%; - background: var(--red); border: none; cursor: pointer; - } - .theme-fd-range:focus { outline: none; } - /* Color Harmony Generator */ - .theme-harmony-row { display: flex; gap: 8px; align-items: flex-end; } - .harmony-generate-btn { - padding: 5px 14px; border: 1px solid var(--red); border-radius: 6px; - background: transparent; color: var(--red); cursor: pointer; - font-size: 12px; white-space: nowrap; transition: all 0.15s; - font-family: inherit; width: 100%; - } - .harmony-generate-btn:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } - .harmony-preview { - display: flex; height: 20px; border-radius: 6px; overflow: hidden; - margin-top: 8px; border: 1px solid var(--border); - } - .harmony-preview:empty { display: none; border: none; } - .harmony-preview span { flex: 1; } - #theme-reset-btn { - margin-top: 6px; width: 100%; padding: 6px; border: 1px solid var(--border); - border-radius: 6px; background: var(--bg); color: var(--fg); cursor: pointer; - font-size: 12px; font-family: inherit; opacity: 0.7; transition: opacity 0.15s; - } - #theme-reset-btn:hover { opacity: 1; } - .theme-adv-toggle { - margin-top: 10px; margin-bottom: 10px; - padding: 6px 8px; cursor: pointer; - font-size: 11px; color: var(--red); opacity: 0.8; - border: 1px solid var(--border); border-radius: 6px; - transition: opacity 0.15s, background 0.15s; - user-select: none; - } - .theme-adv-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 4%, transparent); } - .theme-adv-toggle .theme-adv-arrow { - display: inline-block; transition: transform 0.2s; font-size: 10px; - margin-right: 4px; - } - .theme-adv-toggle.open .theme-adv-arrow { transform: rotate(90deg); } - .theme-adv-section { margin-top: 8px; margin-bottom: 10px; } - .theme-adv-section.hidden { display: none; } - .theme-adv-group { margin-bottom: 8px; } - .theme-adv-group-label { - font-size: 10px; color: var(--fg); opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; - } - .theme-adv-clear-btn { - margin-top: 6px; width: 100%; padding: 5px; border: 1px solid var(--border); - border-radius: 6px; background: transparent; color: var(--fg); opacity: 0.5; - cursor: pointer; font-size: 12px; font-family: inherit; transition: opacity 0.15s; - } - .theme-adv-clear-btn:hover { opacity: 1; } - - /* Customization is an editor, so keep its sections visually related while - preserving the existing controls and their order. */ - #theme-tab-customize { - display: flex; - flex-direction: column; - gap: 10px; - } - #theme-tab-customize > .admin-card, - #theme-tab-customize > .theme-adv-toggle, - #theme-tab-customize > .theme-adv-section { - margin-top: 0; - margin-bottom: 0; - } - #theme-tab-customize > .admin-card { - padding: 12px; - } - #theme-tab-customize > .admin-card > h2 { - display: flex; - align-items: center; - min-height: 20px; - margin: 0 0 10px; - padding-bottom: 7px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 65%, transparent); - font-size: 13px; - } - #theme-tab-customize .theme-custom { - gap: 6px 14px; - } - #theme-tab-customize .color-row { - min-height: 30px; - padding: 3px 6px; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--border) 55%, transparent); - background: color-mix(in srgb, var(--fg) 2%, transparent); - } - #theme-tab-customize .color-row label { - font-size: 12px; - } - #theme-tab-customize .theme-adv-toggle { - flex-shrink: 0; - margin: 0; - background: color-mix(in srgb, var(--fg) 2%, transparent); - } - #theme-tab-customize .theme-adv-section { - padding: 10px 12px 2px; - border: 1px solid color-mix(in srgb, var(--border) 75%, transparent); - border-radius: 7px; - background: color-mix(in srgb, var(--panel) 55%, transparent); - } - #theme-tab-customize .theme-adv-group { - margin: 0; - padding: 0 0 9px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 48%, transparent); - } - #theme-tab-customize .theme-adv-group + .theme-adv-group { - padding-top: 9px; - } - #theme-tab-customize .theme-adv-group:last-of-type { - border-bottom: 0; - } - #theme-tab-customize .theme-adv-group-label { - margin-bottom: 6px; - color: var(--red); - opacity: 0.8; - font-weight: 600; - } - #theme-tab-customize .theme-fd-row, - #theme-tab-customize .theme-harmony-row { - gap: 12px; - margin-bottom: 10px; - } - #theme-tab-customize .theme-fd-row:last-child, - #theme-tab-customize .theme-harmony-row:last-child { - margin-bottom: 0; - } - #theme-tab-customize .theme-fd-group { - min-width: 0; - } - #theme-tab-customize .theme-fd-select { - min-height: 30px; - } - #theme-tab-customize #theme-harmony-card .theme-fd-label { - font-size: 11px; - } - #theme-tab-customize .theme-save-row, - #theme-tab-customize .theme-io-row { - gap: 8px; - } - #theme-tab-customize #theme-reset-btn { - margin-top: 8px; - } - @media (max-width: 560px) { - #theme-tab-customize .theme-custom { - grid-template-columns: 1fr; - } - #theme-tab-customize .theme-fd-row, - #theme-tab-customize .theme-harmony-row { - flex-direction: column; - align-items: stretch; - } - #theme-tab-customize .theme-fd-group, - #theme-tab-customize .theme-harmony-row .theme-fd-group { - width: 100%; - } - } - - - /* Mobile: bottom sheet modals — slide up from bottom */ - @media (max-width: 768px) { - .modal { - align-items: flex-end; - background: rgba(0,0,0,0.4); - pointer-events: auto; - /* Anchor the bottom sheet to the DYNAMIC viewport. The base overlay is - position:fixed; height:100% = the layout viewport, whose bottom sits - UNDER Firefox Android's bottom URL bar — so flex-end parked the sheet - (and its footer / Start button) beneath the bar. dvh excludes the bar - so the footer lands above it. Extra safe-area pad for iOS home bar. */ - height: 100dvh; - padding-bottom: env(safe-area-inset-bottom, 0px); - } - /* Confirm dialog stays centered, not a bottom sheet */ - #styled-confirm-overlay { - align-items: center; - } - .styled-confirm-box { - border-radius: 12px !important; - border: 1px solid var(--border) !important; - animation: modal-enter 0.25s ease-out both !important; - max-height: none !important; - } - .styled-confirm-box.modal-closing { - animation: modal-exit 0.18s ease-in both !important; - } - .styled-confirm-box::before { - display: none !important; - } - .styled-confirm-box .close-btn { - display: none !important; - } - #theme-popup { - width: 100% !important; - height: 65vh !important; - max-height: 65vh !important; - top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important; - position: fixed !important; - border-radius: 14px 14px 0 0; - border: none; - border-top: 1px solid var(--border); - padding: 6px 12px 12px; - animation: sheet-enter 0.2s ease-out forwards; - overflow-y: hidden !important; - } - #theme-popup .theme-tab-panel { - touch-action: pan-y; - overscroll-behavior: contain; - -webkit-overflow-scrolling: touch; - } - #theme-popup.sheet-ready { - animation: none; - } - #theme-popup.modal-closing { - animation: sheet-exit 0.15s ease-in both !important; - } - .modal-content, - .memory-modal-content, - .settings-modal-content, - #compare-model-overlay .modal-content { - width: 100% !important; - /* 85dvh leaves comfortable headroom above the sheet so the user can - still see the chat behind it and the drag handle has breathing - room. Was 65vh (too short, content clipped) → 90vh (too tall, top - hugged the status bar). */ - max-height: 85dvh !important; - max-height: 85vh !important; /* fallback for browsers without dvh */ - height: auto !important; - border-radius: 14px 14px 0 0; - border: none; - border-top: 1px solid var(--border); - padding-top: 6px !important; - /* Clip children to the rounded top corners — otherwise the sticky - modal-header's var(--panel) background paints a darker - rectangle past the radius and the corners look square again. */ - overflow: hidden; - animation: sheet-enter 0.2s ease-out forwards; - } - /* Keep the tall tool drawers on a shared mobile height ceiling. The - final mobile drawer rule below makes this content-sized so shorter - tools remain compact while tall ones leave a visible top gap. */ - #cookbook-modal .modal-content, - #tasks-modal .modal-content, - #calendar-modal .modal-content, - #memory-modal .memory-modal-content, - #settings-modal .settings-modal-content, - #email-lib-modal .modal-content, - #doclib-modal .doclib-modal-content { - max-height: 90vh !important; - max-height: 90dvh !important; - height: auto !important; - /* Reserve the iOS home-indicator / bottom-bar strip so an expanded - skill card's footer (Delete / Edit / Run) isn't hidden under it. - dvh already excludes the URL bar; this handles the safe area. */ - padding-bottom: env(safe-area-inset-bottom, 0px) !important; - } - /* Grab handle pill on the non-standard content classes too. Memory's - desktop rule defines a radial-glow ::before later in the file — - these body-prefixed selectors win the specificity battle on mobile. */ - body .memory-modal-content::before, - body .settings-modal-content::before { - content: ''; - display: block; - position: static; - inset: auto; - width: 36px; height: 4px; - background: var(--fg); - opacity: 0.25; - border-radius: 2px; - margin: 0 auto 4px; - flex-shrink: 0; - padding: 0; - border-top: 10px solid transparent; - border-bottom: 6px solid transparent; - background-clip: padding-box; - animation: none; - } - .memory-modal-content .close-btn, - .memory-modal-content .modal-close, - .settings-modal-content .close-btn, - .settings-modal-content .modal-close { - display: none !important; - } - .memory-modal-content, - .settings-modal-content { - touch-action: pan-y; - overscroll-behavior: contain; - } - /* Library modals use the same bottom-sheet sizing as the other tools. - The shared mobile drawer rule below provides the top breathing room. */ - #cookbook-modal .modal-content, - #calendar-modal .modal-content, - #email-lib-modal .modal-content, - #doclib-modal .modal-content, - #gallery-modal .modal-content, - #tasks-modal .modal-content { - /* vh-first as fallback for very old browsers, then dvh wins so - the modal shrinks/grows with the mobile URL bar. Wrong order - previously made the expanded chat preview extend below the - visible viewport on Chrome/Safari mobile (URL bar covered the - action buttons row). */ - max-height: 90vh !important; - max-height: 90dvh !important; - height: auto !important; - } - /* Anchor those modals to the top so the full height is usable */ - #cookbook-modal, - #calendar-modal, - #email-lib-modal, - #doclib-modal, - #gallery-modal, - #tasks-modal { - padding-top: 0 !important; - align-items: stretch !important; - } - /* Deep Research already gets the swipe grab-handle pill from the - shared `.modal-content::before` rule (the pane carries that class). - The previous header-level pill was redundant and rendered as a - SECOND pill stacked above the real one — removed. */ - /* The inner body must flex to fill the new full height, and the - tasks list inside it must scroll independently — otherwise the - list crops at whatever pre-mobile height the desktop layout had. */ - #tasks-modal .modal-content { - display: flex !important; - flex-direction: column !important; - } - #tasks-modal .modal-body { - flex: 1 1 auto !important; - min-height: 0 !important; - } - /* Memory/Skills: the body lacks flex:1, so on the fixed-height mobile - sheet (overflow:hidden) it grew past the viewport and clipped the - bottom of the skills list + its row action buttons with no way to - scroll there. Bound the body so the inner list scrolls internally. */ - #memory-modal .memory-modal-content { - display: flex !important; - flex-direction: column !important; - } - /* flex-basis MUST be 0 (not auto) — matches the working #doclib-modal - .modal-body. With basis:auto, Firefox (incl. mobile) sizes the body - to its content (~half height) instead of filling, while Chromium - fills it regardless — which is exactly why the skill expand worked - on desktop/Chromium but stuck at ~50% on Firefox mobile. */ - #memory-modal .memory-modal-body { - flex: 1 1 0 !important; - min-height: 0 !important; - overflow: hidden !important; - } - /* Same basis:0 fix down the rest of the skills chain so every layer - fills instead of sizing to content under Firefox. */ - #memory-modal .memory-tab-panel[data-memory-panel="skills"] { - flex: 1 1 0 !important; - min-height: 0 !important; - } - #memory-modal .memory-tab-panel[data-memory-panel="skills"] > .admin-card { - flex: 1 1 0 !important; - min-height: 0 !important; - } - /* The skills modal carries an extra toolbar row (search/sort/select + - bulk bar) the doc/email libraries don't, so the shared 82dvh preview - min-height overflowed here and pushed the expanded footer (Delete / - Edit / Run) off the bottom of the screen. Let flexbox size it from - the resolved card height instead, so the footer always pins inside - the visible area. */ - #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview { - min-height: 0 !important; - } - /* Once enter animation finishes, clear it so inline transform works for dragging */ - .modal-content.sheet-ready { - animation: none; - } - .modal-content.modal-closing { - animation: sheet-exit 0.15s ease-in both !important; - } - /* Grab handle — large touch target, visible pill */ - #theme-popup::before, - .modal-content::before { - content: ''; - display: block; - width: 36px; height: 4px; - background: var(--fg); opacity: 0.25; - border-radius: 2px; - margin: 0 auto 4px; - flex-shrink: 0; - padding: 0; - /* Expand touch target without changing visual size */ - border-top: 10px solid transparent; - border-bottom: 6px solid transparent; - background-clip: padding-box; - } - /* Hide close X on mobile — swipe down to dismiss */ - .modal-content .close-btn, - .modal-content .modal-close, - #theme-popup .close-btn { - display: none !important; - } - /* Hide the auto-injected minimize (_) button on mobile — the dock - chip already represents the minimized state, and the swipe-down - gesture is the canonical minimize action. */ - .modal-minimize-btn, - .minimize-btn, - [data-minimize] { - display: none !important; - } - /* Lock modals to vertical touch only, prevent horizontal dragging */ - .modal-content { - touch-action: pan-y; - overscroll-behavior: contain; - } - .modal-content .cookbook-body, - .modal-content .modal-body { - touch-action: pan-y; - overscroll-behavior: contain; - } - .modal-header { - cursor: default; - touch-action: none; - } - @keyframes sheet-enter { - from { transform: translateY(100%); } - to { transform: translateY(0); } - } - @keyframes sheet-exit { - from { transform: translateY(0); } - to { transform: translateY(100%); } - } - } - - /* ── Model A/B Comparison ── */ - - /* -- Extracted inline-style classes -- */ - .cmp-header-action-btn { - background: none; border: 1px solid var(--border); color: var(--fg); - cursor: pointer; padding: 3px 10px; font-size: 11px; font-weight: 600; - opacity: 0.7; transition: all 0.15s; line-height: 1; border-radius: 4px; - display: inline-flex; align-items: center; font-family: inherit; - } - .cmp-form-control { - padding: 8px; background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; font-size: 0.85em; - } - .cmp-btn-secondary { - background: transparent; color: var(--fg); border: 1px solid var(--border); - border-radius: 6px; cursor: pointer; - } - .cmp-btn-primary { - background: var(--fg); color: var(--bg); border: none; - border-radius: 6px; cursor: pointer; font-weight: 600; - transition: filter 0.12s, background 0.12s, color 0.12s; - } - /* Override global button:hover (which switches bg to var(--panel) = - very dark) — keep the bright primary look and just brighten slightly. */ - .cmp-btn-primary:hover:not(:disabled) { - background: var(--fg); color: var(--bg); - filter: brightness(1.1); - } - .cmp-btn-primary:active:not(:disabled) { filter: brightness(0.95); } - .cmp-btn-secondary:hover:not(:disabled) { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); color: var(--fg); - } - .cmp-model-row { - display: flex; align-items: center; gap: 8px; margin-bottom: 8px; - flex-wrap: wrap; - padding: 2px 0; - transition: margin-left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.15s, border-color 0.15s; - } - .cmp-model-row-duplicate { - margin-left: -4px; - margin-right: -4px; - padding: 4px; - border-radius: 6px; - background: color-mix(in srgb, var(--color-error) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--color-error) 20%, transparent); - } - .cmp-model-row-swap-target { - margin-left: -4px; - margin-right: -4px; - padding: 4px; - border-radius: 6px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); - } - .cmp-model-row-swap-target .cmp-row-label { - color: var(--accent, var(--red)); - opacity: 1; - } - .cmp-row-label { - color: var(--fg); font-size: 0.82em; font-weight: 600; min-width: 20px; - opacity: 0.4; text-align: center; flex-shrink: 0; - } - .cmp-model-meta { - order: 3; - flex: 1 0 calc(100% - 28px); - margin-left: 28px; - margin-top: -4px; - display: flex; - align-items: center; - gap: 5px; - min-width: 0; - font-size: 10px; - line-height: 1.2; - } - .cmp-model-meta-chip, - .cmp-model-meta-warning, - .cmp-model-meta-muted { - display: inline-flex; - align-items: center; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - border-radius: 999px; - padding: 1px 6px; - letter-spacing: 0; - } - .cmp-model-meta-chip { - color: color-mix(in srgb, var(--fg) 60%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - } - .cmp-model-meta-warning { - color: var(--color-error); - background: color-mix(in srgb, var(--color-error) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--color-error) 24%, transparent); - font-weight: 600; - } - .cmp-model-meta-muted { - color: color-mix(in srgb, var(--fg) 36%, transparent); - } - .cmp-rm-btn { - background: none; border: none; color: var(--accent, var(--red)); cursor: pointer; - min-width: 20px; font-size: 16px; font-weight: 600; opacity: 0.3; - transition: all 0.15s; padding: 0; line-height: 1; text-align: center; - position: relative; top: -2px; - order: 2; - margin-left: auto; - align-self: center; - } - .cmp-prov-select { - flex: 0 0 auto; width: 120px; font-size: 0.8em; - } - /* Eval-prompts picker — only shown during compare; absolute top-right - inside .chat-input-top, matching .model-picker-wrap's slot. */ - .chat-input-top > .cmp-eval-wrap { - position: relative; - grid-column: 2; - grid-row: 1; - top: -2px; right: 0; - z-index: 2; - } - .cmp-eval-btn { - display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 11px; font-weight: 500; - font-family: inherit; - cursor: pointer; opacity: 0.75; - transition: opacity 0.15s, border-color 0.15s; - } - .cmp-eval-btn:hover { opacity: 1; border-color: var(--fg); } - .cmp-eval-clear { - display: inline-flex; align-items: center; gap: 4px; margin-left: 4px; - border: 0; background: transparent; color: var(--fg); opacity: .55; - padding: 3px 5px; font: inherit; font-size: 10px; cursor: pointer; - } - .cmp-eval-clear:hover { opacity: 1; } - .cmp-eval-caret { opacity: 0.7; transform: rotate(180deg); } - .cmp-eval-menu { - position: absolute; bottom: calc(100% + 4px); right: 0; - min-width: 220px; max-width: 280px; - max-height: 360px; overflow-y: auto; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 -4px 16px rgba(0,0,0,0.3); - padding: 4px; - z-index: 1000; - } - .cmp-eval-menu.hidden { display: none; } - .cmp-eval-group-label { - font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px; - opacity: 0.45; font-weight: 600; - padding: 6px 8px 2px; - } - .cmp-eval-item { - display: block; width: 100%; - text-align: left; - padding: 5px 8px; - background: none; border: none; - color: var(--fg); font-size: 11px; - font-family: inherit; - border-radius: 4px; - cursor: pointer; - } - .cmp-eval-item:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - } - .cmp-eval-empty { - padding: 10px; text-align: center; - font-size: 11px; opacity: 0.5; - } - /* Tick on items that ship a known expected answer */ - .cmp-eval-item-tick { - float: right; - margin-left: 6px; - font-size: 10px; - color: var(--color-success, #4caf50); - opacity: 0.8; - } - /* Expected-answer panel — floats as its own little window above the - chat-input-bar. Distinct surface, padded, drop-shadow, slightly - lifted so it reads as a separate UI element, not part of the input. */ - .chat-input-bar:has(.cmp-eval-expected) { position: relative; } - .cmp-eval-expected { - position: absolute; - bottom: calc(100% + 8px); - right: 0; - display: inline-flex; align-items: center; gap: 8px; - padding: 8px 12px; - font-size: 11px; - background: var(--panel); - border: 1px solid color-mix(in srgb, var(--color-success, #4caf50) 50%, transparent); - border-radius: 8px; - box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2); - color: var(--fg); - width: fit-content; - z-index: 5; - pointer-events: auto; - } - .cmp-eval-expected.hidden { display: none; } - .cmp-eval-expected-label { - opacity: 0.6; - text-transform: uppercase; - font-size: 9px; - letter-spacing: 0.5px; - font-weight: 600; - } - .cmp-eval-expected-value { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 11px; - } - .cmp-eval-expected-close { - background: none; border: none; color: var(--fg); - font-size: 14px; line-height: 1; padding: 0 0 0 4px; - opacity: 0.5; cursor: pointer; font-family: inherit; - } - .cmp-eval-expected-close:hover { opacity: 1; } - /* Auto-grade badge — stamped on a pane after stream completes when an - eval prompt with a known expected answer was used. */ - .pane-grade-badge { - display: inline-flex; align-items: center; justify-content: center; - width: 18px; height: 18px; - margin: 0 4px; - font-size: 12px; font-weight: 700; - border-radius: 50%; - border: 1px solid currentColor; - flex-shrink: 0; - } - .pane-grade-badge.pass { - color: var(--color-success, #4caf50); - background: color-mix(in srgb, var(--color-success, #4caf50) 12%, transparent); - } - .pane-grade-badge.fail { - color: var(--color-error, #e55); - background: color-mix(in srgb, var(--color-error, #e55) 12%, transparent); - } - - /* Compare probe overlay */ - .compare-probe-overlay { - position: fixed; inset: 0; z-index: 300; - background: rgba(0,0,0,0.5); display: flex; - align-items: center; justify-content: center; - padding: 12px; - box-sizing: border-box; - } - .compare-probe-card { - width: min(460px, calc(100% - 16px)); - max-height: min(620px, calc(100dvh - 24px)); - box-sizing: border-box; - background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px; - display: flex; flex-direction: column; align-items: stretch; gap: 10px; - min-width: 280px; box-shadow: 0 8px 28px rgba(0,0,0,0.28); - overflow: hidden; - font-family: inherit; - } - .compare-probe-title { - font-size: 13px; font-weight: 400; color: var(--fg); - } - .compare-probe-title-label { font-weight: 600; } - .compare-probe-title-status { font-weight: 400; } - .compare-probe-list { - display: grid; grid-template-columns: 1fr 1fr; gap: 6px; min-width: 0; width: 100%; - overflow-y: auto; - } - .compare-probe-row { - display: flex; align-items: center; gap: 6px; padding: 6px 10px; - border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent); - font-size: 12px; transition: background 0.2s; overflow: hidden; - } - .compare-probe-row.fail { - background: color-mix(in srgb, var(--color-error, #f44) 8%, transparent); - } - .compare-probe-spinner { - width: 24px; text-align: center; font-size: 10px; flex-shrink: 0; - letter-spacing: -1px; opacity: 0.6; - } - .compare-probe-spinner.ok { color: var(--color-success); animation: none; } - .compare-probe-spinner.fail { color: var(--color-error, #f44); animation: none; } - .compare-probe-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .compare-probe-status { font-size: 11px; opacity: 0.5; flex-shrink: 0; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .compare-probe-status.ok { color: var(--color-success); opacity: 1; } - .compare-probe-status.fail { color: var(--color-error, #f44); opacity: 1; } - .compare-probe-action-btn { - display: inline-flex; align-items: center; gap: 5px; - min-height: 25px; padding: 3px 8px; background: transparent; color: var(--fg); border: 1px solid var(--border); - border-radius: 5px; cursor: pointer; font-size: 10px; font-family: inherit; - opacity: 0.78; transition: opacity 0.15s, border-color 0.15s, background 0.15s; white-space: nowrap; flex-shrink: 0; - } - .compare-probe-action-btn svg, - .compare-probe-footer-btn svg { width: 12px; height: 12px; flex: 0 0 auto; color: var(--accent, var(--red)); } - .compare-probe-action-btn:hover { opacity: 1; border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--fg) 5%, transparent); } - .compare-probe-feedback { display: flex; flex-direction: column; gap: 5px; } - .compare-probe-feedback:empty { display: none; } - .compare-probe-detail { - display: flex; align-items: center; gap: 6px; min-width: 0; - padding: 6px 8px; font-size: 10px; - background: color-mix(in srgb, var(--color-error, #f44) 6%, transparent); - border-left: 2px solid color-mix(in srgb, var(--color-error, #f44) 72%, transparent); - border-radius: 4px; - } - .compare-probe-detail-icon { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - width: 14px; - height: 14px; - color: var(--accent, var(--red)); - font-family: inherit; - font-size: 11px; - font-weight: 700; - line-height: 1; - } - .compare-probe-detail-message { flex: 1 1 auto; min-width: 0; line-height: 1.35; color: color-mix(in srgb, var(--fg) 72%, transparent); overflow-wrap: anywhere; } - .compare-probe-footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; margin-top: 2px; } - .compare-probe-footer-btn { - display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 5px 10px; - font-family: inherit; font-size: 11px; font-weight: 600; line-height: 1; - } - .compare-probe-start-anyway, - .compare-probe-start-anyway:hover:not(:disabled) { - background: var(--accent, var(--red)); - border: 1px solid var(--accent, var(--red)); - color: var(--bg); - } - .compare-probe-start-anyway svg { color: currentColor; } - .compare-probe-start-anyway span { font-weight: 700; } - @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } - @keyframes pane-shake { - 0%, 100% { transform: translateX(0); } - 15% { transform: translateX(-3px) rotate(-0.5deg); } - 30% { transform: translateX(3px) rotate(0.5deg); } - 45% { transform: translateX(-2px); } - 60% { transform: translateX(2px); } - 75% { transform: translateX(-1px); } - } - - .chat-container.compare-active { - display: flex; - flex-direction: column; - padding: 0; - overflow: hidden; - animation: compare-enter 0.3s ease-out; - } - @keyframes compare-enter { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } - } - .chat-container.compare-active .chat-input-bar { - margin-bottom: 0; - } - .chat-container.compare-active #model-picker-wrap { - display: none !important; - } - .compare-grid { - display: grid; - gap: 4px; - flex: 1 1 0; - min-height: 0; - overflow: hidden; - position: relative; - } - .compare-mobile-tabs { display: none; } - .compare-grid[data-cols="2"] { grid-template-columns: 1fr 1fr; } - .compare-grid[data-cols="3"] { grid-template-columns: 1fr 1fr 1fr; } - .compare-grid[data-cols="4"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="5"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="6"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="7"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="8"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid { grid-auto-rows: 1fr; } - .compare-shuffle-notice { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%) scale(0.94); - z-index: 30; - display: inline-flex; - align-items: center; - gap: 8px; - padding: 10px 14px; - border: 1px solid color-mix(in srgb, var(--red) 28%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 92%, transparent); - color: var(--fg); - box-shadow: 0 10px 30px color-mix(in srgb, #000 24%, transparent); - font-size: 12px; - font-weight: 700; - line-height: 1; - opacity: 0; - pointer-events: none; - transition: opacity 0.14s ease, transform 0.14s ease; - } - .compare-shuffle-notice.show { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - } - .compare-shuffle-notice-icon { - display: inline-flex; - color: var(--red); - animation: compare-shuffle-dice 0.56s ease both; - } - .compare-shuffle-notice-icon svg { - display: block; - width: 18px; - height: 18px; - } - @keyframes compare-shuffle-dice { - 0% { transform: rotate(-18deg) scale(0.9); } - 55% { transform: rotate(380deg) scale(1.08); } - 100% { transform: rotate(360deg) scale(1); } - } - /* Sequential waterfall layout — stacked rows, staggered left, flush right */ - .compare-grid.sequential-layout { - display: flex !important; - flex-direction: column !important; - grid-template-columns: none !important; - gap: 4px; - overflow-y: auto; - } - .compare-grid.sequential-layout .compare-pane { - flex-shrink: 0; - min-height: 200px; - transition: margin-left 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease; - } - /* Herringbone diagonal cut on left side of sequential pane headers */ - .compare-grid.sequential-layout .compare-pane .pane-header { - clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%); - padding-left: 26px; - } - .compare-pane { - display: flex; - flex-direction: column; - border: 1px solid var(--border); - border-radius: 6px; - overflow: hidden; - min-height: 0; - min-width: 0; - position: relative; - transition: border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; - } - .compare-pane::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - z-index: 2; - pointer-events: none; - background: transparent; - transform-origin: left center; - } - .compare-pane.is-streaming { - border-color: color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - } - .compare-pane.is-streaming::before { - background: linear-gradient(90deg, transparent 0%, var(--accent, var(--red)) 35%, var(--red) 50%, var(--accent, var(--red)) 65%, transparent 100%); - animation: compare-pane-progress 1.25s linear infinite; - } - .compare-pane.is-awaiting-input { - border-color: color-mix(in srgb, var(--red) 42%, var(--border)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--red) 12%, transparent); - } - .compare-pane.is-awaiting-input::before { - background: var(--red); - animation: none; - } - .compare-pane.is-done { - border-color: color-mix(in srgb, var(--green, #50fa7b) 20%, var(--border)); - } - .compare-pane.is-failed { - border-color: color-mix(in srgb, var(--color-error) 42%, var(--border)); - } - .compare-pane.is-failed::before { - background: var(--color-error); - } - @keyframes compare-pane-progress { - from { transform: translateX(-100%); } - to { transform: translateX(100%); } - } - .compare-pane .pane-header { - display: flex; - flex-direction: column; - align-items: stretch; - gap: 4px; - padding: 4px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-bottom: 1px solid var(--border); - font-size: 0.82em; - font-weight: 600; - color: var(--fg); - transition: background 0.4s; - flex-shrink: 0; - overflow: visible; - min-width: 0; - flex-wrap: nowrap; - } - .pane-header-row { display: flex; align-items: center; width: 100%; min-width: 0; } - .pane-header-primary { min-height: 24px; } - .pane-header-secondary { min-height: 20px; } - .pane-primary-actions, .pane-stats { display: flex; align-items: center; gap: 4px; min-width: 0; } - .pane-primary-actions { margin-left: auto; flex: 0 0 auto; } - .pane-stats { flex: 1 1 auto; overflow: hidden; } - .compare-header-bar button:hover, - .compare-header-bar button:focus-visible { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)) !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-header-bar button:active { - background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent) !important; - transform: scale(0.96); - } - .compare-more-wrap { position: relative; display: inline-flex; } - .compare-more-btn { - display: inline-flex; align-items: center; justify-content: center; - width: 28px; height: 24px; padding: 0 !important; - border: 1px solid transparent; border-radius: 5px; - background: none; color: var(--fg); cursor: pointer; - opacity: 0.5; transition: opacity .15s, background .15s; - } - .compare-header-bar .compare-close-btn { - width: 24px; height: 24px; min-width: 24px; padding: 0; - border-radius: 999px; font-size: 0; display: inline-flex; - align-items: center; justify-content: center; - } - .compare-header-bar .compare-close-btn:hover, - .compare-header-bar .compare-close-btn:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-header-bar .compare-more-btn:hover, - .compare-header-bar .compare-more-btn:focus-visible, - .compare-header-bar .compare-more-btn[aria-expanded="true"] { - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-more-menu { - position: absolute; top: calc(100% + 5px); right: 0; z-index: 10002; - display: none; flex-direction: column; gap: 3px; min-width: 132px; - padding: 5px; border: 1px solid var(--border); border-radius: 8px; - background: var(--panel, var(--bg)); box-shadow: 0 8px 24px rgba(0,0,0,.3); - } - .compare-more-menu.is-open { display: flex; } - .compare-more-menu > .compare-export-wrap, - .compare-more-menu > button { - width: 100%; display: flex; align-items: center; - } - .compare-more-menu #compare-export-btn, - .compare-more-menu #compare-shuffle-btn, - .compare-more-menu #compare-check-btn, - .compare-more-menu #compare-add-menu-btn { - width: 100%; min-height: 32px; height: 32px; - box-sizing: border-box; justify-content: flex-start; gap: 7px; - padding: 7px 9px !important; - border: 1px solid transparent !important; border-radius: 6px; - background: transparent !important; color: var(--fg) !important; - font-size: 11px; font-weight: 500; opacity: .72; - transition: background .12s ease, opacity .12s ease, color .12s ease; - } - .compare-more-menu #compare-export-btn:hover, - .compare-more-menu #compare-shuffle-btn:hover, - .compare-more-menu #compare-check-btn:hover, - .compare-more-menu #compare-add-menu-btn:hover, - .compare-more-menu #compare-export-btn:focus-visible, - .compare-more-menu #compare-shuffle-btn:focus-visible, - .compare-more-menu #compare-check-btn:focus-visible, - .compare-more-menu #compare-add-menu-btn:focus-visible { - opacity: 1; background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: transparent !important; color: var(--fg) !important; outline: none; - } - .compare-more-menu #compare-export-btn svg, - .compare-more-menu #compare-shuffle-btn svg, - .compare-more-menu #compare-check-btn svg, - .compare-more-menu #compare-add-menu-btn svg { flex: 0 0 auto; opacity: .65; } - .compare-more-menu #compare-export-btn span, - .compare-more-menu #compare-shuffle-btn span, - .compare-more-menu #compare-check-btn span, - .compare-more-menu #compare-add-menu-btn span { - font-size: 11px !important; margin-left: 0 !important; - color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; - } - .compare-more-menu #compare-export-btn, - .compare-more-menu #compare-shuffle-btn, - .compare-more-menu #compare-check-btn, - .compare-more-menu #compare-add-menu-btn { - color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; - } - .compare-more-menu #compare-export-btn svg, - .compare-more-menu #compare-shuffle-btn svg, - .compare-more-menu #compare-check-btn svg, - .compare-more-menu #compare-add-menu-btn svg { color: var(--fg) !important; opacity: .65 !important; } - .compare-export-item:hover, - .compare-export-item:focus-visible { - background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: transparent !important; outline: none; - } - .compare-export-item { display: flex; align-items: center; gap: 8px; } - .compare-export-item > svg { flex: 0 0 auto; opacity: .65; } - .compare-export-menu .compare-export-item, - .compare-export-menu .compare-export-item > span { - color: var(--fg) !important; - opacity: 1 !important; - } - .compare-add-flap { - position: absolute; z-index: 20; right: -8px; top: 50%; - width: 34px; min-width: 34px; height: 30px; box-sizing: border-box; - padding: 0 !important; - display: inline-flex; flex-direction: row; align-items: center; justify-content: center; - gap: 0; border: 1px solid var(--border); - border-radius: 0 7px 7px 0; background: var(--panel, var(--bg)); - color: var(--fg); cursor: pointer; opacity: .7; - box-shadow: 3px 2px 9px color-mix(in srgb, var(--fg) 12%, transparent); - transform: translate(0, -50%); transition: width .18s ease, opacity .15s, background .15s, box-shadow .15s; - } - .compare-add-flap:hover, .compare-add-flap:focus-visible { - width: 70px; opacity: 1; background: color-mix(in srgb, var(--fg) 8%, var(--panel)); - box-shadow: 4px 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); - outline: none; - } - .compare-add-flap span { - max-width: 0; overflow: hidden; white-space: nowrap; margin-left: 0 !important; - font-size: 10px; opacity: 0; transition: max-width .18s ease, margin-left .18s ease, opacity .12s; - } - .compare-add-flap:hover span, .compare-add-flap:focus-visible span { - max-width: 32px; margin-left: 3px !important; opacity: 1; - } - .compare-add-flap svg { flex-shrink: 0; position: relative; left: -3px; } - @media (max-width: 768px) { - .compare-add-flap { - top: 8px; right: 8px; width: auto; min-width: 32px; height: 32px; - padding: 5px 8px !important; flex-direction: row; transform: none; - border-right: 1px solid var(--border); border-radius: 6px; - } - .compare-add-flap span { opacity: 1; } - } - .pane-actions { - display: flex; gap: 4px; align-items: center; margin-left: auto; flex-shrink: 0; - } - .compare-pane-footer { - font-size: 0.72em; opacity: 0.4; padding: 4px 10px; - border-top: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; flex-shrink: 0; - } - /* Per-pane vote button. Sits at the bottom of each compare pane so - the action lives right under the response it judges. */ - .pane-vote-footer { - padding: 6px 8px; - border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex-shrink: 0; - } - .pane-vote-btn { - width: 100%; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 6px 10px; - font-family: inherit; - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: background 0.15s, border-color 0.15s, opacity 0.15s; - } - .pane-vote-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); - border-color: var(--accent, var(--fg)); - } - .pane-vote-btn:disabled { cursor: not-allowed; } - .pane-vote-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; - } - .pane-action-btn { - background: none; border: none; color: var(--fg); cursor: pointer; - opacity: 0.3; padding: 2px; border-radius: 4px; - display: flex; align-items: center; transition: all 0.15s; - } - .pane-action-btn:hover { opacity: 0.8; background: color-mix(in srgb, var(--fg) 6%, transparent); } - /* Pane title as clickable model-swap button */ - .pane-title-btn { - background: none; border: none; cursor: pointer; - font-size: 10px; font-weight: 400; font-family: inherit; - color: var(--fg); padding: 0; - text-align: left; display: flex; align-items: center; gap: 4px; - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - transition: opacity 0.15s; - min-width: 0; flex: 1 1 0; - } - .pane-title-btn:hover { opacity: 0.7; } - .pane-title-caret { font-size: 0.6em; opacity: 0.35; flex-shrink: 0; position: relative; top: 2px; } - .pane-title-btn:hover .pane-title-caret { opacity: 0.7; } - .pane-mode-badge { - display: inline-flex; - align-items: center; - gap: 4px; - flex: 0 0 auto; - height: 18px; - padding: 0 6px; - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - color: color-mix(in srgb, var(--fg) 76%, transparent); - font-size: 9px; - font-weight: 600; - line-height: 1; - letter-spacing: 0; - white-space: nowrap; - } - .pane-mode-dot { - width: 5px; - height: 5px; - border-radius: 50%; - background: currentColor; - opacity: 0.75; - flex: 0 0 auto; - } - .pane-mode-detail { - opacity: 0.52; - font-weight: 500; - } - .pane-mode-badge.pane-mode-icon-only { - width: 20px; - padding: 0; - justify-content: center; - } - .pane-mode-settings { - opacity: 0.65; - cursor: pointer; - } - .pane-settings-btn { width: 20px; height: 20px; justify-content: center; } - .pane-settings-btn[aria-expanded="true"] { opacity: 1; background: color-mix(in srgb, var(--fg) 9%, transparent); } - .response-metrics[data-action="settings"] { cursor: pointer; } - .response-thinking-state { color: color-mix(in srgb, var(--fg) 55%, transparent); } - .pane-mode-settings:hover, - .pane-mode-settings:focus-visible, - .pane-mode-settings[aria-expanded="true"] { - opacity: 1; - color: var(--fg); - background: color-mix(in srgb, var(--fg) 9%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--fg)) 45%, var(--border)); - outline: none; - } - .pane-mode-icon, - .pane-mode-icon svg { - display: block; - width: 12px; - height: 12px; - } - .pane-mode-agent { - color: color-mix(in srgb, var(--fg) 76%, transparent); - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - border-color: color-mix(in srgb, var(--border) 70%, transparent); - } - .pane-mode-search { - color: color-mix(in srgb, var(--accent, var(--red)) 82%, var(--fg)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, var(--border)); - } - .pane-mode-research { - color: color-mix(in srgb, #36c48f 82%, var(--fg)); - background: color-mix(in srgb, #36c48f 10%, var(--bg)); - border-color: color-mix(in srgb, #36c48f 28%, var(--border)); - } - .compare-check-label { - position: relative; - top: -2px; - font-size: 11px; - margin-left: 3px; - } - .compare-check-icon { - position: relative; - top: 1px; - } - #compare-check-btn > .spinner-whirlpool { - position: relative; - top: -2px; - transform: none; - } - .compare-pane .pane-close-btn { opacity: 0.3; } - .compare-pane .pane-close-btn:hover { opacity: 1; color: var(--color-error); } - /* Model swap dropdown under pane title */ - .pane-model-dropdown { - position: absolute; - z-index: 1000; - min-width: 220px; - max-height: 300px; - overflow-y: auto; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 16px rgba(0,0,0,0.3); - padding: 4px; - } - .pane-model-item { - display: block; width: 100%; - box-sizing: border-box; - padding: 8px 10px; font-size: 11px; line-height: 1.3; - text-align: left; background: none; border: none; border-radius: 6px; - color: var(--fg); cursor: pointer; transition: background 0.12s ease; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - } - .pane-model-item:hover, - .pane-model-item:focus-visible { background: color-mix(in srgb, var(--red) 10%, transparent); outline: none; } - .pane-model-item.current { color: var(--red); font-weight: 600; } - .pane-timer { - font-size: 10px; font-weight: 400; opacity: 0.45; font-variant-numeric: tabular-nums; - white-space: nowrap; padding-right: 4px; - } - .pane-summary { - display: inline-flex; - align-items: center; - position: relative; - top: 1px; - min-height: 16px; - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding: 0 5px; - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 9px; - font-weight: 500; - line-height: 1; - font-variant-numeric: tabular-nums; - cursor: pointer; - transition: max-width .15s ease, white-space .15s ease; - } - .pane-summary:empty { display: none; } - .pane-stats-popup { - position: fixed; z-index: 10040; width: 210px; padding: 6px; - background: var(--panel, var(--bg)); color: var(--fg); - border: 1px solid var(--border); border-radius: 7px; - box-shadow: 0 8px 24px rgba(0,0,0,.3); font-size: 11px; - } - .pane-stats-popup-title { padding: 4px 6px 7px; font-weight: 600; border-bottom: 1px solid var(--border); } - .pane-stats-popup-row { display: flex; justify-content: space-between; gap: 12px; padding: 6px; } - .pane-stats-popup-row span { color: color-mix(in srgb, var(--fg) 55%, transparent); } - .pane-stats-popup-row strong { font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; } - /* Metrics stay on the lower row even in dense comparisons. */ - .compare-grid[data-cols="4"] .pane-timer, - .compare-grid[data-cols="5"] .pane-timer, - .compare-grid[data-cols="6"] .pane-timer { - width: auto; order: initial; margin-top: 0; padding-bottom: 0; padding-left: 0; - } - .pane-finish-badge { - font-weight: 600; color: var(--green, #50fa7b); - position: relative; - top: 2px; - } - .compare-pane.winner .pane-header { - background: color-mix(in srgb, var(--green, #50fa7b) 12%, transparent); - border-bottom-color: color-mix(in srgb, var(--green, #50fa7b) 30%, var(--border)); - } - .compare-pane.winner .pane-title { - color: var(--green, #50fa7b); - } - .compare-pane.loser .pane-header { - opacity: 0.5; - } - .confetti-piece { - position: fixed; - pointer-events: none; - z-index: 1000000; - } - .compare-pane.expanded { grid-column: 1 / -1; } - .compare-pane .chat-history { - flex: 1 1 0; - min-height: 0; - overflow-y: auto !important; - overflow-x: hidden; - padding: 8px; - display: flex; - flex-direction: column; - } - .compare-pane .chat-history .msg { - flex-shrink: 0; - } - .compare-gen-image { - max-width: 100%; - max-height: 100%; - object-fit: contain; - border-radius: 4px; - cursor: pointer; - } - .compare-section { - margin-bottom: 14px; - } - .compare-section:last-child { - margin-bottom: 0; - } - .compare-section-label { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.6px; - opacity: 0.5; - margin-bottom: 5px; - font-weight: 600; - } - /* Active-type/mode readout next to "Type:"/"Mode:" — only needed on mobile - (where the tab/toggle text labels are hidden); hidden on desktop. */ - .compare-type-current, - .compare-mode-current { display: none; } - /* Contextual one-liner under the mode toggles describing what you just - changed — empty until the first toggle, then a subtle hint. */ - .compare-mode-hint { - font-size: 11px; - opacity: 0.55; - margin-top: 6px; - min-height: 0; - line-height: 1.3; - } - .compare-mode-hint:empty { display: none; } - .compare-type-tabs .compare-mode-tab > svg, - .compare-type-current > svg { - color: var(--accent, var(--red)); - } - - .compare-mode-tabs { - display: flex; - gap: 4px; - flex-wrap: wrap; - min-width: 0; - } - /* Type tabs match Mode toggles 1:1 (same flex column layout, same metrics) */ - .compare-mode-tab { - display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 56px; height: 38px; flex: 1 1 0; - padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; - cursor: pointer; user-select: none; transition: all 0.15s; - background: none; color: var(--fg); opacity: 0.35; - flex-shrink: 0; gap: 0; - font-family: inherit; - } - .compare-mode-tab > svg { - width: 13px; height: 13px; flex: 0 0 13px; - } - .compare-mode-tab:hover { - opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); - } - .compare-mode-tab.active { - opacity: 1; border-color: var(--fg); - background: color-mix(in srgb, var(--fg) 10%, transparent); - } - .compare-sources-box { - display: flex; align-items: center; gap: 6px; - padding: 6px 10px; margin-bottom: 8px; - border-radius: 6px; font-size: 0.78em; - background: color-mix(in srgb, var(--red) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - cursor: default; - } - .compare-sources-box .sources-label { font-weight: 600; } - /* Compact tool blocks inside compare panes */ - .compare-pane .agent-thread-node { - margin: 4px 0; font-size: 0.85em; - } - .compare-pane .agent-thread-cmd { - max-height: 80px; overflow-y: auto; - } - .compare-pane .agent-tool-output pre { - max-height: 120px; overflow-y: auto; - } - .compare-vote-bar { - display: flex; justify-content: center; gap: 8px; - padding: 8px; border-top: 1px solid var(--border); - flex-shrink: 0; flex-wrap: wrap; - } - .compare-vote-bar.hidden { display: none; } - .compare-vote-btn { - padding: 6px 13px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; font-size: 0.8em; - transition: all 0.15s; white-space: nowrap; - } - .compare-score-label { - position: relative; - top: -2px; - display: inline-block; - } - .compare-vote-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .compare-vote-tie { opacity: 0.7; } - .compare-rematch-btn { - display: flex; align-items: center; gap: 6px; margin-left: 8px; - border-color: color-mix(in srgb, var(--accent, var(--red)) 60%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--fg); opacity: 1; font-weight: 600; - } - .compare-rematch-btn:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent); } - /* Preview button accent in pane header */ - .pane-preview-btn.active { color: var(--red); } - /* Full-pane iframe for HTML preview */ - .compare-pane-iframe { - flex: 1; - width: 100%; - border: none; - border-radius: 0 0 8px 8px; - background: #fff; - } - /* ---- Add-pane "+" button in pane header (last pane only) ---- */ - .pane-add-btn { display: none !important; font-size: 16px; font-weight: 600; } - .compare-pane:last-child .pane-add-btn { display: flex !important; } - /* Dropdown for adding a pane */ - .add-pane-dropdown { - position: absolute; - right: 0; - z-index: 100; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 10px; - max-height: 300px; - overflow-y: auto; - min-width: 220px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); - padding: 6px; - backdrop-filter: blur(12px); - animation: dropdown-in 0.15s ease-out; - } - .add-pane-search { - width: 100%; - padding: 6px 8px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font-size: 0.85em; - box-sizing: border-box; - margin-bottom: 4px; - position: sticky; - top: 0; - z-index: 1; - } - /* Compare toggle buttons — icon + label stacked */ - .compare-toggle-label { - display: block; - font-size: 9px; - line-height: 1; - margin-top: 2px; - font-weight: 500; - } - .compare-blind-toggle, - .compare-save-toggle, - .compare-dice-toggle, - .compare-parallel-toggle, - .compare-reset-toggle { - display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 56px; height: auto; flex: 1 1 0; - padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; - cursor: pointer; user-select: none; transition: all 0.15s; - background: none; color: var(--fg); opacity: 0.35; - flex-shrink: 0; gap: 0; - } - .compare-blind-toggle:hover, - .compare-save-toggle:hover, - .compare-dice-toggle:hover, - .compare-parallel-toggle:hover, - .compare-reset-toggle:hover { - opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); - } - .compare-blind-toggle.active { - opacity: 1; color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - } - .compare-blind-toggle.active > svg { - color: var(--accent, var(--red)); - } - .compare-save-toggle.active { - opacity: 1; color: var(--color-save-green); border-color: var(--color-save-green); - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .compare-dice-toggle.active { - opacity: 1; color: var(--red); border-color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .compare-parallel-toggle { - opacity: 1; color: #e0a050; border-color: #e0a050; - background: rgba(224, 160, 80, 0.1); - } - .compare-parallel-toggle.active { - color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - } - @media (max-width: 520px) { - .compare-toggle-label { display: none; } - /* Tab text labels are hidden on mobile, so spell out the active type next - to "Type:" with its icon. */ - .compare-type-current { - display: inline-flex; - align-items: center; - gap: 4px; - margin-left: 5px; - font-weight: 600; - vertical-align: -3px; - } - .compare-type-current svg { width: 14px; height: 14px; } - /* Mode list — comma-separated, each name already coloured inline. */ - .compare-mode-current { - display: inline; - margin-left: 5px; - font-weight: 600; - } - .compare-blind-toggle, - .compare-save-toggle, - .compare-dice-toggle, - .compare-parallel-toggle, - .compare-reset-toggle { - width: 32px; height: 32px; min-width: 32px; padding: 0; - } - /* The Group tab's seq/parallel toggle is a single full-width button (not - a row of compact icons like the Compare header), so keep its label and - make it a comfortable touch target with the text beside the icon. */ - #group-mode-btn { - flex-direction: row !important; - width: auto !important; - height: auto !important; - min-height: 44px; - padding: 8px 14px !important; - gap: 8px !important; - font-size: 13px; - } - #group-mode-btn .compare-toggle-label { - display: inline !important; - font-size: 13px; - margin-top: 0; - } - #group-mode-btn svg { width: 18px; height: 18px; } - /* Compare header: hide labels + close button, show icons only */ - #compare-shuffle-btn span { - display: none; - } - #compare-shuffle-btn, - #compare-check-btn, - #compare-add-btn { - padding: 3px 6px; - } - /* Save space so the header buttons fit on one row: tighter padding + - smaller labels (icons kept full size). (Score now lives in the vote bar.) */ - .compare-header-bar button { padding: 3px 5px !important; } - .compare-header-bar #compare-check-btn > span, - .compare-header-bar #compare-add-btn > span { - font-size: 10px; margin-left: 2px; - } - /* Compare mobile: keep the close X visible — it's the only way out - now that the hamburger is hidden during compare mode. */ - .compare-header-bar { - padding: 14px 8px 10px 8px !important; - min-height: 44px; - } - .compare-pane .pane-header { - align-items: stretch; - row-gap: 3px; - } - .compare-pane .pane-title-btn { - flex: 1 1 auto; - width: auto; - padding-right: 2px; - } - /* Compare is already visibly in chat mode; the Chat speech-bubble - badge adds a redundant second row on narrow screens. */ - .compare-pane .pane-mode-chat { display: none; } - .compare-pane .pane-timer, - .compare-pane .pane-summary, - .compare-pane .pane-finish-badge, - .compare-pane .pane-mode-badge, - .compare-pane .pane-actions { - order: 1; - align-self: center; - } - .compare-pane .pane-finish-badge { top: 0; } - .compare-pane .pane-summary { - max-width: 110px; - } - .compare-pane .pane-mode-detail { - display: none; - } - .compare-grid[data-cols="4"] .compare-pane .pane-timer, - .compare-grid[data-cols="5"] .compare-pane .pane-timer, - .compare-grid[data-cols="6"] .compare-pane .pane-timer { - width: auto; - order: 1; - margin-top: 0; - padding-bottom: 0; - padding-left: 0; - } - .compare-pane .pane-actions { - margin-left: auto; - } - /* Mode tabs: icons only, centered */ - .compare-mode-tab span { display: none; } - .compare-mode-tabs { justify-content: center; } - /* Header action buttons: hide text labels on Export / Shuffle / - Model on mobile so the close X fits on the right. Score keeps - its "Score" label because its icon (4-square grid) reads too - similar to Shuffle's icon without text. */ - .compare-header-bar #compare-export-btn > span, - .compare-header-bar #compare-shuffle-btn > span { - display: none; - } - /* Override the desktop override: on mobile the model picker overlay - MUST cap its height to the viewport so the dropdown doesn't run - past the bottom of the screen. */ - #compare-model-overlay .modal-content { - max-height: 85dvh !important; - max-height: 85vh !important; - overflow: hidden !important; - } - #compare-model-overlay .modal-body { - overflow: auto !important; - flex: 1 1 auto !important; - min-height: 0 !important; - } - } - /* Hide number input spinners */ - input[type="number"]::-webkit-inner-spin-button, - input[type="number"]::-webkit-outer-spin-button { - -webkit-appearance: none; margin: 0; - } - - /* ── Sensitive info censor ── */ - .censored-item { - filter: blur(5px); - cursor: pointer; - transition: filter 0.2s; - border-radius: 2px; - padding: 0 2px; - background: rgba(255,100,100,0.08); - user-select: none; - } - .censored-item:hover { filter: blur(3px); background: rgba(255,100,100,0.15); } - .censored-item.revealed { - filter: none; - background: rgba(100,255,100,0.08); - user-select: auto; - cursor: text; - } - - #settings-menu-list .list-item.active { - background: color-mix(in srgb, var(--accent) 10%, transparent); - border-color: var(--accent); - } - - /* ── Print / PDF Export ── */ - @media print { - body { background: #fff !important; color: #000 !important; } - #sidebar, .sidebar, #icon-rail, .hamburger-btn, #sidebar-backdrop, .chat-input-bar, .input-bar-wrapper, - #welcome-screen, .chat-top-bar, .chat-meta-overlay, .msg-footer, - .modal, .toast, .overflow-wrapper, .mode-toggle, .incognito-btn, - button, .dropdown, .session-dropdown, - .agent-tool-spinner, .agent-thread-node.running { display: none !important; } - main.chat-container { width: 100% !important; margin: 0 !important; padding: 0 !important; max-height: none !important; overflow: visible !important; } - #chat-history { max-height: none !important; overflow: visible !important; height: auto !important; padding: 0 !important; } - .msg { break-inside: avoid; page-break-inside: avoid; border: none !important; box-shadow: none !important; } - .msg-ai { background: #f5f5f5 !important; color: #000 !important; } - .msg-user { background: #e8e8e8 !important; color: #000 !important; } - .msg .role { color: #333 !important; font-weight: bold; } - .msg .body { color: #000 !important; } - pre, code { background: #f0f0f0 !important; color: #000 !important; border: 1px solid #ccc !important; } - details { display: block !important; } - details[open] summary ~ * { display: block !important; } - details > summary { list-style: none; } - details > summary::before { content: "" !important; } - #chat-history::before { content: attr(data-print-title); display: block; font-size: 1.3em; font-weight: bold; margin-bottom: 1em; color: #000; } - a { color: #000 !important; text-decoration: underline; } - } - - -/* ── Components (from style.css) ── */ - -/* Self-hosted Fira Code font */ -@font-face { font-family: 'Fira Code'; font-weight: 300; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Light.woff2') format('woff2'); } -@font-face { font-family: 'Fira Code'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Regular.woff2') format('woff2'); } -@font-face { font-family: 'Fira Code'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-SemiBold.woff2') format('woff2'); } - -/* Scrollbar styling */ - -/* Code block styling */ -pre, code, .hljs { - font-size: 11px; - line-height: 1.5; -} - -/* WebKit (Chrome, Edge, Safari) */ -/* Utility class for red text */ -.red-text { - color: var(--red); -} - -/* Internal chat links (search results, session references) */ -a.chat-link { - color: var(--hl-function); - text-decoration: none; - border-bottom: 1px dotted var(--hl-function); - cursor: pointer; -} -a.chat-link:hover { - opacity: 0.8; - border-bottom-style: solid; -} - -a.chat-link[href^="#email-"] { - display: inline; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, transparent); - border-radius: 5px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - color: var(--fg); - font-weight: 600; - border-bottom-style: solid; -} -a.chat-link[href^="#email-"]:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -a.chat-link[href^="#notes-more-"], -a.chat-link[href^="#skills-more-"], -a.chat-link[href^="#memories-more-"], -a.chat-link[href^="#events-more-"], -a.chat-link[href^="#sessions-more-"] { - color: var(--muted); - border-bottom: 1px dotted color-mix(in srgb, var(--muted) 55%, transparent); - font-size: 11px; - font-weight: 500; -} -a.chat-link[href^="#notes-more-"]:hover, -a.chat-link[href^="#skills-more-"]:hover, -a.chat-link[href^="#memories-more-"]:hover, -a.chat-link[href^="#events-more-"]:hover, -a.chat-link[href^="#sessions-more-"]:hover { - color: var(--fg); - opacity: 1; - border-bottom-style: solid; -} -.more-list-expanded { - display: block; - max-height: 0; - overflow: hidden; - opacity: 0; - color: inherit; - font: inherit; - line-height: inherit; - transition: max-height 220ms ease, opacity 160ms ease; -} -.more-list-expanded.is-open { - opacity: 1; -} -.more-list-expanded-content { - color: inherit; - font: inherit; - line-height: inherit; -} - -/* Session items */ -.session-item { position: relative; } -.text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.session-menu-btn { padding: 0 2px !important; min-width: 20px; height: 20px; display: inline-flex !important; align-items: center; justify-content: center; background: none !important; border-color: transparent !important; } -.session-menu-btn:hover { background: none !important; border-color: transparent !important; } -@media (max-width: 768px) { - .session-menu-btn { display: none !important; } - .item-drag-handle { display: none !important; } -} -.session-menu-btn svg { transition: transform 0.2s ease; } - -/* First-time swipe hint */ -.swipe-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 0.7rem; - color: var(--color-error, #f44); - opacity: 0.8; - transition: opacity 0.5s ease; - pointer-events: none; - display: flex; - align-items: center; - gap: 4px; - z-index: 2; -} -.swipe-hint-arrow { - animation: swipe-nudge 1s ease-in-out infinite; -} -@keyframes swipe-nudge { - 0%, 100% { transform: translateX(0); } - 50% { transform: translateX(-6px); } -} - -/* Utility classes */ -.muted { opacity: 0.5; } -.muted-sm { opacity: 0.35; font-size: 0.8em; } -.accent-link { color: var(--accent-primary, var(--color-accent)); cursor: pointer; font-size: 0.85em; } -.models-empty-state { text-align: center; padding: 12px 8px; line-height: 1.6; } - -/* Provider logo inside favorite dot */ -.provider-logo { - border: none !important; - background: none !important; - width: 14px !important; height: 14px !important; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.15s; -} -.provider-logo svg { width: 14px; height: 14px; display: block; } -.provider-logo:hover { opacity: 1 !important; transform: scale(1.2); } - -/* Hide session menu button until hover — use width:0 so it doesn't steal space from text */ -.list-item .hamburger { opacity: 0; width: 0; min-width: 0; overflow: hidden; padding: 0 !important; transition: opacity 0.15s, width 0.15s, padding 0.15s; flex-shrink: 0; display: flex; align-items: center; justify-content: center; } -.list-item:hover .hamburger { opacity: 1; width: 24px; min-width: 24px; padding: 0 4px !important; } -@media (max-width: 768px) { - .list-item .hamburger { opacity: 0.5; width: 28px; min-width: 28px; padding: 0 4px !important; } - .list-item .hamburger:active { opacity: 1; } -} - -/* Hamburger menu button styling (overrides default button appearance) */ -button.hamburger { - background: none; - border: none; - padding: 0; - cursor: pointer; -} - -/* ============================================ */ -/* HEADER SIZING FOR CHAT MESSAGES */ -/* ============================================ */ -/* Markdown in chat messages — colorful, scannable */ -.msg h1, .msg h2, .msg h3, .msg h4, .msg h5, .msg h6 { - margin: 0.6em 0 0.3em 0; - line-height: 1.3; - border-bottom: none; - padding-bottom: 0; -} - -.msg h1 { - font-size: 1.15em; - font-weight: 700; - color: var(--hl-keyword, #c678dd); -} - -.msg h2 { - font-size: 1.1em; - font-weight: 600; - color: var(--hl-function, #5b8def); -} - -.msg h3 { - font-size: 1.05em; - font-weight: 600; - color: var(--hl-string, #98c379); -} - -.msg h4 { - font-size: 1.02em; - font-weight: 600; - color: var(--hl-builtin, #e5c07b); -} - -.msg h5 { - font-size: 1em; - font-weight: 600; - color: var(--hl-variable, #61afef); -} - -.msg h6 { - font-size: 0.95em; - font-weight: 600; - color: var(--hl-number, #d19a66); -} - -/* Bold text — subtle accent color */ -.msg strong, .msg b { - color: var(--hl-builtin, #e5c07b); - font-weight: 600; -} - -/* Italic — softer highlight */ -.msg em, .msg i { - color: var(--hl-params, #abb2bf); - font-style: italic; -} - -/* Bold + italic */ -.msg strong em, .msg em strong, -.msg b i, .msg i b, -.msg b em, .msg em b, -.msg strong i, .msg i strong { - color: var(--hl-keyword, #c678dd); -} - -/* Strikethrough */ -.msg del { - color: color-mix(in srgb, var(--fg) 45%, transparent); - text-decoration: line-through; -} - -/* Inline code */ -.msg code:not(pre code) { - color: var(--hl-string, #98c379); - background: color-mix(in srgb, var(--fg) 6%, transparent); - padding: 1px 5px; - border-radius: 4px; - font-size: 0.9em; -} - -/* Blockquotes */ -.msg blockquote { - border-left: 3px solid var(--hl-function, #5b8def); - padding: 4px 12px; - margin: 0.5em 0; - color: color-mix(in srgb, var(--fg) 75%, transparent); - background: var(--bg); - border-radius: 0 6px 6px 0; -} -.msg blockquote p { margin: 0.3em 0; } - -/* Horizontal rules */ -.msg hr { - border: none; - height: 1px; - background: linear-gradient(90deg, transparent, var(--border), transparent); - margin: 0.8em 0; -} - -/* Lists */ -.msg ul, .msg ol { - margin: 0.3em 0 0.3em 1.2em; - padding: 0; -} -.msg li { - margin: 0.15em 0; -} -.msg li::marker { - color: var(--hl-function, #5b8def); -} - -/* Links */ -.msg a { - color: var(--hl-function, #5b8def); - text-decoration: none; - border-bottom: 1px solid rgba(91, 141, 239, 0.3); - transition: border-color 0.15s; -} -.msg a:hover { - border-bottom-color: var(--hl-function, #5b8def); -} -.msg a.is-loading { - opacity: 0.65; - border-bottom-color: var(--accent, #bd93f9); - cursor: progress; -} - -/* Tables */ -.msg table { - border-collapse: collapse; - margin: 0.5em 0; - font-size: 0.9em; - display: block; - width: max-content; - min-width: 100%; - max-width: 100%; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - table-layout: auto; -} -.msg th { - background: color-mix(in srgb, var(--fg) 7%, transparent); - color: var(--hl-keyword, #c678dd); - font-weight: 600; - padding: 6px 12px; - border: 1px solid var(--border); - text-align: left; - min-width: 9ch; - word-break: normal; - overflow-wrap: break-word; -} -.msg td { - padding: 5px 12px; - border: 1px solid var(--border); - min-width: 9ch; - word-break: normal; - overflow-wrap: break-word; -} - -/* Agent UI Styling */ -.agent-controls { - background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 12px; - margin-bottom: 12px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.agent-toggle label { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-weight: 500; - margin-bottom: 8px; -} - -#workflow-selector { - margin-top: 8px; -} - -#workflow-type { - width: 100%; - padding: 6px 12px; - border: 1px solid #ced4da; - border-radius: 4px; - background: white; - font-size: 14px; -} - -.agent-progress { - background: #ffebee; - border: 1px solid #ef9a9a; - border-radius: 6px; - padding: 12px; - margin: 8px 0; - text-align: center; -} - -.agent-working { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - font-style: italic; - color: var(--red); -} - -.loading-dots::after { - content: '...'; - animation: dots 1.5s infinite; -} - -@keyframes dots { - 0%, 20% { opacity: 0; } - 40% { opacity: 0.5; } - 60%, 100% { opacity: 1; } -} - -.workflow-info { - background: #f8f9fa; - border: 1px solid var(--red); - border-radius: 6px; - padding: 8px 12px; - margin: 4px 0; - font-size: 0.9em; - color: var(--red); - text-align: center; -} - -/* Scrollbar uses --red from :root (set at top of file) */ - -/* Loading spinner */ -@keyframes spin { - to { transform: rotate(360deg); } -} -.spinner { - width: 24px; - height: 24px; - margin: 8px auto; - border: 3px solid var(--border); - border-top-color: var(--red); - border-radius: 50%; - animation: spin 0.9s linear infinite; -} - -/* Inline spinner for buttons */ -.btn-spinner { - display: inline-block; - width: 12px; - height: 12px; - border: 2px solid transparent; - border-top-color: currentColor; - border-radius: 50%; - animation: spin 1s linear infinite; - margin-right: 6px; -} - -/* Loading indicator for messages */ -.loading-indicator { - display: flex; - align-items: center; - justify-content: center; - padding: 10px; -} -.loading-dots { - display: flex; - gap: 4px; -} -.loading-dot { - width: 6px; - height: 6px; - background-color: var(--fg); - border-radius: 50%; - opacity: 0.6; -} -.loading-dot:nth-child(1) { - animation: loading-bounce 1.4s infinite ease-in-out both; -} -.loading-dot:nth-child(2) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.32s; -} -.loading-dot:nth-child(3) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.64s; -} -@keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } -} - -/* Hamburger menu button */ -.hamburger { - display: inline-flex; - flex-direction: column; - justify-content: space-between; - width: 24px; - height: 18px !important; - background: none; - border: none; - padding: 0; - cursor: pointer; -} -.hamburger span { - display: block; - width: 100%; - height: 3px; - background: var(--fg); - border-radius: 2px; -} - -/* Agent indicator */ -#agent-indicator { - position: fixed; - top: 20px; - right: 20px; - background: var(--sidebar-bg, var(--panel)); - color: var(--fg); - border: 1px solid var(--border); - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - display: none; - z-index: 100; - cursor: pointer; - transition: all 0.2s ease; -} -#agent-indicator.active { - display: block; - border-color: var(--color-agent-active); - box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); -} -#agent-indicator:hover { - border-color: var(--color-agent-active); - background: var(--panel); -} - -/* Preset buttons */ -.preset-btn { - height: 27.2px; - padding: 0 8.5px; - margin-left: 4px; - border: 1px solid var(--red); - border-radius: 4px; - background: var(--sidebar-bg, var(--panel)); - color: var(--fg); - font-family: inherit; - font-size: 10.2px; - cursor: pointer; - transition: all 0.2s ease; -} - -.preset-btn:hover { - background: var(--panel); - border-color: var(--fg); -} - -.preset-btn.active { - background: var(--panel); - border-color: var(--red); - box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); - font-weight: 600; -} - -/* Custom preset modal — inherits from .modal base class */ - -/* Unified chat input area */ -.chat-input-area { - display: flex; - flex-direction: column; - gap: 8px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin-top: 12px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.chat-controls-row { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; -} - -.chat-controls-left { - display: flex; - align-items: center; - gap: 8px; -} - -.chat-controls-right { - display: flex; - align-items: center; - gap: 8px; -} - -.control-group { - display: flex; - align-items: center; - gap: 4px; -} - -.control-label { - font-size: 11px; - color: var(--fg); - opacity: 0.8; -} - -.toggle-switch { - position: relative; - display: inline-block; - width: 30px; - height: 16px; -} - -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; -} - -.toggle-slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 8px; - transition: background 0.08s; -} - -.toggle-slider:before { - position: absolute; - content: ""; - height: 12px; - width: 12px; - left: 4px; - top: 2px; - background-color: var(--panel); - border-radius: 50%; - transition: transform 0.08s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); -} - -.toggle-switch input:checked + .toggle-slider { - background-color: var(--toggle-active, var(--red)); -} - -.toggle-switch input:checked + .toggle-slider:before { - transform: translateX(14px); -} - -.preset-buttons-row { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: wrap; -} - -.chat-input-form { - display: flex; - gap: 8px; - align-items: flex-end; -} - -#message { - flex: 1; - min-height: 34px; - max-height: 120px; - resize: none; - font-size: 13px !important; - overflow-y: auto !important; - line-height: 1.4 !important; - font-family: inherit !important; -} - -.action-button { - width: 34px; - height: 34px; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - margin: 0; - background: none; - border: 1px solid var(--border); - border-radius: 4px; - cursor: pointer; - color: var(--fg); - transition: all 0.2s ease; -} - -.action-button:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: var(--fg); -} - -.action-button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.action-button.recording { - background: var(--color-recording); - border-color: var(--color-recording); - animation: pulse 1.5s infinite; -} - -@keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } -} - -#stop-icon { - display: none; - width: 14px; - height: 14px; - background: var(--color-recording); - border-radius: 2px; -} - -/* Attachment strip — centered + max-width to match the chat-input-bar below, - otherwise the chip floats flush-left while the input is centered (visible on - desktop where the chat area is wider than 800px). */ -.attach-strip { - display: flex; - gap: 6px; - flex-wrap: wrap; - padding: 2px 8px; - max-width: 800px; - width: 100%; - margin-left: auto; - margin-right: auto; - box-sizing: border-box; -} -.attach-strip:empty { display: none; } - -/* Upload-in-progress feedback: the message bubble shows immediately, so while - the files are still uploading we put a whirlpool ON each attachment chip and - dim the chip's content — making it obvious that file is being sent, not stuck. */ -.attach-strip.attach-uploading .thumb { - position: relative; - pointer-events: none; -} -.attach-strip.attach-uploading .thumb > :not(.thumb-upload-spinner) { - opacity: 0.4; -} -.thumb-upload-spinner { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; - z-index: 3; -} - -.thumb { - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 11%, transparent); - padding: 3px 6px; - font-size: 12px; - display: flex; - gap: 6px; - align-items: center; - border-radius: 4px; - transition: all 0.2s ease; - max-width: 180px; -} -.thumb-img { - max-width: 60px; - max-height: 40px; - border-radius: 3px; - object-fit: cover; -} -.attach-image-preview { - margin: 4px 0; -} -.attach-image-preview img { - box-shadow: 0 1px 4px rgba(0,0,0,0.2); - /* Same border as the chat bubbles. */ - border: 1px solid var(--bubble-border, var(--border)); -} -/* Image chips: image fills the chip, X overlays as a corner accent badge. - Same on desktop and mobile — doc/text chips keep the beside-X layout. */ -.thumb.thumb-image { - position: relative; - padding: 0; -} -.thumb.thumb-image .thumb-img { - max-height: 56px; - display: block; -} -@media (max-width: 768px) { - .thumb.thumb-image .thumb-img { - cursor: pointer; - touch-action: manipulation; - } -} -.thumb.thumb-image button { - position: absolute; - /* Sit on the top-right corner edge as an accent badge. */ - top: -7px; - right: -7px; - width: 24px; - height: 24px; - min-width: 0; - padding: 0; - border: 2px solid var(--bg); - border-radius: 50%; - background: var(--accent-primary, var(--red)); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 15px; - line-height: 1; - z-index: 3; - transition: transform 0.12s ease, filter 0.12s ease, box-shadow 0.12s ease; -} -.thumb.thumb-image button:hover { - transform: scale(1.12); - filter: brightness(1.12); - box-shadow: 0 2px 8px rgba(0,0,0,0.25); -} -.thumb.thumb-image button:active { - transform: scale(0.96); -} - -.attach-crop-overlay { - position: fixed; - inset: 0; - z-index: var(--attach-crop-z, 100001); - background: rgba(0, 0, 0, 0.72); - display: flex; - align-items: center; - justify-content: center; - padding: 14px; - box-sizing: border-box; -} -.attach-crop-panel { - width: min(94vw, 520px); - max-height: min(86vh, 720px); - background: var(--panel, #111); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 18px 50px rgba(0,0,0,0.45); - overflow: hidden; - display: flex; - flex-direction: column; -} -.attach-crop-stage { - position: relative; - min-height: 280px; - height: min(66vh, 560px); - background: #050505; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - touch-action: none; -} -.attach-crop-img { - max-width: 100%; - max-height: 100%; - object-fit: contain; - user-select: none; - pointer-events: none; -} -.attach-crop-box { - position: absolute; - border: 2px solid var(--accent, var(--red)); - box-shadow: 0 0 0 9999px rgba(0,0,0,0.42); - cursor: move; - touch-action: none; - box-sizing: border-box; -} -.attach-crop-box::before, -.attach-crop-box::after { - content: ''; - position: absolute; - inset: 33.333% 0 auto; - border-top: 1px solid rgba(255,255,255,0.38); -} -.attach-crop-box::after { - inset: 66.666% 0 auto; -} -.attach-crop-handle { - position: absolute; - right: -9px; - bottom: -9px; - width: 20px; - height: 20px; - border-radius: 50%; - background: var(--accent, var(--red)); - border: 2px solid var(--bg); - box-sizing: border-box; - cursor: nwse-resize; -} -.attach-crop-actions { - display: flex; - gap: 8px; - padding: 10px; - background: color-mix(in srgb, var(--bg) 78%, transparent); -} -.attach-crop-btn { - flex: 1; - height: 34px; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.attach-crop-primary { - background: var(--accent, var(--red)); - color: #fff; - border-color: var(--accent, var(--red)); -} -@media (max-width: 768px) { - /* Collapsed "N files" badge: use the same corner-X accent badge as image thumbs. */ - .thumb-collapsed { position: relative; } - .thumb-collapsed .thumb-collapsed-x { - position: absolute; - top: -7px; - right: -7px; - width: 24px; - height: 24px; - min-width: 0; - padding: 0; - border: 2px solid var(--bg); - border-radius: 50%; - background: var(--accent-primary, var(--red)); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 15px; - line-height: 1; - z-index: 3; - opacity: 1; - } - /* Bigger remove-X tap target for non-image (doc/text) chips on mobile too. */ - .thumb button { - height: 28px; - min-width: 28px; - font-size: 15px; - } -} -.thumb span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.thumb:hover { - background: color-mix(in srgb, var(--fg) 16%, transparent); - border-color: var(--fg); -} - -.thumb button { - height: 24px; - padding: 0 5px; - font-size: 0.95em; - border-radius: 4px; - color: var(--accent-primary, var(--red)); -} - -.thumb-collapsed { - cursor: pointer; - color: var(--red); - border-color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - font-weight: 600; - gap: 8px; - border-radius: 999px; /* pill — rounder than the square file chips */ - padding-left: 12px; -} -.thumb-collapsed:hover { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.thumb-collapsed-label { white-space: nowrap; } -.thumb-collapsed-x { - height: 24px; padding: 0 7px; font-size: 13px; border-radius: 4px; - color: var(--accent-primary, var(--red)); - background: none; border: none; cursor: pointer; opacity: 0.6; -} -.thumb-collapsed-x:hover { opacity: 1; } - -/* Recording indicator */ -#recording-indicator { - position: fixed; - top: 10px; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin: 10px; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); -} - -#recording-indicator.hidden { - display: none !important; -} - -.recording-content { - display: flex; - align-items: center; - gap: 12px; - color: white; -} - -.recording-icon { - color: var(--color-recording); - font-size: 20px; - animation: pulse 1.5s infinite; -} - -.recording-text { - font-size: 16px; - font-weight: 500; -} - -.stop-recording-btn { - background: var(--color-recording); - color: white; - border: none; - border-radius: 6px; - padding: 6px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s ease; -} - -.stop-recording-btn:hover { - background: var(--color-recording-hover); -} - -#recording-indicator.error { - background: rgba(173, 26, 26, 0.9); -} - -.recording-error { - color: var(--color-recording); - font-size: 14px; - margin-top: 4px; -} - -/* Mermaid diagram containers */ -.mermaid-container { - margin: 12px 0; - padding: 16px; - background: color-mix(in srgb, var(--bg) 95%, var(--fg)); - border: 1px solid var(--border); - border-radius: 8px; - overflow-x: auto; - text-align: center; -} -.mermaid-container svg { max-width: 100%; height: auto; } - -/* KaTeX math overrides */ -.katex-display { margin: 0.8em 0; overflow-x: auto; overflow-y: hidden; } -.katex { font-size: 1.1em; } - -/* Hide thinking sections globally via settings toggle */ -body.hide-thinking .thinking-section { display: none !important; } - -/* Widen chat area via settings toggle */ -body.fullwidth-chat .chat-history { - padding-left: 0 !important; - padding-right: 12px !important; -} - -/* Thinking process styles — colors follow theme accent */ -.msg .body .stream-content { - width: 100%; -} -.thinking-section { - margin: 12px 0; - width: 100%; - max-width: 100%; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - overflow: hidden; - transition: all 0.3s ease; -} - -.thinking-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 6px 12px; - cursor: pointer; - user-select: none; - background: color-mix(in srgb, var(--red) 8%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - transition: background 0.2s ease; -} - -.thinking-header > .token-new { - display: none; -} - -.thinking-header > div:last-child { - flex-shrink: 0; -} - -.thinking-header:hover { - background: color-mix(in srgb, var(--red) 12%, transparent); -} - -.thinking-header-left { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9em; - color: var(--red); - font-weight: 500; - overflow: hidden; - min-width: 0; -} -.thinking-header-left span { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - transition: opacity 0.2s ease; -} - -.thinking-icon { - font-size: 1.1em; -} - -.thinking-toggle { - font-size: 0.9em; - color: var(--red); - transition: transform 0.3s ease; -} -.thinking-toggle::after { - content: '\25BC'; /* ▼ */ -} - -.thinking-toggle.expanded { - transform: rotate(180deg); -} - -.thinking-content { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; - padding: 0 12px; -} - -.thinking-content.expanded { - max-height: 300px; - overflow-y: auto; - padding: 12px; -} - -.thinking-content-inner { - font-size: 0.85em; - color: var(--fg); - opacity: 0.9; - line-height: 1.5; -} -.live-reply-content { - animation: fadeSlideIn 0.3s ease-out; -} -@keyframes fadeSlideIn { - from { opacity: 0; transform: translateY(4px); } - to { opacity: 1; transform: translateY(0); } -} - -/* Thinking indicator animation */ -.thinking-indicator { - display: flex; - align-items: center; - gap: 4px; - color: var(--red); - font-style: italic; - padding: 8px 0; -} - -.thinking-dots::after { - content: '...'; - animation: thinking-dots 1.5s infinite; - display: inline-block; - width: 20px; - text-align: left; -} - -@keyframes thinking-dots { - 0%, 20% { content: '.'; } - 40% { content: '..'; } - 60%, 100% { content: '...'; } -} - -.thinking-complete { - color: var(--red); - font-size: 0.9em; - padding: 4px 0; - opacity: 0.8; -} - -.research-context-continue-hint { - display: inline-flex; - align-items: center; - gap: 4px; - margin: 5px 2px 0; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - opacity: 0.55; -} -.research-context-continue-hint svg { - flex: 0 0 auto; - opacity: 0.8; -} - -/* ── Sources section — collapsible source citations ── */ -.sources-section { - margin: 8px 0 12px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - overflow: hidden; - transition: all 0.3s ease; -} -.sources-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 12px; - cursor: pointer; - background: color-mix(in srgb, var(--red) 8%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - transition: background 0.2s ease; - user-select: none; -} -.sources-header:hover { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.sources-header-left { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.85em; - font-weight: 500; - color: var(--red); -} -.sources-header-left svg { - width: 14px; - height: 14px; - flex-shrink: 0; - opacity: 0.7; -} -.sources-toggle { - font-size: 0.8em; - color: var(--red); - opacity: 0.7; - transition: none; -} -.sources-toggle::after { - content: '\25B6'; /* ▶ right arrow */ -} -.sources-toggle[data-arrow="down"]::after { - content: '\25BC'; /* ▼ down arrow */ -} -.sources-content { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; - padding: 0 10px; -} -.sources-content.expanded { - max-height: 3000px; - padding: 8px 10px; -} -.sources-content-inner { - display: flex; - flex-direction: column; - gap: 4px; -} -.source-link { - display: flex; - align-items: center; - gap: 8px; - padding: 5px 8px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - text-decoration: none; - color: var(--fg); - transition: background 0.15s ease; -} -.source-link:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.source-num { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 20px; - height: 20px; - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 15%, transparent); - color: var(--fg); - font-size: 0.7em; - font-weight: 600; - flex-shrink: 0; -} -.source-title { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 0.82em; -} -.source-domain { - font-size: 0.72em; - opacity: 0.45; - flex-shrink: 0; -} - -.ai-spinner { - color: var(--red); -} -/* Nudge the Tidy button 2px left. */ -#memory-tidy-btn { position: relative; left: -2px; } -/* Tidy button's whirlpool nudge — sits 1px lower so it visually centers on - the Tidy label baseline. */ -#memory-tidy-btn .ai-spinner-whirlpool, -#memory-tidy-btn .spinner-whirlpool { - position: relative; - top: 1px; -} -.list-item.stream-complete { - animation: none; -} -.session-run-state { - flex: 0 0 auto; - margin-left: 5px; - font-size: 10px; - line-height: 1; - color: var(--fg-dim, #888); - opacity: 0.9; -} -.session-run-state.is-working { - color: var(--accent, var(--red)); -} -.session-run-state .session-run-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 !important; - display: inline-flex; - align-items: center; -} -.session-run-state .session-run-whirlpool .ai-spinner-whirlpool { - display: inline-flex; - width: 12px; - height: 12px; -} -.session-run-state.is-done { - color: var(--color-success, #4caf50); - font-weight: 600; -} -.cookbook-notif-active svg { opacity: 1 !important; } - -/* Rail notification dot — pulsing indicator on icon-rail buttons */ -.icon-rail-btn.rail-notify { - opacity: 1 !important; - position: relative; -} -.icon-rail-btn.rail-notify::before { - content: ''; - position: absolute; - top: 5px; - right: 4px; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--accent, var(--red)); - animation: rail-notif-pulse 2s ease-in-out infinite; - z-index: 1; -} -.icon-rail-btn.rail-notify.rail-notify-success::before { - background: var(--color-success, #4caf50); -} -@keyframes rail-notif-pulse { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.4; transform: scale(0.8); } -} -@keyframes stream-complete-pulse { - 0%, 100% { box-shadow: none; } - 50% { box-shadow: inset 0 0 0 1.5px var(--accent); } -} - -/* ===== SLASH COMMAND RESPONSES ===== */ -.msg.msg-system { - padding: 6px 12px; margin: 4px auto 4px 8px; - max-width: 85%; background: none; border-left: 2px solid var(--border); -} -.msg.msg-system .body { padding: 0; } -.msg.msg-system pre { margin: 4px 0; white-space: pre-wrap; font-size: 0.85em; } -.dropdown-divider { - height: 1px; - margin: 3px 6px; - background: color-mix(in srgb, var(--border) 60%, transparent); -} -.msg.stream-done-toast { - cursor: pointer; - border-left-color: var(--accent, var(--border)); - border-radius: 6px; - background: color-mix(in srgb, var(--accent) 6%, transparent); - transition: background 0.15s; -} -.msg.stream-done-toast:hover { - background: color-mix(in srgb, var(--accent) 12%, transparent); -} -.msg.stream-done-toast .body { - display: flex; - align-items: center; - gap: 8px; -} -.stream-done-indicator { - font-family: monospace; - font-size: 1.1em; - line-height: 1; - color: var(--accent); - flex-shrink: 0; - animation: bar-pulse 1.2s ease-in-out infinite; -} -@keyframes bar-pulse { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } -} - -/* ===== AGENT MULTI-BUBBLE ===== */ -.msg.msg-tool { - display: none; /* legacy — hidden, replaced by agent-thread */ -} -.msg.msg-continuation { - margin-top: 2px; -} - -/* ===== AGENT THREAD TIMELINE ===== */ -.agent-thread { - position: relative; - margin: 2px 0 2px 28px; - padding: 4px 0 4px 22px; - max-width: calc(85% - 20px); - box-sizing: border-box; -} -.agent-thread::before { - content: ''; - position: absolute; - left: 5px; - top: 14px; - bottom: 14px; - width: 2px; - background: color-mix(in srgb, var(--red) 18%, transparent); - border-radius: 1px; -} -/* Extend line to connect to chat bubble above/below */ -.agent-thread.has-top::before { - top: -6.5px; -} -.agent-thread.has-bottom::before { - bottom: -5px; -} -.agent-thread.has-ask-user-bottom::before { - bottom: -12px; -} -/* Terminating dot at bottom when no bubble below and last node is expanded */ -.agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { - content: ''; - position: absolute; - /* Centre the 6px dot on the rail: the rail sits at left:5px + 2px wide, - so its centre is 6px from the thread's left edge and the dot's left - edge belongs at 3px — i.e. 3px - padding-left. Keep this in step with - .agent-thread-dot and with the mobile padding override below. */ - left: -19px; - bottom: 5px; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--red); - box-shadow: 0 0 4px 1px color-mix(in srgb, var(--red) 55%, transparent), - 0 0 0 3px color-mix(in srgb, var(--red) 18%, transparent); -} - -/* Synapse pulse — a bright dot traveling down the line */ -.agent-thread::after { - content: ''; - position: absolute; - left: 4px; - width: 4px; - height: 4px; - border-radius: 50%; - background: var(--red); - box-shadow: 0 0 3px 1px color-mix(in srgb, var(--red) 50%, transparent); - pointer-events: none; - top: 0%; - opacity: 0; -} -.agent-thread.streaming::after { - animation: synapse-capped-short 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-top::after { - animation: synapse-capped 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-bottom::after { - animation: synapse-travel-short 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-top.has-bottom::after { - animation: synapse-travel 0.8s ease-in-out infinite; -} -@keyframes synapse-travel { - 0% { top: 0%; opacity: 0; } - 5% { opacity: 0.5; } - 85% { opacity: 0.35; } - 100% { top: 100%; opacity: 0; } -} -@keyframes synapse-capped { - 0% { top: 0%; opacity: 0; } - 5% { opacity: 0.5; } - 70% { opacity: 0.35; top: calc(100% - 20px); } - 100% { opacity: 0; top: calc(100% - 20px); } -} -@keyframes synapse-travel-short { - 0% { top: 14px; opacity: 0; } - 5% { opacity: 0.5; } - 85% { opacity: 0.35; } - 100% { top: 100%; opacity: 0; } -} -@keyframes synapse-capped-short { - 0% { top: 14px; opacity: 0; } - 5% { opacity: 0.5; } - 70% { opacity: 0.35; top: calc(100% - 20px); } - 100% { opacity: 0; top: calc(100% - 20px); } -} -.agent-thread-node { - position: relative; - padding: 5px 0; -} -.agent-thread-node + .agent-thread-node { - margin-top: 2px; -} -/* Supervisor ladder cards — same chrome as tool cards but tinted so the - user can tell at a glance "this is the agent recovering" vs "this is - the agent doing work". Stop rung gets the red accent. */ -.agent-thread-node.supervisor-step .agent-thread-tool { - color: color-mix(in srgb, var(--accent, #c08a3e) 80%, var(--fg)); - font-style: italic; -} -.agent-thread-node.supervisor-step .agent-thread-dot { - background: color-mix(in srgb, var(--accent, #c08a3e) 60%, transparent); -} -.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-tool { - color: var(--red, #d65a5a); -} -.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-dot { - background: color-mix(in srgb, var(--red, #d65a5a) 60%, transparent); -} -.agent-thread-dot { - position: absolute; - left: -20px; - top: 10px; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--red); - border: 2px solid var(--bg); - z-index: 1; -} -.agent-thread-node.running .agent-thread-dot { - background: var(--red); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 25%, transparent); - animation: thread-pulse 1.5s ease-in-out infinite; - top: 10px; -} -@keyframes thread-pulse { - 0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 20%, transparent); } - 50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--red) 10%, transparent); } -} -.agent-thread-node.error .agent-thread-dot { - background: var(--color-error); -} -.agent-thread-header { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 70%, transparent); - user-select: none; - padding: 2px 0; -} -.agent-thread-header:hover { - color: var(--fg); -} -.agent-thread-icon { - font-size: 0.9em; - color: var(--red); -} -.agent-thread-tool-icon { - width: 14px; - height: 14px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - color: color-mix(in srgb, var(--fg) 64%, transparent); - opacity: 0.85; -} -.agent-thread-tool-icon svg { - display: block; - width: 12px; - height: 12px; -} -.agent-thread-tool-icon-link { - color: inherit; - text-decoration: none; - cursor: pointer; -} -.agent-thread-tool-icon-link:hover { - color: var(--red); - opacity: 1; -} -.agent-thread-node.error .agent-thread-icon { - color: var(--color-error); -} -.agent-thread-tool { - font-weight: 600; - color: var(--red); - text-transform: uppercase; - letter-spacing: 0.3px; - font-size: 0.9em; -} -.agent-thread-header-link { - width: 17px; - height: 17px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 17px; - margin-left: -3px; - color: var(--red); - opacity: 0.72; - text-decoration: none; - border: 0; - background: transparent; -} -.agent-thread-header-link:hover { - opacity: 1; - color: var(--red); -} -.agent-thread-header-link svg { - display: block; -} -.agent-thread-header-link-calendar { - transform: translateY(-2px); -} -.msg-ai .body a[href^="#event-"], -.msg-ai .body a[href^="#email-"], -.msg-ai .body a[href^="#document-"], -.msg-ai .body a[href^="#note-"], -.msg-ai .body a[href^="#task-"], -.msg-ai .body a[href^="#skill-"], -.msg-ai .body a[href^="#memory-"], -.msg-ai .body a[href^="#session-"] { - display: inline-flex; - align-items: baseline; - max-width: 100%; - padding: 1px 5px; - border-radius: 5px; - background: color-mix(in srgb, var(--red) 12%, transparent); - color: var(--red); - font-weight: 600; - text-decoration: none; - box-decoration-break: clone; - -webkit-box-decoration-break: clone; -} -.msg-ai .body a[href^="#event-"], -.msg-ai .body a[href^="#note-"], -.msg-ai .body a[href^="#skill-"], -.msg-ai .body a[href^="#memory-"], -.msg-ai .body a[href^="#session-"] { - font-size: 11px; - line-height: 1.35; -} -.msg-ai .body p:has(> .emoji:first-child):has(> a.chat-link[href^="#note-"]) { - margin-top: 0.15em; - margin-bottom: 0.15em; - line-height: 1.35; -} -.msg-ai .body a[href^="#event-"]:hover, -.msg-ai .body a[href^="#email-"]:hover, -.msg-ai .body a[href^="#document-"]:hover, -.msg-ai .body a[href^="#note-"]:hover, -.msg-ai .body a[href^="#task-"]:hover, -.msg-ai .body a[href^="#skill-"]:hover, -.msg-ai .body a[href^="#memory-"]:hover, -.msg-ai .body a[href^="#session-"]:hover { - background: color-mix(in srgb, var(--red) 18%, transparent); - text-decoration: underline; -} -.agent-thread-status { - font-size: 0.85em; - opacity: 0.5; -} -.agent-thread-chevron { - width: 12px; - height: 12px; - flex: 0 0 12px; - display: inline-grid; - place-items: center; - opacity: 0.4; -} -.agent-thread-chevron::before { - content: ""; - width: 0; - height: 0; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 6px solid currentColor; - transform-origin: 50% 50%; - transition: transform 0.2s ease; -} -.agent-thread-node.open .agent-thread-chevron::before { - transform: rotate(90deg); -} -.agent-thread-node.browser-preview-node .agent-thread-chevron { - display: none; -} -.agent-thread-wave { - font-family: monospace; - font-size: 0.85em; - color: var(--red); - letter-spacing: -1px; -} -/* Live "cooking" timer on a running tool — prominent (accent, tabular) so a - long-running command always reads as alive, not frozen. */ -.agent-thread-elapsed { - margin: 0 6px 0 4px; - font-size: 11px; - font-weight: 600; - font-variant-numeric: tabular-nums; - color: var(--accent, var(--red)); -} -/* Stall watchdog banner — shown when the stream has been silent for ~1min. */ -.stall-banner { - display: flex; - align-items: center; - gap: 8px; - margin: 8px auto; - padding: 8px 12px; - max-width: 90%; - font-size: 12px; - border-radius: 8px; - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, var(--bg)); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 40%, transparent); -} -.stall-banner-txt { flex: 1; opacity: 0.85; } -.stall-banner-btn { - font-size: 11px; - font-weight: 600; - padding: 4px 10px; - border-radius: 6px; - border: none; - background: var(--accent, var(--red)); - color: #fff; - cursor: pointer; - flex-shrink: 0; -} -.stall-banner-stop { - background: none; - color: var(--fg); - border: 1px solid var(--border); -} -.agent-thread-content { - display: none; - padding: 4px 0 2px 0; - overflow-x: auto; - overflow-y: hidden; -} -.agent-thread-node.open .agent-thread-content { - display: block; -} -.agent-thread-cmd { - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - padding: 8px 12px; - margin: 4px 0; - color: var(--fg); - font-size: 0.85em; - white-space: pre-wrap; - word-break: break-word; - line-height: 1.4; - overflow-x: auto; -} -.agent-thread-summary { - display: inline-flex; - align-items: center; - gap: 6px; - max-width: 100%; - margin: 4px 0; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 26%, var(--border)); - border-radius: 6px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - color: color-mix(in srgb, var(--fg) 84%, var(--accent, var(--red))); - font-size: 12px; - font-variant-numeric: tabular-nums; - line-height: 1.35; -} -.agent-thread-summary-link { - cursor: pointer; - text-decoration: none; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-thread-summary-link:hover { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} - -.agent-image-progress { - margin: 6px 0 0; -} -.agent-image-progress-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - font-size: 11px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 72%, transparent); -} -.agent-image-progress-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-image-progress-value { - flex-shrink: 0; - font-variant-numeric: tabular-nums; - color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); -} -.agent-image-progress-track { - display: none !important; -} - -.private-browser-preview { - width: 100%; - max-height: min(300px, 36vh); - margin: 6px 0 2px; - display: flex; - flex-direction: column; - overflow: hidden; - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - color: var(--fg); - box-shadow: none; -} -.private-browser-preview-header { - height: 32px; - min-height: 32px; - display: flex; - align-items: center; - gap: 8px; - padding: 0 6px 0 10px; - border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); - background: color-mix(in srgb, var(--red) 10%, transparent); - font-size: 0.85em; - font-weight: 500; - cursor: pointer; -} -.private-browser-preview-title { - flex-shrink: 0; - color: var(--red); - font-size: inherit; - font-weight: inherit; - line-height: 1; - letter-spacing: 0; -} -.private-browser-preview-status { - min-width: 0; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-size: inherit; - line-height: 1; -} -.private-browser-preview-fold { - width: 18px; - height: 18px; - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - left: 2px; - flex-shrink: 0; - padding: 0; - border: 0; - border-radius: 0; - background: transparent; - color: var(--red); - cursor: pointer; - font-size: 0.95em; - font-family: inherit; - font-weight: 500; - line-height: 1; - text-shadow: none; - -webkit-text-stroke: 0; -} -.private-browser-preview-fold:hover { - background: transparent; - color: var(--red); -} -.private-browser-preview-frame { - position: relative; - min-height: 130px; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - background: color-mix(in srgb, #000 72%, var(--bg)); - box-shadow: - inset 0 1px 0 color-mix(in srgb, #fff 8%, transparent), - inset 0 0 0 1px color-mix(in srgb, #000 35%, transparent), - 0 8px 18px color-mix(in srgb, #000 18%, transparent); -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame { - cursor: zoom-in; -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame::after { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - opacity: 0.55; - transition: opacity 150ms ease; - background: - radial-gradient(circle at center, transparent 42%, color-mix(in srgb, #000 30%, transparent) 100%), - color-mix(in srgb, var(--bg) 10%, transparent); -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame:hover::after { - opacity: 0.9; -} -.private-browser-preview.folded .private-browser-preview-frame { - display: none; -} -.private-browser-preview.folded .private-browser-preview-header { - border-bottom: 0; -} -.private-browser-preview-frame img { - width: 100%; - height: auto; - max-height: calc(min(300px, 36vh) - 32px); - display: block; - object-fit: contain; - transform: scale(1.1); - transform-origin: center center; - animation: private-browser-image-in 180ms ease-out both; -} -.private-browser-preview-frame img:not([src]), -.private-browser-preview-frame img[src=""] { - display: none; -} -.private-browser-preview-empty { - padding: 22px 14px; - width: auto; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 7px; - color: color-mix(in srgb, var(--fg) 48%, transparent); - font-size: 11px; - line-height: 1.35; -} -.private-browser-preview-spinner { - width: 13px; - height: 13px; - flex: 0 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.private-browser-preview-spinner .spinner-whirlpool, -.private-browser-preview-spinner .ai-spinner-whirlpool { - margin: 0 !important; -} -@keyframes private-browser-image-in { - from { opacity: 0; transform: scale(1.08); } - to { opacity: 1; transform: scale(1.1); } -} - -@media (max-width: 700px) { - .private-browser-preview-frame img { - max-height: calc(36vh - 32px); - } -} - -/* Mobile: constrain thread content */ -@media (max-width: 768px) { - .agent-thread { - margin-left: 16px; - padding-left: 18px; - max-width: calc(90% - 16px); - } - .agent-thread-cmd { - font-size: 0.78em; - padding: 6px 8px; - max-width: 100%; - overflow-x: auto; - } - .agent-thread-content { - max-width: calc(100vw - 90px); - } - .agent-tool-output pre { - font-size: 0.8em; - max-width: 100%; - overflow-x: auto; - } - .agent-thread-header { - font-size: 0.8em; - } - .agent-thread-dot { - left: -16px; - top: 10px; - } - /* padding-left drops to 18px here, so the terminating dot needs its own - offset (3px - 18px) to stay centred on the rail. */ - .agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { - left: -15px; - } -} - -/* ===== AGENT TOOL OUTPUT (inside thread nodes) ===== */ -.agent-tool-output { - margin-top: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 8px; - overflow: hidden; -} -.agent-tool-output summary { - color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); - cursor: pointer; - font-size: 0.85em; - user-select: none; - padding: 6px 10px; - font-weight: 500; - /* Chevron on the right (like the thinking section) instead of the default - left-side disclosure triangle. */ - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - list-style: none; -} -.agent-tool-output summary::-webkit-details-marker { display: none; } -/* File-write diff — neutral chrome (not the red error tint) + colored lines */ -.agent-tool-diff { - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.agent-tool-diff summary { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-bottom-color: color-mix(in srgb, var(--fg) 12%, transparent); -} -.agent-tool-diff .diff-stat { - font-weight: 600; - opacity: 0.7; - font-family: var(--mono, monospace); -} -/* Collapsed diff summary: filename + +adds/−dels (theme green/red). */ -.agent-tool-diff summary { - display: flex; - align-items: center; - gap: 8px; -} -.agent-tool-diff .diff-file { - font-family: var(--mono, monospace); - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-tool-diff .diff-summary-stats { - margin-left: auto; - font-family: var(--mono, monospace); - font-weight: 600; - flex-shrink: 0; -} -.agent-tool-diff .diff-summary-stats .diff-stat-add { color: var(--green, #2ecc71); } -.agent-tool-diff .diff-summary-stats .diff-stat-del { color: var(--red, #e74c3c); } -.agent-tool-diff .diff-summary-stats .diff-stat-new { color: var(--accent, var(--red)); opacity: 0.85; } -.diff-pre { - margin: 0; - padding: 8px 10px; - overflow-x: auto; - font-family: var(--mono, monospace); - font-size: 0.82em; - line-height: 1.45; -} -.diff-pre span { display: block; white-space: pre; } -.diff-pre .diff-add { background: color-mix(in srgb, #2ecc71 22%, transparent); } -.diff-pre .diff-del { background: color-mix(in srgb, #e74c3c 22%, transparent); } -.diff-pre .diff-hunk { color: var(--accent); opacity: 0.85; } -.diff-pre .diff-meta { opacity: 0.55; } -.diff-pre .diff-ctx { opacity: 0.8; } -/* Suppress the global `summary::before { content: '▶' }` left arrow — this - section uses a right-side chevron instead. */ -.agent-tool-output summary::before { content: none; } -.agent-tool-output summary::after { - content: '\25BC'; /* ▼ */ - color: var(--red); - font-size: 0.9em; - transition: transform 0.3s ease; -} -.agent-tool-output[open] > summary::after { - transform: rotate(180deg); -} -.agent-tool-output summary:hover { - background: color-mix(in srgb, var(--red) 15%, transparent); -} -.agent-thinking-dots .ai-spinner { - font-size: 12px; - letter-spacing: 0.5px; -} - -.agent-tool-output[open] { - background: color-mix(in srgb, var(--red) 6%, transparent); -} -.agent-tool-output[open] > :not(summary) { - animation: detail-reveal 0.25s ease-out both; -} -@keyframes detail-reveal { - from { opacity: 0; transform: translateY(-4px); } - to { opacity: 1; transform: translateY(0); } -} -.agent-tool-output pre { - background: transparent; - border: none; - border-radius: 0; - padding: 10px 14px; - margin-top: 0; - max-height: 300px; - overflow-y: auto; - font-size: 0.95em; - color: var(--fg); - opacity: 0.85; - white-space: pre-wrap; - word-break: break-all; - line-height: 1.5; -} - -/* Mobile responsive styles */ -@media (max-width: 768px) { - #recording-indicator { - margin: 8px; - padding: 10px; - } - - .recording-text { - font-size: 14px; - } - - .stop-recording-btn { - padding: 6px 10px; - font-size: 12px; - } -} -/* ===== PANE STYLES (shared by compare) ===== */ - -.pane-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 0 4px; - border-bottom: 1px solid var(--border); -} -.close-split-btn, -.pane-close-btn { - background: none; - border: none; - color: var(--color-error); - font-size: 18px; - cursor: pointer; - padding: 0 4px; - line-height: 1; - opacity: 0; - transition: opacity 0.15s; -} -.pane-header:hover .close-split-btn, -.pane-header:hover .pane-close-btn { - opacity: 1; -} -.close-split-btn:hover, -.pane-close-btn:hover { - color: var(--color-error-light); -} - -/* Compare pane close controls use the same shared circular close treatment - as modal/title close buttons. */ -.compare-pane .pane-close-btn.close-btn { - width: 24px; - height: 24px; - min-width: 24px; - padding: 0; - border: 1px solid transparent; - border-radius: 999px; - background: transparent; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 0; - opacity: 0.5; - display: inline-flex; - align-items: center; - justify-content: center; - transition: background 0.15s, color 0.15s, opacity 0.15s; -} -.compare-pane .pane-close-btn.close-btn:hover, -.compare-pane .pane-close-btn.close-btn:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); - opacity: 1; - outline: none; -} -.compare-pane .pane-action-btn[data-action="expand"] { - width: 24px; - height: 24px; - min-width: 24px; - padding: 0; - border: 1px solid transparent; - border-radius: 999px; - justify-content: center; - transition: background 0.15s, color 0.15s, opacity 0.15s; -} -.compare-pane .pane-action-btn[data-action="expand"]:hover, -.compare-pane .pane-action-btn[data-action="expand"]:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); - opacity: 1; - outline: none; -} - - -/* ============================================ */ -/* RESEARCH DETAILS EXPANDABLE SECTION - UPDATED */ -/* ============================================ */ - -/* Style the details element */ -details { - background: color-mix(in srgb, var(--hl-string) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--hl-string) 30%, transparent); - border-radius: 8px; - margin: 12px 0; - padding: 0; - overflow: hidden; - transition: all 0.3s ease; -} - -details[open] { - background: color-mix(in srgb, var(--hl-string) 8%, transparent); -} -details[open] > :not(summary) { - animation: detail-reveal 0.25s ease-out both; -} - -/* Style the summary (clickable header) - NO CURSIVE, NORMAL SIZE */ -summary { - cursor: pointer; - padding: 10px 14px; - background: color-mix(in srgb, var(--hl-string) 10%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--hl-string) 20%, transparent); - font-weight: 500; - font-size: 0.95em; - font-style: normal; - font-family: inherit; - color: var(--hl-string); - user-select: none; - list-style: none; - display: flex; - align-items: center; - gap: 8px; - transition: background 0.2s ease; -} - -summary:hover { - background: color-mix(in srgb, var(--hl-string) 15%, transparent); -} - -/* Add custom arrow */ -summary::before { - content: '▶'; - display: inline-block; - transition: transform 0.3s ease; - font-size: 0.75em; /* Smaller arrow */ -} - -details[open] summary::before { - transform: rotate(90deg); -} - -/* Hide default marker in webkit browsers */ -summary::-webkit-details-marker { - display: none; -} - -/* Style the content inside details - SMALLER, MORE COMPACT */ -details > div, -details > p, -details > ul, -details > ol { - padding: 12px 14px; /* Less padding */ - animation: fadeIn 0.3s ease; - font-size: 0.9em; /* Smaller text */ - line-height: 1.5; -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Style research findings inside details - SMALLER HEADINGS */ -details h3 { - margin-top: 12px; - margin-bottom: 6px; - color: var(--hl-string); - font-size: 0.95em; - font-weight: 500; -} - -details h4 { - margin-top: 10px; - margin-bottom: 5px; - color: var(--hl-string); - font-size: 0.9em; - font-weight: 500; -} - -details ul { - margin-left: 18px; - margin-bottom: 10px; -} - -details li { - margin-bottom: 5px; - line-height: 1.4; - font-size: 0.85em; /* Smaller list items */ -} - -details strong { - color: var(--hl-string); - font-weight: 500; -} - -details a { - color: var(--accent); - text-decoration: none; - transition: color 0.2s ease; -} - -details a:hover { - color: var(--color-link-hover); - text-decoration: underline; -} - -/* Research metadata - SMALLER */ -details .research-meta { - font-size: 0.8em; - color: var(--fg); - opacity: 0.8; - margin-top: 4px; -} - -/* Source links - SMALLER */ -details .source-link { - display: inline-block; - margin-top: 3px; - padding: 2px 5px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); - border-radius: 4px; - font-size: 0.68em; - color: var(--accent); -} - -details .source-link:hover { - background: color-mix(in srgb, var(--accent) 20%, transparent); - border-color: var(--accent); -} - -/* Research report links - clean and subtle */ -details a { - color: var(--accent); - text-decoration: none; - font-weight: normal; /* Remove bold */ - font-size: 0.85em; - opacity: 0.9; - transition: all 0.2s ease; - word-break: break-all; /* Break long URLs nicely */ -} - -details a:hover { - color: var(--color-link-hover); - text-decoration: underline; - opacity: 1; -} - -/* ============================================ */ -/* CUSTOM SYNTAX HIGHLIGHTING - Theme-Reactive */ -/* ============================================ */ -.hljs { - background: var(--code-bg, var(--hl-bg, var(--panel))); - color: var(--code-fg, var(--hl-fg, var(--fg))); - padding: 8px; - border-radius: 4px; -} - -/* Keywords & control flow — purple/magenta */ -.hljs-keyword, -.hljs-selector-tag { color: var(--hl-keyword); } - -/* Strings & regex — warm yellow/orange */ -.hljs-string, -.hljs-regexp, -.hljs-addition { color: var(--hl-string); } - -/* Comments & docs — muted. font-style intentionally omitted: italics shift - glyph widths in the highlight overlay relative to the transparent textarea - above it, which makes the caret drift away from the visible character. */ -.hljs-comment, -.hljs-quote, -.hljs-meta { color: var(--hl-comment); } - -/* Functions & method names — blue */ -.hljs-function, -.hljs-title, -.hljs-title.function_, -.hljs-section { color: var(--hl-function); } - -/* Numbers & constants — distinct from strings */ -.hljs-number, -.hljs-literal { color: var(--hl-number, var(--hl-string)); } - -/* Built-ins & types — teal/cyan tint */ -.hljs-built_in, -.hljs-type, -.hljs-class, -.hljs-title.class_ { color: var(--hl-builtin, var(--hl-function)); } - -/* Variables & identifiers — fg with slight distinction */ -.hljs-variable, -.hljs-template-variable, -.hljs-attr { color: var(--hl-variable, var(--hl-fg, var(--fg))); } - -/* Operators & punctuation — slightly dimmed fg */ -.hljs-operator, -.hljs-punctuation { color: var(--hl-fg, var(--fg)); opacity: 0.8; } - -/* Parameters */ -.hljs-params { color: var(--hl-params, var(--hl-fg, var(--fg))); } - -/* Property access, attributes in HTML/XML */ -.hljs-property, -.hljs-selector-class, -.hljs-selector-id { color: var(--hl-variable, var(--hl-function)); } - -/* Tags (HTML) */ -.hljs-tag { color: var(--hl-keyword); } -.hljs-name { color: var(--hl-keyword); } - -/* Deletion/diff */ -.hljs-deletion { color: var(--red); } - -/* Symbol, special */ -.hljs-symbol { color: var(--hl-string); } -.hljs-link { color: var(--hl-function); text-decoration: underline; } - -/* Emphasis — applied globally (chat messages, rendered markdown in docs - preview, etc.) but explicitly NEUTRALIZED in the doc editor's overlay so - the textarea-and-overlay caret alignment stays bit-perfect. Bold / italic - shift glyph widths and that drifts the click-to-row mapping. */ -.hljs-emphasis { font-style: italic; } -.hljs-strong { font-weight: bold; } -.doc-editor-highlight .hljs-emphasis, -.doc-editor-highlight .hljs-strong { - font-style: normal !important; - font-weight: inherit !important; -} diff --git a/static/css/style-part-04.css b/static/css/style-part-04.css deleted file mode 100644 index ce6a9add4..000000000 --- a/static/css/style-part-04.css +++ /dev/null @@ -1,6011 +0,0 @@ -/* Fragment 5/9 of the former static/style.css. - * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. - * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. - */ -.admin-ep-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} - -/* Status messages */ -.admin-error { color: var(--color-error); font-size: 11px; margin-top: 4px; } -.admin-success { color: var(--color-success); font-size: 11px; margin-top: 4px; } -.admin-empty { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 12px; - padding: 10px 0; - text-align: center; -} -/* Endpoint-list empty states ("No endpoints configured" / "None") get a - left-aligned, accent-colored treatment so they read as "this row is your - placeholder, click Add" rather than a centered "nothing here" grey blob. */ -#adm-epList-local .admin-empty, -#adm-epList-api .admin-empty { - text-align: left; - padding: 8px 4px; - color: var(--accent-primary, var(--accent, var(--red))); -} - -/* RAG upload zone */ -.admin-rag-upload-zone { - border: 2px dashed var(--border); - border-radius: 8px; - padding: 14px; - text-align: center; - cursor: pointer; - transition: border-color 0.2s, background 0.2s; - margin-bottom: 8px; -} -.admin-rag-upload-zone:hover, -.admin-rag-upload-zone.dragover { - border-color: var(--red); - background: color-mix(in srgb, var(--red) 7%, transparent); -} -.admin-rag-upload-zone p { - color: color-mix(in srgb, var(--fg) 50%, transparent); - font-size: 11px; - margin-top: 4px; -} -.admin-rag-icon { font-size: 18px; } -.admin-rag-dir-row { - display: flex; - gap: 6px; - margin-bottom: 8px; -} -.admin-rag-dir-row input { - flex: 1; - padding: 5px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-rag-dir-row input:focus { outline: none; border-color: var(--red); } -.admin-rag-dir-row button { - padding: 5px 8px; - border: none; - border-radius: 6px; - background: var(--red); - color: #fff; - cursor: pointer; - font-size: 12px; - white-space: nowrap; -} -.admin-rag-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: 6px; -} -.admin-rag-section-label { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin: 8px 0 4px; -} -.admin-rag-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 8px; - background: var(--bg); - border-radius: 6px; - margin-bottom: 4px; - font-size: 12px; -} -.admin-rag-item-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--fg); -} -.admin-rag-item-meta { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 10px; - margin: 0 6px; - flex-shrink: 0; -} -.admin-rag-status { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-top: 6px; -} - -/* Token reveal */ -.admin-token-reveal { - margin-top: 8px; - padding: 8px; - background: color-mix(in srgb, var(--red) 8%, var(--bg)); - border: 1px solid color-mix(in srgb, var(--red) 25%, var(--border)); - border-radius: 8px; -} - -/* Selects in admin cards */ -.admin-card select { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-card input { - font-family: inherit; - font-size: 12px; -} -.admin-card label { - font-size: 12px; -} - -/* ---- Document Library ---- */ - -.doclib-modal-content { - width: min(600px, 92vw); - max-height: 85vh; - font-size: 12px; - background: var(--bg); -} -/* Anchor doclib modal to top so only the bottom edge moves on resize */ -#doclib-modal { - align-items: flex-start; - padding-top: 8vh; -} -.doclib-modal-content .modal-header h4 { - font-size: 1rem; -} -/* The "open in new tab" email modal inherited the 1rem doclib header, which - read way bigger than the email subject in the regular (inline) reader. Pin - it smaller so the two views are consistent. */ -.email-reader-tab-modal .modal-header h4 { - font-size: 13px; - font-weight: 600; -} -/* Match the sticky modal-header to the doclib modal-content body color - (var(--bg)) instead of the global var(--panel) default — otherwise the - header reads as a darker stripe above the email list. */ -.doclib-modal-content > .modal-header { - background: var(--bg); -} -.doclib-stats { - font-size: 11px; - color: var(--fg-muted); - margin-bottom: 8px; -} -.doclib-toolbar { - display: flex; - gap: 8px; - margin-bottom: 8px; - align-items: center; -} -.doclib-search { - flex: 1; - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - outline: none; - transition: border-color 0.15s; -} -#archive-select-btn { height: auto; padding: 5px 8px; } -.doclib-search:focus { - border-color: var(--red); -} -.doclib-sort { - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - outline: none; - cursor: pointer; -} -.doclib-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 8px; -} -.doclib-chip { - padding: 2px 10px; - border-radius: 12px; - font-size: 10px; - border: 1px solid var(--border); - background: transparent; - color: var(--fg-muted); - cursor: pointer; - user-select: none; - transition: background 0.15s, border-color 0.15s; -} -.doclib-chip:hover { - border-color: var(--red); -} -.doclib-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Document library — language chip row */ -.doclib-lang-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - padding: 2px 0; - max-height: 52px; - overflow-y: auto; -} -.doclib-lang-chips:empty { display: none; } -/* Mobile: keep tag chips on ONE row and scroll horizontally instead of wrapping - into a tall multi-line block (Serve, library — anywhere .doclib-lang-chips is used). */ -@media (max-width: 768px) { - .doclib-lang-chips { - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - max-height: none; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .doclib-lang-chips::-webkit-scrollbar { display: none; } - .doclib-lang-chips > * { flex-shrink: 0; } -} -#doclib-tidy-btn, #doclib-select-btn, #doclib-chats-tidy-btn { - position: relative; - top: -3px; -} -/* Document library — list layout */ -.doclib-grid { - display: flex; - flex-direction: column; - gap: 4px; - max-height: 400px; - overflow-y: auto; - padding: 0; - position: relative; -} - -/* Gallery grid domino-in cascade on open — same recipe as the document/email - libraries. Applied to #gallery-grid in gallery.js on the first render after - each open, removed after the longest delay so re-renders feel instant. */ -.gallery-just-opened > .gallery-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.gallery-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.gallery-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.gallery-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.gallery-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.gallery-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.gallery-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.gallery-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.gallery-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.gallery-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.gallery-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.gallery-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.gallery-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.gallery-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.gallery-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.gallery-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.gallery-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.gallery-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.gallery-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.gallery-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.gallery-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.gallery-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Tasks list domino-in cascade on open — mirrors gallery / doclib. Applied to - #tasks-list in tasks.js on the first render after each open, removed after - the longest delay so re-renders feel instant. */ -.tasks-just-opened > .task-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.tasks-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.tasks-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.tasks-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.tasks-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.tasks-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.tasks-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.tasks-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.tasks-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.tasks-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.tasks-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.tasks-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.tasks-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.tasks-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.tasks-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.tasks-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.tasks-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.tasks-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.tasks-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.tasks-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.tasks-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.tasks-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Document library cascade — same recipe as email library, applied per-tab - the first time content loads (chats / archive / research / documents). - Module-level Set in documentLibrary.js prevents re-firing on tab swaps - or filter/sort re-renders within the same page session. */ -.doclib-just-opened > .memory-item, -.doclib-just-opened > .doclib-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.doclib-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.doclib-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.doclib-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.doclib-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.doclib-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.doclib-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.doclib-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.doclib-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.doclib-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.doclib-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.doclib-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.doclib-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.doclib-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.doclib-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.doclib-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.doclib-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.doclib-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.doclib-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.doclib-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.doclib-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.doclib-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Domino cascade on first open of the email library — mirrors the sidebar - .section-just-expanded recipe so list items spring in staggered. Class - is applied to #email-lib-grid in emailLibrary.js on the first render - only, then removed after the longest delay so re-renders feel instant. */ -.email-lib-just-opened :is(.doclib-card, .date-section-header) { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(1) { animation-delay: 0.02s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(2) { animation-delay: 0.04s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(3) { animation-delay: 0.06s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(4) { animation-delay: 0.08s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(5) { animation-delay: 0.10s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(6) { animation-delay: 0.12s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(7) { animation-delay: 0.14s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(8) { animation-delay: 0.16s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(9) { animation-delay: 0.18s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(10) { animation-delay: 0.20s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(11) { animation-delay: 0.22s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(12) { animation-delay: 0.24s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(13) { animation-delay: 0.26s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(14) { animation-delay: 0.28s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(15) { animation-delay: 0.30s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(16) { animation-delay: 0.32s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(17) { animation-delay: 0.34s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(18) { animation-delay: 0.36s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(19) { animation-delay: 0.38s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(20) { animation-delay: 0.40s; } -/* Cap the cascade at 20 — beyond that they animate together so opening a - long inbox doesn't take forever to settle. */ -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(n+21) { animation-delay: 0.42s; } - -/* Inside the email library modal, the grid needs to grow with the modal — - but `flex: 1 1 0` collapses to 0 when the parent isn't height-constrained - (which is the non-fullscreen default). Use `auto` basis + a sensible - `min-height` floor so it shows ~400px naturally and absorbs more when - the modal is resized / fullscreened. */ -#email-lib-modal .doclib-grid { - max-height: none; - height: auto; - flex: 1 1 auto; - min-height: 400px; -} - -/* ── Mobile compose FAB (email) ── - Replaces the top-right "New" button on phones with a round mail-icon button - bottom-right that collapses to a circle while the list scrolls and springs - back out to "New" when scrolling stops. Desktop is unchanged. */ -.email-lib-fab { display: none; } -@media (max-width: 768px) { - /* The absolute FAB anchors to the email panel card. */ - #email-lib-modal .admin-card { position: relative; } - /* Hide the toolbar "New" button — the FAB is the mobile entry point. */ - #email-lib-compose-btn { display: none; } - - #email-lib-modal .email-lib-fab { - --fab-size: 56px; - display: flex; - align-items: center; - gap: 9px; - /* Hidden until the email list has rendered; JS adds .fab-revealed to pop - it in (scale-from-center). Avoids the button flashing at the top and - sliding down before _positionFab() places it. */ - transform: scale(0); - opacity: 0; - transform-origin: center center; - position: absolute; - right: calc(16px + env(safe-area-inset-right, 0px)); - bottom: calc(18px + env(safe-area-inset-bottom, 0px)); - height: var(--fab-size); - min-width: var(--fab-size); - padding: 0 20px 0 18px; - border: none; - border-radius: 999px; - background: var(--accent-primary, var(--red)); - color: #fff; - font-family: inherit; - font-size: 15px; - font-weight: 600; - line-height: 1; - cursor: pointer; - box-shadow: 0 6px 18px rgba(0,0,0,.38), 0 2px 6px rgba(0,0,0,.28); - z-index: 30; - pointer-events: auto; - /* This (base) transition governs the EXPAND — slower + graceful. */ - transition: - padding 420ms cubic-bezier(0.22, 1, 0.36, 1), - box-shadow 240ms ease, - transform 140ms ease, - background 140ms ease; - will-change: padding, transform; - } - #email-lib-modal .email-lib-fab svg { flex: 0 0 auto; display: block; } - #email-lib-modal .email-lib-fab .email-lib-fab-label { - overflow: hidden; - white-space: nowrap; - max-width: 120px; - opacity: 1; - /* EXPAND timing — label eases out a touch after the width opens up. */ - transition: - max-width 420ms cubic-bezier(0.22, 1, 0.36, 1), - opacity 300ms 60ms cubic-bezier(0.22, 1, 0.36, 1), - margin 420ms cubic-bezier(0.22, 1, 0.36, 1); - } - /* Collapsed (while scrolling) → pure circle, icon only. We DON'T set an - explicit width: the container is auto-width and shrinks smoothly as the - padding + label max-width animate (min-width keeps it circular). Setting a - fixed width here made the collapse snap, since auto↔px width can't tween. - This (.collapsed) transition governs the COLLAPSE — kept snappy. */ - #email-lib-modal .email-lib-fab.collapsed { - padding: 0; - justify-content: center; - transition: - padding 230ms cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 200ms ease, - transform 140ms ease; - } - #email-lib-modal .email-lib-fab.collapsed .email-lib-fab-label { - max-width: 0; - opacity: 0; - margin-left: -9px; - transition: - max-width 230ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), - margin 230ms cubic-bezier(0.4, 0, 0.2, 1); - } - /* Entrance: circle-expand-from-middle pop, played once when revealed. No - `forwards` fill so the resting transform (scale 1) comes from this rule — - that keeps the :active press transform working afterwards. */ - #email-lib-modal .email-lib-fab.fab-revealed { - transform: scale(1); - opacity: 1; - animation: emailFabPop 380ms cubic-bezier(0.34, 1.56, 0.64, 1); - } - #email-lib-modal .email-lib-fab:active { - transform: scale(0.93); - filter: brightness(0.92); - box-shadow: 0 3px 10px rgba(0,0,0,.4); - } -} -@keyframes emailFabPop { - from { transform: scale(0); opacity: 0; } - 60% { transform: scale(1.06); opacity: 1; } - to { transform: scale(1); opacity: 1; } -} -@media (prefers-reduced-motion: reduce) { - #email-lib-modal .email-lib-fab, - #email-lib-modal .email-lib-fab .email-lib-fab-label { transition-duration: 1ms; } - #email-lib-modal .email-lib-fab.fab-revealed { animation: none; } -} -/* When the modal-content has explicit height (fullscreen or user-resized), - drop the floor so the grid sizes purely from flex. */ -#email-lib-modal.email-lib-fullscreen .doclib-grid, -#email-lib-modal .modal-content[style*="height"] .doclib-grid { - min-height: 0; -} -/* Document library: same fix as the email library (#74). When fullscreened - the modal-content gets an inline `height: 100vh`, but the inner - .doclib-grid stays capped at the 400px default so the list looks like a - tiny strip floating in a giant empty modal. Mirror the email recipe: - make the modal a flex column, give the body/admin-card claim of the - remaining height, and let the grid take the rest. Scoped to the - fullscreen class so windowed-mode layout is unchanged. */ -/* Inner flex chain that lets the chats/documents grid claim the full - remaining height of the modal. Applies in BOTH fullscreen AND - right-docked states — without the docked state included, dragging a - fullscreen doclib to the right snap zone breaks the flex layout - because exitFullscreen removes .doclib-fullscreen, and the grid - falls back to its base max-height: 400px showing only ~8 items. - The CSS variable :is() selector lets both states share one rule. */ -#doclib-modal.doclib-fullscreen .doclib-modal-content, -#doclib-modal.modal-right-docked .doclib-modal-content, -#doclib-modal.modal-left-docked .doclib-modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -/* Fullscreen positioning — scoped to NOT apply when also right-docked. - Without this exclusion, dragging a fullscreen doclib to the right snap - zone keeps .doclib-fullscreen on the modal and these !important rules - override the dock's inline styles, leaving the modal stuck fullscreen - instead of becoming a side panel. */ -#doclib-modal.doclib-fullscreen:not(.modal-right-docked) .doclib-modal-content { - position: fixed !important; - top: 0 !important; - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; - right: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; -} -#doclib-modal.doclib-fullscreen .modal-header, -#doclib-modal.doclib-fullscreen .lib-tabs, -#doclib-modal.modal-right-docked .modal-header, -#doclib-modal.modal-right-docked .lib-tabs, -#doclib-modal.modal-left-docked .modal-header, -#doclib-modal.modal-left-docked .lib-tabs { - flex: 0 0 auto; -} -#doclib-modal.doclib-fullscreen .modal-body, -#doclib-modal.modal-right-docked .modal-body, -#doclib-modal.modal-left-docked .modal-body { - flex: 1 1 0; - min-height: 0; - overflow: hidden; - display: flex; - flex-direction: column; -} -#doclib-modal.doclib-fullscreen .admin-card, -#doclib-modal.modal-right-docked .admin-card, -#doclib-modal.modal-left-docked .admin-card { - flex: 1 1 0; - min-height: 0; - display: flex; - flex-direction: column; -} -#doclib-modal.doclib-fullscreen .doclib-grid:not(:has(.doclib-card-expanded)), -#doclib-modal.modal-right-docked .doclib-grid:not(:has(.doclib-card-expanded)), -#doclib-modal.modal-left-docked .doclib-grid:not(:has(.doclib-card-expanded)) { - max-height: none; - height: auto; - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* "Reply" docks the open email modal to the left as a sidebar so the doc - panel (which slides in from the right of the chat area) is visible - side-by-side. The .modal backdrop is already pointer-events:none for - email modals — only the content takes pointer events — so the chat / - doc panel underneath stays interactive. */ -.modal.email-snap-left { - align-items: stretch; - justify-content: flex-start; - left: 0 !important; - width: 100% !important; -} -.modal.email-snap-left .modal-content { - left: var(--email-doc-split-left-x, 0px) !important; - width: var(--email-doc-split-email-w, 420px) !important; - max-width: var(--email-doc-split-email-w, 420px) !important; - border-right: 1px solid var(--border); - box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; -} -@media (min-width: 769px) { - body.email-doc-split-active.doc-view #email-lib-modal, - body.email-doc-split-active.doc-view.email-front #email-lib-modal, - body.email-doc-split-active.doc-view .modal[id^="email-reader-"], - body.email-doc-split-active.doc-view.email-front .modal[id^="email-reader-"] { - z-index: 150 !important; - } - body.email-doc-split-active #email-lib-modal.email-snap-left, - body.email-doc-split-active #email-lib-modal.modal-left-docked { - left: var(--email-doc-split-left-x, 0px) !important; - width: var(--email-doc-split-email-w, 420px) !important; - overflow: hidden !important; - z-index: 155 !important; - } - body.email-doc-split-active #email-lib-modal.email-snap-left:not(.modal-dragging) .modal-content, - body.email-doc-split-active #email-lib-modal.modal-left-docked:not(.modal-dragging) .modal-content, - body.email-doc-split-active .modal[id^="email-reader-"].email-snap-left:not(.modal-dragging) .modal-content, - body.email-doc-split-active .modal[id^="email-reader-"].modal-left-docked:not(.modal-dragging) .modal-content { - position: absolute !important; - left: 0 !important; - top: 0 !important; - bottom: 0 !important; - right: auto !important; - width: var(--email-doc-split-email-w, 420px) !important; - max-width: var(--email-doc-split-email-w, 420px) !important; - height: 100vh !important; - max-height: 100vh !important; - transform: none !important; - margin: 0 !important; - } - body.email-doc-split-active.doc-view .doc-divider { - display: none !important; - } - body.email-doc-split-active.doc-view .doc-editor-pane { - position: fixed !important; - left: var(--email-doc-split-right-x, 420px) !important; - right: var(--right-dock-w, 0px) !important; - top: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - z-index: 260 !important; - margin-top: 0 !important; - transform: none !important; - border-left: none !important; - } -} - -/* Edge docking. Docked panels are fixed to the viewport edge; the workspace - panes reserve room with margins so left + right docks can be active at the - same time without skewing the entire body box. */ -body.right-dock-active { - padding-right: 0; -} -body.left-dock-active { - padding-left: 0; -} -body.left-dock-active:not(.email-doc-split-active) .chat-container { - margin-left: var(--left-dock-w, 0px); -} -body.right-dock-active .chat-container, -body.right-dock-active:not(.email-doc-split-active) .doc-editor-pane { - margin-right: var(--right-dock-w, 0px); -} -.modal.modal-right-docked { - align-items: stretch; - justify-content: flex-end; -} -.modal.modal-right-docked .modal-content { - border-left: 1px solid var(--border); - box-shadow: -4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; -} -.modal.modal-left-docked { - align-items: stretch; - justify-content: flex-start; -} -.modal.modal-left-docked .modal-content { - border-right: 1px solid var(--border); - box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; - /* Pin transitions OFF on the dock's positioning properties. The wide - sidebar collapse/expand changes --sidebar-w instantly, which means - the docked modal's `left: calc(...)` jumps by ~240px. Any CSS - transition on `left` (inherited or otherwise) would animate that - jump as a fly-across. Same defense for right-docked / fullscreen - panels in case future themes add a generic transition. */ - transition: none !important; -} -.modal.modal-right-docked .modal-content, -#email-lib-modal.email-lib-fullscreen .modal-content, -#doclib-modal.doclib-fullscreen .doclib-modal-content { - transition: none !important; -} -.modal.modal-right-docked .email-reader-header, -.modal.modal-left-docked .email-reader-header { - flex-direction: column; - /* Stretch the meta to the full header width so the absolute - action cluster's right:0 lands at the right edge (was sizing - meta to chip content width and centering it). */ - align-items: stretch; - gap: 6px; -} -/* Docked panes: 4px between From→To and 4px between To→Cc. - Meta has its own 2px gap (From→details) so an extra 2px on - details margin-top gives a 4px From→To distance, and gap:4 on - the details flex column gives 4px between To and Cc. */ -.modal.modal-right-docked .email-reader-meta-details, -.modal.modal-left-docked .email-reader-meta-details { - margin-top: 2px; - gap: 4px; -} -/* Docked: always show To/Cc — there's vertical room reserved for - the action cluster already and the chevron toggle just adds - friction. Force the hidden attribute open and hide the chevron. */ -.modal.modal-right-docked .email-reader-meta-details[hidden], -.modal.modal-left-docked .email-reader-meta-details[hidden] { - display: flex !important; -} -.modal.modal-right-docked .email-reader-meta-toggle, -.modal.modal-left-docked .email-reader-meta-toggle { - display: none; -} -.modal.modal-right-docked .email-reader-header > .email-reader-actions, -.modal.modal-left-docked .email-reader-header > .email-reader-actions { - position: static; - align-self: flex-end; - background: none; - padding-left: 0; -} -/* Docked modal: keep all meta rows as flex so From / To / Cc - labels align at the same x. Chips already overflow-scroll - inside each row when there are too many. No per-row margin-top - here — the spacing between rows comes entirely from - .email-reader-meta-details gap so From→To and To→Cc match. */ -.modal.modal-right-docked .email-reader-meta-row:not(.email-reader-meta-from), -.modal.modal-left-docked .email-reader-meta-row:not(.email-reader-meta-from) { - padding-right: 0; -} -.modal.modal-right-docked .email-reader-meta-row strong, -.modal.modal-left-docked .email-reader-meta-row strong { - min-width: 0; -} -.modal.modal-right-docked .recipient-chip, -.modal.modal-left-docked .recipient-chip { - max-width: 100%; -} -.archive-list { - margin-top: 8px; - border-top: 1px solid var(--border); - padding-top: 8px; -} -#archive-modal .memory-sort-select, -#archive-modal .memory-toolbar-btn { - height: 32px; -} -.archive-menu-btn { - position: relative; - top: 2px; -} -#archive-chips .doclib-chip { - height: 22px; - display: inline-flex; - align-items: center; - font-size: 10px; - padding: 0 8px; -} -/* Library tab panels — Chats / Archive / Research / Documents share the - same toolbar dimensions so the sort dropdown + Select button line up - identically across tabs. */ -#doclib-panel-chats .memory-sort-select, -#doclib-panel-archive .memory-sort-select, -#doclib-panel-research .memory-sort-select, -[data-doclib-panel="documents"] .memory-sort-select { - height: 24px; - font-size: 11px; - width: 86px; -} -#doclib-panel-chats .memory-toolbar-btn, -#doclib-panel-archive .memory-toolbar-btn, -#doclib-panel-research .memory-toolbar-btn, -[data-doclib-panel="documents"] .memory-toolbar-btn { - height: 24px; - font-size: 11px; - position: relative; - top: -3px; -} -/* Research's Recent (sort) + Select sat 1px lower than the other tabs — nudge up. */ -#doclib-panel-research .memory-sort-select { position: relative; top: 1.5px; } -#doclib-panel-research .memory-toolbar-btn { top: -4.5px; } -.library-sort-picker { - width: 108px; - flex: 0 0 108px; - top: 1px; -} -.library-sort-picker .library-sort-btn { - top: 0; - width: 100%; -} -.library-sort-picker .library-sort-menu { - top: calc(100% + 4px); - width: max(100%, 154px); -} -.library-sort-picker .library-sort-item[aria-selected="true"] { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); -} -#doclib-panel-chats #doclib-chats-tidy-btn, -#doclib-panel-chats #doclib-chats-select-btn, -#doclib-panel-archive #doclib-arc-select-btn { - top: 1px; -} -#doclib-panel-chats .doclib-chip-scroll-arrow { - top: calc(50% - 9px) !important; -} -[data-doclib-panel="documents"] .doclib-chip-scroll-arrow { - top: calc(50% - 9px) !important; -} -#doclib-modal .doclib-chip-scroll-arrow { - top: calc(50% - 19px) !important; -} -[data-doclib-panel="documents"] #doclib-tidy-btn, -[data-doclib-panel="documents"] #doclib-select-btn { - top: 1px; -} -#doclib-panel-research #doclib-research-tidy-btn, -#doclib-panel-research #doclib-research-select-btn { - top: -0.5px; -} -#doclib-panel-research .library-sort-picker { top: -0.5px; } -[data-doclib-panel] { padding-top: 6px !important; } -#doclib-panel-chats, #doclib-panel-archive { padding-top: 14px !important; } -#doclib-panel-chats .memory-desc, #doclib-panel-archive .memory-desc, #doclib-panel-research .memory-desc { margin-top: 7px; } -#doclib-panel-chats .memory-search-input, -#doclib-panel-archive .memory-search-input { - height: 30px !important; - min-height: 30px !important; - flex-shrink: 0; - font-size: 11px; -} -/* Unified search bar across Library tabs + Memory modal — same height, - same magnifying-glass icon at the start, consistent padding. */ -#doclib-panel-chats .memory-search-input, -#doclib-panel-archive .memory-search-input, -#doclib-panel-research .memory-search-input, -[data-doclib-panel="documents"] .memory-search-input, -#memory-modal .memory-search-input, -#tasks-modal .memory-search-input { - height: 30px; - min-height: 30px; - font-size: 11px; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: 9px center; - padding-left: 28px; -} -#doclib-panel-chats .doclib-chip { - height: 28px; -} -#doclib-panel-archive .doclib-chip { - height: 28px; -} -#doclib-panel-chats .doclib-chip, -#doclib-panel-archive .doclib-chip { - display: inline-flex; - align-items: center; - font-size: 9px; - padding: 0 8px; - border-radius: 10px; -} -.doclib-grid:has(.doclib-card-expanded) > .doclib-card:not(.doclib-card-expanded) { - display: none; -} -/* Hide everything except the grid when a card is expanded */ -.admin-card:has(.doclib-card-expanded) > *:not(.doclib-grid):not(.hwfit-cached-list) { - opacity: 0; - max-height: 0; - overflow: hidden; - pointer-events: none; - margin: 0 !important; - padding: 0 !important; - transition: opacity 0.12s ease, max-height 0.2s ease; -} -.admin-card:has(.doclib-card-expanded) > .memory-bulk-bar { - display: none; -} -.admin-card:has(.doclib-card-expanded) > .doclib-grid, -.admin-card:has(.doclib-card-expanded) > .hwfit-cached-list { - max-height: none !important; - min-height: 0 !important; - overflow: visible !important; -} -/* Firefox-mobile (no :has()) fallback: when reading an email, fully remove the - list-mode chrome (accounts row, toolbar, bulk bar, desc) — display:none so no - residual flex-gap leaves an empty strip above the reader — and zero the - admin-card gap so the grid/reader starts flush at the top. */ -#email-lib-modal.email-reading .admin-card > *:not(.doclib-grid):not(.hwfit-cached-list) { - display: none !important; -} -#email-lib-modal .admin-card:has(.doclib-card-expanded), -#email-lib-modal.email-reading .admin-card { - gap: 0 !important; - padding: 0 !important; - margin-bottom: 0 !important; - border: 0 !important; - border-radius: 0 !important; - background: transparent !important; -} -/* Override for chat-rows + research-rows: those expand inline with a - bounded preview (max-height: 60vh), so the grid must stay clipped - and scrollable. Without this scoping the overflow:visible above lets - the expanded chat preview escape the grid and overlap whatever's - underneath/beside the modal (e.g., a docked sibling panel) — what - the user calls "merging with other windows". */ -.admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - overflow: hidden auto !important; -} -/* Email modal needs the grid to STAY a constrained flex container when - expanded — overflow:visible (set above for cookbook) lets content escape - instead of letting the expanded reader fill via flex:1. We keep the same - max-height:none + min-height:0 but flip overflow back so the reader - actually fills the modal. */ -#email-lib-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid, -#email-lib-modal.email-reading .admin-card > .doclib-grid { - overflow: hidden !important; - display: flex !important; - flex: 1 1 auto !important; -} -#email-lib-modal.email-reading #email-lib-grid > .date-section-header { - display: none !important; -} -#email-lib-modal.email-reading .doclib-modal-content { - min-height: var(--email-reading-modal-min-h, auto); -} -#email-lib-modal .doclib-card.doclib-card-expanded { - flex: 1 1 auto !important; - height: 100% !important; - /* Neutral frame on the active email — the accent border + glow felt - overbearing on desktop; the size jump alone is enough signal. */ - border: 1px solid var(--border) !important; - box-shadow: 0 6px 18px rgba(0,0,0,0.12) !important; - animation: none !important; -} -/* Desktop-only, ONLY on the currently-expanded email card. Nudges the title - row down 6px / right 2px, bolds the subject, hides the timestamp from the - meta line (the reader header carries its own date), and pulls the prev/next - nav arrows in 4px. */ -@media (min-width: 769px) { - #email-lib-modal .doclib-card-expanded .email-card-titlerow, - #email-lib-modal .doclib-card-expanded .email-card-titlerow > * { - position: relative; - top: 6px; - left: 2px; - } - /* Smaller subject — the reader header below already carries weight. */ - #email-lib-modal .doclib-card-expanded .email-card-titlerow .memory-item-title { - font-weight: 700; - font-size: 12px; - } - /* A bit more breathing room around the title row so the smaller bold text - doesn't crowd the meta line below. (Timestamp kept — user changed mind.) */ - #email-lib-modal .doclib-card-expanded .email-card-titlerow { - padding: 4px 0 6px; - line-height: 1.35; - } - /* Nudge the date down/left in the meta row, and give the meta line +4px of - extra bottom space so the shifted date isn't clipped. */ - #email-lib-modal .doclib-card-expanded .email-meta-date { - position: relative; - top: 4px; - left: 0; - } - #email-lib-modal .doclib-card-expanded .memory-item-meta { - padding-bottom: 4px; - } - #email-lib-modal .doclib-card-expanded .email-card-nav-arrows { - position: relative; - left: -4px; - } -} -/* Skills modal: same mechanism as email above. The default expanded-grid - rule (overflow:visible, no flex) lets the card expand inline to ~content - height instead of filling — which is why the skill preview stopped at - partial height. Flip the grid back to a constrained flex container and - let the expanded card fill it, exactly like the email reader. */ -#memory-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid { - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - flex: 1 1 auto !important; -} -#memory-modal .doclib-card.doclib-card-expanded { - flex: 1 1 auto !important; - height: 100% !important; -} -#email-lib-modal .modal-content { - transition: width 0.22s ease-out, height 0.22s ease-out; -} -@media (min-width: 769px) { - body:not(.email-doc-split-active) #email-lib-modal:not(.email-lib-fullscreen):not(.modal-left-docked):not(.modal-right-docked) .modal-content:not([style*="height"]) { - min-height: min(560px, 85vh); - } -} - -/* Serve panel specifically: the admin-card inline style is - `overflow:hidden` (so the toolbar/header don't drift), and the list - inside has overflow:visible. On short windows that combination - clipped the cards off the bottom with no scrollbar. Make the list - itself the scroll surface so the rest of the card stays put. */ -.cookbook-group[data-backend-group="Serve"] > .admin-card { - min-height: 0; -} -.cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list, -.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list { - flex: 1 1 0; - min-height: 0; - overflow-y: auto !important; - overscroll-behavior: contain; -} -.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list .doclib-card.doclib-card-expanded { - flex: 0 0 auto !important; - overflow: visible !important; -} -/* Launch model cards use the same domino entrance as the other library lists - when a cached-model scan finishes. The class is removed after the one-shot - entrance, so filtering and sorting stay instantaneous. */ -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(1) { animation-delay: 0.04s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(2) { animation-delay: 0.08s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(3) { animation-delay: 0.12s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(4) { animation-delay: 0.16s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(5) { animation-delay: 0.20s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(6) { animation-delay: 0.24s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(7) { animation-delay: 0.28s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(8) { animation-delay: 0.32s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(9) { animation-delay: 0.36s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(10) { animation-delay: 0.40s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(11) { animation-delay: 0.44s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(12) { animation-delay: 0.48s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(n+13) { animation-delay: 0.48s; } -@media (prefers-reduced-motion: reduce) { - #cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { - animation: none; - } -} -/* Drag-and-drop visual hint for the email compose pane. Subtle accent - outline + tinted overlay so it's obvious files will attach if dropped. */ -.doc-editor-pane.email-dragover { - outline: 2px dashed var(--accent, #2563eb); - outline-offset: -8px; - background: color-mix(in srgb, var(--accent, #2563eb) 6%, transparent); -} -.doc-editor-pane.email-dragover::after { - content: 'Drop to attach'; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: var(--accent, #2563eb); - color: #fff; - padding: 8px 16px; - border-radius: 6px; - font-size: 12px; - pointer-events: none; - z-index: 1000; -} - -/* Email reader window — make the body always fill the available vertical - space regardless of how the window is positioned/resized. The user can - drag the bottom-right corner to resize; the body re-flexes automatically. */ -.email-window-modal .modal-content { - display: flex !important; - flex-direction: column !important; - resize: both; - overflow: hidden; - min-width: 320px; - min-height: 240px; - /* When user resizes/drags so the window covers a side, force the inner - body to fill all the way to the bottom. The default max-height: 85vh - (set inline by _makeDraggable's exitFullscreen) and 80vh from the - opener both cap at the *viewport* — if the browser window then changes - size (e.g. OS aero-snap), the modal stays at the old height and - truncates rows. Drop the cap whenever an explicit width OR height has - been written inline by the resize handle / drag handler. */ -} -.email-window-modal .modal-content[style*="width"], -.email-window-modal .modal-content[style*="height"] { - max-height: calc(100vh - 16px) !important; -} -.email-window-modal .email-window-body { - flex: 1 1 auto !important; - min-height: 0 !important; - /* Explicit basis of 0 makes the body grow to fill remaining space rather - than its content's intrinsic height (which was capping the thread). */ - flex-basis: 0 !important; - max-height: none !important; - overflow: auto !important; - overscroll-behavior: contain; -} - -/* Individual email reader window — fullscreen mode (drag-to-top snap or - double-click header). Same recipe as the library modal: pin to viewport - edges and let the inner body absorb the remaining height. */ -body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .modal-content { - position: fixed !important; - inset: 0 !important; - left: 0 !important; - top: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; - display: flex !important; - flex-direction: column !important; -} -body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .email-window-body { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; -} - -/* Email library modal: fullscreen mode + larger mobile sheet (mirrors the - cookbook treatment so the toolbar and bulk actions stay reachable). - Leaves whichever navigation strip is currently visible on the left - (the wide sidebar OR the icon rail — they're mutually exclusive, so - one of the two CSS vars is 0 at any given time, set by init.js). */ -body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { - position: fixed !important; - top: 0 !important; - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; - right: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; -} -/* Mobile: the /email route adds .email-lib-fullscreen, but the desktop full- - bleed rule above squares off the corners and offsets the email by the icon - rail. On phones the email should be the normal bottom-sheet — full width, - rounded top corners. Same specificity as the rule above + later in source, - so it wins on mobile. */ -@media (max-width: 768px) { - body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { - left: 0 !important; - right: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - height: 100dvh !important; - max-height: 100dvh !important; - border-radius: 14px 14px 0 0 !important; - border-top: 1px solid var(--border) !important; - } -} -/* Make the inner panes actually grow to fill the fullscreen container - instead of staying at their natural size. The body owns the remaining - height below the header; the admin-card + grid then expand into it. */ -#email-lib-modal.email-lib-fullscreen .modal-body { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; -} -#email-lib-modal.email-lib-fullscreen .modal-body > .admin-card { - flex: 1 1 auto !important; - min-height: 0 !important; -} -#email-lib-modal.email-lib-fullscreen .doclib-grid { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow: auto !important; -} -@media (max-width: 768px) { - /* Mobile email modal sizing — keep in sync with the rule earlier in the - file. 75dvh tall always so flex children (the expanded email reader) - have a real height to grow into. */ - #email-lib-modal .modal-content { - max-height: 90dvh !important; - max-height: 90vh !important; - height: 90dvh !important; - height: 90vh !important; - } - /* Inner panes grow to fill the modal-content — without flex:1 on the - body, the expanded email reader sits in a tiny box because there's - nothing pushing it to take the remaining height. overflow:hidden + - min-height:0 lets each layer pass its constraints down. */ - #email-lib-modal .modal-body, - #email-lib-modal .admin-card { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; - max-height: none !important; - } - #email-lib-modal .doclib-grid { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow-y: auto !important; - } - /* modal-content keeps its scroll OFF here — the inner flex children - (doclib-grid for the list view, email-reader-body for the expanded - reader) own the scroll surfaces. Without this, double-scroll layouts - trap touches and the reader can't claim full height. */ - #email-lib-modal .modal-content { - overflow: hidden !important; - -webkit-overflow-scrolling: touch; - overscroll-behavior: contain; - touch-action: pan-y; - } - /* Attachment row on phones: cap to ~2 rows so it never dominates the - reader. If more chips exist, the row scrolls vertically. Smaller chip - padding + max-width keeps each chip compact so two rows usually fit - everything anyway. */ - #email-lib-modal .email-reader-atts { - padding: 4px 10px !important; - gap: 3px !important; - max-height: calc(2 * 22px + 12px); /* 2 rows × chip height + padding */ - overflow-y: auto; - align-content: flex-start; - } -#email-lib-modal .email-attachment-chip { - padding: 1px 6px !important; - font-size: 10px !important; - max-width: min(240px, calc(100vw - 44px)) !important; - line-height: 18px !important; - } -} - - -/* Mobile: only ONE scroll surface inside the cookbook modal. The - modal-content is the scroller. Everything inside (cookbook-body, - cookbook-group, all the inner lists/panels) gets overflow: visible - so touch-pan never gets trapped in a nested scroller. - Without this, three levels of overflow:auto + max-height combinations - produce dead-zone areas where swipes do nothing. */ -@media (max-width: 768px) { - #cookbook-modal .modal-content { - overflow-y: auto !important; - -webkit-overflow-scrolling: touch; - overscroll-behavior: contain; - touch-action: pan-y; - } - #cookbook-modal .cookbook-body, - #cookbook-modal .cookbook-group, - #cookbook-modal .cookbook-section-body, - #cookbook-modal .hwfit-cached-list, - #cookbook-modal .doclib-grid, - #cookbook-modal .hwfit-list, - #cookbook-modal .hwfit-panel-cmd { - overflow: visible !important; - max-height: none !important; - min-height: 0 !important; - } - /* Running tmux output: cap how tall an expanded card gets (a long-lived job - can leave thousands of lines) so it doesn't balloon, but keep its own - scroll so you can read it. Outputs default collapsed on mobile now, so - you're usually looking at one expanded card at a time — no wall of nested - scrollers, and you collapse it to move past. */ - #cookbook-modal .cookbook-output-pre, - #cookbook-modal .cookbook-task .cookbook-output-pre { - max-height: 45vh !important; - min-height: 0 !important; - overflow-y: auto !important; - overflow-x: hidden !important; - overscroll-behavior: auto; /* chain to the modal at the scroll boundary */ - } - /* cookbook-body's flex:1 was needed when it owned scrolling — drop it - so the inner content drives modal-content's scroll height. */ - #cookbook-modal .cookbook-body { - flex: 0 0 auto !important; - height: auto !important; - } - /* Launch tab's cached-list normally has `flex: 1 1 0; min-height: 0` - (so it fills the modal on desktop). On mobile the parent now has - `height: auto`, which collapses `flex: 1 1 0` to ZERO PX — - models render but the list area is invisible because the flex - basis is 0 and there's no free space to grow into. Switch to - content-sized flex so the list grows with its children. */ - #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list, - #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list { - flex: 0 0 auto !important; - overflow: visible !important; - } -} -#cookbook-modal .hwfit-cached-list { - flex-shrink: 0; -} -.memory-toolbar { - transition: opacity 0.12s ease, max-height 0.2s ease; - max-height: 120px; -} -.doclib-card { - background: color-mix(in srgb, var(--fg) 3%, transparent); - border: 1px solid var(--border); - border-radius: 8px; - cursor: pointer; - transition: all 0.15s, opacity 0.3s, max-height 0.3s ease; - position: relative; -} -.doclib-card.doclib-card-deleting { - opacity: 0; - max-height: 0 !important; - overflow: hidden; - margin: 0; - padding: 0; - border-color: transparent; -} -.doclib-card.selected, -/* Universal selection highlight: any card-shaped row (documents, chats, - archive, research, skills, memories, tasks) gets the same accent outline - when its checkbox is checked. Done via :has() so the highlight follows - selection without per-renderer JS changes. The canonical checkbox class - is .memory-select-cb across every list. */ -.memory-item:has(.memory-select-cb:checked), -.doclib-card:has(.memory-select-cb:checked), -.doclib-chat-row:has(.memory-select-cb:checked) { - border-color: color-mix(in srgb, var(--red) 40%, transparent); - background: color-mix(in srgb, var(--red) 4%, transparent); - /* 2px accent outline overlaid on the 1px border — reads as a thicker - selected border without shifting layout (every card would otherwise - need a 2px transparent border to keep the same width). */ - outline: 2px solid var(--accent-primary, var(--red)); - outline-offset: -1px; -} -.doclib-card-header { - display: flex; - align-items: center; - padding: 7px 8px; - gap: 6px; - min-width: 0; -} -/* Mobile only: push the unexpanded email card's title-row text down 4px so - it sits visually centered with the surrounding card padding. Targets the - actual title-row class (.email-card-titlerow) — the earlier attempt used - .doclib-card-header which the email card builds differently. */ -@media (max-width: 768px) { - #email-lib-modal .doclib-card:not(.doclib-card-expanded) .email-card-titlerow { - margin-top: 4px; - } -} -.doclib-card-title { - font-size: 11px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; - flex: 1; -} -.doclib-card-ver { - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - color: var(--accent, var(--red)); - font-size: 9px; - flex-shrink: 0; - font-weight: 600; - letter-spacing: 0.3px; - min-width: 24px; - text-align: left; - box-sizing: border-box; -} -.doclib-card-ver-muted { - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.doclib-card-lang { - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - color: var(--accent); - font-size: 9px; - text-align: left; - flex-shrink: 0; - min-width: 52px; - box-sizing: border-box; -} -.doclib-card-session { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 60px; - width: 100px; - max-width: 100px; - font-size: 9px; - color: var(--fg-muted); - flex-shrink: 0; - text-align: left; -} -.doclib-card-time { - flex-shrink: 0; - opacity: 0.5; - font-size: 9px; - color: var(--fg-muted); - min-width: 50px; - text-align: left; -} -/* Footer — used by archive cards */ -.doclib-card-footer { - display: contents; -} -/* Preview — hidden by default, shown on expand */ -.doclib-card-preview { - display: none; - position: relative; - padding: 8px 12px; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.5; - color: var(--code-fg, var(--hl-fg, var(--fg))); - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin: 0; -} -.doclib-card-preview pre { - margin: 0; - white-space: pre-wrap; - word-break: break-all; -} -.doclib-card-expanded .doclib-card-preview pre { - flex: 1 1 0; - overflow-y: auto; - min-height: 0; -} -.doclib-card-preview code.hljs { - background: none; - padding: 0; -} -/* Expanded-only action bar — inside preview. Buttons shifted up 4px by - trimming the row's top margin so they sit closer to the preview text. */ -.doclib-card-expanded-actions { - display: none; - align-items: flex-start; - gap: 6px; - padding: 8px 0 2px; - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 4px; -} -.doclib-card-expanded-actions > .doclib-card-action-btn, -.doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - position: relative; - top: -4px; -} -.doclib-action-group { - display: flex; - flex-direction: column; - gap: 3px; -} -.doclib-action-btn-row { - display: flex; - gap: 6px; -} -.doclib-action-hint-row { - display: flex; - gap: 6px; -} -/* Match the chat/research footer buttons exactly: flat, bordered, - app font, accent on hover. */ -.doclib-card-action-btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - box-sizing: border-box; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - cursor: pointer; - /* These buttons live inside .doclib-card-preview, which forces a - monospace font. The chat/research footer buttons instead inherit the - modal font, which is the app font (Fira Code) on mobile and Inter on - desktop (.modal-content's Inter rule is inside @media min-width:769px). - Mirror that here so all three footers match in both contexts. */ - font-family: var(--font-family, 'Fira Code', monospace); - transition: border-color 0.15s, color 0.15s; -} -@media (min-width: 769px) { - .doclib-card-action-btn { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - letter-spacing: -0.015em; - } -} -.doclib-card-action-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -.doclib-card-text-btn-danger { - color: var(--color-danger, #e06c75); - border-color: var(--color-danger, #e06c75); -} -.doclib-card-text-btn-danger:hover { - color: #ff4d4d; - border-color: #ff4d4d; -} -.doclib-card-expanded-actions > .doclib-card-action-btn { - font-size: 10px; - padding: 3px 8px; -} -/* Push the Open/Clone group to the right edge of the action bar — Delete - anchors the left, primary actions sit opposite. */ -.doclib-card-expanded-actions > .doclib-action-group { - margin-left: auto; -} -.doclib-card-expanded-actions > .doclib-expanded-delete-btn, -.doclib-card-expanded-actions > .doclib-expanded-archive-btn { - left: 0 !important; -} -.doclib-expanded-mobile-more { display: none; } -.doclib-btn-hint { - font-weight: normal; - opacity: 0.35; - font-size: 8px; - min-width: 58px; - padding: 0 8px; - box-sizing: border-box; -} -@media (max-width: 768px) { - .doclib-btn-hint { display: none !important; } -} - -/* Chat row expand preview — matches the documents-tab expand style. */ -.doclib-card-chevron { - display: inline-flex; - align-items: center; - justify-content: center; - align-self: center; - flex-shrink: 0; - width: 14px; - height: 14px; - line-height: 0; - opacity: 0.3; - transform: rotate(0deg); - transition: transform 0.2s ease, opacity 0.15s; -} -.doclib-card-chevron svg { display: block; } -.doclib-card-expanded .doclib-card-chevron, -.task-card.expanded .doclib-card-chevron { - opacity: 0.8; - transform: rotate(180deg); -} -.doclib-card > .memory-item-actions { order: 1; } -.doclib-card > .doclib-card-chevron { order: 2; } -.doclib-chat-header .memory-item-actions, -.task-card .memory-item-actions { order: 2; } -.doclib-chat-header .doclib-card-chevron, -.task-card .doclib-card-chevron { order: 3; } -.hwfit-cached-list > .doclib-card > .memory-item-actions { order: 2; } -.hwfit-cached-list > .doclib-card > .doclib-card-chevron { order: 3; } -#email-lib-modal .doclib-card > .memory-item-actions { order: 1; align-self: center; } -#email-lib-modal .doclib-card > .doclib-card-chevron { display: none; } -.doclib-chat-row { flex-direction: column; align-items: stretch; } -.doclib-chat-row .doclib-card-chevron { - display: inline-flex; - opacity: 0.55; - transition: transform 0.2s ease, opacity 0.15s; - flex-shrink: 0; -} -.doclib-chat-row:not(.doclib-card-expanded) .doclib-card-chevron { - transform: rotate(0deg); -} -.doclib-chat-row.doclib-card-expanded .doclib-card-chevron { - opacity: 0.8; - transform: rotate(180deg); -} -/* Release .memory-item's base max-height: 200px when a chat row is - expanded. Without this, the row clips at 200px on desktop and the - preview's messages/actions visually spill past the row's box — - "colliding" with the next chat in the column. The mobile takeover - below already handles this via the same override; this rule is the - desktop-or-any-viewport equivalent. */ -.doclib-chat-row.doclib-card-expanded { - max-height: none; - overflow: visible; -} -/* When a chat row is expanded, take over the grid the same way documents - do: hide siblings, hide the admin-card toolbar, and let this row plus - its preview claim the full available height. .memory-item's default - max-height: 200px must be overridden or the preview will be tiny. */ -/* Mobile-only "card takeover" for an expanded chat — hides siblings, - hides the admin-card toolbar, and lets this row + its preview claim - the full available height. On desktop the expanded preview just - renders inline below the row with its messages constrained by - max-height + overflow:auto so the user gets a normal scroll. */ -@media (max-width: 820px) { - .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - flex: 1 1 auto !important; - max-height: none !important; - overflow-y: auto !important; - } - .doclib-chat-row.doclib-card-expanded { - flex: 1 1 auto !important; - max-height: none !important; - min-height: 0 !important; - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - align-items: stretch !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-header { - flex: 0 0 auto !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - padding-bottom: 4px !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { - -webkit-overflow-scrolling: touch; - max-height: none !important; - flex: 1 1 auto !important; - min-height: 0 !important; - } -} -.doclib-chat-preview { - font-size: 11px; - padding: 0 4px 6px; -} -.doclib-chat-preview .doclib-chat-open-btn { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - color: var(--fg-muted); - border: 1px solid var(--border); - background: none; - cursor: pointer; - font-family: inherit; - transition: border-color 0.15s, color 0.15s; -} -.doclib-chat-preview .task-completed-report-btn { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); -} -.doclib-chat-preview .doclib-chat-open-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -.doclib-chat-preview-messages { - margin-top: 6px; - max-height: 320px; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - padding-right: 4px; -} -/* Desktop expanded state — match the documents-tab behavior: hide - sibling chats, hide the admin-card toolbar/header, and let the - expanded row + its messages preview claim the full modal height. - Chat rows use `memory-item doclib-chat-row` (no `doclib-card` class) - so the document-tab's global rules at line 12284-12304 skip them — - these mirror those rules scoped to chat rows. The mobile media query - above (820px) keeps the same takeover behavior for phones. */ -@media (min-width: 821px) { - .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - flex: 1 1 auto !important; - max-height: none !important; - overflow: hidden auto !important; - } - .doclib-chat-row.doclib-card-expanded { - flex: 1 1 auto !important; - max-height: none !important; - min-height: 0 !important; - display: flex !important; - flex-direction: column !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { - flex: 1 1 auto !important; - min-height: 0 !important; - display: flex !important; - flex-direction: column !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow-y: auto !important; - } -} -.doclib-chat-preview-actions { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 8px; - padding: 2px 0 2px; - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 2px; - flex-shrink: 0; - /* Whole action footer nudged down 5px. */ - position: relative; - top: 5px; -} -.doclib-chat-delete-btn, -.doclib-chat-archive-btn, -.doclib-chat-restore-btn, -.doclib-chat-discuss-btn, -.doclib-chat-copy-btn { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - background: none; - cursor: pointer; - font-family: inherit; - transition: border-color 0.15s, color 0.15s; -} -.doclib-chat-archive-btn, -.doclib-chat-restore-btn, -.doclib-chat-discuss-btn, -.doclib-chat-copy-btn { - color: var(--fg-muted); - border: 1px solid var(--border); -} -.doclib-chat-restore-btn:hover { - color: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -.doclib-chat-archive-btn:hover, -.doclib-chat-discuss-btn:hover, -.doclib-chat-copy-btn:hover { - color: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -.doclib-chat-delete-btn { - color: var(--color-danger, #e06c75); - border: 1px solid var(--color-danger, #e06c75); -} -.doclib-chat-delete-btn:hover { - color: #ff4d4d; - border-color: #ff4d4d; -} -/* When a chat is archived there's no Archive button, so Delete becomes - the only left-side action and needs the auto margin to push Open right. */ -.doclib-chat-preview-actions:not(:has(.doclib-chat-archive-btn)) > .doclib-chat-delete-btn { - margin-right: auto; -} -/* Hide the "..." menu while the chat card is expanded — archive + - delete live in the preview footer instead. */ -.doclib-chat-row.doclib-card-expanded ._chat-menu { display: none; } -.doclib-chat-preview-actions.doclib-chat-preview-actions-top { - border-top: none; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 4px; - margin-bottom: 6px; - padding: 0 0 6px; - justify-content: flex-start; -} -.doclib-chat-preview-messages .doclib-chat-msg { - margin: 4px 0 10px; - padding-left: 8px; - border-left: 2px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.doclib-chat-preview-messages .doclib-chat-msg.user { - border-left-color: color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); -} -.doclib-chat-preview-messages .doclib-chat-msg-role { - font-size: 9px; - font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - opacity: 0.55; - margin-bottom: 2px; -} -.doclib-chat-preview-messages .doclib-chat-msg.user .doclib-chat-msg-role { - color: var(--accent, var(--red)); - opacity: 0.85; -} -.doclib-chat-preview-messages .doclib-chat-msg-body { - white-space: pre-wrap; - word-break: break-word; - opacity: 0.85; - line-height: 1.45; -} -/* Chat-bubble preview — mirrors the real chat layout. User bubbles hug - right with an accent tint; assistant bubbles hug left with a neutral - panel tint and a small model tag at the top. */ -.doclib-chat-bubble-row { - display: flex; - margin: 6px 0; -} -.doclib-chat-bubble-row.user { justify-content: flex-end; } -.doclib-chat-bubble-row.assistant { justify-content: flex-start; } -.doclib-chat-bubble { - max-width: 85%; - padding: 6px 10px 8px; - border-radius: 14px; - font-size: 11px; - line-height: 1.45; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.doclib-chat-bubble-row.user .doclib-chat-bubble { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); - border-bottom-right-radius: 4px; -} -.doclib-chat-bubble-row.assistant .doclib-chat-bubble { - border-bottom-left-radius: 4px; -} -.doclib-chat-msg-model { - display: inline-block; - font-size: 8px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; - opacity: 0.55; - margin-bottom: 3px; - padding: 1px 6px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg-muted, var(--fg)); -} -.doclib-chat-bubble-body { word-break: break-word; } -.doclib-chat-bubble-body p { margin: 4px 0; } -.doclib-chat-bubble-body p:first-child { margin-top: 0; } -.doclib-chat-bubble-body p:last-child { margin-bottom: 0; } -.doclib-chat-bubble-body pre { - font-size: 10px; - margin: 4px 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--bg) 60%, transparent); - border-radius: 6px; - overflow-x: auto; -} -.doclib-chat-bubble-body code { - font-size: 10px; - padding: 1px 4px; - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.doclib-chat-bubble-body pre code { background: none; padding: 0; } -.doclib-chat-bubble-body ul, -.doclib-chat-bubble-body ol { - margin: 4px 0; - padding-left: 18px; -} -.doclib-card-collapse-chevron { - display: none; - align-items: center; - justify-content: center; - width: 24px; - height: 22px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: transparent; - color: var(--color-muted, var(--fg)); - font: inherit; - opacity: 0.3; - cursor: pointer; - flex-shrink: 0; - margin: 0 0 0 4px; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.doclib-card-collapse-chevron:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); -} -.cookbook-serve-title { - display: flex; - align-items: center; - min-width: 0; -} -.cookbook-serve-title .doclib-card-collapse-chevron { - display: inline-flex; - margin-left: auto; - margin-top: 0; -} -.doclib-card-expanded .doclib-card-collapse-chevron { - display: inline-flex; -} -/* Expanded card — fills the whole grid */ -.doclib-card.doclib-card-expanded { - flex: 1; - min-height: 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); - border: 1px solid var(--border); - border-radius: 8px; - display: flex; - flex-direction: column; - overflow: hidden; - animation: doclib-expand 0.2s ease-out; -} -.doclib-card.doclib-card-expanded { - flex-direction: column !important; - align-items: stretch !important; - max-height: none !important; - gap: 4px !important; -} -.doclib-card.doclib-card-expanded > .memory-item-actions { display: none; } -/* Non-preview children of an expanded doc card carry an inline flex:1 from - the row layout. With the card now flex-column those children would steal - half the height and shrink the preview — pin them at auto height. */ -.doclib-card.doclib-card-expanded > div:not(.doclib-card-preview):not(.doclib-card-header):not(.memory-item-actions):not(.email-card-reader) { - flex: 0 0 auto !important; -} -/* The email reader IS the scroll container — keep its flex:1 so its - internal body can claim the rest of the card's height and scroll. */ -.doclib-card.doclib-card-expanded > .email-card-reader { - flex: 1 1 auto !important; - min-height: 0 !important; -} -.doclib-card.doclib-card-expanded .doclib-card-preview { - display: flex; - flex-direction: column; - flex: 1 1 auto !important; - /* The preview matches chat rows: document content scrolls, while the - footer stays in the card rather than becoming the last scroll item. */ - overflow: hidden; - min-height: 0; -} -.doclib-card.doclib-card-expanded .doclib-card-expanded-actions { - display: flex; - flex-shrink: 0; -} -/* Document cards can be expanded in the normal windowed library, where the - grid has no definite height. A zero-basis then collapses to nothing - while the footer remains visible. Keep the document text's intrinsic height - in that layout; bounded/fullscreen layouts can still shrink it and scroll. */ -#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview > pre { - flex: 1 1 auto; - min-height: 1.5em; -} - -/* Collapsible skills section headers (Your skills / Built-in). */ -.skills-section-header { - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - user-select: none; - padding: 6px 4px 4px; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.06em; - opacity: 0.6; - font-weight: 600; -} -.skills-section-header:hover { opacity: 0.9; } -.skills-section-chevron { transition: transform 0.15s ease; flex-shrink: 0; } -.skills-section-header.collapsed .skills-section-chevron { transform: rotate(-90deg); } -.skills-section-count { - margin-left: auto; - opacity: 0.6; - font-variant-numeric: tabular-nums; - font-weight: normal; -} -.skill-card-section-hidden { display: none !important; } -/* Warning banner shown when previewing/editing a built-in capability. */ -.skill-builtin-warn { - font-size: 9.5px; - line-height: 1.45; - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 35%, transparent); - border-radius: 6px; - padding: 6px 9px; - margin: 0 0 8px; - flex-shrink: 0; -} - -/* #skills-list wears both .memory-list and .doclib-grid. doclib-grid's - max-height:400px would cap the list AND clamp an expanded card — keep - the memory-list fill-the-modal behaviour instead. */ -#skills-list.doclib-grid { - max-height: none; - flex: 1; - min-height: 0; -} -#skills-list.doclib-grid:has(.doclib-card-expanded) { - overflow: hidden; /* expanded card owns its own scroll */ -} -/* When a skill is expanded, hide the sibling Add-Skill form card so the - expanded SKILL.md uses the full panel. Otherwise the two admin-cards - split the height ~50/50 and the expanded skill is stuck in its half - while the Add form idles in the other. */ -.memory-tab-panel[data-memory-panel="skills"]:has(.doclib-card-expanded) > .admin-card:nth-of-type(2) { - display: none !important; -} - -/* Skills cards reuse the doclib expand/footer machinery. The SKILL.md - preview + the edit textarea need to fill the expanded card and own - their own scroll, same as a document preview. */ -/* Collapsed skill rows used a smaller (0.9em code) title, so the click row - read slimmer than document/chat/library cards. Give the header a min-height - so the tap target matches the other library items. */ -.skill-card > .skill-card-header { - min-height: 53px; - box-sizing: border-box; - display: flex; - align-items: center; - gap: 6px; -} -/* Skill rows share Tasks' card hover colors. Draft state belongs in the - status badge, not in a second opacity layer across the whole row. */ -/* Name + description column. Name wraps to 2 lines (skill slugs are long — - use the space rather than truncating to "check-model-downl…"). */ -.skill-card-textcol { - flex: 1 1 auto; - min-width: 0; - overflow: hidden; -} -.skill-card-title-row { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.skill-card-name { - font-family: inherit; - font-weight: 500; - font-size: 12px; - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - word-break: normal; - line-height: normal; -} -.skill-card-title-row .skill-card-name { min-width: 0; } -.skill-status-published { - width: 20px; - height: 20px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 15%, transparent); - border-color: color-mix(in srgb, var(--color-success, #4ade80) 32%, transparent); - border-radius: 50%; -} -.skill-stats .skill-status-published { - width: 15px; - height: 15px; - margin-left: 3px; - vertical-align: middle; -} -.skill-stats .skill-status-published svg { - width: 9px; - height: 9px; -} -.skill-card-desc { - font-size: 10px; - opacity: 0.4; - margin-top: -1px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.skills-audit-model-field { - display: flex; - align-items: center; - gap: 8px; - margin-top: 10px; - font-size: 11px; -} -.skills-audit-model-field > span { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 68%, transparent); -} -.skills-audit-model-field select { - min-width: 0; - flex: 1; - height: 26px; - padding: 0 6px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.skill-card-tags { - display: flex; - gap: 4px; - flex-wrap: wrap; - margin-top: 4px; -} -.skill-tag-pill { - padding: 1px 5px; - font-size: 9px; -} -.skill-usage-pill { - margin-left: auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.skill-tag-filter-chip { cursor: pointer; } - -@media (max-width: 768px) { - .skill-card > .skill-card-header { - align-items: flex-start; - } - .skill-card-textcol { - min-width: 0; - } - .skill-card-tags { - max-width: 100%; - overflow: hidden; - } - .skill-card-tags .skill-tag-pill { - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -} -/* Right cluster — pills (right-aligned), stats, then the menu/chevron. */ -.skill-card-right { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; - margin-left: auto; -} -.skill-card-right .memory-cat-badge { flex-shrink: 0; } -.skill-status-bin { - background: color-mix(in srgb, var(--color-danger, #d9534f) 20%, transparent); - color: var(--color-danger, #d9534f); -} -.skill-model-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-model-student { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.skill-audit-pass { - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 16%, transparent); -} -.skill-audit-needs_work { - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 16%, transparent); -} -.skill-audit-fail { - color: var(--color-danger, #e06c75); - background: color-mix(in srgb, var(--color-danger, #e06c75) 16%, transparent); -} -.skill-audit-inconclusive, -.skill-audit-skipped, -.skill-audit-unknown { - color: color-mix(in srgb, var(--fg) 64%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-model-teacher { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 24%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-necessity-duplicate { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 22%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-trivial { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 62%, transparent); - border-color: color-mix(in srgb, var(--fg) 16%, transparent); -} -.skill-necessity-irrelevant { - background: color-mix(in srgb, var(--color-danger, #e06c75) 18%, transparent); - color: var(--color-danger, #e06c75); -} -.skills-summary-strip { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - margin: -3px 0 8px; -} -.skills-summary-strip::-webkit-scrollbar { display: none; } -.skills-summary-strip:empty { - display: none; -} -.skills-summary-chip { - display: inline-flex; - align-items: center; - gap: 5px; - height: 23px; - padding: 0 8px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 10px; - line-height: 1; - cursor: pointer; - flex: 0 0 auto; -} -.skills-summary-chip:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.skills-summary-chip.active { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - color: var(--fg); -} -.skills-summary-chip strong { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10px; - color: var(--fg); -} - -/* Shared filter/tag strip contract. These rows all sit immediately below - their screen's search or primary toolbar and stay one line at every width. */ -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -) { - align-items: center; - flex-wrap: nowrap !important; - gap: 6px; - width: 100%; - max-width: 100%; - min-width: 0; - max-height: none; - overflow-x: auto !important; - overflow-y: hidden !important; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - box-sizing: border-box; - padding: 0; - margin: -3px 0 8px; -} -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -)::-webkit-scrollbar { display: none; } -:is( - .skills-summary-strip > .skills-summary-chip, - #memory-category-filters > .skills-summary-chip, - .memory-category-filters > .memory-cat-chip, - .doclib-lang-chips > :is(.memory-cat-chip, .skills-summary-chip), - .doclib-chips > .doclib-chip, - .notes-labels-bar > .notes-label-chip, - .tasks-activity-filters > .memory-cat-chip, - .gallery-tag-chips > .gallery-chip, - .gallery-album-chips > .gallery-chip, - .gallery-ai-tags > .gallery-ai-chip, - .cal-filters > .cal-filter-item -) { - flex: 0 0 auto; - box-sizing: border-box; - height: 23px !important; - min-height: 23px; - padding: 0 8px !important; - border-radius: 6px !important; - font-size: 10px !important; - line-height: 1; - white-space: nowrap; -} -.doclib-lang-chips { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.doclib-chip-scroll-frame { - position: relative; - display: flex; - align-items: center; - flex: 1 1 100%; - min-width: 0; - max-width: 100%; -} -.doclib-chip-scroll-frame > .chip-scroll-strip { - flex: 1 1 auto; - min-width: 0; - margin-left: 0; - margin-right: 0; -} -.doclib-chip-scroll-arrow { - display: none; - position: absolute; - top: calc(50% - 1px) !important; - transform: translateY(-50%); - margin-top: 0; - z-index: 2; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - box-sizing: border-box; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 18px; - cursor: pointer; - opacity: 0.84; - transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease; -} -.doclib-chip-scroll-arrow-glyph { - display: inline-block; - position: relative; - top: -1px; - line-height: 1; -} -.doclib-chip-scroll-arrow:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); -} -@media (max-width: 768px) { - .doclib-chip-scroll-arrow { display: none !important; } -} -.doclib-chip-scroll-frame.has-overflow > .doclib-chip-scroll-arrow:not([hidden]) { - display: inline-flex; -} -.doclib-chip-scroll-arrow.left { left: 0; } -.doclib-chip-scroll-arrow.right { right: 0; } -.doclib-chip-scroll-frame.has-overflow > .chip-scroll-strip { - padding-left: 0 !important; - padding-right: 22px !important; -} -.doclib-chip-scroll-frame > .chip-scroll-strip.is-pointer-dragging { - cursor: grabbing; - user-select: none; -} -.doclib-chip-scroll-frame > .chip-scroll-strip:not(.is-pointer-dragging) { - cursor: grab; -} -/* Calendar tags are wrapped by chipScroll.js after render. Keep that wrapper - content-sized inside the calendar's column layout; the default flex growth - otherwise turns the one-line tag strip into a large empty panel. */ -#cal-body > .doclib-chip-scroll-frame:has(> .cal-filters) { - flex: 0 0 auto; - min-height: 0; -} -/* The completed-task filters are a compact toolbar, not a flexible panel. - Without this override the shared chip frame grows into the available - height of the card, which is especially visible the first time the mobile - view switches to Completed. */ -.tasks-completed-card > .doclib-chip-scroll-frame:has(> #tasks-completed-status-chips) { - flex: 0 0 auto; - min-height: 0; -} -/* The active Tasks filter row is also a compact toolbar. Keep its wrapper - content-sized so changing a filter cannot make the mobile panel grow a - large empty gap between the chips and the task cards. */ -.admin-card > .doclib-chip-scroll-frame:has(> #tasks-filter-chips) { - flex: 0 0 auto; - min-height: 0; -} -#memory-category-filters { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.cookbook-serve-tag-label { - display: inline-flex; - align-items: center; - min-width: 0; -} -#serve-search, -#hwfit-cache-select, -#serve-tags { - position: relative; - top: -2px; -} -#hwfit-cache-select { - top: -4px; -} -/* Calendar previously framed its tags as a separate card. It now follows the - same unframed placement as the other filter strips. */ -.cal-filters { - border: 0; - background: none; - box-shadow: none; -} -.skill-duplicate-keep { - background: color-mix(in srgb, var(--color-success, #4ade80) 22%, transparent); - color: var(--color-success, #4ade80); -} -.skill-duplicate-group { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - color: color-mix(in srgb, var(--fg) 62%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-duplicate-lower { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); - color: var(--color-danger, #e06c75); -} -.skill-stats { - font-size: 9px; - font-family: monospace; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.skill-turns-saved { - font-size: 9px; - opacity: 0.82; - font-variant-numeric: tabular-nums; - white-space: nowrap; - flex: 0 0 auto; -} -.skill-expanded-savings { - display: flex; - align-items: center; - min-height: 18px; - margin: 0 0 5px; -} -.skill-turns-positive { - color: color-mix(in srgb, var(--color-success, #4ade80) 82%, var(--fg)); -} -.skill-turns-zero, -.skill-turns-unknown { - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.skill-turns-negative { - color: color-mix(in srgb, var(--color-danger, #e06c75) 80%, var(--fg)); -} -.skill-conf { - display: inline-block; - min-width: 4ch; - text-align: right; - font-weight: 700; - color: var(--accent, var(--red)); -} -.skill-verified, -.skill-teachermark, -.skill-needsmark { - display: inline-flex; - align-items: center; - vertical-align: middle; - margin-right: 3px; -} -.skill-verified { color: var(--accent, #4ade80); } -.skill-teachermark { color: var(--color-warning, #f0ad4e); } -.skill-needsmark { color: var(--color-danger, #e06c75); cursor: help; } - -/* The card currently being processed by an "Audit now" run glows + pulses so - it's obvious which one the audit is on. */ -@keyframes skill-audit-pulse { - 0%, 100% { box-shadow: 0 0 6px 0 color-mix(in srgb, var(--accent, #4ade80) 55%, transparent); } - 50% { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent, #4ade80) 85%, transparent); } -} -.skill-card.skill-audit-active { - border-color: var(--accent, #4ade80) !important; - animation: skill-audit-pulse 1.3s ease-in-out infinite; -} - -/* A test is running for this skill — the app's whirlpool spinner is injected - next to the name from JS (see _setCardRunning) so the collapsed/folded card - still makes it obvious work is in progress. */ -/* Collapsed bar shows the kebab menu; expanded shows an up-chevron to - collapse. (Toggled by the .doclib-card-expanded class.) */ -.skill-kebab-btn { - display: inline-flex; - align-items: center; - justify-content: center; - background: none; - border: none; - padding: 4px; - margin: 0; - cursor: pointer; - color: var(--fg); - opacity: 0.5; - border-radius: 5px; - flex-shrink: 0; - position: relative; - top: -2px; -} -.skill-kebab-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-chevron-up { display: none; align-items: center; opacity: 0.5; flex-shrink: 0; } -/* Kebab dropdown */ -.skill-kebab-menu { - position: fixed; - /* z-index is set inline via topPortalZ() at open time (#4720); a static - value here loses once the modal bring-to-front counter climbs past it. */ - min-width: auto; - width: max-content; - padding: 4px; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 6px 20px rgba(0,0,0,0.22); - display: flex; - flex-direction: column; - gap: 1px; -} -.skill-kebab-item { - display: flex; - align-items: center; - gap: 10px; - width: 100%; - padding: 6px 8px; - background: none; - border: none; - border-radius: 8px; - cursor: pointer; - color: var(--fg); - font-size: 11px; - text-align: left; -} -.skill-kebab-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-kebab-item.danger { color: var(--color-error, #e55); } -.skill-kebab-item.danger:hover { background: color-mix(in srgb, var(--color-error, #e55) 15%, transparent); } -/* Section labels separating "Your skills" from the read-only "Built-in - capabilities" list. */ -.skills-section-label { - font-size: 10px; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.04em; - margin: 10px 2px 4px; - flex-shrink: 0; -} -.skills-section-label:first-child { margin-top: 0; } -/* When a learned skill is expanded the grid hides sibling cards; hide the - section labels too so they don't orphan above the collapsed list. */ -#skills-list:has(.doclib-card-expanded) .skills-section-label { display: none; } -/* Built-in cards are read-only — no expand/hover-pointer affordance. */ -.skill-builtin-card { cursor: default; } -.skill-builtin-card:hover { border-color: var(--border); } -.skill-card-preview .skill-md-pre { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - white-space: pre-wrap; - word-break: break-word; -} -.skill-preview-copy { - position: absolute; - top: 6px; - right: 8px; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - padding: 0; - opacity: 0; - color: var(--fg); - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - cursor: pointer; - transition: opacity .15s, color .15s, border-color .15s; -} -.skill-card-preview:hover .skill-preview-copy { opacity: 0.7; } -.skill-preview-copy:hover, -.skill-preview-copy:focus-visible { opacity: 1 !important; } -.skill-preview-copy.copied { - opacity: 1; - color: var(--color-save-green, #4caf50); - border-color: var(--color-save-green, #4caf50); -} -/* :has()-free expand layout. skills.js adds .skills-has-expanded to the - skills admin-card when a skill opens, so this works on engines that don't - support :has() (e.g. older Firefox mobile, where the expand stuck at - ~50%). Hide everything but the list so the grid fills the card; the - absolutely-positioned expanded card (mobile) then fills the grid. */ -.admin-card.skills-has-expanded > *:not(#skills-list) { - display: none !important; -} -.admin-card.skills-has-expanded > #skills-list.doclib-grid { - flex: 1 1 auto !important; - height: 100% !important; - min-height: 0 !important; - overflow: hidden !important; -} -/* Expand animation — a clean opacity fade. The mobile card snaps to a - full-screen overlay (position:absolute), so any translate/scale on it - reads as a jarring "explosion"; a pure fade just appears smoothly. (No - transform also keeps skills.js's height measurement accurate.) */ -@keyframes skill-card-expand { - from { opacity: 0; } - to { opacity: 1; } -} -.skill-card.doclib-card-expanded { - animation: skill-card-expand 0.18s ease-out; -} -/* Switching directly from one expanded card to another: no fade (it would - show the previous card collapsing through the semi-transparent new one). */ -.skill-card.doclib-card-expanded.skill-expand-instant { - animation: none !important; -} -@media (prefers-reduced-motion: reduce) { - .skill-card.doclib-card-expanded, - .skill-card.doclib-card-expanded .doclib-card-preview { - animation: none !important; - } -} -/* The preview WRAPPER inherits flex:1 (grow) from the generic doclib expand - rule — that's what stretched the card and left a void / pushed the footer - out. Size it to content (shrink + scroll only when long) so the footer - sits right under the preview and stays fully visible. */ -.skill-card.doclib-card-expanded .doclib-card-preview { - flex: 0 1 auto !important; - min-height: 0; - /* The footer lives INSIDE this wrapper. Don't let the wrapper scroll, or - the footer scrolls off with the content. Keep it clipped; the - inside is the scroller, so the footer stays pinned at the bottom. */ - overflow: hidden !important; -} -.skill-card.doclib-card-expanded .skill-md-pre { - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them - back down 4px so they're vertically centered in the skill card footer. */ -.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn, -.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - top: 0; -} -/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */ -#skills-bulk-bar .memory-toolbar-btn { - position: relative; - top: -2px; -} -/* ── Audit-all progress panel ── */ -.skills-audit-panel { - border: 1px solid var(--border); - border-radius: 8px; - padding: 8px 10px; - margin: 6px 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } -.skills-audit-title { font-size: 12px; font-weight: 600; } -.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; } -.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; } -.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; } -.skills-audit-log { - max-height: 22vh; overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10.5px; line-height: 1.5; opacity: 0.8; -} -/* ── Skill test monitor + AI eval verdict ── */ -.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; } -.skill-test-log { - flex: 1 1 auto; - min-height: 120px; - max-height: 48vh; - overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.45; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - white-space: pre-wrap; - word-break: break-word; -} -.skill-test-log > div { margin: 1px 0; } -.skill-test-meta { opacity: 0.5; } -.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; } -.skill-test-round { opacity: 0.6; margin-top: 6px !important; } -.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; } -.skill-test-out { opacity: 0.7; padding-left: 10px; } -.skill-test-say { white-space: pre-wrap; } -.skill-test-err { color: var(--color-danger, #e06c75); } -.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } -.skill-eval-badge { - font-size: 10px; font-weight: 700; letter-spacing: 0.03em; - padding: 2px 8px; border-radius: 10px; flex-shrink: 0; -} -.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); } -.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); } -.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); } -.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); } -.skill-eval-summary { font-size: 11px; opacity: 0.85; } -.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; } -.skill-eval-issues li { margin: 2px 0; } -.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } -.skill-eval-approve.suggested { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; -} -/* Already published — the button confirms the state (click to unpublish). */ -.skill-eval-approve.is-approved { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; - background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important; -} -/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title, - When to use, How, Tags) is accent-colored while the rest stays muted — real - placeholders can't be partially colored. The native placeholder is a single - space so :placeholder-shown still toggles the overlay. */ -.skill-ph-wrap { position: relative; } -.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; } -.skill-rich-ph { - position: absolute; - left: 11px; right: 11px; top: 50%; - transform: translateY(-50%); - pointer-events: none; - font-size: 12px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 40%, transparent); - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -/* Textarea: align the hint to the first line instead of vertical-centering. */ -.skill-rich-ph-top { top: 7px; transform: none; } -.skill-rich-ph .k { color: var(--accent, var(--red)); } -/* Keep the memory-input enter hint pinned to the field's right edge instead - of letting it drift after the example text. */ -#new-memory-input ~ .skill-rich-ph > svg { - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - margin-left: 0 !important; -} -/* Hide the overlay once the field has real content. */ -.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; } -.skill-md-editor { - flex: 1 1 auto; - min-height: 0; - width: 100%; - box-sizing: border-box; - resize: none; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); -} -.doclib-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -/* Unified loading row across every Library tab (Chats / Documents / Research / - Archive): one opacity, one size, with the whirlpool spinner next to it. */ -.lib-loading-row { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 28px 16px; - font-size: 12px; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.pdf-loading-state { - min-height: 120px; - margin: 0; - box-sizing: border-box; - flex-direction: column; - gap: 10px; - border: 0; - border-radius: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - box-shadow: none; - font-size: 12px; - font-weight: 600; - letter-spacing: 0.01em; -} -.pdf-loading-state .ai-spinner-whirlpool { - color: var(--accent, var(--red)); - opacity: 0.95; -} -.pdf-loading-state .ai-spinner-whirlpool canvas { - display: block; -} -.doclib-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg-muted); - font-size: 11px; - cursor: pointer; - transition: border-color 0.15s, color 0.15s; - flex-shrink: 0; - position: relative; - top: 10px; -} -/* Soft dark gradient above the button — blends the list content into the - load-more strip instead of a hard cutoff. Spans the modal width and - sits behind/above the button. */ -.doclib-load-more::before { - content: ""; - position: absolute; - left: 50%; - transform: translateX(-50%); - bottom: 100%; - margin-bottom: 4px; - width: min(600px, 92vw); - height: 34px; - pointer-events: none; - background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent); -} -.doclib-load-more:hover { - border-color: var(--red); - color: var(--red); -} -.doclib-inline-load-more { - border: 0; - border-radius: 0; - padding: 0; - background: none; - top: 0; - font-size: 9px !important; - line-height: 1.2 !important; -} - -/* Document library toolbar buttons */ -.doclib-toolbar-btn { - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - font-size: 11px; - padding: 5px 10px; - border-radius: 6px; - cursor: pointer; - transition: all 0.15s; - white-space: nowrap; - font-family: inherit; - display: inline-flex; - align-items: center; - gap: 3px; -} -.doclib-toolbar-btn:hover { - color: var(--fg); - border-color: var(--fg); -} -.doclib-toolbar-btn.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -.doclib-toolbar-btn.danger { - color: var(--color-error, #e55); -} -.doclib-toolbar-btn.danger:hover:not(:disabled) { - border-color: var(--color-error, #e55); -} -.doclib-toolbar-btn:disabled { - opacity: 0.4; - cursor: default; -} - -/* Bulk action bar */ -.doclib-bulk-bar { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--red) 5%, transparent); - font-size: 10px; - margin-bottom: 8px; -} -.doclib-bulk-bar.hidden { - display: none; -} -.doclib-bulk-check-all { - display: flex; - align-items: center; - gap: 3px; - cursor: pointer; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-size: 10px; -} -#doclib-bulk-bar .memory-toolbar-btn { - position: relative; - top: -3px; -} - -/* Custom checkboxes */ -.doclib-select-cb, -.doclib-bulk-check-all input { - -webkit-appearance: none; - appearance: none; - width: 13px; - height: 13px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - cursor: pointer; - flex-shrink: 0; - margin: 0; - position: relative; - transition: all 0.15s; -} -.doclib-select-cb:hover, -.doclib-bulk-check-all input:hover { - border-color: var(--red); -} -.doclib-select-cb:checked, -.doclib-bulk-check-all input:checked { - background: var(--red); - border-color: var(--red); -} -.doclib-select-cb:checked::after, -.doclib-bulk-check-all input:checked::after { - content: ''; - position: absolute; - left: 3px; - top: 0px; - width: 4px; - height: 8px; - border: solid var(--bg); - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); -} - - -/* + button outside scroll area (when doc is on right side) */ - -@media (max-width: 600px) { - .doclib-toolbar { - flex-direction: column; - } - .doclib-card-session, - .doclib-card-time { - display: none; - } - .doclib-card-preview { - max-height: 40vh; - } -} - -/* Collapse card tags before they compete with attachment/favorite controls. */ -.email-card-tags .email-tags-more { - color: var(--email-tags-more-color, var(--fg)); - background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent); -} -.email-tags-more-single, -[data-email-tags-count-mini] { display: none; } -.email-tags-more-single svg { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag, -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra { - display: none; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single, -.email-card-tags.email-tags-expanded .email-tags-more-single { - display: inline-flex; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini], -.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; } - -/* Keep the reader actions in drafting order. */ -.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; } -.email-reader-actions-inline > [data-act="reply"] { order: 2; } -.email-reader-actions-inline > [data-act="reply-all"] { order: 3; } -.email-reader-actions-inline > [data-act="forward"] { order: 4; } -.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed { - display: none !important; -} - -/* ── Archive browser ── */ -.archive-list { - grid-template-columns: 1fr !important; - gap: 4px !important; -} -.archive-row { - flex-direction: row !important; -} -.archive-row .doclib-card-header { - flex: 1; - padding: 8px 10px; -} -.archive-row .doclib-card-title { - flex: 1; - min-width: 0; -} -/* Archive column layout */ -.archive-header { - display: flex; - align-items: center; - padding: 4px 10px; - gap: 6px; - font-size: 9px; - font-weight: 600; - opacity: 0.35; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.archive-col-title { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: -4px; -} -.archive-col-msgs { - width: 40px; - flex-shrink: 0; - text-align: left; - margin-left: -8px; -} -.archive-col-model { - width: 90px; - flex-shrink: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.archive-col-time { - width: 55px; - flex-shrink: 0; - text-align: right; -} -.archive-col-menu { - width: 24px; - flex-shrink: 0; -} -.archive-menu-btn { - background: transparent; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - padding: 4px; - line-height: 0; - border-radius: 4px; - transition: background .15s, opacity .15s; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - margin-left: 4px; - vertical-align: middle; - height: 100%; -} -.archive-menu-btn svg { - display: block; - margin-top: -6px; -} -.archive-menu-btn:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - opacity: 1; -} - -/* ── Gallery (image library) ── */ - -.gallery-modal-content { - width: min(820px, 94vw); - max-height: 92vh; - font-size: 12px; -} -/* Keep the Gallery frame anchored while tabs or bulk-selection controls - change the content height. Centering the frame made every shorter tab move - the whole window vertically. */ -@media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal, - #cookbook-modal { - align-items: flex-start; - padding-top: 4vh; - box-sizing: border-box; - } -} -/* While the Edit tab is active the editor needs a *definite* height so - the right-side tool panel (`.ge-right-panel { overflow-y:auto }`) - scrolls INTERNALLY. Without this the modal only has `max-height`, so - `.gallery-editor { height:100% }` can't resolve — the editor grows to - its full content height, overflows the modal body, and the modal-body - scrollbar that appears can't be wheel-scrolled because the editor's - `overscroll-behavior:contain` blocks the chain. Pinning a real height - here makes the panel bounded and scrollable as designed. Scoped to - the editor view (matched via the container's inline `display:flex`) - so the Photos/Albums views keep sizing to their content. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) { - height: 92vh; -} -/* The editor tab owns the modal body's scroll context. Keep the landing - chooser and the mounted editor inside the available sheet height so a long - saved-project list cannot push the launch controls below the viewport. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body { - display: flex; - min-height: 0; - overflow: hidden; -} -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container { - min-height: 0; - overflow: hidden; -} -/* Photo-detail view sizing (issue #314). - The detail view is rendered as a `position:absolute; inset:0` overlay - *inside* `.gallery-images-container`, painted over the photo grid. Because - it's absolutely positioned it can't contribute to the container's height — - the container (and therefore the overlay's `inset:0` box) collapses to the - height of the grid sitting behind it. When the library only has a few - photos that grid is short, so the detail view is crushed: the image is - clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a - tiny, internally-scrolling strip. (With a large library the grid is tall, - which is why it looked fine in the demo video but cramped for users with - few photos.) - Fix: when the detail view is open, hide the grid-view siblings and drop the - overlay into normal flow. The container — and the window, up to its 92vh - max-height — then sizes to the detail's own content (image + metadata), so - nothing is clipped or squeezed regardless of how many photos exist. Scoped - via the detail element's inline `display:flex` so the grid / albums views - keep sizing to their own content. Works on both desktop and the mobile - full-screen sheet. */ -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) { - display: none !important; -} -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail { - position: static; -} -/* Containing block for the photo-detail overlay — keeps it inside the body - so it sits below the modal header and the tab strip instead of covering them. */ -.gallery-images-container { - position: relative; -} -.gallery-modal-content .modal-header h4 { - font-size: 1rem; -} -.gallery-stats { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-bottom: 4px; -} -.gallery-toolbar { - display: flex; - gap: 6px; - margin-bottom: 4px; - align-items: center; -} -.gallery-toolbar-break { display: none; } -/* Keep the hint anchored while independently seating the input in the row. */ -.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; } -.gallery-search { - position: relative; - top: 4px; - flex: 1; - width: 100%; - padding: 4px 8px; - padding-right: 78px; /* room for the enter hint */ - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - font: inherit; - font-size: 12px; - outline: none; -} -.gallery-search-enter-hint { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - font-weight: 600; - color: var(--accent, var(--red)); - pointer-events: none; - opacity: 0; - transition: opacity 0.12s; - white-space: nowrap; -} -/* Show the hint only once the user has typed something (placeholder gone). */ -.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; } -.gallery-search:focus { - border-color: var(--red); -} -.gallery-model-filter, -.gallery-sort { - padding: 4px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - position: relative; - top: 4px; -} -.gallery-selection-bar { - margin-bottom: 4px; - position: relative; - top: 2px; - padding-right: 18px; -} -.gallery-tag-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 4px; -} -.gallery-chip { - height: 28px; - box-sizing: border-box; - padding: 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - border-radius: 14px; - font-size: 12px; - line-height: 1; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: border-color 0.15s, background 0.15s; -} -.gallery-chip:hover { - border-color: var(--red); -} -.gallery-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Album chips row */ -.gallery-album-chips { - display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px; -} -.gallery-chip-fav { color: var(--red); } -.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); } -.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; } -/* Active-album indicator — appears when the Photos grid is filtered to one album. */ -.gallery-chip-active-album, -.gallery-active-filter-pill { - display: inline-flex; align-items: center; gap: 4px; - background: color-mix(in srgb, var(--red) 14%, transparent); - border-color: color-mix(in srgb, var(--red) 45%, transparent); - position: relative; - top: 6px; -} -.gallery-active-filter-pill { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - color: color-mix(in srgb, var(--fg) 86%, transparent); -} -.gallery-clear-all-filters { - height: 28px; - border-style: dashed; - opacity: 0.72; -} -.gallery-clear-all-filters:hover { - opacity: 1; - color: var(--red); -} -.gallery-chip-clear { - background: none; - border: none; - color: inherit; - opacity: 0.6; - cursor: pointer; - padding: 0; - margin-left: 1px; - width: 12px; - height: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 12px; - line-height: 1; - position: relative; - top: -4px; -} -.gallery-chip-clear:hover { opacity: 1; } - - - -/* Favorite button on card */ -/* Video cards: fills the same slot as , plus a small play badge - so it's clear the thumbnail represents a video. */ -.gallery-card video { - width: 100%; - aspect-ratio: 1 / 1; - object-fit: cover; - background: #000; - display: block; -} -.gallery-card-play { - position: absolute; - left: 50%; top: 50%; - transform: translate(-50%, -50%); - width: 36px; height: 36px; - display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.55); - color: #fff; - border-radius: 50%; - pointer-events: none; - z-index: 1; -} -.gallery-card-play svg { margin-left: 2px; } - -.gallery-fav-btn { - position: absolute; top: 0px; right: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; font-size: 14px; - color: rgba(255,255,255,0.6); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s; - padding: 0; -} -.gallery-fav-btn.gallery-fav-active { opacity: 1; color: var(--red); } - -.gallery-dl-btn { - position: absolute; top: 0px; left: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; - color: rgba(255,255,255,0.75); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s, background 0.15s; - padding: 0; -} -.gallery-dl-btn:hover { background: rgba(0,0,0,0.7); color: #fff; } -/* In select mode the per-thumbnail hover buttons (favorite + download) just - get in the way of picking — hide them so the card is a clean select target. - `body.gallery-selecting` is the authoritative signal (one class for the - whole grid) — the per-card .gallery-card-selectable is kept as a backup - in case the body class is missed. */ -body.gallery-selecting .gallery-fav-btn, -body.gallery-selecting .gallery-dl-btn, -.gallery-card-selectable .gallery-fav-btn, -.gallery-card-selectable .gallery-dl-btn { - display: none !important; -} - -/* AI tag chips in detail view */ -.gallery-ai-tags { display: flex; gap: 4px; flex-wrap: wrap; } -.gallery-ai-chip { - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); - border-radius: 10px; padding: 3px 8px; font-size: 10px; - color: var(--fg); opacity: 0.8; - font: inherit; font-size: 10px; - cursor: pointer; - transition: background 0.12s, opacity 0.12s, border-color 0.12s; -} -.gallery-ai-chip:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); -} -/* AI-generated tags: muted neutral with a tiny sparkle marker so the - user can tell at a glance these came from the model, not them. */ -.gallery-aitag-chip { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-aitag-chip:hover { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.gallery-aitag-mark { - font-size: 8px; - opacity: 0.55; - color: var(--accent, var(--red)); -} -/* User-applied tags keep the accent-colored chip — they're the personal/ - curated tags, so they get the strongest visual weight. */ -.gallery-user-chip { - font-weight: 600; -} -/* × to remove a user tag (appears inside the chip). */ -.gallery-tag-x { - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 5px; - width: 13px; - height: 13px; - border-radius: 50%; - font-size: 12px; - line-height: 1; - opacity: 0.55; - transition: opacity 0.12s, background 0.12s, color 0.12s; -} -.gallery-tag-x:hover { - opacity: 1; - background: color-mix(in srgb, var(--red, #ff5555) 22%, transparent); - color: var(--red, #ff5555); -} - -.gallery-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - align-content: start; - gap: 8px; - max-height: calc(92vh - 165px); - overflow-y: auto; - padding: 2px; - transition: border-color 0.15s, background 0.15s; -} -.gallery-grid.gallery-dragover { - border: 2px dashed var(--red); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.gallery-card { - position: relative; - aspect-ratio: 1; - border-radius: 6px; - overflow: hidden; - border: 1px solid var(--border); - cursor: pointer; - transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s; -} -.gallery-card.gallery-card-focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 36%, transparent); -} -/* Upload-affordance tile pinned to the top-left of the Photos grid. Matches - the Upload album tile in the Albums tab — dashed border, centered icon. */ -.gallery-card-upload { - border-style: dashed; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - opacity: 0.75; - transition: opacity 0.12s, border-color 0.15s, transform 0.15s; -} -/* The "Start AI tag" button turns into a Cancel control during a tag run. */ -.gallery-tag-cancelling { - color: var(--red) !important; - border-color: color-mix(in srgb, var(--red) 45%, var(--border)) !important; -} -/* Skeleton/shimmer placeholder tiles shown while the first page of photos loads. */ -.gallery-card-skeleton { - cursor: default; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.gallery-card-skeleton:hover { transform: none; box-shadow: none; border-color: var(--border); } -/* Chat attachment image: shimmer skeleton + centered whirlpool shown while a - sent photo uploads / its thumbnail loads (see chatRenderer buildAttachCards). */ -.attach-image-skeleton { - position: relative; - width: 160px; height: 120px; - border-radius: 6px; - display: flex; align-items: center; justify-content: center; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.attach-image-skeleton > .spinner-whirlpool { margin: 0 !important; } - -/* Corner "Aa" button on a chat photo thumbnail — opens the vision/OCR editor - so the user can correct what the vision model fed to the LLM. */ -.attach-image-preview { position: relative; } -.attach-vision-model, -.attach-image-name { - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.attach-vision-model { - font-size: 10px; - color: var(--accent, var(--red)); - opacity: 0.85; - margin-top: 3px; -} -.attach-image-name { - font-size: 10px; - opacity: 0.5; - margin-top: 1px; -} -.attach-ocr-btn { - position: absolute; top: 4px; right: 4px; - z-index: 5; /* sit above msg-action overlays so the click lands here */ - height: 22px; - background: rgba(0,0,0,0.55); color: #fff; - border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; - padding: 0 8px 0 6px; cursor: pointer; - display: inline-flex; align-items: center; justify-content: center; gap: 5px; - font-size: 11px; font-weight: 500; line-height: 1; - opacity: 0.75; transition: opacity 0.15s; -} -.attach-ocr-btn:hover { opacity: 1; } -.attach-ocr-btn svg { display: block; } -/* On mobile keep just the (larger) edit icon — the "Caption" label crowds - the corner of the small chat photo. */ -@media (max-width: 768px) { - .attach-ocr-btn .attach-ocr-label { display: none; } - .attach-ocr-btn { - width: 28px; height: 28px; - padding: 0; - } - .attach-ocr-btn svg { width: 16px; height: 16px; } -} - -/* Vision/OCR text editor modal — opened from the "Aa" button above. */ -.vision-editor-overlay { - position: fixed; inset: 0; z-index: 9999; - background: rgba(0,0,0,0.55); - display: flex; align-items: center; justify-content: center; - padding: 16px; -} -.vision-editor-panel { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 14px 16px; - width: min(560px, 92vw); - max-height: 88vh; - display: flex; flex-direction: column; gap: 8px; - box-shadow: 0 14px 40px rgba(0,0,0,0.4); -} -.vision-editor-title { - font-size: 13px; font-weight: 600; color: var(--fg); - display: flex; align-items: center; gap: 6px; -} -.vision-editor-desc { font-size: 11px; opacity: 0.6; line-height: 1.4; } -.vision-editor-text { - width: 100%; min-height: 180px; - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 8px 10px; - font-family: inherit; font-size: 12px; line-height: 1.5; - resize: vertical; outline: none; -} -.vision-editor-text:focus { border-color: var(--accent-primary, var(--red)); } -.vision-editor-hint { font-size: 10.5px; opacity: 0.55; line-height: 1.4; } -.vision-editor-actions { - display: flex; gap: 8px; justify-content: flex-end; - margin-top: 4px; -} -.vision-editor-btn { - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; - transition: border-color 0.15s, background 0.15s; - display: inline-flex; align-items: center; gap: 5px; -} -.vision-editor-btn svg { display: block; } -/* Optical nudge: the button text sits 1px higher than the SVG glyphs - alongside it. Drop the text down 1px so they line up. */ -.vision-editor-btn .vision-btn-label { position: relative; top: 1px; } -.vision-editor-btn:hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); } -.vision-editor-btn-primary { - background: var(--accent-primary, var(--red)); - color: #fff; border-color: transparent; -} -.vision-editor-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } - -/* Quick full-size lightbox shown when the user taps a chat photo thumbnail. */ -.attach-lightbox { - position: fixed; inset: 0; z-index: 9998; - background: rgba(0, 0, 0, 0.85); - display: flex; align-items: center; justify-content: center; - padding: 16px; cursor: zoom-out; - animation: attach-lightbox-fade 0.12s ease-out; -} -.attach-lightbox img { - max-width: 100%; max-height: 100%; - border-radius: 6px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); - object-fit: contain; -} -.attach-lightbox-err { - position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); - background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 10px; font-size: 12px; -} -@keyframes attach-lightbox-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -.gallery-card-upload:hover { - opacity: 1; - border-color: var(--red); - transform: translateY(-1px); -} -.gallery-card-upload-inner { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 6px; - color: var(--fg); -} -.gallery-card-upload-label { - font-size: 12px; - font-weight: 500; - opacity: 0.8; -} -.gallery-card:hover { - border-color: var(--red); - box-shadow: 0 2px 8px color-mix(in srgb, var(--red) 15%, transparent); - transform: translateY(-1px); -} -.gallery-card-overlay { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 8px; - background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 34%, rgba(0,0,0,0.22)); - color: #fff; - opacity: 0; - pointer-events: none; - transition: opacity 0.15s; -} -.gallery-card-badges { - display: flex; - gap: 4px; - flex-wrap: wrap; - align-items: flex-end; - justify-content: flex-end; - margin-top: auto; - margin-bottom: 34px; -} -.gallery-card-badge { - padding: 2px 6px; - border-radius: 999px; - background: rgba(0, 0, 0, 0.55); - border: 1px solid rgba(255, 255, 255, 0.14); - color: rgba(255, 255, 255, 0.88); - font-size: 9px; - font-weight: 600; - line-height: 1.1; -} -.gallery-card-badge-fav { - color: #ff5f6f; -} -.gallery-select-dot { - position: absolute; top: 6px; left: 6px; z-index: 2; - display: block; - width: 10px; height: 10px; border-radius: 50%; cursor: pointer; - background: color-mix(in srgb, var(--fg) 30%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 50%, transparent); - transition: background 0.15s; -} -.gallery-select-dot.selected { background: var(--red); border-color: var(--red); } -.gallery-select-dot:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -.gallery-select-btn { - padding: 9px 10px 11px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; transition: all 0.15s; - margin-top: -4px !important; -} -.gallery-select-btn:hover { opacity: 1; } -/* Match the library Select toggle's red tint (.memory-toolbar-btn.active). - Bumped contrast so the toggled state actually reads on mobile — the prior - 15% red on transparent was nearly invisible against the page background. */ -.gallery-select-btn.active { - background: color-mix(in srgb, var(--red) 28%, transparent); - color: var(--red); - border-color: var(--red); - font-weight: 600; - opacity: 1; -} -/* Toolbar action buttons sit 2px lower than the generic select-btn so - they baseline-align with the toolbar's selects/inputs instead of - floating above them. */ -.gallery-toolbar-action { - margin-top: 0 !important; - display: inline-flex; - align-items: center; - gap: 2px; -} -.gallery-bulk-bar { - display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 12px; - justify-content: flex-end; -} -.gallery-bulk-bar.hidden { display: none; } -.gallery-bulk-all { - display: inline-flex; align-items: center; gap: 4px; - font-size: 11px; opacity: 0.75; cursor: pointer; margin-right: auto; -} -.gallery-bulk-all input { accent-color: var(--accent, var(--red)); cursor: pointer; } -.gallery-bulk-delete { - padding: 3px 10px; background: transparent; color: var(--red); - border: 1px solid var(--red); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; -} -.gallery-bulk-delete:hover { background: var(--red); color: #fff; } -.gallery-bulk-cancel { - padding: 3px 10px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; -} -.gallery-bulk-cancel:hover { opacity: 1; } -.gallery-card img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} -.gallery-card-info { - position: absolute; - bottom: 0; - left: 0; - right: 0; - padding: 6px 8px; - background: linear-gradient(transparent, rgba(0,0,0,0.75)); - color: #fff; -} -.gallery-card-prompt { - font-size: 10px; - line-height: 1.3; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.gallery-card-meta { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - font-size: 9px; - opacity: 0.8; -} -.gallery-card-model { - padding: 1px 5px; - border-radius: 6px; - background: rgba(255,255,255,0.15); -} -.gallery-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -.gallery-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: none; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-load-more:hover { - border-color: var(--red); - color: var(--red); -} - -/* Gallery detail overlay */ -.gallery-detail { - position: absolute; - inset: 0; - background: var(--panel); - z-index: 10; - display: flex; - flex-direction: column; - overflow-y: auto; -} -.gallery-detail-header { - display: flex; - align-items: center; - gap: 6px; - padding: 2px 8px 4px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - /* Pin to top so a tall (portrait) photo can't cover the Back / ⋮ - menu when the detail body grows past the visible area. */ - position: sticky; - top: 0; - background: var(--panel); - /* Above the image's rotate/nav buttons (z-index:3) so they scroll cleanly - behind the header instead of colliding with it. */ - z-index: 5; - min-height: 0; -} -.gallery-detail-title { - max-width: min(360px, 40vw); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 12px; - font-weight: 600; - opacity: 0.82; - position: relative; - top: 1px; -} -.gallery-detail-back { - background: none; - border: none; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 12px; - padding: 4px 8px; - border-radius: 4px; - position: relative; - top: -2px; -} -/* Slightly larger header icons (back/edit/heart/⋮) — override the inline 14px. */ -.gallery-detail-header svg { width: 16px; height: 16px; } -.gallery-detail-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.gallery-detail-action { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - padding: 3px 8px; - border-radius: 4px; - transition: border-color 0.15s; -} -.gallery-detail-action:hover { - border-color: var(--fg); -} -.gallery-detail-action.danger { - color: var(--color-error, #e55); -} -/* Overflow menu — replaces the seven individual action buttons that used - to crowd the right side of the detail header. */ -.gallery-detail-menu-wrap { position: relative; } -.gallery-detail-menu-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 26px; - padding: 0; - position: relative; - top: -2px; -} -.gallery-detail-menu { - position: absolute; - top: calc(100% + 4px); - right: 0; - min-width: 150px; - padding: 3px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 12; - display: flex; - flex-direction: column; - gap: 0; -} -.gallery-detail-menu[hidden] { display: none; } -.gallery-detail-menu .dropdown-item-compact { - width: 100%; - background: none; - border: none; - text-align: left; - font: inherit; - /* Tighter than the global default — the photo-detail menu has 8 - items so every pixel of vertical padding adds up. */ - padding: 4px 8px; - font-size: 11px; - gap: 8px; - line-height: 1.2; -} -.gallery-detail-menu .dropdown-item-compact .dropdown-icon, -.gallery-detail-menu .dropdown-item-compact .dropdown-icon svg { - width: 12px; - height: 12px; -} -.gallery-detail-body { - display: flex; - gap: 16px; - padding: 12px; - flex: 1; - min-height: 0; - overflow: hidden; -} -.gallery-detail-image { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - min-width: 0; - min-height: 0; - height: 100%; - padding-inline: 56px; - box-sizing: border-box; - overflow: hidden; -} -/* Shrink-wraps the actual image so overlay children (heart, face boxes) - anchor to the IMAGE bounds, not the wider letterboxed wrapper. */ -.gallery-detail-img-frame { - position: relative; - display: inline-flex; - max-width: 100%; - max-height: 100%; -} -.gallery-detail-nav { - position: absolute; - top: 50%; - transform: translateY(-50%); - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-nav:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-nav-prev { left: 8px; } -.gallery-detail-nav-next { right: 8px; } - -/* Rotate buttons — same pattern as the prev/next arrows but pinned to the - top corners. Hover-revealed so they don't clutter the image. */ -.gallery-detail-rotate { - position: absolute; - top: 8px; - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-rotate:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-rotate-ccw { left: 8px; } -.gallery-detail-rotate-cw { right: 8px; } -/* Inline heart that lives next to the "Date" label in the sidebar. - Tiny outline icon by default; fills red when favorited. */ -.gallery-detail-date-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; -} -.gallery-detail-fav-inline { - background: none; - border: none; - padding: 0; - /* Nudge up 8px so the heart sits visually above the Date label - baseline rather than centred on it. */ - margin-top: -8px; - border-radius: 4px; - color: var(--fg-muted); - opacity: 0.6; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; - transition: opacity 0.15s, color 0.15s; -} -/* Shrink the SVG so the heart matches the height of the small uppercase - Date label sitting next to it. */ -.gallery-detail-fav-inline svg { - width: 12px; - height: 12px; - display: block; -} -.gallery-detail-fav-inline:hover { - opacity: 1; -} -.gallery-detail-fav-inline.active { - opacity: 1; - color: var(--red); -} -.gallery-detail-sidebar { - width: 290px; - flex-shrink: 0; - /* Vertical scroll for long metadata, but never horizontal — overflow:auto - alone would let a wide child (e.g. long album name in the select) push - the sidebar into horizontal-scroll mode. */ - overflow-x: hidden; - overflow-y: auto; - min-width: 0; -} -.gallery-detail-inspector-head { - padding-bottom: 10px; - margin-bottom: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.gallery-detail-eyebrow { - font-size: 9px; - font-weight: 700; - color: var(--accent, var(--red)); - text-transform: uppercase; - letter-spacing: 0.08em; - margin-bottom: 3px; -} -.gallery-detail-inspector-title { - font-size: 14px; - font-weight: 650; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-meta-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 6px; - margin: 4px 0 12px; -} -.gallery-detail-meta-card { - min-width: 0; - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-radius: 7px; - padding: 7px 8px; -} -.gallery-detail-meta-card span { - display: block; - font-size: 9px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 48%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 3px; -} -.gallery-detail-meta-card strong { - display: block; - min-width: 0; - font-size: 11px; - font-weight: 600; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-inline-link { - appearance: none; - background: none; - border: none; - color: var(--accent, var(--red)); - padding: 0; - font: inherit; - font-weight: inherit; - cursor: pointer; - text-align: left; -} -.gallery-detail-mini-actions { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 7px; -} -.gallery-detail-mini-btn { - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 8px; - font: inherit; - font-size: 10.5px; - cursor: pointer; - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-detail-mini-btn:hover { - border-color: color-mix(in srgb, var(--fg) 42%, var(--border)); -} -/* Constrain any child that could otherwise stretch the sidebar wider than - its declared width (selects with long option text, long URLs, etc.). */ -.gallery-detail-sidebar select, -.gallery-detail-sidebar input, -.gallery-detail-sidebar .gallery-detail-prompt { - max-width: 100%; - box-sizing: border-box; -} -.gallery-date-rel { - opacity: 0.55; - margin-left: 4px; - font-size: 11px; - font-style: italic; -} -.gallery-detail-section label { - display: block; - font-size: 10px; - font-weight: 600; - opacity: 0.6; - margin-bottom: 3px; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.gallery-detail-section div { - font-size: 12px; - word-break: break-word; -} -.gallery-detail-prompt { - white-space: pre-wrap; - line-height: 1.4; -} -.gallery-tag-input, -.gallery-detail-name-input { - width: 100%; - padding: 5px 8px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - font: inherit; - font-size: 11px; - margin-bottom: 4px; -} -.gallery-tag-input:focus, -.gallery-detail-name-input:focus { - border-color: var(--red); - outline: none; -} -/* ↵ enter hint inside the Add-a-tag field (accent), replacing the "press Enter" - placeholder text. */ -.gallery-tag-input-wrap { position: relative; } -.gallery-tag-enter-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; /* the input has 4px bottom margin; center on the box */ - color: var(--accent, var(--red)); - font-size: 13px; - line-height: 1; - pointer-events: none; - opacity: 0.8; -} -.gallery-detail-name-input { font-size: 13px; font-weight: 500; } -/* ↵ enter hint on the image name field — accent, shown while editing to signal - that Enter saves. */ -.gallery-name-wrap { position: relative; } -.gallery-name-enter { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; - color: var(--accent, var(--red)); - opacity: 0.85; - pointer-events: none; - display: none; -} -.gallery-tag-save { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 10px; - padding: 3px 10px; - border-radius: 4px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-tag-save:hover { - border-color: var(--red); - color: var(--red); -} - -@media (max-width: 600px) { - .gallery-grid { - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - } - /* Single-row toolbar on mobile: search + Sources + Recent (sort) all share - one row. The search bar shrinks so the dropdowns fit beside it. */ - .gallery-toolbar { - flex-wrap: nowrap; - gap: 4px; - } - .gallery-search-wrap { - flex: 1 1 0; - min-width: 0; - } - .gallery-search { - /* Drop the desktop right-padding reserved for the "enter to tag" hint — - hide the hint on mobile (below) so the input can use its full width. */ - padding-right: 8px; - } - .gallery-search-enter-hint { display: none; } - /* Disable the row break so the filters stay inline with search. */ - .gallery-toolbar-break { display: none; } - .gallery-model-filter, - .gallery-sort { - flex: 0 0 auto; - /* Cap each dropdown so search keeps a usable amount of width. */ - max-width: 100px; - font-size: 11px; - padding: 4px 6px; - /* Keep the same vertical alignment as the desktop toolbar. */ - position: relative; - top: 4px; - } - .gallery-select-btn { - padding: 6px 8px 8px; - font-size: 9px; - flex-shrink: 0; - /* Push Select to the far right of the single-row toolbar, regardless - of its DOM position (sits between search and the dropdowns). */ - order: 99; - margin-left: auto; - } - /* Tabs scroll horizontally rather than wrapping when narrow. */ - .gallery-tabs { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .gallery-tabs::-webkit-scrollbar { display: none; } - .gallery-tab { flex-shrink: 0; } - /* Album chips also scroll horizontally so they don't stack. */ - .gallery-album-chips { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .gallery-album-chips::-webkit-scrollbar { display: none; } - .gallery-chip { flex-shrink: 0; } - /* Tasks filter chips — same horizontal-swipe behaviour as the gallery - albums / library chips on mobile: one row, slide-to-see-more. */ - .tasks-activity-filters { - overflow-x: auto; - flex-wrap: nowrap !important; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .tasks-activity-filters::-webkit-scrollbar { display: none; } - .tasks-activity-filters > * { flex-shrink: 0; } - /* Detail view */ - .gallery-detail-body { - flex-direction: column; - overflow: visible; - } - .gallery-detail-image { - flex: 0 0 auto; - width: 100%; - height: clamp(280px, 58vh, 520px); - padding-inline: 48px; - } - .gallery-detail-sidebar { - width: 100%; - } - .gallery-detail-title { - display: none; - } - .gallery-detail-meta-grid { - grid-template-columns: 1fr; - } - /* Album grid: smaller tile minimum on mobile. */ - .gallery-albums-grid { - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 8px; - } -} - - -/* ── Scoreboard table ── */ -.scoreboard-table { - width: 100%; - border-collapse: collapse; - font-size: 0.88em; -} -.scoreboard-table th { - text-align: left; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 55%, transparent); - text-transform: uppercase; - letter-spacing: 0.5px; -} -.scoreboard-table th:not(:first-child) { - text-align: center; -} -.scoreboard-table td { - padding: 5px 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; -} -.scoreboard-table td.scoreboard-model { - text-align: left; - font-weight: 500; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.scoreboard-table td.scoreboard-pct { - font-weight: 600; - color: var(--red); -} -.scoreboard-table tbody tr:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} - -#scoreboard-overlay svg { - color: var(--accent, var(--red)); -} - -/* ── Compare search results ── */ -.compare-search-results { - display: flex; - flex-direction: column; - gap: 2px; -} -.compare-search-result { - padding: 6px 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); -} -.compare-search-result:last-child { - border-bottom: none; -} -.search-result-title { - color: var(--color-accent); - text-decoration: none; - font-weight: 500; - font-size: 0.9em; - display: block; - line-height: 1.3; -} -.search-result-title:hover { - color: var(--color-link-hover); - text-decoration: underline; -} -.search-result-snippet { - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.82em; - line-height: 1.4; - margin-top: 2px; -} -.search-result-url { - color: color-mix(in srgb, var(--fg) 35%, transparent); - font-size: 0.75em; - margin-top: 3px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - - -/* ── Cookbook Tool ── */ - -/* ── Cookbook ── */ -.cookbook-serve-preset { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - margin-bottom: 3px; - cursor: pointer; - transition: background 0.15s; -} -.cookbook-serve-preset:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cookbook-serve-preset-name { - font-size: 11px; - font-weight: 500; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-serve-preset-meta { - font-size: 9px; - font-family: 'Fira Code', monospace; - color: var(--fg-muted); - opacity: 0.5; -} -.cookbook-serve-preset-launch { - background: none; - border: none; - color: #50fa7b; - cursor: pointer; - font-size: 10px; - opacity: 0.6; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-launch:hover { opacity: 1; } -.cookbook-serve-preset-rm { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 10px; - opacity: 0.3; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-rm:hover { opacity: 0.8; color: var(--red); } -.cookbook-body { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - flex: 1; - min-height: 0; - scrollbar-width: thin; -} -#cookbook-modal .modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -#cookbook-modal .modal-header { - flex: 0 0 auto; -} -#cookbook-modal .cookbook-body { - min-height: 0; - overflow-y: auto; - overflow-x: hidden; -} -#cookbook-modal .cookbook-group { - min-height: 0; -} -#cookbook-modal .cookbook-group > .admin-card { - min-height: 0; - /* Let .cookbook-body be the SINGLE scroll surface. Nesting another - overflow:auto here trapped the wheel inside the cached-list when a - serve panel expanded — the page couldn't scroll past the panel's - bottom (Launch button got hidden). */ - overflow: visible !important; -} -#cookbook-modal .cookbook-section-body { - min-height: 0; -} -.cookbook-group { - display: flex; - flex-direction: column; - gap: 8px; - flex: 1; - min-height: 0; -} - -/* Cards — match admin-card */ -.cookbook-card { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-card-header { - display: flex; - align-items: center; - justify-content: space-between; -} -.cookbook-card-header-actions { display: flex; gap: 4px; } -.cookbook-card-title { font-size: 13px; font-weight: 600; } -.cookbook-card-desc { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} - -/* Fields grid */ -.cookbook-fields { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 6px; - margin-top: 4px; -} -.cookbook-field-label { - display: flex; - flex-direction: column; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - gap: 3px; -} - -/* Inputs — match admin-add-form input */ -.cookbook-field-input { - padding: 5px 8px; - font-size: 12px; - font-family: inherit; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - outline: none; -} -.cookbook-field-input:focus { border-color: var(--red); } -#hwfit-dl-server, #hwfit-usecase, #hwfit-server-select, #hwfit-cache-server, #serve-sort { height: 28px; width: 88px; } -/* Serve tab — match Documents sizing, but leave room for the active "Cancel" - label and center it vertically so the descenders don't clip. */ -#hwfit-cache-select { - min-width: 58px; - height: 28px; - position: relative; - top: -3px; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; -} - -/* Command preview */ -.cookbook-cmd-preview { - margin: 4px 0 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - color: var(--fg); -} - -/* Buttons — match admin-btn-sm */ -.cookbook-actions { display: flex; gap: 6px; margin-top: 2px; } -.cookbook-btn { - padding: 4px 10px; - min-width: 54px; - text-align: center; - font-size: 11px; - font-family: inherit; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - cursor: pointer; - transition: all 0.15s; -} -.cookbook-btn:hover { - background: var(--border); - border-color: var(--accent); -} -.cookbook-btn:disabled { opacity: 0.5; cursor: not-allowed; } -.cookbook-run-btn { - background: var(--accent); - color: var(--panel); - border-color: var(--accent); - font-weight: 600; -} -.cookbook-run-btn:hover { opacity: 0.85; } -.cookbook-stop-btn { - background: var(--color-error); - border-color: var(--color-error); -} - -/* Output */ -.cookbook-output-wrap { - position: relative; - margin: 0; -} -.cookbook-output-kill { - position: absolute; - top: 4px; - right: 34px; - width: 22px; - height: 22px; - padding: 0; - background: none; - border: 1px solid transparent; - border-radius: 4px; - color: var(--fg-muted); - font-size: 16px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity .15s, color .15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-output-kill:hover { color: var(--color-error, var(--warn)); border-color: var(--border); } -.cookbook-output-wrap .copy-code { - position: absolute; - top: 6px; - right: 6px; -} -.cookbook-output-wrap:hover .copy-code { opacity: 0.7; } -.cookbook-output-pre { - margin: 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - max-height: 180px; - overflow-y: auto; -} -.cookbook-output-error { color: var(--color-error); } - -/* Downloaded/cached models */ -.hwfit-cached-item { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 6px; - padding: 6px 8px; - margin: 2px 0; - border-radius: 6px; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 3%, transparent); - cursor: default; - font-size: 12px; -} -.hwfit-cached-item .hwfit-serve-panel { - flex-basis: 100%; - margin-top: 4px; -} -.hwfit-cached-item:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); -} -.hwfit-cached-header { - font-size: 10px; - opacity: 0.4; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - border: none !important; - background: none !important; - padding: 2px 8px; - cursor: default !important; -} -.hwfit-cached-header:hover { background: none !important; } -.hwfit-cached-name { - font-weight: 600; - flex-shrink: 0; -} -.hwfit-cached-repo { - flex: 1; - color: var(--fg-muted); - font-size: 11px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-cached-size, -.hwfit-cached-files { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; -} -.hwfit-cached-serve, -.hwfit-cached-delete { - flex-shrink: 0; -} -.hwfit-cached-delete { - opacity: 0.4; - font-size: 10px; -} -.hwfit-cached-delete:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} -.hwfit-cached-badge { - font-size: 9px; - padding: 1px 6px; - border-radius: 3px; - font-weight: 600; - white-space: nowrap; -} -.hwfit-cached-ready { - background: color-mix(in srgb, var(--color-success, #4caf50) 20%, transparent); - color: var(--color-success, #4caf50); -} -.hwfit-cached-dl { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 20%, transparent); - color: var(--color-warning, #f0ad4e); -} - -/* Cookbook notification dot */ -#tool-cookbook-btn { - position: relative; -} -#rail-cookbook { - position: relative; -} -.cookbook-open-loading { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - flex-shrink: 0; - margin-left: 2px; - color: var(--accent, var(--red)); - opacity: 0.9; -} -.cookbook-open-loading .ai-spinner, -.cookbook-open-loading .ai-spinner-whirlpool { - display: inline-flex !important; - width: 14px; - height: 14px; - margin: 0 !important; - gap: 0 !important; -} -.cookbook-open-loading canvas { - display: block; -} -.cookbook-section-loading-wp, -.cookbook-section-loading-wp canvas { - width: 22px !important; - height: 22px !important; - display: block !important; -} -#rail-cookbook .cookbook-open-loading { - position: absolute; - right: 2px; - top: -2px; - width: 12px; - height: 12px; - margin-left: 0; -} -/* The serve-list 'downloading' / 'download stalled' status text sits a hair - low against the rest of the meta line on mobile — nudge it up 2px. */ -@media (max-width: 768px) { - .cookbook-dl-status { - position: relative; - top: -2px; - } -} -.cookbook-notif-dot { - width: 6px; - height: 6px; - flex-shrink: 0; - border-radius: 50%; - background: var(--color-success, #4caf50); - /* Drives the breathing glow in the keyframes (matches email's dot). */ - --notif-glow: var(--color-success, #4caf50); - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -.cookbook-notif-dot.cookbook-notif-error { - background: var(--color-error, #f44); - --notif-glow: var(--color-error, #f44); - position: relative; - left: -2px; - top: -1px; -} -.rail-notify-error { color: var(--color-error, #f44) !important; } -.cookbook-tab-error-dot { - display: inline-block; width: 5px; height: 5px; border-radius: 50%; - background: var(--color-error, #f44); margin-left: 4px; vertical-align: middle; - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -@keyframes cookbook-notif-pulse { - 0%, 100% { - opacity: 1; - box-shadow: 0 0 0 0 color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 60%, transparent); - } - 50% { - opacity: 0.85; - box-shadow: 0 0 6px 2px color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 55%, transparent); - } -} -.cookbook-clear-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-clear-btn:hover { - color: var(--red) !important; - border-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 8%, transparent) !important; -} -.cookbook-bulk-bar { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - margin-bottom: 4px; - background: color-mix(in srgb, var(--red) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 6px; - font-size: 11px; -} -.cookbook-bulk-bar.hidden { display: none; } -.serve-select-cb { - width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; cursor: pointer; - background: var(--border); transition: background 0.15s; - align-self: center; - position: relative; - top: -2px; -} -#serve-bulk-bar { - position: relative; - top: -6px; -} -#serve-bulk-bar #serve-bulk-cancel { - top: -2px !important; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 28px; - height: 24px; - line-height: 1; -} -#serve-bulk-bar #serve-bulk-cancel svg { - top: 0; -} -.serve-select-cb.selected { background: var(--red); } -.serve-select-cb:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -#hwfit-cache-select.active { - background: var(--red); - color: #fff; - border-color: var(--red); -} -.cookbook-serve-dirs { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.cookbook-serve-dir-pill { - font-size: 10px; - font-family: 'Fira Code', monospace; - padding: 2px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); - color: var(--fg-muted); - letter-spacing: 0.2px; -} -/* Status pills shown inline in a Serve-tab card title (next to the model - name) when the model has a live serve / download task. Shared base - class so "running" and "downloading" sit on the same row with the - same chrome; only color varies. */ -.cookbook-serve-running-pill, -.cookbook-serve-downloading-pill { - display: inline-block; - margin-left: 6px; - padding: 1px 7px; - border-radius: 10px; - font-size: 9px; - font-weight: 500; - text-transform: lowercase; - letter-spacing: 0.3px; - vertical-align: 2px; - position: relative; - top: 1px; /* nudged down 2px from the old -1px so it sits - flush with the cap-height of the title text */ -} -.cookbook-serve-running-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); -} -.cookbook-serve-downloading-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); - opacity: 0.85; -} -.cookbook-serve-downloading-pill.is-stalled { - /* Stalled downloads stay visible but read as warning, not progress. */ - color: var(--fg-muted, #888); - background: color-mix(in srgb, var(--fg-muted, #888) 10%, transparent); - border-color: color-mix(in srgb, var(--fg-muted, #888) 30%, transparent); - opacity: 1; -} -.cookbook-serve-running-pill.is-clickable { - cursor: pointer; - transition: background 0.12s, border-color 0.12s; -} -.cookbook-serve-running-pill.is-clickable:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -.cookbook-serve-dir-edit { - font-size: 9px; - color: var(--fg-muted); - opacity: 0.4; - cursor: pointer; - text-decoration: underline; - text-underline-offset: 2px; -} -.cookbook-serve-dir-edit:hover { - opacity: 0.7; - color: var(--accent, var(--red)); -} -.cookbook-gpu-group { - display: flex; - gap: 3px; - margin-top: -4px; -} -.cookbook-gpu-btn { - width: 30px; height: 30px; - font-size: 12px; - font-family: 'Fira Code', monospace; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - color: var(--fg-muted); - cursor: pointer; - opacity: 0.4; - transition: all 0.15s; - padding: 0; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-gpu-btn:hover { - opacity: 0.7; - border-color: var(--fg-muted); -} -.cookbook-gpu-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 55%, transparent); - color: var(--fg); - font-weight: 600; -} -/* Probe annotation states (set by "Free?" button) */ -.cookbook-gpu-btn.gpu-free { - opacity: 1; - border-color: #4ade80; - color: #4ade80; -} -.cookbook-gpu-btn.gpu-busy { - opacity: 0.85; - border-color: color-mix(in srgb, var(--red) 70%, transparent); - color: var(--red); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-btn.gpu-missing { - opacity: 0.2; - text-decoration: line-through; - cursor: not-allowed; -} -/* "Clear Server" lives in the actions row next to "Probe GPUs". - Inherits .cookbook-btn sizing; this rule just adds the destructive - red tint on hover so the "this kills processes" cue stays. */ -.cookbook-gpu-clear:hover:not(:disabled) { - color: var(--red); - border-color: color-mix(in srgb, var(--red) 60%, var(--border)); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -/* GPU probe popup — per-GPU process list with kill buttons */ -.cookbook-gpu-popup { - /* Fixed positioning (relative to viewport) so we never get pulled into - a scrolling/transform stacking context from an ancestor. Z-index has - to clear the cookbook modal (260) and the rest of the high-z UI - layers (themed-confirm and various overlays sit around 9000-10000). */ - position: fixed; - z-index: 10010; - min-width: 280px; - max-width: 420px; - background: var(--panel, #1a1a1a); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 8px 24px rgba(0,0,0,0.4); - font-family: 'Fira Code', monospace; - font-size: 11px; - color: var(--fg); -} -.cookbook-gpu-popup-head { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; -} -.cookbook-gpu-popup-stats { - flex: 1; - font-weight: 400; - color: var(--fg-muted); - font-size: 10px; -} -.cookbook-gpu-popup-close { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 14px; - padding: 0 4px; -} -.cookbook-gpu-popup-close:hover { color: var(--fg); } -.cookbook-gpu-popup-body { - padding: 4px 0; - max-height: 320px; - overflow-y: auto; -} -.cookbook-gpu-popup-empty { - padding: 10px; - color: var(--fg-muted); - font-style: italic; -} -.cookbook-gpu-proc { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; -} -.cookbook-gpu-proc:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); } -.cookbook-gpu-proc-info { - flex: 1; - display: flex; - gap: 8px; - align-items: center; - min-width: 0; -} -.cookbook-gpu-proc-pid { - color: var(--fg-muted); - width: 56px; - flex-shrink: 0; -} -.cookbook-gpu-proc-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.cookbook-gpu-proc-mem { - color: var(--fg-muted); - flex-shrink: 0; -} -.cookbook-gpu-proc-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} -.cookbook-gpu-kill { - border: 1px solid color-mix(in srgb, var(--red) 60%, transparent); - background: none; - color: var(--red); - border-radius: 3px; - padding: 2px 8px; - cursor: pointer; - font-family: inherit; - font-size: 10px; -} -.cookbook-gpu-kill[data-sig="KILL"] { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.cookbook-gpu-kill:hover:not(:disabled) { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.cookbook-gpu-kill:disabled { opacity: 0.4; cursor: wait; } -.cookbook-serve-title { - display: flex; - align-items: center; - gap: 4px; - min-width: 0; -} -.cookbook-serve-title-name { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-hf-link { - font-size: 9px; - text-decoration: none; - color: var(--fg-muted); - opacity: 0.5; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - vertical-align: 1px; - letter-spacing: 0.3px; - font-weight: 600; - flex-shrink: 0; -} -.cookbook-hf-link:hover { - opacity: 0.8; - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -/* In the Trending models list the HF pill is the row's main affordance — - tint it accent so it stands out next to the model name. */ -#cookbook-hf-latest-list .cookbook-hf-link { - color: var(--accent, var(--red)); - opacity: 0.85; - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); -} - -/* Running tab sections */ -.cookbook-saved-section, -.cookbook-serve-section, -.cookbook-dl-section { - border: 1px solid var(--border); - border-radius: 6px; - margin-bottom: 6px; - overflow: hidden; -} -/* Divider between server-group sections in the Active tab so the - transition between e.g. local + remote-host server blocks reads - visually instead of running into each other. */ -.cookbook-group[data-backend-group="Running"] > [class*="cookbook-server-section-"] + [class*="cookbook-server-section-"] { - margin-top: 14px; - padding-top: 14px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.cookbook-section-header { - display: flex; - align-items: center; - gap: 6px; - padding: 7px 10px; - cursor: pointer; - user-select: none; - /* Persistent surface + border so it reads as a clickable bar instead of - blending into the panel background. */ - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - transition: background 0.15s, border-color 0.15s; -} -.cookbook-section-header:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: color-mix(in srgb, var(--fg) 22%, transparent); -} -.cookbook-section-header.has-server-color { - color: var(--fg); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 45%, var(--border)); - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 4%, var(--bg)); - box-shadow: inset 6px 0 0 var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color:hover { - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 7%, var(--bg)); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 60%, var(--border)); -} -.cookbook-section-header.has-server-color .cookbook-section-title, -.cookbook-section-header.has-server-color .cookbook-section-chevron { - color: var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color .cookbook-srv-status.ok { - background: var(--cookbook-server-accent, var(--cookbook-server-color)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 55%, transparent); -} -.cookbook-section-header .cookbook-clear-btn { - margin-left: 0; - position: relative; - top: -3px; -} -@media (max-width: 768px) { - .cookbook-section-header .cookbook-clear-btn { - top: -3px; - } - .cookbook-task-check { - top: 4px; - } -} -/* "Stop all" sits just left of "Clear finished"; it carries the auto margin so - the pair is pushed together to the right of the section title. */ -.cookbook-section-header .cookbook-stop-all-btn { - margin-left: auto; - margin-right: 6px; - position: relative; - top: -3px; -} -.cookbook-stop-all-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-stop-all-btn:hover { - color: var(--warn, #f0ad4e) !important; - border-color: var(--warn, #f0ad4e) !important; - background: color-mix(in srgb, var(--warn, #f0ad4e) 8%, transparent) !important; -} -.cookbook-section-chevron { - flex-shrink: 0; - opacity: 0.6; - transition: transform 0.15s ease, opacity 0.15s ease; -} -.cookbook-section-body { - padding: 0; -} -.cookbook-saved-toggle-header { - font-size: 10px; - font-weight: 600; - color: var(--fg-muted); - text-transform: uppercase; - letter-spacing: 0.3px; - margin-bottom: 4px; -} -.cookbook-saved-toggle:hover { color: var(--fg); } -.cookbook-saved-item { - margin: 2px 0; - border-radius: 4px; - font-size: 11px; -} -.cookbook-saved-header { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 6px; - border-radius: 4px; - transition: background 0.08s; -} -.cookbook-saved-header:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.cookbook-saved-item .hwfit-serve-panel { - margin: 4px 0 4px 6px; -} -.cookbook-saved-host { - font-size: 10px; - color: var(--fg-muted); - font-family: 'Fira Code', monospace; -} -.cookbook-saved-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-saved-launch { - font-size: 10px; - padding: 2px 8px; -} -.cookbook-saved-del { - background: none; - border: none; - color: var(--fg-muted); - opacity: 0.3; - cursor: pointer; - font-size: 11px; - padding: 0 2px; -} -.cookbook-saved-del:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} - -/* Serve config panel */ -.hwfit-serve-panel { - margin-top: 6px; - padding: 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex: 1; - display: flex; - flex-direction: column; - position: relative; -} -.cookbook-serve-slots { - display: flex; - gap: 3px; - justify-content: flex-end; - margin-bottom: 4px; -} -/* When the Save split sits inside Row 1 (next to GPUs), align it with the - input baseline (the row's grid cells stretch top-down; without this the - Save buttons sit above the GPU button group). */ -.hwfit-serve-row .cookbook-serve-slots { - align-self: end; - margin-bottom: 4px; -} -.cookbook-slot-btn { - min-width: 22px; height: 28px; - padding: 0 6px; - font-size: 10px; font-weight: 600; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - white-space: nowrap; - max-width: 80px; - overflow: hidden; - text-overflow: ellipsis; - color: var(--fg-muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.5; - transition: all 0.15s; -} -.cookbook-slot-btn:hover { opacity: 0.9; border-color: var(--fg-muted); } -/* Saved-configs split button: "Save" + dropdown-arrow joined into one control, - but each segment keeps its own style — Save uses the filled accent - "Done"-button look; the arrow stays a subtle outlined menu trigger. */ -.cookbook-saved-split { gap: 0; } -.cookbook-saved-save, -.cookbook-saved-arrow { max-width: none; } -.cookbook-saved-save { - padding: 0 10px; - gap: 4px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - font-weight: 600; - opacity: 1; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.cookbook-saved-save:hover { - background: var(--border); - border-color: var(--accent); - opacity: 1; -} -.cookbook-saved-arrow { - padding: 0 6px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - opacity: 1; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: none; -} -.cookbook-saved-menu .cookbook-saved-favorite { - border-left: 3px solid var(--red); - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-saved-fav-btn { - width: 20px; - height: 20px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: none; - color: var(--fg-muted); - opacity: 0.55; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -.cookbook-saved-fav-btn:hover, -.cookbook-saved-fav-btn.active { - color: var(--red); - opacity: 1; - background: color-mix(in srgb, var(--red) 10%, transparent); -} -.cookbook-saved-fav-badge { - flex-shrink: 0; - background: transparent; - padding: 0; -} -.cookbook-serve-favorite-model { - border-left-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-serve-fav-badge { - display: inline-flex; - align-items: center; - vertical-align: 1px; - margin-left: 5px; - margin-right: 2px; - background: transparent; - padding: 0; -} -.cookbook-slot-saved { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); } -.cookbook-slot-saved:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); } -.cookbook-slot-btn.active { opacity: 1; background: var(--accent); color: #fff; border-color: var(--accent); -} -.cookbook-slot-wrap { - position: relative; - display: inline-flex; -} -.cookbook-slot-del { - position: absolute; - top: -5px; - right: -5px; - width: 14px; - height: 14px; - padding: 0; - border: 1px solid var(--border); - border-radius: 50%; - background: var(--bg, #1a1a1a); - color: var(--fg-muted); - font-size: 12px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, color 0.15s, border-color 0.15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-slot-del:hover { - color: #f44; - border-color: #f44; -} -.cookbook-card-backend { display: none; } -/* Dependencies row — aligned tags */ -.cookbook-dep-row { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel); -} -/* Let the deps list fill the available window height instead of being - capped at the default .doclib-grid 400-px box. */ -[data-backend-group="Dependencies"] #cookbook-deps-list { - flex: 1; - max-height: none; -} - -/* Settings tab: stack the HF token + Servers cards as distinct blocks - (matches the Download tab's block-per-section layout). */ -.cookbook-settings-stack { - display: flex; - flex-direction: column; - gap: 10px; - flex: 1; - /* Scroll the whole settings panel so the Servers card can grow to hold every - server (it used to be a cramped internal scroll box that clipped them). */ - overflow-y: auto; - min-height: 0; -} -.cookbook-settings-stack.hidden { display: none; } -.cookbook-dep-row.cookbook-dep-blocked { opacity: 0.4; } -.cookbook-dep-info { flex: 1; min-width: 0; } -.cookbook-dep-section { - display: flex; - align-items: baseline; - gap: 8px; - margin: 12px 2px 4px; -} -.cookbook-dep-section-title { - font-size: 11px; - font-weight: 600; - letter-spacing: 0.03em; -} -.cookbook-dep-section-note { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-subgroup { - margin: 6px 0 8px 12px; - padding: 7px 8px 8px; - border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - border-radius: 0 6px 6px 0; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-dep-subgroup-title { - display: flex; - align-items: baseline; - gap: 7px; - margin: 0 1px 1px; - font-size: 10.5px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 78%, transparent); -} -.cookbook-dep-subgroup-title em { - font-size: 9.5px; - font-style: normal; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.cookbook-dep-tag { - font-size: 9px; - padding: 0 8px; - border-radius: 4px; - white-space: nowrap; - text-align: center; - min-width: 62px; - box-sizing: border-box; - line-height: 1; - /* Explicit height so the tags (Install / Installed) are the EXACT - same height as the sibling tags — Firefox gives buttons a taller - native box otherwise. Mirrors the server-row tag rule (height:24px). */ - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.cookbook-dep-target { - border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-cat { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -/* Rebuild tag — same look as the LLM category tag, sits to its left. */ -.cookbook-dep-rebuild { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 75%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent); - cursor: pointer; - font-family: inherit; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - position: relative; - top: -2px; -} -.cookbook-dep-reinstall { - top: -3px; -} -.cookbook-dep-rebuild:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); -} -.cookbook-dep-installed { - background: color-mix(in srgb, var(--green, #50fa7b) 18%, transparent); - color: var(--green, #50fa7b); - border: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); - /* Match Install + Installed ▾ so all three variants align in mixed rows. */ - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-na { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); - cursor: help; - /* Match other dep tag widths so N/A rows line up with Install / Installed. */ - min-width: 75.85px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-install { - background: var(--accent, var(--red)); - color: #fff; - border: none; - cursor: pointer; - font-family: inherit; - font-weight: 500; - position: relative; - top: -3px; - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - /* Strip the native button box so it's the same height as the sibling tags - (Firefox renders taller otherwise); height comes from .cookbook-dep-tag. */ - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-build-deps { - width: auto; - min-width: 87.7px; - max-width: 100%; - white-space: normal; - overflow-wrap: anywhere; - line-height: 1.15; - text-align: center; - height: auto; - min-height: 24px; -} -/* The install state is the terminal control in a dependency row. Keep it on - the far edge even when optional rebuild/category controls are present. */ -.cookbook-dep-row > .cookbook-dep-installed, -.cookbook-dep-row > .cookbook-dep-na, -.cookbook-dep-row > .cookbook-dep-install:not(.cookbook-dep-build-deps) { - margin-left: auto; -} -/* Center the "?" glyph inside the help chip. Without text-align it sits 0.5px - left of true center because of the character's natural baseline offset. */ -.hwfit-help-chip { - text-align: center; - padding-left: 0.5px; -} -.cookbook-dep-install:hover { opacity: 0.85; } -/* Installed split button: "Installed" label + separator + ▾ caret; clicking it - opens the actions menu (Update). Replaces the old ⋮ button. */ -.cookbook-dep-installed-btn { - padding: 0; - width: 87.7px; - min-width: 87.7px; - cursor: pointer; - font-family: inherit; - overflow: hidden; - position: relative; - top: -3px; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-installed-btn .cookbook-dep-caret { - display: inline-flex; - align-items: center; - align-self: stretch; - padding: 0 5px; - font-size: 14px; - line-height: 1; - position: relative; - top: 1px; - left: 4px; - opacity: 0.85; - border-left: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); -} -.hwfit-serve-row { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - margin-bottom: 6px; -} -/* Tighter min on the core-knobs row only so Dtype / TP / GPU Mem / Max - Seqs fit on one line without crushing Row 1's GPU button strip or - the Backend picker. */ -.hwfit-serve-row-core { - grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); -} -/* Save / saved-config split aligned to the right end of Row 1 and - nudged up 4px so it sits flush with the rest of the row. */ -.hwfit-serve-row .cookbook-serve-slots { - justify-self: end; - position: relative; - top: -4px; -} -/* The Settings label wraps the Save split — right-align its title text - so the word "Settings" sits above the right-aligned Save button - instead of floating off to the left of its grid cell. 8px right - padding now (16px → 8px) per follow-up tweak, brings the title back - over the actual Save button instead of overshooting it. */ -.hwfit-serve-row label:has(> .cookbook-serve-slots) { - grid-column: -2 / -1; - justify-self: end; - text-align: right; - padding-right: 0; -} -.hwfit-serve-row label:has(> .cookbook-serve-slots) > span { - display: inline-block; - position: relative; - left: -33px; -} -.hwfit-serve-topline { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - margin: 0 0 6px; -} -.hwfit-serve-topline .hwfit-serve-runtime-text { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-serve-preset-row { - display: flex; - justify-content: flex-end; - margin: 0; - flex: 0 0 auto; - position: relative; - top: -6px; -} -.hwfit-serve-row label { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; - letter-spacing: 0.3px; -} -.hwfit-serve-row-core .hwfit-context-label { - grid-column: span 2; - width: auto; - min-width: 0; - max-width: none; - justify-self: start; -} -.hwfit-context-control { - display: block; - position: relative; - align-items: center; - margin-top: 2px; - width: 100px; -} -.hwfit-context-control .hwfit-sf[data-field="ctx"] { - min-width: 0; - width: 100%; - padding-right: 40px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]) { - position: relative; - left: -66px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - position: relative; - left: -64px; -} -.hwfit-serve-row-core .hwfit-gpus-label { - position: relative; - left: -70px; -} -.hwfit-context-calc-btn { - position: absolute; - right: 3px; - top: -4px; - width: 34px; - height: 28px; - min-width: 34px; - padding: 0; - font-size: 10px; - line-height: 1; - display: inline-flex; - align-items: center; - justify-content: center; - text-align: center; - overflow: hidden; - white-space: nowrap; -} -.hwfit-context-calc-btn .spinner-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 auto !important; -} -.hwfit-context-calc-btn .ai-spinner-whirlpool { - width: 12px !important; - height: 12px !important; -} -@media (min-width: 769px) { - [data-llama-mode-toggle].mode-toggle.mode-toggle-three .mode-toggle-btn > span { - top: -6px !important; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { - font-size: 0; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { - content: 'Unif'; - font-size: 11px; - } -} -@media (max-width: 768px) { - .hwfit-serve-row-core .hwfit-context-label { - display: flex; - flex-direction: column; - align-items: flex-start; - } - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]), - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - left: 0; - } - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_env_preset"]), - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="extra_env"]) { - grid-column: 1 / -1 !important; - } - .hwfit-context-control { - display: inline-flex; - align-items: center; - gap: 4px; - width: auto; - } - .hwfit-context-control .hwfit-sf[data-field="ctx"] { - width: 100px; - padding-right: 6px; - } - .hwfit-context-calc-btn { - position: relative; - top: -2px; - left: 2px; - right: auto; - flex: 0 0 34px; - width: 34px; - min-width: 34px; - } -} -.hwfit-auto-ctx-note { - display: block; - margin-top: 3px; - font-size: 9px; - opacity: 0.6; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Ctx slider — ported from origin/main. Sits in the Scan/Download toolbar - next to the quant dropdown. Drives _ctxValue() in cookbook-hwfit.js. */ -.hwfit-ctx-control { - height: 28px; - min-width: 134px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - gap: 5px; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - font-size: 11px; /* slightly smaller than .cookbook-field-input so Context reads quieter next to Engine/Quant */ -} -.hwfit-ctx-control span { - /* Match Quant/Engine select label style: no uppercase, no letter-spacing. */ - text-transform: none; - letter-spacing: 0; - opacity: 0.9; -} -.hwfit-ctx-control > span:first-child { - font-size: 9.5px; -} -/* Editor-style slider (same look as the gallery editor sliders): thin pill - rail that fattens on interaction, circular red thumb that grows on hover. */ -.hwfit-ctx-control input[type="range"] { - width: 64px; - min-width: 64px; - height: 4px; - padding: 0; - border: 0; - -webkit-appearance: none; - appearance: none; - /* Hard-coded grey so the rail is GUARANTEED visible regardless of theme — - every theme-derived color we tried (--fg-muted, --border, accent-bg mix) - kept blending into the panel background on at least one theme. */ - background: rgba(150, 150, 150, 0.65); - border-radius: 999px; - accent-color: var(--red); - cursor: pointer; - transition: height 0.15s ease, background 0.15s ease; -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - background: var(--fg); -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - height: 10px; -} -.hwfit-ctx-control input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]::-moz-range-thumb { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]:hover::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:focus::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:active::-webkit-slider-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control input[type="range"]:hover::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:focus::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:active::-moz-range-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control output { - min-width: 28px; - text-align: right; - color: var(--fg); - font-weight: 600; -} -.hwfit-sf { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - font-family: inherit; - font-size: 12px; - padding: 0 6px; - height: 28px; - box-sizing: border-box; -} -.hwfit-sf[data-field="backend"], -.hwfit-sf[data-field="dtype"], -.hwfit-sf[data-field="tp"] { - height: 32px; - box-sizing: border-box; - width: 100%; -} -.hwfit-sf:focus { - border-color: var(--accent, var(--red)); - outline: none; -} -.hwfit-sf[type="text"] { width: 100%; } -.hwfit-sf.hwfit-sf-wide { width: 100%; } -.hwfit-serve-checks { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 6px; -} -.hwfit-sf-cb { - display: flex; - flex-direction: row; - align-items: center; - gap: 6px; - font-size: 11px; - color: var(--fg-muted); - cursor: pointer; - padding: 3px 0; -} diff --git a/static/index.html b/static/index.html index cba90c7c9..ad04daf89 100644 --- a/static/index.html +++ b/static/index.html @@ -246,15 +246,24 @@ real request is discarded and the font fetched a second time. --> - - - - - - - - - + + + + + + + + + + + + + + + + + +
with hljs spans) and the transparent textarea use two + different rendering paths internally — the textarea uses the browser's + native form line-breaker, the overlay uses CSS line-breaking — and they + can never be guaranteed to wrap byte-identical no matter how many CSS + properties are pinned. Pick one source of truth: make the textarea its + own visible text, hide the overlay. Trade-off: no syntax highlighting in + the live editor (rendered markdown preview / chat output still has it). + The caret is now glued to the typed text by definition since both are + rendered by the same element. */ +.doc-editor-highlight, +#doc-editor-code.email-mode .doc-editor-highlight { + display: none !important; +} +/* Find bar is open: float the highlight overlay ON TOP of the + textarea with transparent background + transparent text, so only + the solid spans are visible. pointer-events + off so clicks still go through to the textarea below. */ +body.doc-find-active .doc-editor-highlight, +body.doc-find-active #doc-editor-code.email-mode .doc-editor-highlight { + display: block !important; + z-index: 5 !important; + background: transparent !important; + pointer-events: none; +} +body.doc-find-active .doc-editor-highlight, +body.doc-find-active .doc-editor-highlight * { + color: transparent !important; + background: transparent !important; +} +.doc-editor-textarea, +#doc-editor-code.email-mode .doc-editor-textarea { + color: var(--fg) !important; + caret-color: var(--fg) !important; + z-index: 1; +} + +/* Emoji picker (monochrome icon picker) */ +.emoji-picker-btn { + background: none; border: none; color: var(--fg); opacity: 0.35; + padding: 4px 7px; cursor: pointer; min-height: 28px; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.1s; +} +.emoji-picker-btn:hover { opacity: 1; } +.emoji-picker { + width: min(300px, 92vw); max-height: min(300px, 55vh); + background: var(--panel); border: 1px solid var(--border); border-radius: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.3); + display: flex; flex-direction: column; overflow: hidden; +} +.emoji-picker-search { + padding: 8px 12px; background: var(--panel); border: none; + border-bottom: 1px solid var(--border); color: var(--fg); + font-size: 12px; font-family: inherit; outline: none; +} +.emoji-picker-groups { overflow-y: auto; padding: 4px 0; flex: 1; } +.emoji-picker-group { padding: 0 6px 4px 6px; } +.emoji-picker-group-name { + font-size: 10px; font-weight: 600; opacity: 0.5; + text-transform: uppercase; letter-spacing: 0.5px; + padding: 4px 4px 2px 4px; +} +.emoji-picker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; } +.emoji-picker-item { + background: none; border: none; cursor: pointer; + padding: 4px; border-radius: 4px; + display: flex; align-items: center; justify-content: center; + color: var(--fg); transition: background 0.1s; +} +.emoji-picker-item:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } +.emoji-picker-item svg { width: 16px; height: 16px; } + +.email-avatar { + width: 28px; height: 28px; border-radius: 50%; background: var(--accent, #4a9eff); + color: #fff; display: flex; align-items: center; justify-content: center; + font-size: 12px; font-weight: 600; flex-shrink: 0; margin-top: 1px; +} +.email-item-content { flex: 1; min-width: 0; overflow: hidden; } +.email-item-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } +.email-sender { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.email-filter-chip { + display: flex; + align-items: center; + justify-content: space-between; + gap: 6px; + margin: 4px 8px 6px; + padding: 4px 8px; + border-radius: 12px; + font-size: 11px; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + color: var(--fg); +} +.email-filter-chip-clear { + background: none; + border: none; + color: inherit; + cursor: pointer; + font-size: 14px; + line-height: 1; + padding: 0 2px; + opacity: 0.6; +} +.email-date { font-size: 10px; opacity: 0.5; white-space: nowrap; flex-shrink: 0; } +.email-subject { font-size: 11px; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } +.email-sent-chip, +.email-folder-chip { + display: inline-flex; + align-items: center; + height: 14px; + padding: 0 5px; + margin-right: 5px; + border-radius: 999px; + font-size: 8px; + line-height: 1; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + vertical-align: 1px; +} +.email-folder-chip { + position: relative; + top: 4px; + color: color-mix(in srgb, var(--fg) 75%, transparent); + background: rgba(127, 127, 127, 0.13); + border-color: rgba(127, 127, 127, 0.24); +} +.email-meta-date-group { + display: inline-flex; + align-items: center; + gap: 5px; + white-space: nowrap; +} +.email-meta-date-group .email-folder-chip { + margin-right: 0; +} +.email-card-expanded .email-sent-chip, +.email-card-reader .email-sent-chip { + position: relative; + top: 4px; +} +.email-tags { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 3px; margin-left: 6px; vertical-align: middle; position: relative; top: -2px; } +.email-card-tags { + flex: 0 0 auto; + min-width: 0; + max-width: 48%; + position: relative; + top: -4px; +} +.email-card-expanded .email-tags, +.email-card-reader .email-tags { top: -4px; } +.doclib-card-expanded .email-card-tags { top: -4px; } +.email-card-expanded .email-card-titlerow .email-tag, +.email-card-expanded .email-card-titlerow .email-tags-more { + transform: translateY(-2px); +} +@media (max-width: 768px) { + .email-card-expanded .email-card-titlerow .email-tag, + .email-card-expanded .email-card-titlerow .email-tags-more { + transform: translateY(2px); + } +} +.email-card-tags.email-tags-expanded { + position: absolute; + z-index: 4; + right: calc(var(--email-status-width, 44px) + 6px); + top: -5px; + max-width: min(58%, 260px); + padding: 4px 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent); +} +.email-tags-more { + appearance: none; + border: 0; + box-sizing: border-box; + border-radius: 999px; + background: rgba(127, 127, 127, 0.16); + color: var(--fg); + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 1px; + height: 13px !important; + min-height: 0 !important; + min-width: 23px; + padding: 1px 4px; + font-family: inherit; + font-size: 9px; + line-height: 1; + margin: 0; + opacity: 0.68; + position: relative; + top: 0; + vertical-align: middle; +} +.email-tags-more svg { display: block; flex: 0 0 auto; } +.email-tags-expanded .email-tags-more svg { transform: rotate(180deg); } +.email-tags-toggle-inline { + position: absolute; + right: 94px; + top: 50%; + transform: translateY(calc(-50% - 5px)); + height: 20px !important; + min-width: 34px; + padding: 2px 6px !important; + font-size: 10px !important; + line-height: 1 !important; + opacity: 0.62; + border-radius: 999px !important; +} +.email-tags-toggle-inline.active { + opacity: 1; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 36%, transparent); +} +.email-tags-toggle-inline.email-tags-empty { + color: var(--warning, #d6b35a); + background: color-mix(in srgb, var(--warning, #d6b35a) 12%, transparent); + border-color: color-mix(in srgb, var(--warning, #d6b35a) 34%, transparent); +} +.email-tag { + display: inline-block; + font-size: 9px; + line-height: 1; + padding: 2px 5px; + border-radius: 8px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.3px; + background: rgba(127, 127, 127, 0.18); + color: var(--fg); +} +.email-tag-clickable { + appearance: none; + border: 0; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + font-family: inherit; + font-size: 9px; + font-weight: 500; + height: 13px !important; + min-height: 0 !important; + line-height: 1; + margin: 0; + padding: 2px 5px !important; + text-transform: uppercase; + vertical-align: middle; + flex: 0 0 auto; +} +.email-tag-work { background: rgba(96, 165, 250, 0.22); color: #60a5fa; } +.email-tag-personal { background: rgba(74, 222, 128, 0.22); color: #4ade80; } +.email-tag-finance { background: rgba(250, 204, 21, 0.22); color: #facc15; } +.email-tag-bills { background: rgba(251, 146, 60, 0.22); color: #fb923c; } +.email-tag-receipt { background: rgba(251, 146, 60, 0.22); color: #fb923c; } +.email-tag-travel { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } +.email-tag-newsletter { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } +.email-tag-promo, +.email-tag-marketing { background: rgba(244, 114, 182, 0.22); color: #f472b6; } +.email-tag-notification { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } +.email-tag-security { background: rgba(248, 113, 113, 0.22); color: #f87171; } +.email-tag-urgent { background: color-mix(in srgb, var(--color-error, #e06c75) 25%, transparent); color: var(--color-error, #e06c75); font-weight: 600; } +.email-tag-reply-soon { background: rgba(240, 173, 78, 0.22); color: #f0ad4e; } +.email-tag-social { background: rgba(56, 189, 248, 0.22); color: #38bdf8; } +.email-tag-shopping { background: rgba(236, 72, 153, 0.22); color: #ec4899; } +.email-tag-calendar { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } +.email-menu-wrap { position: relative; flex-shrink: 0; } +.email-menu-btn { + background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; + padding: 4px; transition: opacity 0.15s; + position: relative; top: 0px; +} +.email-dropdown { + position: absolute; right: 0; top: 100%; z-index: 9999; + min-width: 120px; background: var(--bg); border: 1px solid var(--border); + border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); padding: 4px 0; +} +.email-loading { padding: 16px; text-align: center; font-size: 12px; opacity: 0.5; } +.email-load-more { padding: 8px; text-align: center; } +.email-load-more-btn { + background: transparent; border: 1px solid var(--border); border-radius: 4px; + color: var(--fg); padding: 4px 12px; font-size: 11px; cursor: pointer; +} +.email-load-more-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } +.email-spinner { + display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); + border-top-color: #fff; border-radius: 50%; animation: email-spin 0.6s linear infinite; + vertical-align: middle; margin-right: 4px; +} + +/* Email section doesn't collapse — hide the auto-injected chevron */ +#email-section .section-collapse-btn { display: none !important; } + +#email-section:hover #email-compose-btn { + opacity: 0.7 !important; +} +#email-compose-btn:hover { + opacity: 1 !important; + background: none !important; +} +#email-compose-btn svg { + width: 11px; + height: 11px; + transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); +} +#email-compose-btn .list-item-plus-label { + top: calc(50% - 0.5px); + color: var(--fg); + font-size: 10px; +} +#email-compose-btn:hover svg { + transform: rotate(180deg) scale(1.15); +} + +/* Library row "+" — identical to the email compose "+": hidden until the row + is hovered, no accent box, and the same rotate-in animation. */ +#library-new-doc-btn, +#notes-new-note-btn { + opacity: 0.4 !important; + transition: opacity 0.15s ease; +} +#tool-library-btn:hover #library-new-doc-btn, +#tool-notes-btn:hover #notes-new-note-btn { + opacity: 0.7 !important; +} +#library-new-doc-btn:hover, +#notes-new-note-btn:hover { + opacity: 1 !important; + background: none !important; +} +#library-new-doc-btn svg, +#notes-new-note-btn svg { + transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); +} +#library-new-doc-btn .list-item-plus-label, +#notes-new-note-btn .list-item-plus-label { + top: calc(50% + 1px); +} +#library-new-doc-btn:hover svg, +#notes-new-note-btn:hover svg { + transform: rotate(180deg) scale(1.15); +} + + +/* MD toolbar attach button — matches other toolbar buttons */ +.md-toolbar-attach-btn { + background: none; border: none; color: var(--fg); opacity: 0.35; + padding: 4px 7px; cursor: pointer; min-height: 28px; + display: inline-flex; align-items: center; justify-content: center; + transition: opacity 0.1s; +} +.md-toolbar-attach-btn:hover { opacity: 1; } + +/* Email reader icon buttons — vertical icon + label stack. */ +.memory-toolbar-btn.reader-icon-btn { + width: auto; + height: 44px; + /* padding: 4px 2px; */ + position: relative; + top: 1px; + display: inline-flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + flex: 0 0 auto; +} +.memory-toolbar-btn.reader-icon-btn svg { width: 18px; height: 18px; } +.memory-toolbar-btn.reader-icon-btn[data-act="reply"] svg { + color: inherit; + stroke: currentColor; +} +.memory-toolbar-btn.reader-icon-btn.active { + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); + color: var(--accent, var(--red)); +} +.memory-toolbar-btn.reader-icon-btn[data-act="more"] { + color: inherit; +} +.memory-toolbar-btn.reader-icon-btn[data-act="more"] svg { fill: currentColor; } +.memory-toolbar-btn.reader-icon-btn.reader-more-active { + background: color-mix(in srgb, var(--fg) 12%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); + color: inherit; +} +/* Cookbook Serve dropdown triggers (the cached-model kebab + the saved-config + arrow) — accent-colored to signal they open a menu, with a filled tint when + their dropdown is open. Matches the email reader More button pattern. */ +.hwfit-cached-menu-btn, +.cookbook-saved-arrow { + color: var(--accent, var(--red)); + /* Base .memory-item-btn is flex with align-items but no justify-content, + so the kebab SVG sat left-aligned inside the button instead of being + horizontally centered. Pin it center. */ + justify-content: center; +} +.hwfit-cached-menu-btn.cookbook-menu-active, +.cookbook-saved-arrow.cookbook-menu-active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); + color: var(--accent, var(--red)); +} + +/* Card action menus share one hit target and baseline. Keep this late in the + cascade because modal buttons and Cookbook Serve add their own offsets. */ +.memory-item-actions > .memory-item-btn, +.memory-item-actions .hwfit-cached-menu-btn { + box-sizing: border-box; + width: 28px; + min-width: 28px; + height: 22px; + min-height: 22px; + margin: 0 !important; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-muted); + font-size: 11px; + display: inline-flex; + align-items: center; + align-self: center; + justify-content: center; + position: relative; + top: 0 !important; + transform: none !important; + line-height: 1; + transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s; +} +.memory-item-actions .hwfit-cached-menu-btn:hover { + opacity: 1 !important; + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: color-mix(in srgb, var(--fg) 24%, transparent); + color: var(--fg); +} +.memory-item-actions .hwfit-cached-menu-btn { + transform: translateY(2px) !important; +} + +@media (min-width: 769px) { + .task-card .memory-item-actions { + top: 6px; + } + .task-card .memory-item-actions > .memory-item-btn { + transform: none !important; + } +} + +/* All kebab menus use the same muted tone as the expand/collapse chevron. */ +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +) { + color: var(--color-muted, var(--fg)) !important; +} +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +) svg { + color: inherit; + fill: currentColor; +} +/* Card kebabs are lightweight menu affordances, not bordered buttons. The + old base hover rule gave only some library cards a boxed outline while + chats, tasks, and cookbook stayed borderless. */ +:is( + .memory-menu-btn, + .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), + .hwfit-cached-menu-btn, + .cookbook-task-menu-btn, + .skill-kebab-btn, + .archive-menu-btn, + .doc-tab-menu-btn, + .email-menu-btn +):hover { + background: none !important; + border-color: transparent !important; + color: var(--fg) !important; + opacity: 1; +} +/* Skills used a separate, larger kebab with a different hover treatment. + Match every other card action trigger. */ +.skill-kebab-btn { + box-sizing: border-box; + width: 28px; + min-width: 28px; + height: 22px; + min-height: 22px; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + color: var(--color-muted) !important; + opacity: 0.55; + top: -2px; +} +@media (max-width: 768px) { + .memory-item-actions > .memory-item-btn, + .memory-item-actions .hwfit-cached-menu-btn { + width: 40px; + min-width: 40px; + height: 40px; + min-height: 40px; + } + /* Card menus all need the same comfortable tap target. These components + previously escaped the shared action-button sizing through more-specific + desktop selectors. */ + .memory-menu-btn, + .skill-kebab-btn, + .task-card .memory-item-actions .memory-item-btn { + width: 40px !important; + min-width: 40px !important; + height: 40px !important; + min-height: 40px !important; + } + .memory-menu-btn { opacity: 0.7; } + /* Document cards seat their actions on a slightly higher title baseline + than chats/research cards. Bring only that kebab down 4px. */ + #doclib-modal .doclib-card[data-doc-id] > .doclib-document-header .memory-item-actions .memory-item-btn { + transform: translateY(-2px) !important; + } +} +/* Mobile: center the kebab/menu dropdowns in the viewport so they don't + slide left/right as the window width changes (they were positioned via + inline `right: ...px` relative to the kebab button). Stable position is + easier to land on by touch. The inline `top:` set by the menu code is + preserved; we only override the horizontal axis. */ +@media (max-width: 768px) { + .email-card-dropdown, + .cookbook-saved-menu, + .cookbook-dep-menu { + left: 50% !important; + right: auto !important; + transform: translateX(-50%); + } +} +/* Mobile-only Cancel item appended to dropdowns (kebab/More menus). On + desktop outside-click closes them cleanly; on touch that's fiddly, so + give an explicit Cancel row. Visually separated from the action items + above by a top divider + slight muted color. */ +.dropdown-cancel-mobile { display: none; } +@media (max-width: 768px) { + .dropdown-cancel-mobile { + display: flex; + border-top: 1px solid var(--border); + margin-top: 4px; + padding-top: 8px; + opacity: 0.85; + } +} +/* Launch-command Copy button morphs into Cancel once Launch has been clicked. */ +.hwfit-serve-copy.hwfit-serve-cancel { + border-color: var(--color-danger, #e06c75) !important; + color: var(--color-danger, #e06c75) !important; +} +.hwfit-serve-copy.hwfit-serve-cancel:hover { + background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); +} + +/* One visual contract for card kebab menus. These menus are built by several + modules, and native button defaults previously made identical rows render + with different fonts, heights, and hover movement. */ +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) { + box-sizing: border-box; + padding: 4px !important; + color: var(--fg); + background: var(--panel, var(--bg)) !important; + border: 1px solid var(--border) !important; + border-radius: 8px !important; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important; + font-family: inherit !important; + font-size: 11px !important; + line-height: 1.2; +} +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) > .dropdown-item-compact { + box-sizing: border-box; + width: 100%; + min-height: 28px; + padding: 6px 8px; + gap: 10px; + border: 0; + border-radius: 6px; + color: var(--fg); + background: transparent; + font: inherit !important; + line-height: 1.2; + text-align: left; + white-space: nowrap; +} +:is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu +) > .dropdown-item-compact:hover:not(:disabled) { + color: var(--fg); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + transform: none; +} +@media (max-width: 768px) { + :is( + .session-dropdown-menu, + .email-card-dropdown, + .memory-item-dropdown, + .task-dropdown, + .skill-kebab-menu, + .doclib-card-dropdown, + .gallery-detail-menu + ) > .dropdown-item-compact { + min-height: 44px; + padding: 12px !important; + gap: 10px !important; + font-size: 14px !important; + } +} +/* Email modal title unread badge — small accent pill next to "Email". */ +.email-lib-unread-badge { + display: inline-block; + font-size: 0.55em; + font-weight: 700; + background: var(--accent-primary, var(--red)); + color: var(--bg, #1a1a1a); + border-radius: 9px; + padding: 1px 7px; + margin-left: 8px; + vertical-align: 2px; + line-height: 1.3; + letter-spacing: 0.02em; + cursor: pointer; +} +.email-lib-unread-badge.email-lib-away-badge { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); + padding: 0 7px; +} +.email-lib-auto-reply-badge { + display: inline-block; + margin-left: 7px; + padding: 1px 7px; + border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border)); + border-radius: 9px; + background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); + color: var(--accent, var(--red)); + font-size: 0.55em; + font-weight: 700; + line-height: 1.3; + letter-spacing: 0.01em; + vertical-align: 2px; + cursor: pointer; +} +.email-lib-auto-reply-status-dot { + width: 6px; + height: 6px; + margin-left: 6px; + border-radius: 50%; + background: var(--accent, var(--red)); + box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), + 0 0 8px color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); + vertical-align: 2px; +} +.modal-header h4 > .email-lib-auto-reply-badge { + font-size: 0.65em; + white-space: nowrap; + vertical-align: 1px; +} +#email-lib-modal.email-lib-unread-active .doclib-modal-content { + box-shadow: + 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent), + 0 0 18px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), + var(--shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.35)); +} +#email-lib-modal .email-lib-header-actions .minimize-btn { + position: relative; + left: 6px; +} +/* Per-card prev/next nav — only visible on the currently expanded card, + sits at the right of the title row next to the done check. */ +.email-card-nav-arrows { + display: none; + margin-left: auto; + gap: 2px; + flex-shrink: 0; + align-items: center; + position: relative; + top: -3px; + left: 19px; +} +.doclib-card.email-card-expanded .email-card-nav-arrows { + display: inline-flex; +} +.email-card-header-menu { + display: none; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 28px; + height: 28px; + padding: 0; + border: none; + border-radius: 6px; + background: none; + color: var(--fg); + opacity: 0.65; + cursor: pointer; + position: relative; + top: -3px; + transition: opacity 0.12s, background 0.12s; +} +.doclib-card.email-card-expanded .email-card-header-menu { + display: inline-flex; +} +.email-card-header-menu:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.email-card-nav-btn { + background: none; + border: none; + color: var(--fg); + opacity: 0.65; + cursor: pointer; + padding: 6px 8px; + border-radius: 6px; + display: inline-flex; + align-items: center; + justify-content: center; + transition: opacity 0.12s, background 0.12s; +} +.email-card-nav-btn:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, transparent); +} + +/* ── Email document editor ── */ +.doc-email-header { + display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; + border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; +} +.doc-email-fields { + display: flex; + flex-direction: column; + gap: 6px; + min-height: 0; +} +.doc-email-collapse-btn { + width: 100%; + min-height: 24px; + display: none; + align-items: center; + gap: 7px; + padding: 2px 4px 3px; + border: none; + background: transparent; + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; + opacity: 0.72; + text-align: left; +} +.doc-email-collapse-btn:hover { opacity: 1; color: var(--accent, var(--red)); } +.doc-email-collapse-btn svg { + flex-shrink: 0; + opacity: 0.65; + transition: transform 0.14s ease; +} +.doc-email-collapse-summary { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + opacity: 0.74; +} +.doc-email-header.doc-email-header-collapsed .doc-email-collapse-btn svg { + transform: rotate(180deg); +} +.email-field { display: flex; align-items: center; gap: 8px; position: relative; } +.email-field label { font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.5; min-width: 50px; text-align: right; flex-shrink: 0; } +.email-field input { + flex: 1; width: 100%; background: transparent; border: 1px solid var(--border); border-radius: 4px; + padding: 5px 8px; font-size: 13px; font-family: inherit; color: var(--fg); outline: none; + min-width: 0; +} +.email-field input:focus { border-color: var(--accent, #4a9eff); } +@container docpane (max-width: 460px) { + .doc-email-header .email-field { + display: grid; + grid-template-columns: 1fr; + gap: 3px; + align-items: stretch; + } + .doc-email-header .email-field label { + min-width: 0; + text-align: left; + } + .doc-email-header .email-field input { + width: 100%; + } +} +/* Cc toggle and attach button are absolute so they don't steal width + from the To input. Anchored to the input's vertical center + 4px + nudge so the label sits where the user expects it. */ +.email-field .email-cc-toggle { + position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); + z-index: 2; +} +/* Accent-tinted prefix label (To / Cc / Bcc / Subject) sitting at + the left edge of each input. Replaces both the old tag + and the all-placeholder accent. */ +.email-field .email-field-prefix { + position: absolute; + left: 10px; + top: 50%; + transform: translateY(-50%); + font-size: 11px; + font-weight: 600; + color: var(--accent-primary, var(--red)); + pointer-events: none; + z-index: 1; + line-height: 1; +} +.email-field input { padding-left: 44px; } +.email-field #doc-email-subject { padding-left: 64px; } +/* X close button inside Cc / Bcc fields. */ +.email-field .email-cc-close { + position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); + background: none; border: none; color: var(--fg); + opacity: 0.4; cursor: pointer; + width: 22px; height: 22px; + display: inline-flex; align-items: center; justify-content: center; + border-radius: 4px; + padding: 0; + z-index: 2; +} +.email-field .email-cc-close:hover { + opacity: 1; + color: var(--accent, var(--red)); + background: color-mix(in srgb, currentColor 10%, transparent); +} +.email-field input { padding-right: 60px; } +.email-field #doc-email-cc, .email-field #doc-email-bcc { padding-right: 32px; } +.email-field #doc-email-subject { padding-right: 8px; } + +.doc-email-actions { + display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; + border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; + align-items: center; + position: relative; + z-index: 30; + pointer-events: auto; +} +#doc-actions-footer .email-send-split { + margin-left: auto; +} +.doc-save-state-icon { display: none; flex: 0 0 auto; } +.doc-save-button[data-save-state="dirty"] { color: var(--accent, var(--red)); } +.doc-save-button[data-save-state="saving"] { color: var(--fg-muted, var(--fg)); } +.doc-save-button[data-save-state="saved"] { color: var(--fg); } +.doc-save-button[data-save-state="error"] { color: var(--color-danger, #e06c75); } +.doc-stats-wrap { + position: relative; + margin-left: 0; + flex: 0 0 auto; +} +.doc-stats-btn { + height: 28px; + border: 0; + border-radius: 4px; + padding: 0 7px; + background: transparent; + color: var(--fg-muted, var(--fg)); + font: inherit; + font-size: 10px; + cursor: pointer; + white-space: nowrap; +} +.doc-stats-btn:hover, +.doc-stats-btn[aria-expanded="true"] { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 7%, transparent); +} +.doc-stats-btn.doc-stats-selected { + color: var(--accent, var(--red)); + font-weight: 600; +} +.doc-stats-popover { + position: fixed; + right: auto; + bottom: auto; + width: 190px; + padding: 8px 10px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); + color: var(--fg); + font-size: 11px; +} +.doc-stats-scope { + padding-bottom: 6px; + margin-bottom: 5px; + border-bottom: 1px solid var(--border); + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + text-transform: uppercase; +} +.doc-stats-row { + min-height: 22px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 14px; +} +/* WYSIWYG email body — what the recipient sees, edited in place. */ +.doc-email-richbody { + flex: 1; + min-height: 0; + overflow-y: auto; + padding: 12px 16px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.6; + color: var(--fg); + background: var(--bg); + outline: none; + white-space: pre-wrap; + text-align: left; + word-wrap: break-word; +} +.doc-email-richbody.richtext-mode { + background: var(--bg); +} +.doc-rich-empty-import { + position: absolute; + top: 90px; + right: 18px; + bottom: auto; + left: auto; + z-index: 2; + display: flex; + pointer-events: none; +} +.doc-rich-empty-import .doc-rich-empty-import-btn { + position: static; + float: none; + margin: 0; + opacity: 1; + pointer-events: auto; + transform: none; +} +.doc-email-richbody.richtext-mode ul.rich-checklist { + list-style: none; + padding-left: 1.8em; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li { + position: relative; + min-height: 1.6em; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li::before { + content: ""; + position: absolute; + left: -1.55em; + top: 0.3em; + width: 0.95em; + height: 0.95em; + box-sizing: border-box; + border: 1.5px solid color-mix(in srgb, var(--fg) 48%, transparent); + border-radius: 3px; + background: var(--sidebar-bg, var(--panel)); + cursor: pointer; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::before { + border-color: var(--accent, var(--red)); + background: var(--accent, var(--red)); +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::after { + content: ""; + position: absolute; + left: -1.27em; + top: 0.44em; + width: 0.28em; + height: 0.5em; + border: solid #fff; + border-width: 0 1.5px 1.5px 0; + transform: rotate(45deg); + pointer-events: none; +} +.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"] { + color: color-mix(in srgb, var(--fg) 62%, transparent); + text-decoration: line-through; +} +.doc-email-richbody.richtext-mode hr { + border: 0; + border-top: 1px solid var(--border); + margin: 1.25em 0; +} +.doc-email-richbody.richtext-mode hr.richtext-page-break { + height: 8px; + border-top: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); + border-bottom: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); + margin: 1.6em 0; + opacity: 0.8; +} +.doc-email-richbody.richtext-mode table { + width: 100%; + margin: 12px 0; + border-collapse: collapse; + table-layout: fixed; +} +.doc-email-richbody.richtext-mode th, +.doc-email-richbody.richtext-mode td { + min-width: 48px; + padding: 7px 8px; + border: 1px solid var(--border); + vertical-align: top; + overflow-wrap: anywhere; +} +.doc-email-richbody.richtext-mode th { + background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 85%, var(--fg) 15%); + font-weight: 600; +} +.doc-email-richbody.richtext-mode code, +.doc-email-richbody.richtext-mode span[style*="OdysseusInlineCode"] { + padding: 0.12em 0.35em; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + border-radius: 3px; + background: color-mix(in srgb, var(--fg) 8%, transparent); + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + white-space: break-spaces; +} +.doc-email-richbody.richtext-mode pre { + margin: 1em 0; + padding: 12px 14px; + overflow-x: auto; + border: 1px solid var(--border); + border-radius: 4px; + background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 82%, #000 18%) !important; + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.9em; + line-height: 1.5; + white-space: pre-wrap; + tab-size: 2; +} + +::highlight(doc-find-results) { + color: inherit; + background-color: color-mix(in srgb, #facc15 48%, transparent); +} + +::highlight(doc-find-current) { + color: inherit; + background-color: color-mix(in srgb, var(--accent, #ef4444) 68%, #facc15); +} + +::highlight(doc-ai-selections) { + color: inherit; + background-color: color-mix(in srgb, var(--accent, #ef4444) 30%, transparent); +} +.doc-email-richbody img.richtext-image { + display: block; + margin: 12px 0; + max-width: 100%; + height: auto; + border-radius: 4px; + cursor: pointer; +} +.doc-email-richbody figure.richtext-image { + margin: 12px 0; + max-width: 100%; +} +.doc-email-richbody figure.richtext-image:not(:has(img)) { display: none; } +.doc-email-richbody figure.richtext-image img.richtext-image { + display: block; + margin-top: 0; + margin-bottom: 0; + max-width: 100%; + height: auto; + border-radius: 4px; +} +.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-60) .richtext-image-caption { width: 60%; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-35) .richtext-image-caption { width: 35%; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-left) .richtext-image-caption { margin-left: 0; margin-right: auto; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-center) .richtext-image-caption { margin-left: auto; margin-right: auto; } +.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-right) .richtext-image-caption { margin-left: auto; margin-right: 0; } +.doc-email-richbody img.richtext-image-size-100 { width: 100%; } +.doc-email-richbody img.richtext-image-size-60 { width: 60%; } +.doc-email-richbody img.richtext-image-size-35 { width: 35%; } +.doc-email-richbody img.richtext-image-align-left { margin-left: 0; margin-right: auto; } +.doc-email-richbody img.richtext-image-align-center { margin-left: auto; margin-right: auto; } +.doc-email-richbody img.richtext-image-align-right { margin-left: auto; margin-right: 0; } +.doc-email-richbody img.richtext-image[data-editor-image-selected] { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 3px; +} +.doc-rich-image-toolbar { + position: fixed; + display: inline-flex; + align-items: center; + gap: 2px; + padding: 2px; + border: 1px solid var(--border); + border-radius: 5px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); + z-index: 10032; +} +.doc-rich-image-toolbar button { + width: 24px; + height: 24px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + border: 0; + border-radius: 3px; + background: transparent; + color: inherit; + cursor: pointer; +} +.doc-rich-image-toolbar button:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--accent, var(--red)); +} +.doc-rich-image-toolbar .doc-rich-image-remove:hover { + color: var(--accent, var(--red)); +} +.doc-rich-image-source-menu { + position: fixed; + width: 260px; + min-width: 260px; + padding: 5px; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 4px 16px color-mix(in srgb, var(--fg) 20%, transparent); + z-index: 10033; + font-size: 11px; +} +.doc-rich-image-source-title { + padding: 5px 7px 4px; + color: var(--fg); + font-weight: 700; + opacity: .75; +} +.doc-rich-image-source-menu > button, +.doc-rich-gallery-item { + width: 100%; + display: flex; + align-items: center; + gap: 7px; + padding: 7px 8px; + border: 0; + border-radius: 4px; + background: transparent; + color: var(--fg); + font: inherit; + text-align: left; + cursor: pointer; +} +.doc-rich-image-source-menu > button[data-image-source="computer"] { + padding-left: 9px; +} +.doc-rich-image-source-menu > button:hover, +.doc-rich-gallery-item:hover { + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + color: var(--accent, var(--red)); +} +.doc-rich-gallery-list { + display: grid; + gap: 2px; + max-height: 300px; + overflow-y: auto; +} +.doc-rich-gallery-item { + min-height: 44px; + min-width: 0; +} +.doc-rich-gallery-item > span:last-child { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.doc-rich-gallery-thumb { + width: 40px; + height: 40px; + flex: 0 0 40px; + border: 1px solid var(--border); + border-radius: 4px; + background-position: center; + background-size: cover; + background-repeat: no-repeat; +} +.rich-color-letter { + border-bottom: 2px solid var(--accent, var(--red)); + font-weight: 700; + line-height: 14px; +} +.rich-color-swatch { + width: 13px !important; + height: 13px !important; + border: 1px solid color-mix(in srgb, var(--fg) 35%, transparent); + border-radius: 50%; +} +.rich-highlight-swatch { border-radius: 50%; } +.rich-color-swatch.is-transparent { + background: + linear-gradient(135deg, transparent 43%, var(--red) 44%, var(--red) 56%, transparent 57%), + var(--bg); +} +.rich-color-palette-menu { + min-width: min(292px, calc(100vw - 16px)); +} +.rich-color-palette-menu .rich-color-reset { + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + margin-bottom: 3px; + padding-bottom: 7px; +} +.rich-color-palette-grid { + display: grid; + grid-template-columns: repeat(2, minmax(126px, 1fr)); + gap: 0 4px; + padding: 0 4px; +} +.rich-color-palette-group { + min-width: 0; +} +.rich-color-palette-label { + padding: 4px 8px 3px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 9px; + font-weight: 600; + text-transform: uppercase; +} +.rich-color-palette-group .doc-overflow-item { + min-width: 0; + padding-inline: 8px; +} +.rich-toolbar-color-custom { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-top: 3px; + padding: 7px 9px 4px 10px; + border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); + color: var(--fg); + font-size: 11px; +} +.rich-toolbar-color-input.cp-swatch-input { + width: 22px; + height: 22px; + flex: 0 0 22px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 28%, var(--border)); + border-radius: 50%; + cursor: pointer; +} +.rich-toolbar-color-input.cp-swatch-input:focus-visible { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 2px; +} + +.rich-toolbar-slider-row { + display: grid; + grid-template-columns: auto minmax(104px, 1fr) 42px; + align-items: center; + gap: 9px; + min-width: 220px; + padding: 10px; + color: var(--fg); + font-size: 11px; +} +.rich-toolbar-range { + width: 100%; + min-width: 0; + height: 8px; + margin: 0; + border: 0; + border-radius: 999px; + background: color-mix(in srgb, var(--fg) 25%, transparent); + accent-color: var(--red); + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} +.rich-toolbar-range::-webkit-slider-thumb { + width: 9px; + height: 9px; + border: 0; + border-radius: 50%; + background: var(--red); + cursor: pointer; + -webkit-appearance: none; + appearance: none; + transition: width 0.12s ease, height 0.12s ease; +} +.rich-toolbar-range:hover::-webkit-slider-thumb, +.rich-toolbar-range:focus::-webkit-slider-thumb, +.rich-toolbar-range:active::-webkit-slider-thumb { + width: 16px; + height: 16px; +} +.rich-toolbar-range::-moz-range-thumb { + width: 9px; + height: 9px; + border: 0; + border-radius: 50%; + background: var(--red); + cursor: pointer; + transition: width 0.12s ease, height 0.12s ease; +} +.rich-toolbar-range:hover::-moz-range-thumb, +.rich-toolbar-range:focus::-moz-range-thumb, +.rich-toolbar-range:active::-moz-range-thumb { + width: 16px; + height: 16px; +} +.rich-toolbar-range:focus-visible { + outline: 2px solid var(--accent, var(--red)); + outline-offset: 4px; +} +.rich-toolbar-slider-value { + width: 42px; + box-sizing: border-box; + padding: 2px 3px; + border: 1px solid transparent; + border-radius: 3px; + background: transparent; + text-align: right; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-variant-numeric: tabular-nums; + -webkit-appearance: textfield; + appearance: textfield; +} +.rich-toolbar-slider-value::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +.rich-toolbar-slider-value:focus { + border-color: var(--accent, var(--red)); + outline: none; + color: var(--fg); +} + +.rich-script-icon, +.rich-font-icon, +.rich-font-size-icon { + display: inline-block; + line-height: 1; + font-size: 11px; +} +.rich-highlight-icon { + display: block; + flex-shrink: 0; + color: var(--accent-primary, var(--red)); +} + +.rich-script-icon sup, +.rich-script-icon sub { + font-size: 7px; + line-height: 0; +} + +.rich-font-icon { + font-family: Georgia, serif; + font-size: 12px; +} + +.rich-font-size-icon { + min-width: 16px; + font-size: 10px; + font-weight: 600; + font-variant-numeric: tabular-nums; + text-align: center; +} +.rich-align-center { text-align: center; } +.rich-align-right { text-align: right; } +.doc-email-richbody .email-quoted-history { + margin-top: 14px; + padding: 10px 0 10px 12px; + border-left: 3px solid var(--border); + color: color-mix(in srgb, var(--fg) 70%, transparent); + font-size: 0.92em; + line-height: 1.45; + white-space: pre-wrap; + user-select: text; + pointer-events: none; +} +.doc-email-richbody h1, .doc-email-richbody h2, .doc-email-richbody h3, +.doc-email-richbody h4, .doc-email-richbody h5, .doc-email-richbody h6 { margin: 0.4em 0; } +.email-more-menu { + position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; + min-width: 160px; background: var(--bg); border: 1px solid var(--border); + border-radius: 6px; box-shadow: 0 -4px 12px rgba(0,0,0,0.2); padding: 4px; + z-index: 100; +} +.email-send-btn { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 20%, transparent); + color: var(--accent-primary, var(--red)); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); + border-radius: 6px; + padding: 0 14px; height: 28px; font-size: 11px; font-weight: 600; cursor: pointer; + font-family: inherit; transition: all 0.15s; white-space: nowrap; + display: inline-flex; align-items: center; gap: 6px; +} +/* Split send button: [ Send │ ▾ ] — the caret drops the send-options menu UP. */ +.email-send-split { position: relative; display: inline-flex; } +.email-send-split .email-send-main { + border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; +} +.email-send-split .email-send-caret { + border-top-left-radius: 0; border-bottom-left-radius: 0; + padding: 0 8px; gap: 0; + /* its left border is the single divider between the two halves */ + border-left: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); +} +.email-send-split .email-send-caret svg { transition: transform 0.15s ease; } +.email-send-split .email-send-caret[aria-expanded="true"] svg { transform: none; } +.email-send-btn svg { flex-shrink: 0; } +.email-send-btn:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); + border-color: var(--accent-primary, var(--red)); +} +.email-draft-btn { + background: none; border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border-radius: 6px; + padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; + font-family: inherit; transition: all 0.15s; white-space: nowrap; +} +.email-draft-btn:hover { border-color: var(--fg); color: var(--fg); } +.email-discard-btn { + background: transparent; + /* Dim the label text via color-mix instead of `opacity:0.6` on the whole + button — opacity multiplies through to the SVG and washes out the accent + X glyph. Per-element color dimming keeps the X at full accent strength. */ + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid var(--border); border-radius: 6px; + padding: 6px 14px; font-size: 12px; cursor: pointer; font-family: inherit; +} +/* The ✕ glyph itself reads in accent. */ +.email-discard-btn svg { color: var(--accent-primary, var(--red)); opacity: 1; } +.email-discard-btn:hover { opacity: 1; color: var(--red, #e55); border-color: var(--red, #e55); } + +/* Compose email "more" menu button — icon only, matches draft btn height */ +#doc-email-more-btn { + background: none; border: 1px solid var(--border); + color: color-mix(in srgb, var(--fg) 60%, transparent); + border-radius: 6px; + width: 28px; height: 28px; padding: 0; + cursor: pointer; font-family: inherit; transition: all 0.15s; + display: inline-flex; align-items: center; justify-content: center; +} +#doc-email-more-btn:hover { + border-color: var(--fg); color: var(--fg); +} +.email-more-wrap { display: inline-flex; } +.email-attachments { + display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 0 58px; +} +.email-attachment-chip { + display: inline-flex; align-items: center; gap: 4px; + padding: 4px 8px; font-size: 11px; background: var(--hover-bg, rgba(255,255,255,0.05)); + border: 1px solid var(--border); border-radius: 12px; color: var(--fg); + text-decoration: none; cursor: pointer; transition: background 0.15s, border-color 0.15s; + max-width: 200px; min-width: 0; +} +.email-attachment-chip:hover { + background: color-mix(in srgb, var(--accent) 15%, transparent); + border-color: var(--accent); +} +.email-attachment-chip.is-expanded { + max-width: 90vw; +} +.email-attachment-chip > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + flex: 1 1 auto; min-width: 0; +} +.email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-attachment-chip-related { + border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); +} +/* "Open in editor" launch icon — same prominent style on desktop AND mobile + (was 24px / dim / no border on desktop, easy to miss). Accent-tinted + background + border makes it read as a real action. */ +.email-attachment-open, +.email-attachment-download, +.email-attachment-calendar-open, +.cal-event-email-source { + display: inline-flex; align-items: center; gap: 4px; + height: 22px; padding: 0 9px; border-radius: 999px; + margin-left: 6px; flex: 0 0 auto; + min-width: 52px; justify-content: center; + font-size: 10px; font-weight: 500; letter-spacing: 0.02em; + color: var(--accent-primary, var(--red)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); + cursor: pointer; transition: background 0.1s, border-color 0.1s, color 0.1s; +} +.email-attachment-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-open svg, +.email-attachment-open > svg, +.email-attachment-download svg, +.email-attachment-download > svg, +.email-attachment-calendar-open svg, +.email-attachment-calendar-open > svg, +.cal-event-email-source svg, +.cal-event-email-source > svg { + width: 12px; height: 12px; + opacity: 0.9; +} +.email-attachment-open:hover, +.email-attachment-download:hover, +.email-attachment-calendar-open:hover, +.cal-event-email-source:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 70%, transparent); +} +.email-attachment-download { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-calendar-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.cal-event-email-source { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-open-label { line-height: 1; } +.email-attachment-chip.is-expanded .email-attachment-open, +.email-attachment-chip.is-expanded .email-attachment-download, +.email-attachment-chip.is-expanded .email-attachment-calendar-open { + width: 24px; + min-width: 24px; + padding: 0; + margin-left: 4px; +} +.email-attachment-chip.is-expanded .email-attachment-open-label { + display: none; +} +@media (max-width: 768px) { + .doc-email-collapse-btn { + background: inherit; + } + /* Mobile: keep the pill but ensure a comfortable touch target. */ + .email-attachment-open { + height: 26px; padding: 0 10px; + min-height: 26px !important; + min-width: 58px; + } + .email-attachments, + .email-compose-atts { + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + padding-left: 0; + padding-bottom: 2px; + } + .email-attachments::-webkit-scrollbar, + .email-compose-atts::-webkit-scrollbar { + display: none; + } + /* Attachment chip body — modest minimum height so the open icon sits + neatly without dominating. */ + .email-attachment-chip, + .email-compose-chip { + flex: 0 0 auto; + padding: 6px 8px !important; + min-height: 36px !important; + } + .email-attachment-chip.is-expanded { + flex-wrap: nowrap; + align-items: center; + max-width: min(92vw, 360px); + } + .email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { + flex: 1 1 auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1.25; + min-width: 0; + } + .email-attachment-chip.is-expanded .att-size { + margin-left: 0; + } + .email-attachment-chip.is-expanded .email-attachment-open, + .email-attachment-chip.is-expanded .email-attachment-download, + .email-attachment-chip.is-expanded .email-attachment-calendar-open { + width: 28px; + min-width: 28px; + padding: 0; + margin-left: 4px; + } + .email-compose-chip .compose-chip-name { + max-width: 190px; + } +} + +/* Compose attachment chips (when sending new email) */ +.email-compose-atts { + display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; + padding: 6px 0 0 0; +} +.email-compose-chip { + display: inline-flex; align-items: center; gap: 4px; + flex: 0 1 auto; + min-width: 0; + max-width: min(240px, 100%); + height: 24px; min-height: 24px; box-sizing: border-box; + padding: 2px 3px 2px 7px; font-size: 11px; + background: var(--hover-bg, rgba(255,255,255,0.05)); + border: 1px solid var(--border); border-radius: 12px; color: var(--fg); +} +.email-compose-chip .compose-chip-name { max-width: 180px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.email-compose-chip .compose-chip-remove { + background: none; border: none; color: var(--fg); opacity: 0.5; + height: 18px; font-size: 14px; line-height: 18px; padding: 0 3px; cursor: pointer; + flex-shrink: 0; +} +.email-compose-chip .compose-chip-remove:hover { opacity: 1; color: var(--red, #e55); } + +.pdf-annotation-line-value { + width: 48px; + height: 22px; + box-sizing: border-box; + padding: 0 5px; + border: 1px solid var(--border); + border-radius: 4px; + outline: none; + background: var(--bg-elev, var(--panel)); + color: var(--fg); + font-family: inherit; + font-size: 10px; + font-weight: 500; + line-height: 1; + text-align: right; +} +.pdf-annotation-line-value:hover { border-color: color-mix(in srgb, var(--fg) 38%, var(--border)); } +.pdf-annotation-line-value:focus { border-color: var(--accent, var(--red)); } + +.email-odysseus-attach-menu { + position: fixed; + z-index: 10050; + width: 300px; + max-width: calc(100vw - 16px); + max-height: min(420px, calc(100vh - 16px)); + padding: 7px; + border: 1px solid var(--border); + border-radius: 7px; + background: var(--bg); + color: var(--fg); + box-shadow: 0 12px 30px rgba(0,0,0,0.28); + overflow: hidden; +} +.email-odysseus-attach-local { + width: 100%; + height: 30px; + display: flex; + align-items: center; + gap: 7px; + padding: 0 9px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, var(--border)); + border-radius: 5px; + background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); + color: var(--fg); + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-local:hover { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 16%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); +} +.email-odysseus-attach-tabs { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 5px; + margin: 7px 0; +} +.email-odysseus-attach-tabs button { + height: 26px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border: 1px solid var(--border); + border-radius: 5px; + background: transparent; + color: var(--fg); + opacity: 0.72; + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-tabs button.active { + opacity: 1; + color: var(--accent-primary, var(--red)); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); +} +.email-odysseus-attach-tabs button svg { + flex-shrink: 0; +} +.email-odysseus-attach-search-wrap { + height: 28px; + display: flex; + align-items: center; + gap: 6px; + margin: -1px 0 7px; + padding: 0 8px; + border: 1px solid var(--border); + border-radius: 5px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + color: color-mix(in srgb, var(--fg) 48%, transparent); +} +.email-odysseus-attach-search-wrap:focus-within { + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + color: var(--accent-primary, var(--red)); + background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, transparent); +} +.email-odysseus-attach-search { + min-width: 0; + flex: 1; + border: 0; + outline: 0; + background: transparent; + color: var(--fg); + font: inherit; + font-size: 11px; +} +.email-odysseus-attach-search::placeholder { + color: color-mix(in srgb, var(--fg) 38%, transparent); +} +.email-odysseus-attach-list { + max-height: 320px; + overflow: auto; + display: flex; + flex-direction: column; + gap: 4px; +} +.email-odysseus-attach-row { + width: 100%; + min-height: 38px; + display: flex; + align-items: center; + gap: 8px; + padding: 6px 7px; + border: 1px solid transparent; + border-radius: 5px; + background: transparent; + color: var(--fg); + text-align: left; + font: inherit; + cursor: pointer; +} +.email-odysseus-attach-row:hover { + background: var(--hover-bg, rgba(255,255,255,0.06)); + border-color: var(--border); +} +.email-odysseus-attach-row.is-selected { + background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); + border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); +} +.email-odysseus-attach-dot { + width: 8px; + height: 8px; + flex: 0 0 auto; + display: inline-block; + border: 1px solid var(--border); + border-radius: 50%; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.email-odysseus-attach-row.is-selected .email-odysseus-attach-dot { + border-color: var(--accent-primary, var(--red)); + background: var(--accent-primary, var(--red)); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); +} +.email-odysseus-attach-icon, +.email-odysseus-attach-thumb { + width: 28px; + height: 28px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + background: color-mix(in srgb, var(--fg) 7%, transparent); + overflow: hidden; +} +.email-odysseus-attach-main { + min-width: 0; + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; +} +.email-odysseus-attach-title, +.email-odysseus-attach-meta { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-odysseus-attach-title { + font-size: 11px; + line-height: 1.2; +} +.email-odysseus-attach-meta { + font-size: 10px; + line-height: 1.15; + opacity: 0.52; +} +.email-odysseus-attach-empty { + padding: 14px 8px; + color: var(--muted, #888); + font-size: 11px; + text-align: center; +} +.email-odysseus-attach-actions { + display: flex; + align-items: center; + gap: 6px; + margin-top: 2px; + padding-top: 5px; + border-top: 1px solid color-mix(in srgb, var(--border) 75%, transparent); + background: var(--bg); + position: sticky; + bottom: 0; +} +.email-odysseus-attach-count { + flex: 1; + min-width: 0; + color: var(--muted, #888); + font-size: 11px; +} +.email-odysseus-attach-selected { + height: 28px; + min-width: 72px; + padding: 0 12px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); + background: var(--accent-primary, var(--red)); + color: #fff; + font: inherit; + font-size: 11px; + cursor: pointer; +} +.email-odysseus-attach-selected svg { + flex-shrink: 0; +} + +.email-cc-toggle { + background: none; border: none; color: var(--fg); + opacity: 0.4; font-size: 11px; cursor: pointer; + padding: 4px 8px; font-family: inherit; +} +.email-cc-toggle:hover { + opacity: 1; + color: var(--accent, #4a9eff); + background: none !important; +} + +@media (max-width: 768px) { + .doc-email-collapse-btn { + display: flex; + } +} + +.email-autocomplete { + position: absolute; top: 100%; left: 58px; right: 0; z-index: 1000; + background: var(--bg); border: 1px solid var(--border); border-radius: 6px; + box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 240px; overflow-y: auto; + margin-top: 2px; +} +.contact-suggestion { + display: flex; justify-content: space-between; gap: 8px; + padding: 6px 10px; font-size: 12px; cursor: pointer; + border-bottom: 1px solid var(--border); +} +.contact-suggestion:last-child { border-bottom: none; } +.contact-suggestion:hover, +.contact-suggestion.active, +.contact-suggestion[aria-selected="true"] { + background: color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 15%, transparent); + outline: 1px solid color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 40%, transparent); + outline-offset: -1px; +} +.contact-suggestion .contact-name { font-weight: 600; color: var(--fg); } +.contact-suggestion .contact-email { opacity: 0.6; font-size: 11px; } +.contact-suggestion-status { + padding: 7px 10px; + color: color-mix(in srgb, var(--fg) 65%, transparent); + font-size: 11px; + cursor: default; +} +.contact-suggestion-status.is-loading { opacity: 0.72; } +.contact-suggestion-status.is-unavailable { + color: color-mix(in srgb, var(--red) 82%, var(--fg)); +} +.email-ai-reply-btn { + background: none; + color: color-mix(in srgb, var(--fg) 60%, transparent); + border: 1px solid var(--border); + border-radius: 6px; + padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; + font-family: inherit; transition: all 0.15s; + display: inline-flex; align-items: center; white-space: nowrap; +} +.email-ai-reply-btn:hover { + border-color: var(--accent, #4a9eff); + color: var(--accent, #4a9eff); +} +/* Align select/new buttons in popup library toolbar */ +#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn, #email-lib-compose-btn { position: relative; top: -4px; } +/* Select + Refresh sit slightly lower than Compose on desktop. */ +#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -2px; } + +#email-lib-modal.email-settings-mode .email-accounts-row, +#email-lib-modal.email-settings-mode .admin-card > .memory-toolbar, +#email-lib-modal.email-settings-mode #email-lib-bulk, +#email-lib-modal.email-settings-mode #email-lib-grid, +#email-lib-modal.email-settings-mode #email-lib-sync-status, +#email-lib-modal.email-settings-mode #email-lib-fab { + display: none !important; +} +#email-lib-settings-page[hidden] { + display: none !important; +} +#email-lib-modal.email-settings-mode #email-lib-settings-page { + display: flex; +} +.email-settings-page { + display: flex; + flex: 1; + min-height: 0; + min-width: 0; + flex-direction: column; + gap: 6px; + overflow: hidden; + padding: 2px 0 8px; + width: 100%; + box-sizing: border-box; + -webkit-overflow-scrolling: touch; +} +.settings-email-preferences-card { + display: contents; +} +.settings-email-preferences-card .email-settings-section { + margin: 0 0 10px; + padding: 14px 12px 12px; + background: var(--panel); +} +.settings-email-preferences-card .email-settings-section:last-child { + margin-bottom: 0; +} +.settings-email-preferences-card .email-settings-style-section { + margin-bottom: 4px; +} +.settings-email-default-card #settings-email-default-host { + display: flex; + justify-content: flex-end; + min-width: 0; + margin-left: auto; +} +.settings-email-default-card .settings-email-default-title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; +} +.settings-email-default-card .settings-email-default-title > span { + min-width: 0; +} +.settings-email-default-card .settings-email-default-title > span > svg { + color: var(--accent, var(--red)); +} +.settings-email-default-card .email-settings-account-picker { + margin-left: 0 !important; +} +.settings-email-default-card .email-settings-support-copy { + position: relative; + top: 6px; +} +.settings-email-default-spacer { + height: 12px; +} +.settings-email-default-card .cookbook-srv-default-label { + position: relative; + top: 2px; +} +.settings-email-default-card .cookbook-srv-default-icon > svg { + position: relative; + top: 7px; +} +.settings-email-settings-legacy { + display: none; +} + +/* Keep the standalone Email Settings cards aligned with the expandable + preference cards below them. */ +[data-settings-panel="email"] > .admin-card { + padding-top: 14px; +} +.email-settings-page-host { + display: flex; + flex: 0 0 auto; + min-height: 0; + padding: 0; + overflow: visible; +} +.settings-email-preferences-card > .email-settings-page-host { width: 100%; } +.email-settings-page-head { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; + min-height: 32px; + padding-bottom: 2px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); +} +.email-settings-account-picker { + min-width: 0; + max-width: 100%; +} +.email-settings-account-picker select { + min-width: 0; + max-width: 100%; +} +.email-settings-back { + position: relative; + top: -2px; +} +.email-settings-header-back { + background: transparent; + color: color-mix(in srgb, var(--fg) 58%, transparent); + border: 1px solid transparent; + width: auto; + height: 24px; + padding: 0 8px 0 6px; + align-items: center; + justify-content: center; + gap: 4px; + cursor: pointer; + border-radius: 999px; + flex-shrink: 0; + position: relative; + font-size: 11px; + font-weight: 600; + line-height: 1; +} +.email-settings-header-back:hover { + background: color-mix(in srgb, var(--fg) 10%, transparent); + border-color: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--fg); +} +.email-settings-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; +} +.email-settings-title { + display: inline-flex; + align-items: center; + gap: 6px; + flex: 1 1 auto; + width: 100%; + min-width: 0; + font-size: 12px; + font-weight: 700; +} +.email-settings-title svg { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-settings-close { + width: 24px; + height: 24px; + border: 1px solid var(--border); + border-radius: 6px; + background: transparent; + color: color-mix(in srgb, var(--fg) 70%, transparent); + cursor: pointer; + font-size: 14px; + line-height: 1; +} +.email-settings-close:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)); +} +.email-settings-body { + display: flex; + flex-direction: column; + flex: 1 1 auto; + gap: 6px; + padding-top: 4px; + box-sizing: border-box; + min-width: 0; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + overscroll-behavior: contain; +} +.email-settings-loading, +.email-settings-error { + font-size: 12px; + color: color-mix(in srgb, var(--fg) 65%, transparent); + padding: 8px 2px; +} +.email-settings-loading { + display: inline-flex; + align-items: center; + gap: 7px; +} +.email-settings-loading-css-whirlpool { + display: inline-block; + width: 14px; + height: 14px; + box-sizing: border-box; + border: 1.5px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); + border-top-color: var(--accent, var(--red)); + border-right-color: color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); + border-radius: 50%; + animation: whirlpool-spin 0.72s linear infinite; + flex: 0 0 auto; +} +.email-settings-loading-spinner { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: var(--accent, var(--red)); +} +.email-settings-section { + display: flex; + flex-direction: column; + flex: 0 0 auto; + gap: 7px; + margin: 0; + padding: 14px 8px 8px; + min-width: 0; + box-sizing: border-box; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, transparent); + transition: opacity 0.15s ease, background 0.15s ease; +} +.email-settings-section.is-disabled { + opacity: 0.58; + background: color-mix(in srgb, var(--fg) 2%, transparent); +} +.email-settings-section-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.email-settings-section-head > div:first-child { + min-width: 0; + flex: 1 1 auto; +} +.email-settings-section-head > .email-settings-enabled-control, +.email-settings-section-head > button { + margin-left: auto; +} +.email-settings-section-toggle { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + width: 100%; + min-width: 0; + padding: 0; + border: 0; + background: transparent; + color: inherit; + text-align: left; + cursor: pointer; +} +.email-settings-section-toggle:hover, +.email-settings-section-toggle:active { + background: transparent; +} +.email-settings-section-toggle-copy { + display: flex; + flex-direction: column; + align-items: flex-start; + min-width: 0; + flex: 1 1 auto; +} +.email-settings-section-toggle > .email-settings-enabled-control { + margin-left: auto; + flex: 0 0 auto; +} +.email-settings-section-chevron { + flex: 0 0 auto; + color: color-mix(in srgb, var(--fg) 62%, transparent); + transition: transform .14s ease, color .14s ease; +} +.email-settings-section-toggle:hover .email-settings-section-chevron, +.email-settings-section-toggle:focus-visible .email-settings-section-chevron { + color: var(--accent, var(--red)); +} +.email-settings-section-title { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 14px; + line-height: 1; + font-weight: 600; + letter-spacing: 0; + margin: 0; + padding: 0; +} +.email-settings-section-title svg { + color: var(--accent, var(--red)); + opacity: 0.6; + flex-shrink: 0; +} +.email-settings-account-scope { + margin-left: 4px; + font-size: 11px; + font-weight: 500; + color: color-mix(in srgb, var(--fg) 60%, transparent); +} +.email-settings-section-body { + display: flex; + flex-direction: column; + gap: 7px; + min-width: 0; + width: 100%; +} +.email-settings-section[open] > .email-settings-section-body { + min-height: 0; + flex: 0 0 auto; + height: auto; + max-height: none; + overflow: visible; + overscroll-behavior: contain; + padding: 0 4px 4px 0; + scrollbar-width: thin; +} +.email-settings-section[open] { + min-height: 0; + flex: 0 0 auto; +} +.email-settings-auto-reply-section[open], +.email-settings-cleanup-section[open]:has(.email-unsub-card) { + flex: 1 1 auto; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + align-content: stretch; +} +.email-settings-auto-reply-section[open] > .email-settings-section-body, +.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body { + flex: 0 0 auto; + height: 100%; + max-height: 100%; + overflow-y: auto; +} +.email-settings-section[open] > .email-settings-section-body > * { + flex: 0 0 auto; +} +.email-settings-section-body-head { + display: flex; + justify-content: flex-end; +} +.email-settings-display-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} +.email-settings-display-title { + display: inline-flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.email-settings-display-title > svg { + flex: 0 0 auto; + color: var(--accent, var(--red)); +} +.email-settings-display-title-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + position: relative; + top: 10px; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-display-title-row .email-settings-section-title { + flex: 1 1 auto; + width: auto; + padding-bottom: 0; + border-bottom: 0; +} +.email-settings-style-section .email-settings-section-title { + display: flex; + width: 100%; + position: relative; + top: 0; + padding-bottom: 8px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-style-section .email-settings-section-head, +.email-settings-style-section .email-settings-section-body { + position: static; +} +.email-settings-style-section .email-settings-section-head { margin-top: 8px; } +.email-settings-style-section .email-settings-section-body { margin-top: 12px; } +.email-settings-style-section .email-settings-section-desc { + margin-top: 16px; + top: 0; +} +.email-settings-auto-reply-section .email-settings-section-title, +.email-settings-cleanup-section .email-settings-section-title { + display: flex; + width: 100%; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-cleanup-section .email-settings-section-title { + position: relative; + top: 10px; +} +.email-settings-auto-reply-section .email-settings-section-desc, +.email-settings-cleanup-section .email-settings-section-desc { + margin-top: 10px; +} +.email-settings-display-section .email-settings-section-desc, +.email-settings-cleanup-section .email-settings-section-desc, +.email-settings-inline-images-section .email-settings-section-desc { + margin-top: 12px; + top: 12px; +} +.email-settings-auto-reply-title-row { + display: flex; + align-items: center; + gap: 12px; + width: 100%; + position: relative; + top: 10px; + padding-bottom: 6px; + border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); +} +.email-settings-auto-reply-section .email-settings-section-head { + margin-top: 8px; +} +.email-settings-auto-reply-section .email-settings-section-body { + margin-top: 8px; +} +.email-settings-auto-reply-section .email-settings-section-desc { + margin-top: 12px; + top: 0; +} +.email-settings-display-section .email-settings-section-desc { + margin-top: 10px; + top: 10px; +} +.email-settings-auto-reply-section .email-settings-section-body { + margin-top: 14px; +} +.email-settings-auto-reply-title-row .email-settings-section-title { + flex: 1 1 auto; + width: auto; + padding-bottom: 0; + border-bottom: 0; +} +.email-settings-support-copy { + color: color-mix(in srgb, var(--fg) 65%, transparent); + font-size: 11px; + line-height: 1.3; +} +.email-settings-clean-btn > svg { + color: var(--accent, var(--red)); +} +.email-settings-clean-btn { + height: 32px; +} +.email-unsub-rescan-btn { + height: 32px; +} +.email-settings-clean-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 7px; + min-width: 0; + margin-left: auto; +} +.email-settings-clean-status { + display: none; + font-size: 10.5px !important; + flex: 0 1 auto; + width: auto !important; + max-width: min(52vw, 360px); + min-width: 0; + overflow: hidden; + text-align: right; + white-space: nowrap; + text-overflow: ellipsis; + position: relative; + top: 2px; +} +.email-settings-clean-status.is-busy { + order: 0; + align-self: center; + width: auto !important; + max-width: 100%; + margin-right: auto; + text-align: left; +} +.email-settings-clean-btn { + flex: 0 0 auto; +} +.email-settings-default .cookbook-srv-default-icon > svg { + position: relative; + top: 3px; +} +.email-unsub-auto-safe-btn > svg { + position: relative; + top: 2px; +} +.email-unsub-auto-safe-btn { + height: 29px; +} +.email-unsub-status, +.email-unsub-panel-status { + position: relative; + top: 2px; +} +.email-unsub-panel-status { + flex: 1 1 220px; + min-width: 0; + margin-right: auto; + overflow: hidden; + color: color-mix(in srgb, var(--fg) 68%, transparent); + font-size: 11px; + line-height: 1.3; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; +} +.email-unsub-panel-status.is-error { + color: var(--red, #e57373); +} +.email-unsub-card .email-tag { + color: var(--accent, var(--red)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); +} +.email-unsub-duplicate-badge { + position: relative; + top: 2px; +} +.email-unsub-accent-icon { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-unsub-card { + position: relative; + transition: opacity .16s ease; +} +.email-unsub-card-details { + display: flex; + flex-direction: column; + gap: 7px; +} +.email-unsub-card-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 24px; + height: 24px; + padding: 0; + border: 0; + background: transparent; + color: color-mix(in srgb, var(--fg) 62%, transparent); + cursor: pointer; +} +.email-unsub-card-toggle:hover, +.email-unsub-card-toggle:focus-visible { + color: var(--accent, var(--red)); +} +.email-unsub-card-toggle > svg { + transition: transform .14s ease; +} +.email-unsub-done-badge { + display: inline-flex; + align-items: center; + gap: 3px; +} +.email-unsub-done-btn { + display: inline-flex; + align-items: center; + gap: 3px; + flex: 0 0 auto; + padding: 3px 6px; + position: relative; + top: -6px; +} +.email-unsub-card-details .email-unsub-done-btn { + top: 0; +} +.email-unsub-done-btn > svg { + color: var(--accent, var(--red)); +} +.email-unsub-ignore-btn { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 22px; + height: 22px; + padding: 0; + position: absolute; + top: 5px; + right: 6px; + color: color-mix(in srgb, var(--fg) 58%, transparent); +} +.email-unsub-card-details .email-unsub-ignore-btn { + position: static; +} +.email-unsub-ignore-btn:hover { + color: var(--accent, var(--red)); +} +.email-unsub-remaining-actions { + display: inline-flex; + align-items: center; + gap: 7px; + flex-wrap: wrap; +} +.email-unsub-remaining-actions button { + display: inline-flex; + align-items: center; + gap: 4px; + position: relative; + top: -2px; +} +.email-unsub-remaining-actions svg { + flex: 0 0 auto; +} +.email-unsub-link-btn { + position: relative; + top: 2px; +} +.email-unsub-status.is-error { + color: var(--accent, var(--red)) !important; +} +.email-settings-section-desc { + position: relative; + top: 8px; + margin-top: 4px; + font-size: 10.5px; + line-height: 1.25; + color: color-mix(in srgb, var(--fg) 55%, transparent); +} +.email-settings-inline-images-section .email-settings-section-desc { + margin-bottom: 4px; +} +.email-settings-inline-images-spacer { + height: 12px; +} +.email-settings-display-spacer { + height: 10px; +} +.email-settings-display-section .email-settings-section-desc { + margin-top: 10px; + top: 10px; +} +.email-settings-display-section, +.email-settings-inline-images-section { + padding-bottom: 18px; +} +.email-settings-cleanup-section .email-settings-section-desc { + margin-top: 14px; + top: 14px; +} +.email-settings-auto-reply-section .email-settings-section-desc { + margin-top: 12px; + top: 10px; +} +.email-settings-toggle, +.email-settings-check { + display: flex; + align-items: center; + gap: 7px; + font-size: 12px; + line-height: 1.25; +} +.email-settings-toggle { + flex-shrink: 0; +} +.email-settings-enabled-control { + display: inline-flex; + align-items: center; + gap: 7px; + flex-shrink: 0; +} +.email-settings-enabled-state { + font-size: 10px; + font-weight: 700; + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.email-settings-section.is-enabled .email-settings-enabled-state { + color: var(--accent, var(--red)); +} +.email-settings-section.is-disabled .email-settings-enabled-state { + color: color-mix(in srgb, var(--fg) 50%, transparent); +} +.email-settings-display-section.is-enabled .email-settings-enabled-state { + color: var(--accent, var(--red)); +} +.email-settings-check { + min-height: 24px; + padding: 3px 0; +} +.email-settings-check > span:first-child { + min-width: 0; + margin-right: auto; +} +.email-settings-check-spacer { + flex: 1; +} +.email-settings-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 7px; + min-width: 0; +} +.email-settings-grid label, +.email-settings-field { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; +} +.email-settings-grid span, +.email-settings-field > span { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0; + color: color-mix(in srgb, var(--fg) 56%, transparent); +} +.email-settings-page input[type="date"], +.email-settings-page input[type="datetime-local"], +.email-settings-page select, +.email-settings-page textarea { + width: 100%; + min-width: 0; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--panel, var(--bg)); + color: var(--fg); + font: inherit; + font-size: 12px; +} +.email-settings-page input[type="date"], +.email-settings-page input[type="datetime-local"], +.email-settings-page select { + height: 28px; + padding: 0 7px; +} +.email-settings-page textarea { + resize: vertical; + min-height: 72px; + padding: 7px; + line-height: 1.35; +} +.email-settings-static-value { + min-height: 28px; + display: flex; + align-items: center; + padding: 0 7px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--panel, var(--bg)) 74%, transparent); + color: color-mix(in srgb, var(--fg) 72%, transparent); + font-size: 12px; +} +.email-settings-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 8px; + margin-top: 2px; +} + +/* Email Settings actions use the same quiet outlined treatment everywhere: + the Settings links, the modal settings page, and unsubscribe actions. */ +[data-settings-panel="email"] #set-email-open-integrations, +[data-settings-panel="email"] #set-email-open-tasks, +[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn, +#email-lib-settings-page .email-settings-section button.memory-toolbar-btn { + background: transparent; + border: 1px solid var(--border); + color: var(--fg); + font-family: inherit; + font-weight: 400; +} +[data-settings-panel="email"] #set-email-open-integrations:hover, +[data-settings-panel="email"] #set-email-open-tasks:hover, +[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn:hover, +#email-lib-settings-page .email-settings-section button.memory-toolbar-btn:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); + color: var(--fg); +} +[data-settings-panel="email"] #set-email-open-integrations > svg, +[data-settings-panel="email"] #set-email-open-tasks > svg { + color: var(--accent, var(--red)); + opacity: 1 !important; +} +[data-settings-panel="email"] .email-style-settings-extract, +[data-settings-panel="email"] .email-style-settings-save, +#email-lib-settings-page .email-style-settings-extract, +#email-lib-settings-page .email-style-settings-save { + background: transparent; + border: 1px solid var(--border); + color: var(--fg); + font-weight: 400; +} +[data-settings-panel="email"] .email-style-settings-extract:hover, +[data-settings-panel="email"] .email-style-settings-save:hover, +#email-lib-settings-page .email-style-settings-extract:hover, +#email-lib-settings-page .email-style-settings-save:hover { + background: color-mix(in srgb, var(--fg) 8%, transparent); + border-color: var(--fg); + color: var(--fg); +} +.email-settings-status, +.email-style-settings-status { + margin-right: auto; + font-size: 11px; + color: color-mix(in srgb, var(--fg) 62%, transparent); +} +.email-settings-status.is-success, +.email-style-settings-status.is-success { + color: var(--color-success, #4caf50); + position: relative; + top: 2px; +} +.email-settings-status.is-error, +.email-style-settings-status.is-error { + color: var(--accent, var(--red)); + position: relative; + top: 2px; +} +.email-settings-save, +.email-style-settings-save { + height: 28px; + min-width: 64px; + justify-content: center; + display: inline-flex; + align-items: center; + gap: 5px; +} +.email-settings-save { + height: 32px; +} +.email-style-settings-extract { + height: 32px; +} +.email-style-settings-save { + height: 32px; +} +.email-settings-save svg, +.email-style-settings-save svg { + color: var(--accent, var(--red)); + flex-shrink: 0; +} +.email-style-settings-extract svg { + color: var(--accent, var(--red)); + position: relative; + top: 2px; +} +.email-settings-task-setting { + color: color-mix(in srgb, var(--fg) 72%, transparent); +} +.email-settings-inline-link { + appearance: none; + border: 0; + padding: 0; + background: transparent; + color: var(--accent, var(--red)); + font: inherit; + text-decoration: underline; + cursor: pointer; +} +.email-style-extract-help { + width: 18px; + height: 18px; + min-width: 18px; + padding: 0; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); + background: color-mix(in srgb, var(--fg) 5%, transparent); + color: color-mix(in srgb, var(--fg) 68%, transparent); + display: inline-flex; + align-items: center; + justify-content: center; + font: inherit; + font-size: 11px; + font-weight: 700; + line-height: 1; + cursor: help; +} +.email-style-extract-help:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); +} +@media (max-width: 420px) { + .email-settings-grid { + grid-template-columns: 1fr; + } +} + + +/* ── Notes ── */ +/* Notes panel — body-level flex sibling of chat-container */ +body.notes-view .chat-container { flex: 1; min-width: 0; } + +/* Mobile: notes panel takes over full screen */ +@media (max-width: 768px) { + body.notes-view .notes-pane { + position: fixed; + inset: 0; + max-width: 100%; + width: 100% !important; + /* Desktop sets height: min(80vh, 820px) which wins over inset:0's bottom:0 + and leaves a gap at the bottom of the viewport. Force full height here. */ + height: 100dvh !important; + max-height: 100dvh !important; + z-index: 170; + /* Bottom-sheet treatment like the document editor: stroked, rounded top, + slides up from the bottom. */ + border: 1px solid var(--border); + border-bottom: none; + border-radius: 14px 14px 0 0; + /* Pane itself never scrolls — its inner .notes-pane-body is the scroller. + Two nested scrollers caused the body's flex:1 to lose its height bound + on Firefox mobile and the last row got clipped off-screen. */ + overflow: hidden; + animation: sheet-enter 0.25s cubic-bezier(0.2, 0.8, 0.2, 1) both; + transform-origin: bottom center; + } + /* Required for the flex:1 body to actually constrain to remaining pane + height instead of expanding to its content (default min-height:auto on + flex items). Without this both grid and list views overflow the viewport + bottom on phones. */ + body.notes-view .notes-pane-body { + min-height: 0; + -webkit-overflow-scrolling: touch; + } + body.notes-view #notes-divider { + display: none; + } + body.notes-view .notes-mobile-grabber { + display: block; + flex-shrink: 0; + height: 18px; + position: relative; + background: var(--bg); + touch-action: none; + cursor: grab; + } + body.notes-view .notes-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; + } + body.notes-view .chat-container { + display: none; + } + body.notes-view .mobile-new-chat-btn, + body.notes-view .hamburger-btn { + display: none !important; + } +} +/* ── Mobile notes UX ──────────────────────────────────── + Tiles become read-only previews on touch ≤768px wide. + Tap opens a fullscreen overlay where editing happens; long-press + toggles drag-to-reorder. Wired up in static/js/notes.js. */ +body.notes-mobile-mode .note-card-corner, +body.notes-mobile-mode .note-card-corner-edit, +body.notes-mobile-mode .note-card-corner-archive, +body.notes-mobile-mode .note-card-corner-copy, +body.notes-mobile-mode .note-card-corner-trash, +body.notes-mobile-mode .note-card-corner-unarchive, +body.notes-mobile-mode .note-card-corner-done, +body.notes-mobile-mode .note-card-edit-corner, +body.notes-mobile-mode .note-card-done, +body.notes-mobile-mode .note-card-actions, +body.notes-mobile-mode .note-cl-quickadd, +body.notes-mobile-mode .note-card .note-checkbox-rm, +/* Panel-fullscreen toggle is redundant on mobile — the panel always + fills the viewport already. */ +body.notes-mobile-mode #notes-fullscreen-toggle { + display: none !important; +} + +/* Header toggle icons (archive view, list/grid switch) — defaults are + tuned for the dense desktop header; on mobile bump them slightly so + they read as tap targets without dominating the header. */ +body.notes-mobile-mode #notes-archive-toggle, +body.notes-mobile-mode #notes-view-toggle { + width: 30px; + height: 30px; + padding: 6px; +} +body.notes-mobile-mode #notes-archive-toggle svg, +body.notes-mobile-mode #notes-view-toggle svg { + width: 17px; + height: 17px; +} +/* The whole card is the tap target on mobile — bigger feedback */ +body.notes-mobile-mode .note-card { + cursor: pointer; + -webkit-tap-highlight-color: transparent; + transition: transform 0.12s ease, box-shadow 0.12s ease; +} + +/* Drag-to-reorder mode — desaturate the grid and sweep a subtle + shimmer across every tile so it's obvious you're in rearrange mode + (the Google Keep "active edit" cue). Also disable scroll inside the + panel so the drag works smoothly. */ +body.notes-drag-mode .notes-pane-body { + overflow: hidden; + touch-action: none; +} diff --git a/static/css/style-part-07.css b/static/css/15-notes.css similarity index 55% rename from static/css/style-part-07.css rename to static/css/15-notes.css index 0c6fc8fee..3c4103273 100644 --- a/static/css/style-part-07.css +++ b/static/css/15-notes.css @@ -1,3002 +1,19 @@ -/* Fragment 8/9 of the former static/style.css. +/* 15-notes - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -.doc-editor-textarea, -#doc-editor-code.email-mode .doc-editor-textarea { - color: var(--fg) !important; - caret-color: var(--fg) !important; - z-index: 1; -} - -/* Emoji picker (monochrome icon picker) */ -.emoji-picker-btn { - background: none; border: none; color: var(--fg); opacity: 0.35; - padding: 4px 7px; cursor: pointer; min-height: 28px; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.1s; -} -.emoji-picker-btn:hover { opacity: 1; } -.emoji-picker { - width: min(300px, 92vw); max-height: min(300px, 55vh); - background: var(--panel); border: 1px solid var(--border); border-radius: 8px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3); - display: flex; flex-direction: column; overflow: hidden; -} -.emoji-picker-search { - padding: 8px 12px; background: var(--panel); border: none; - border-bottom: 1px solid var(--border); color: var(--fg); - font-size: 12px; font-family: inherit; outline: none; -} -.emoji-picker-groups { overflow-y: auto; padding: 4px 0; flex: 1; } -.emoji-picker-group { padding: 0 6px 4px 6px; } -.emoji-picker-group-name { - font-size: 10px; font-weight: 600; opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.5px; - padding: 4px 4px 2px 4px; -} -.emoji-picker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; } -.emoji-picker-item { - background: none; border: none; cursor: pointer; - padding: 4px; border-radius: 4px; - display: flex; align-items: center; justify-content: center; - color: var(--fg); transition: background 0.1s; -} -.emoji-picker-item:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); } -.emoji-picker-item svg { width: 16px; height: 16px; } - -.email-avatar { - width: 28px; height: 28px; border-radius: 50%; background: var(--accent, #4a9eff); - color: #fff; display: flex; align-items: center; justify-content: center; - font-size: 12px; font-weight: 600; flex-shrink: 0; margin-top: 1px; -} -.email-item-content { flex: 1; min-width: 0; overflow: hidden; } -.email-item-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; } -.email-sender { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.email-filter-chip { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; - margin: 4px 8px 6px; - padding: 4px 8px; - border-radius: 12px; - font-size: 11px; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - color: var(--fg); -} -.email-filter-chip-clear { - background: none; - border: none; - color: inherit; - cursor: pointer; - font-size: 14px; - line-height: 1; - padding: 0 2px; - opacity: 0.6; -} -.email-date { font-size: 10px; opacity: 0.5; white-space: nowrap; flex-shrink: 0; } -.email-subject { font-size: 11px; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; } -.email-sent-chip, -.email-folder-chip { - display: inline-flex; - align-items: center; - height: 14px; - padding: 0 5px; - margin-right: 5px; - border-radius: 999px; - font-size: 8px; - line-height: 1; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - vertical-align: 1px; -} -.email-folder-chip { - position: relative; - top: 4px; - color: color-mix(in srgb, var(--fg) 75%, transparent); - background: rgba(127, 127, 127, 0.13); - border-color: rgba(127, 127, 127, 0.24); -} -.email-meta-date-group { - display: inline-flex; - align-items: center; - gap: 5px; - white-space: nowrap; -} -.email-meta-date-group .email-folder-chip { - margin-right: 0; -} -.email-card-expanded .email-sent-chip, -.email-card-reader .email-sent-chip { - position: relative; - top: 4px; -} -.email-tags { display: inline-flex; align-items: center; flex-wrap: nowrap; gap: 3px; margin-left: 6px; vertical-align: middle; position: relative; top: -2px; } -.email-card-tags { - flex: 0 0 auto; - min-width: 0; - max-width: 48%; - position: relative; - top: -4px; -} -.email-card-expanded .email-tags, -.email-card-reader .email-tags { top: -4px; } -.doclib-card-expanded .email-card-tags { top: -4px; } -.email-card-expanded .email-card-titlerow .email-tag, -.email-card-expanded .email-card-titlerow .email-tags-more { - transform: translateY(-2px); -} -@media (max-width: 768px) { - .email-card-expanded .email-card-titlerow .email-tag, - .email-card-expanded .email-card-titlerow .email-tags-more { - transform: translateY(2px); - } -} -.email-card-tags.email-tags-expanded { - position: absolute; - z-index: 4; - right: calc(var(--email-status-width, 44px) + 6px); - top: -5px; - max-width: min(58%, 260px); - padding: 4px 5px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent); -} -.email-tags-more { - appearance: none; - border: 0; - box-sizing: border-box; - border-radius: 999px; - background: rgba(127, 127, 127, 0.16); - color: var(--fg); - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 1px; - height: 13px !important; - min-height: 0 !important; - min-width: 23px; - padding: 1px 4px; - font-family: inherit; - font-size: 9px; - line-height: 1; - margin: 0; - opacity: 0.68; - position: relative; - top: 0; - vertical-align: middle; -} -.email-tags-more svg { display: block; flex: 0 0 auto; } -.email-tags-expanded .email-tags-more svg { transform: rotate(180deg); } -.email-tags-toggle-inline { - position: absolute; - right: 94px; - top: 50%; - transform: translateY(calc(-50% - 5px)); - height: 20px !important; - min-width: 34px; - padding: 2px 6px !important; - font-size: 10px !important; - line-height: 1 !important; - opacity: 0.62; - border-radius: 999px !important; -} -.email-tags-toggle-inline.active { - opacity: 1; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 36%, transparent); -} -.email-tags-toggle-inline.email-tags-empty { - color: var(--warning, #d6b35a); - background: color-mix(in srgb, var(--warning, #d6b35a) 12%, transparent); - border-color: color-mix(in srgb, var(--warning, #d6b35a) 34%, transparent); -} -.email-tag { - display: inline-block; - font-size: 9px; - line-height: 1; - padding: 2px 5px; - border-radius: 8px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.3px; - background: rgba(127, 127, 127, 0.18); - color: var(--fg); -} -.email-tag-clickable { - appearance: none; - border: 0; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - font-family: inherit; - font-size: 9px; - font-weight: 500; - height: 13px !important; - min-height: 0 !important; - line-height: 1; - margin: 0; - padding: 2px 5px !important; - text-transform: uppercase; - vertical-align: middle; - flex: 0 0 auto; -} -.email-tag-work { background: rgba(96, 165, 250, 0.22); color: #60a5fa; } -.email-tag-personal { background: rgba(74, 222, 128, 0.22); color: #4ade80; } -.email-tag-finance { background: rgba(250, 204, 21, 0.22); color: #facc15; } -.email-tag-bills { background: rgba(251, 146, 60, 0.22); color: #fb923c; } -.email-tag-receipt { background: rgba(251, 146, 60, 0.22); color: #fb923c; } -.email-tag-travel { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } -.email-tag-newsletter { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } -.email-tag-promo, -.email-tag-marketing { background: rgba(244, 114, 182, 0.22); color: #f472b6; } -.email-tag-notification { background: rgba(148, 163, 184, 0.22); color: #94a3b8; } -.email-tag-security { background: rgba(248, 113, 113, 0.22); color: #f87171; } -.email-tag-urgent { background: color-mix(in srgb, var(--color-error, #e06c75) 25%, transparent); color: var(--color-error, #e06c75); font-weight: 600; } -.email-tag-reply-soon { background: rgba(240, 173, 78, 0.22); color: #f0ad4e; } -.email-tag-social { background: rgba(56, 189, 248, 0.22); color: #38bdf8; } -.email-tag-shopping { background: rgba(236, 72, 153, 0.22); color: #ec4899; } -.email-tag-calendar { background: rgba(167, 139, 250, 0.22); color: #a78bfa; } -.email-menu-wrap { position: relative; flex-shrink: 0; } -.email-menu-btn { - background: none; border: none; color: var(--fg); opacity: 0; cursor: pointer; - padding: 4px; transition: opacity 0.15s; - position: relative; top: 0px; -} -.email-dropdown { - position: absolute; right: 0; top: 100%; z-index: 9999; - min-width: 120px; background: var(--bg); border: 1px solid var(--border); - border-radius: 6px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); padding: 4px 0; -} -.email-loading { padding: 16px; text-align: center; font-size: 12px; opacity: 0.5; } -.email-load-more { padding: 8px; text-align: center; } -.email-load-more-btn { - background: transparent; border: 1px solid var(--border); border-radius: 4px; - color: var(--fg); padding: 4px 12px; font-size: 11px; cursor: pointer; -} -.email-load-more-btn:hover { background: var(--hover-bg, rgba(255,255,255,0.05)); } -.email-spinner { - display: inline-block; width: 12px; height: 12px; border: 2px solid rgba(255,255,255,0.3); - border-top-color: #fff; border-radius: 50%; animation: email-spin 0.6s linear infinite; - vertical-align: middle; margin-right: 4px; -} - -/* Email section doesn't collapse — hide the auto-injected chevron */ -#email-section .section-collapse-btn { display: none !important; } - -#email-section:hover #email-compose-btn { - opacity: 0.7 !important; -} -#email-compose-btn:hover { - opacity: 1 !important; - background: none !important; -} -#email-compose-btn svg { - width: 11px; - height: 11px; - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); -} -#email-compose-btn .list-item-plus-label { - top: calc(50% - 0.5px); - color: var(--fg); - font-size: 10px; -} -#email-compose-btn:hover svg { - transform: rotate(180deg) scale(1.15); -} - -/* Library row "+" — identical to the email compose "+": hidden until the row - is hovered, no accent box, and the same rotate-in animation. */ -#library-new-doc-btn, -#notes-new-note-btn { - opacity: 0.4 !important; - transition: opacity 0.15s ease; -} -#tool-library-btn:hover #library-new-doc-btn, -#tool-notes-btn:hover #notes-new-note-btn { - opacity: 0.7 !important; -} -#library-new-doc-btn:hover, -#notes-new-note-btn:hover { - opacity: 1 !important; - background: none !important; -} -#library-new-doc-btn svg, -#notes-new-note-btn svg { - transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); -} -#library-new-doc-btn .list-item-plus-label, -#notes-new-note-btn .list-item-plus-label { - top: calc(50% + 1px); -} -#library-new-doc-btn:hover svg, -#notes-new-note-btn:hover svg { - transform: rotate(180deg) scale(1.15); -} - - -/* MD toolbar attach button — matches other toolbar buttons */ -.md-toolbar-attach-btn { - background: none; border: none; color: var(--fg); opacity: 0.35; - padding: 4px 7px; cursor: pointer; min-height: 28px; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.1s; -} -.md-toolbar-attach-btn:hover { opacity: 1; } - -/* Email reader icon buttons — vertical icon + label stack. */ -.memory-toolbar-btn.reader-icon-btn { - width: auto; - height: 44px; - /* padding: 4px 2px; */ - position: relative; - top: 1px; - display: inline-flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 3px; - flex: 0 0 auto; -} -.memory-toolbar-btn.reader-icon-btn svg { width: 18px; height: 18px; } -.memory-toolbar-btn.reader-icon-btn[data-act="reply"] svg { - color: inherit; - stroke: currentColor; -} -.memory-toolbar-btn.reader-icon-btn.active { - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - color: var(--accent, var(--red)); -} -.memory-toolbar-btn.reader-icon-btn[data-act="more"] { - color: inherit; -} -.memory-toolbar-btn.reader-icon-btn[data-act="more"] svg { fill: currentColor; } -.memory-toolbar-btn.reader-icon-btn.reader-more-active { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); - color: inherit; -} -/* Cookbook Serve dropdown triggers (the cached-model kebab + the saved-config - arrow) — accent-colored to signal they open a menu, with a filled tint when - their dropdown is open. Matches the email reader More button pattern. */ -.hwfit-cached-menu-btn, -.cookbook-saved-arrow { - color: var(--accent, var(--red)); - /* Base .memory-item-btn is flex with align-items but no justify-content, - so the kebab SVG sat left-aligned inside the button instead of being - horizontally centered. Pin it center. */ - justify-content: center; -} -.hwfit-cached-menu-btn.cookbook-menu-active, -.cookbook-saved-arrow.cookbook-menu-active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - color: var(--accent, var(--red)); -} - -/* Card action menus share one hit target and baseline. Keep this late in the - cascade because modal buttons and Cookbook Serve add their own offsets. */ -.memory-item-actions > .memory-item-btn, -.memory-item-actions .hwfit-cached-menu-btn { - box-sizing: border-box; - width: 28px; - min-width: 28px; - height: 22px; - min-height: 22px; - margin: 0 !important; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - color: var(--color-muted); - font-size: 11px; - display: inline-flex; - align-items: center; - align-self: center; - justify-content: center; - position: relative; - top: 0 !important; - transform: none !important; - line-height: 1; - transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.15s; -} -.memory-item-actions .hwfit-cached-menu-btn:hover { - opacity: 1 !important; - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); - color: var(--fg); -} -.memory-item-actions .hwfit-cached-menu-btn { - transform: translateY(2px) !important; -} - -@media (min-width: 769px) { - .task-card .memory-item-actions { - top: 6px; - } - .task-card .memory-item-actions > .memory-item-btn { - transform: none !important; - } -} - -/* All kebab menus use the same muted tone as the expand/collapse chevron. */ -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -) { - color: var(--color-muted, var(--fg)) !important; -} -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -) svg { - color: inherit; - fill: currentColor; -} -/* Card kebabs are lightweight menu affordances, not bordered buttons. The - old base hover rule gave only some library cards a boxed outline while - chats, tasks, and cookbook stayed borderless. */ -:is( - .memory-menu-btn, - .memory-item-actions .memory-item-btn:not(.save):not(.pin):not(.delete):not(.doclib-card-action-btn), - .hwfit-cached-menu-btn, - .cookbook-task-menu-btn, - .skill-kebab-btn, - .archive-menu-btn, - .doc-tab-menu-btn, - .email-menu-btn -):hover { - background: none !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1; -} -/* Skills used a separate, larger kebab with a different hover treatment. - Match every other card action trigger. */ -.skill-kebab-btn { - box-sizing: border-box; - width: 28px; - min-width: 28px; - height: 22px; - min-height: 22px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - color: var(--color-muted) !important; - opacity: 0.55; - top: -2px; -} -@media (max-width: 768px) { - .memory-item-actions > .memory-item-btn, - .memory-item-actions .hwfit-cached-menu-btn { - width: 40px; - min-width: 40px; - height: 40px; - min-height: 40px; - } - /* Card menus all need the same comfortable tap target. These components - previously escaped the shared action-button sizing through more-specific - desktop selectors. */ - .memory-menu-btn, - .skill-kebab-btn, - .task-card .memory-item-actions .memory-item-btn { - width: 40px !important; - min-width: 40px !important; - height: 40px !important; - min-height: 40px !important; - } - .memory-menu-btn { opacity: 0.7; } - /* Document cards seat their actions on a slightly higher title baseline - than chats/research cards. Bring only that kebab down 4px. */ - #doclib-modal .doclib-card[data-doc-id] > .doclib-document-header .memory-item-actions .memory-item-btn { - transform: translateY(-2px) !important; - } -} -/* Mobile: center the kebab/menu dropdowns in the viewport so they don't - slide left/right as the window width changes (they were positioned via - inline `right: ...px` relative to the kebab button). Stable position is - easier to land on by touch. The inline `top:` set by the menu code is - preserved; we only override the horizontal axis. */ -@media (max-width: 768px) { - .email-card-dropdown, - .cookbook-saved-menu, - .cookbook-dep-menu { - left: 50% !important; - right: auto !important; - transform: translateX(-50%); - } -} -/* Mobile-only Cancel item appended to dropdowns (kebab/More menus). On - desktop outside-click closes them cleanly; on touch that's fiddly, so - give an explicit Cancel row. Visually separated from the action items - above by a top divider + slight muted color. */ -.dropdown-cancel-mobile { display: none; } -@media (max-width: 768px) { - .dropdown-cancel-mobile { - display: flex; - border-top: 1px solid var(--border); - margin-top: 4px; - padding-top: 8px; - opacity: 0.85; - } -} -/* Launch-command Copy button morphs into Cancel once Launch has been clicked. */ -.hwfit-serve-copy.hwfit-serve-cancel { - border-color: var(--color-danger, #e06c75) !important; - color: var(--color-danger, #e06c75) !important; -} -.hwfit-serve-copy.hwfit-serve-cancel:hover { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); -} - -/* One visual contract for card kebab menus. These menus are built by several - modules, and native button defaults previously made identical rows render - with different fonts, heights, and hover movement. */ -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) { - box-sizing: border-box; - padding: 4px !important; - color: var(--fg); - background: var(--panel, var(--bg)) !important; - border: 1px solid var(--border) !important; - border-radius: 8px !important; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3) !important; - font-family: inherit !important; - font-size: 11px !important; - line-height: 1.2; -} -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) > .dropdown-item-compact { - box-sizing: border-box; - width: 100%; - min-height: 28px; - padding: 6px 8px; - gap: 10px; - border: 0; - border-radius: 6px; - color: var(--fg); - background: transparent; - font: inherit !important; - line-height: 1.2; - text-align: left; - white-space: nowrap; -} -:is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu -) > .dropdown-item-compact:hover:not(:disabled) { - color: var(--fg); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - transform: none; -} -@media (max-width: 768px) { - :is( - .session-dropdown-menu, - .email-card-dropdown, - .memory-item-dropdown, - .task-dropdown, - .skill-kebab-menu, - .doclib-card-dropdown, - .gallery-detail-menu - ) > .dropdown-item-compact { - min-height: 44px; - padding: 12px !important; - gap: 10px !important; - font-size: 14px !important; - } -} -/* Email modal title unread badge — small accent pill next to "Email". */ -.email-lib-unread-badge { - display: inline-block; - font-size: 0.55em; - font-weight: 700; - background: var(--accent-primary, var(--red)); - color: var(--bg, #1a1a1a); - border-radius: 9px; - padding: 1px 7px; - margin-left: 8px; - vertical-align: 2px; - line-height: 1.3; - letter-spacing: 0.02em; - cursor: pointer; -} -.email-lib-unread-badge.email-lib-away-badge { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); - padding: 0 7px; -} -.email-lib-auto-reply-badge { - display: inline-block; - margin-left: 7px; - padding: 1px 7px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border)); - border-radius: 9px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - color: var(--accent, var(--red)); - font-size: 0.55em; - font-weight: 700; - line-height: 1.3; - letter-spacing: 0.01em; - vertical-align: 2px; - cursor: pointer; -} -.email-lib-auto-reply-status-dot { - width: 6px; - height: 6px; - margin-left: 6px; - border-radius: 50%; - background: var(--accent, var(--red)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); - vertical-align: 2px; -} -.modal-header h4 > .email-lib-auto-reply-badge { - font-size: 0.65em; - white-space: nowrap; - vertical-align: 1px; -} -#email-lib-modal.email-lib-unread-active .doclib-modal-content { - box-shadow: - 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 22%, transparent), - 0 0 18px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent), - var(--shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.35)); -} -#email-lib-modal .email-lib-header-actions .minimize-btn { - position: relative; - left: 6px; -} -/* Per-card prev/next nav — only visible on the currently expanded card, - sits at the right of the title row next to the done check. */ -.email-card-nav-arrows { - display: none; - margin-left: auto; - gap: 2px; - flex-shrink: 0; - align-items: center; - position: relative; - top: -3px; - left: 19px; -} -.doclib-card.email-card-expanded .email-card-nav-arrows { - display: inline-flex; -} -.email-card-header-menu { - display: none; - align-items: center; - justify-content: center; - flex-shrink: 0; - width: 28px; - height: 28px; - padding: 0; - border: none; - border-radius: 6px; - background: none; - color: var(--fg); - opacity: 0.65; - cursor: pointer; - position: relative; - top: -3px; - transition: opacity 0.12s, background 0.12s; -} -.doclib-card.email-card-expanded .email-card-header-menu { - display: inline-flex; -} -.email-card-header-menu:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.email-card-nav-btn { - background: none; - border: none; - color: var(--fg); - opacity: 0.65; - cursor: pointer; - padding: 6px 8px; - border-radius: 6px; - display: inline-flex; - align-items: center; - justify-content: center; - transition: opacity 0.12s, background 0.12s; -} -.email-card-nav-btn:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} - -/* ── Email document editor ── */ -.doc-email-header { - display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; - border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; -} -.doc-email-fields { - display: flex; - flex-direction: column; - gap: 6px; - min-height: 0; -} -.doc-email-collapse-btn { - width: 100%; - min-height: 24px; - display: none; - align-items: center; - gap: 7px; - padding: 2px 4px 3px; - border: none; - background: transparent; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - opacity: 0.72; - text-align: left; -} -.doc-email-collapse-btn:hover { opacity: 1; color: var(--accent, var(--red)); } -.doc-email-collapse-btn svg { - flex-shrink: 0; - opacity: 0.65; - transition: transform 0.14s ease; -} -.doc-email-collapse-summary { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - opacity: 0.74; -} -.doc-email-header.doc-email-header-collapsed .doc-email-collapse-btn svg { - transform: rotate(180deg); -} -.email-field { display: flex; align-items: center; gap: 8px; position: relative; } -.email-field label { font-size: 11px; font-weight: 600; color: var(--fg); opacity: 0.5; min-width: 50px; text-align: right; flex-shrink: 0; } -.email-field input { - flex: 1; width: 100%; background: transparent; border: 1px solid var(--border); border-radius: 4px; - padding: 5px 8px; font-size: 13px; font-family: inherit; color: var(--fg); outline: none; - min-width: 0; -} -.email-field input:focus { border-color: var(--accent, #4a9eff); } -@container docpane (max-width: 460px) { - .doc-email-header .email-field { - display: grid; - grid-template-columns: 1fr; - gap: 3px; - align-items: stretch; - } - .doc-email-header .email-field label { - min-width: 0; - text-align: left; - } - .doc-email-header .email-field input { - width: 100%; - } -} -/* Cc toggle and attach button are absolute so they don't steal width - from the To input. Anchored to the input's vertical center + 4px - nudge so the label sits where the user expects it. */ -.email-field .email-cc-toggle { - position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); - z-index: 2; -} -/* Accent-tinted prefix label (To / Cc / Bcc / Subject) sitting at - the left edge of each input. Replaces both the old tag - and the all-placeholder accent. */ -.email-field .email-field-prefix { - position: absolute; - left: 10px; - top: 50%; - transform: translateY(-50%); - font-size: 11px; - font-weight: 600; - color: var(--accent-primary, var(--red)); - pointer-events: none; - z-index: 1; - line-height: 1; -} -.email-field input { padding-left: 44px; } -.email-field #doc-email-subject { padding-left: 64px; } -/* X close button inside Cc / Bcc fields. */ -.email-field .email-cc-close { - position: absolute; right: 6px; top: calc(50% + 1px); transform: translateY(-50%); - background: none; border: none; color: var(--fg); - opacity: 0.4; cursor: pointer; - width: 22px; height: 22px; - display: inline-flex; align-items: center; justify-content: center; - border-radius: 4px; - padding: 0; - z-index: 2; -} -.email-field .email-cc-close:hover { - opacity: 1; - color: var(--accent, var(--red)); - background: color-mix(in srgb, currentColor 10%, transparent); -} -.email-field input { padding-right: 60px; } -.email-field #doc-email-cc, .email-field #doc-email-bcc { padding-right: 32px; } -.email-field #doc-email-subject { padding-right: 8px; } - -.doc-email-actions { - display: flex; gap: 8px; justify-content: flex-end; padding: 10px 14px; - border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; - align-items: center; - position: relative; - z-index: 30; - pointer-events: auto; -} -#doc-actions-footer .email-send-split { - margin-left: auto; -} -.doc-save-state-icon { display: none; flex: 0 0 auto; } -.doc-save-button[data-save-state="dirty"] { color: var(--accent, var(--red)); } -.doc-save-button[data-save-state="saving"] { color: var(--fg-muted, var(--fg)); } -.doc-save-button[data-save-state="saved"] { color: var(--fg); } -.doc-save-button[data-save-state="error"] { color: var(--color-danger, #e06c75); } -.doc-stats-wrap { - position: relative; - margin-left: 0; - flex: 0 0 auto; -} -.doc-stats-btn { - height: 28px; - border: 0; - border-radius: 4px; - padding: 0 7px; - background: transparent; - color: var(--fg-muted, var(--fg)); - font: inherit; - font-size: 10px; - cursor: pointer; - white-space: nowrap; -} -.doc-stats-btn:hover, -.doc-stats-btn[aria-expanded="true"] { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 7%, transparent); -} -.doc-stats-btn.doc-stats-selected { - color: var(--accent, var(--red)); - font-weight: 600; -} -.doc-stats-popover { - position: fixed; - right: auto; - bottom: auto; - width: 190px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.24); - color: var(--fg); - font-size: 11px; -} -.doc-stats-scope { - padding-bottom: 6px; - margin-bottom: 5px; - border-bottom: 1px solid var(--border); - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - text-transform: uppercase; -} -.doc-stats-row { - min-height: 22px; - display: flex; - align-items: center; - justify-content: space-between; - gap: 14px; -} -/* WYSIWYG email body — what the recipient sees, edited in place. */ -.doc-email-richbody { - flex: 1; - min-height: 0; - overflow-y: auto; - padding: 12px 16px; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.6; - color: var(--fg); - background: var(--bg); - outline: none; - white-space: pre-wrap; - text-align: left; - word-wrap: break-word; -} -.doc-email-richbody.richtext-mode { - background: var(--bg); -} -.doc-rich-empty-import { - position: absolute; - top: 90px; - right: 18px; - bottom: auto; - left: auto; - z-index: 2; - display: flex; - pointer-events: none; -} -.doc-rich-empty-import .doc-rich-empty-import-btn { - position: static; - float: none; - margin: 0; - opacity: 1; - pointer-events: auto; - transform: none; -} -.doc-email-richbody.richtext-mode ul.rich-checklist { - list-style: none; - padding-left: 1.8em; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li { - position: relative; - min-height: 1.6em; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li::before { - content: ""; - position: absolute; - left: -1.55em; - top: 0.3em; - width: 0.95em; - height: 0.95em; - box-sizing: border-box; - border: 1.5px solid color-mix(in srgb, var(--fg) 48%, transparent); - border-radius: 3px; - background: var(--sidebar-bg, var(--panel)); - cursor: pointer; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::before { - border-color: var(--accent, var(--red)); - background: var(--accent, var(--red)); -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"]::after { - content: ""; - position: absolute; - left: -1.27em; - top: 0.44em; - width: 0.28em; - height: 0.5em; - border: solid #fff; - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); - pointer-events: none; -} -.doc-email-richbody.richtext-mode ul.rich-checklist > li[data-checked="true"] { - color: color-mix(in srgb, var(--fg) 62%, transparent); - text-decoration: line-through; -} -.doc-email-richbody.richtext-mode hr { - border: 0; - border-top: 1px solid var(--border); - margin: 1.25em 0; -} -.doc-email-richbody.richtext-mode hr.richtext-page-break { - height: 8px; - border-top: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); - border-bottom: 1px dashed color-mix(in srgb, var(--fg) 42%, transparent); - margin: 1.6em 0; - opacity: 0.8; -} -.doc-email-richbody.richtext-mode table { - width: 100%; - margin: 12px 0; - border-collapse: collapse; - table-layout: fixed; -} -.doc-email-richbody.richtext-mode th, -.doc-email-richbody.richtext-mode td { - min-width: 48px; - padding: 7px 8px; - border: 1px solid var(--border); - vertical-align: top; - overflow-wrap: anywhere; -} -.doc-email-richbody.richtext-mode th { - background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 85%, var(--fg) 15%); - font-weight: 600; -} -.doc-email-richbody.richtext-mode code, -.doc-email-richbody.richtext-mode span[style*="OdysseusInlineCode"] { - padding: 0.12em 0.35em; - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 0.9em; - white-space: break-spaces; -} -.doc-email-richbody.richtext-mode pre { - margin: 1em 0; - padding: 12px 14px; - overflow-x: auto; - border: 1px solid var(--border); - border-radius: 4px; - background: color-mix(in srgb, var(--sidebar-bg, var(--panel)) 82%, #000 18%) !important; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 0.9em; - line-height: 1.5; - white-space: pre-wrap; - tab-size: 2; -} - -::highlight(doc-find-results) { - color: inherit; - background-color: color-mix(in srgb, #facc15 48%, transparent); -} - -::highlight(doc-find-current) { - color: inherit; - background-color: color-mix(in srgb, var(--accent, #ef4444) 68%, #facc15); -} - -::highlight(doc-ai-selections) { - color: inherit; - background-color: color-mix(in srgb, var(--accent, #ef4444) 30%, transparent); -} -.doc-email-richbody img.richtext-image { - display: block; - margin: 12px 0; - max-width: 100%; - height: auto; - border-radius: 4px; - cursor: pointer; -} -.doc-email-richbody figure.richtext-image { - margin: 12px 0; - max-width: 100%; -} -.doc-email-richbody figure.richtext-image:not(:has(img)) { display: none; } -.doc-email-richbody figure.richtext-image img.richtext-image { - display: block; - margin-top: 0; - margin-bottom: 0; - max-width: 100%; - height: auto; - border-radius: 4px; -} -.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-60) .richtext-image-caption { width: 60%; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-size-35) .richtext-image-caption { width: 35%; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-left) .richtext-image-caption { margin-left: 0; margin-right: auto; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-center) .richtext-image-caption { margin-left: auto; margin-right: auto; } -.doc-email-richbody figure.richtext-image:has(img.richtext-image-align-right) .richtext-image-caption { margin-left: auto; margin-right: 0; } -.doc-email-richbody img.richtext-image-size-100 { width: 100%; } -.doc-email-richbody img.richtext-image-size-60 { width: 60%; } -.doc-email-richbody img.richtext-image-size-35 { width: 35%; } -.doc-email-richbody img.richtext-image-align-left { margin-left: 0; margin-right: auto; } -.doc-email-richbody img.richtext-image-align-center { margin-left: auto; margin-right: auto; } -.doc-email-richbody img.richtext-image-align-right { margin-left: auto; margin-right: 0; } -.doc-email-richbody img.richtext-image[data-editor-image-selected] { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 3px; -} -.doc-rich-image-toolbar { - position: fixed; - display: inline-flex; - align-items: center; - gap: 2px; - padding: 2px; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 10032; -} -.doc-rich-image-toolbar button { - width: 24px; - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - border: 0; - border-radius: 3px; - background: transparent; - color: inherit; - cursor: pointer; -} -.doc-rich-image-toolbar button:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--accent, var(--red)); -} -.doc-rich-image-toolbar .doc-rich-image-remove:hover { - color: var(--accent, var(--red)); -} -.doc-rich-image-source-menu { - position: fixed; - width: 260px; - min-width: 260px; - padding: 5px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 4px 16px color-mix(in srgb, var(--fg) 20%, transparent); - z-index: 10033; - font-size: 11px; -} -.doc-rich-image-source-title { - padding: 5px 7px 4px; - color: var(--fg); - font-weight: 700; - opacity: .75; -} -.doc-rich-image-source-menu > button, -.doc-rich-gallery-item { - width: 100%; - display: flex; - align-items: center; - gap: 7px; - padding: 7px 8px; - border: 0; - border-radius: 4px; - background: transparent; - color: var(--fg); - font: inherit; - text-align: left; - cursor: pointer; -} -.doc-rich-image-source-menu > button[data-image-source="computer"] { - padding-left: 9px; -} -.doc-rich-image-source-menu > button:hover, -.doc-rich-gallery-item:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--accent, var(--red)); -} -.doc-rich-gallery-list { - display: grid; - gap: 2px; - max-height: 300px; - overflow-y: auto; -} -.doc-rich-gallery-item { - min-height: 44px; - min-width: 0; -} -.doc-rich-gallery-item > span:last-child { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.doc-rich-gallery-thumb { - width: 40px; - height: 40px; - flex: 0 0 40px; - border: 1px solid var(--border); - border-radius: 4px; - background-position: center; - background-size: cover; - background-repeat: no-repeat; -} -.rich-color-letter { - border-bottom: 2px solid var(--accent, var(--red)); - font-weight: 700; - line-height: 14px; -} -.rich-color-swatch { - width: 13px !important; - height: 13px !important; - border: 1px solid color-mix(in srgb, var(--fg) 35%, transparent); - border-radius: 50%; -} -.rich-highlight-swatch { border-radius: 50%; } -.rich-color-swatch.is-transparent { - background: - linear-gradient(135deg, transparent 43%, var(--red) 44%, var(--red) 56%, transparent 57%), - var(--bg); -} -.rich-color-palette-menu { - min-width: min(292px, calc(100vw - 16px)); -} -.rich-color-palette-menu .rich-color-reset { - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - margin-bottom: 3px; - padding-bottom: 7px; -} -.rich-color-palette-grid { - display: grid; - grid-template-columns: repeat(2, minmax(126px, 1fr)); - gap: 0 4px; - padding: 0 4px; -} -.rich-color-palette-group { - min-width: 0; -} -.rich-color-palette-label { - padding: 4px 8px 3px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 9px; - font-weight: 600; - text-transform: uppercase; -} -.rich-color-palette-group .doc-overflow-item { - min-width: 0; - padding-inline: 8px; -} -.rich-toolbar-color-custom { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-top: 3px; - padding: 7px 9px 4px 10px; - border-top: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - color: var(--fg); - font-size: 11px; -} -.rich-toolbar-color-input.cp-swatch-input { - width: 22px; - height: 22px; - flex: 0 0 22px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 28%, var(--border)); - border-radius: 50%; - cursor: pointer; -} -.rich-toolbar-color-input.cp-swatch-input:focus-visible { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 2px; -} - -.rich-toolbar-slider-row { - display: grid; - grid-template-columns: auto minmax(104px, 1fr) 42px; - align-items: center; - gap: 9px; - min-width: 220px; - padding: 10px; - color: var(--fg); - font-size: 11px; -} -.rich-toolbar-range { - width: 100%; - min-width: 0; - height: 8px; - margin: 0; - border: 0; - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 25%, transparent); - accent-color: var(--red); - cursor: pointer; - -webkit-appearance: none; - appearance: none; -} -.rich-toolbar-range::-webkit-slider-thumb { - width: 9px; - height: 9px; - border: 0; - border-radius: 50%; - background: var(--red); - cursor: pointer; - -webkit-appearance: none; - appearance: none; - transition: width 0.12s ease, height 0.12s ease; -} -.rich-toolbar-range:hover::-webkit-slider-thumb, -.rich-toolbar-range:focus::-webkit-slider-thumb, -.rich-toolbar-range:active::-webkit-slider-thumb { - width: 16px; - height: 16px; -} -.rich-toolbar-range::-moz-range-thumb { - width: 9px; - height: 9px; - border: 0; - border-radius: 50%; - background: var(--red); - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.rich-toolbar-range:hover::-moz-range-thumb, -.rich-toolbar-range:focus::-moz-range-thumb, -.rich-toolbar-range:active::-moz-range-thumb { - width: 16px; - height: 16px; -} -.rich-toolbar-range:focus-visible { - outline: 2px solid var(--accent, var(--red)); - outline-offset: 4px; -} -.rich-toolbar-slider-value { - width: 42px; - box-sizing: border-box; - padding: 2px 3px; - border: 1px solid transparent; - border-radius: 3px; - background: transparent; - text-align: right; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-variant-numeric: tabular-nums; - -webkit-appearance: textfield; - appearance: textfield; -} -.rich-toolbar-slider-value::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -.rich-toolbar-slider-value:focus { - border-color: var(--accent, var(--red)); - outline: none; - color: var(--fg); -} - -.rich-script-icon, -.rich-font-icon, -.rich-font-size-icon { - display: inline-block; - line-height: 1; - font-size: 11px; -} -.rich-highlight-icon { - display: block; - flex-shrink: 0; - color: var(--accent-primary, var(--red)); -} - -.rich-script-icon sup, -.rich-script-icon sub { - font-size: 7px; - line-height: 0; -} - -.rich-font-icon { - font-family: Georgia, serif; - font-size: 12px; -} - -.rich-font-size-icon { - min-width: 16px; - font-size: 10px; - font-weight: 600; - font-variant-numeric: tabular-nums; - text-align: center; -} -.rich-align-center { text-align: center; } -.rich-align-right { text-align: right; } -.doc-email-richbody .email-quoted-history { - margin-top: 14px; - padding: 10px 0 10px 12px; - border-left: 3px solid var(--border); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.92em; - line-height: 1.45; - white-space: pre-wrap; - user-select: text; - pointer-events: none; -} -.doc-email-richbody h1, .doc-email-richbody h2, .doc-email-richbody h3, -.doc-email-richbody h4, .doc-email-richbody h5, .doc-email-richbody h6 { margin: 0.4em 0; } -.email-more-menu { - position: absolute; bottom: 100%; right: 0; margin-bottom: 6px; - min-width: 160px; background: var(--bg); border: 1px solid var(--border); - border-radius: 6px; box-shadow: 0 -4px 12px rgba(0,0,0,0.2); padding: 4px; - z-index: 100; -} -.email-send-btn { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 20%, transparent); - color: var(--accent-primary, var(--red)); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); - border-radius: 6px; - padding: 0 14px; height: 28px; font-size: 11px; font-weight: 600; cursor: pointer; - font-family: inherit; transition: all 0.15s; white-space: nowrap; - display: inline-flex; align-items: center; gap: 6px; -} -/* Split send button: [ Send │ ▾ ] — the caret drops the send-options menu UP. */ -.email-send-split { position: relative; display: inline-flex; } -.email-send-split .email-send-main { - border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: none; -} -.email-send-split .email-send-caret { - border-top-left-radius: 0; border-bottom-left-radius: 0; - padding: 0 8px; gap: 0; - /* its left border is the single divider between the two halves */ - border-left: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 50%, transparent); -} -.email-send-split .email-send-caret svg { transition: transform 0.15s ease; } -.email-send-split .email-send-caret[aria-expanded="true"] svg { transform: none; } -.email-send-btn svg { flex-shrink: 0; } -.email-send-btn:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 30%, transparent); - border-color: var(--accent-primary, var(--red)); -} -.email-draft-btn { - background: none; border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border-radius: 6px; - padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; - font-family: inherit; transition: all 0.15s; white-space: nowrap; -} -.email-draft-btn:hover { border-color: var(--fg); color: var(--fg); } -.email-discard-btn { - background: transparent; - /* Dim the label text via color-mix instead of `opacity:0.6` on the whole - button — opacity multiplies through to the SVG and washes out the accent - X glyph. Per-element color dimming keeps the X at full accent strength. */ - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; font-family: inherit; -} -/* The ✕ glyph itself reads in accent. */ -.email-discard-btn svg { color: var(--accent-primary, var(--red)); opacity: 1; } -.email-discard-btn:hover { opacity: 1; color: var(--red, #e55); border-color: var(--red, #e55); } - -/* Compose email "more" menu button — icon only, matches draft btn height */ -#doc-email-more-btn { - background: none; border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border-radius: 6px; - width: 28px; height: 28px; padding: 0; - cursor: pointer; font-family: inherit; transition: all 0.15s; - display: inline-flex; align-items: center; justify-content: center; -} -#doc-email-more-btn:hover { - border-color: var(--fg); color: var(--fg); -} -.email-more-wrap { display: inline-flex; } -.email-attachments { - display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 0 0 58px; -} -.email-attachment-chip { - display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px; font-size: 11px; background: var(--hover-bg, rgba(255,255,255,0.05)); - border: 1px solid var(--border); border-radius: 12px; color: var(--fg); - text-decoration: none; cursor: pointer; transition: background 0.15s, border-color 0.15s; - max-width: 200px; min-width: 0; -} -.email-attachment-chip:hover { - background: color-mix(in srgb, var(--accent) 15%, transparent); - border-color: var(--accent); -} -.email-attachment-chip.is-expanded { - max-width: 90vw; -} -.email-attachment-chip > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - flex: 1 1 auto; min-width: 0; -} -.email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-attachment-chip-related { - border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); -} -/* "Open in editor" launch icon — same prominent style on desktop AND mobile - (was 24px / dim / no border on desktop, easy to miss). Accent-tinted - background + border makes it read as a real action. */ -.email-attachment-open, -.email-attachment-download, -.email-attachment-calendar-open, -.cal-event-email-source { - display: inline-flex; align-items: center; gap: 4px; - height: 22px; padding: 0 9px; border-radius: 999px; - margin-left: 6px; flex: 0 0 auto; - min-width: 52px; justify-content: center; - font-size: 10px; font-weight: 500; letter-spacing: 0.02em; - color: var(--accent-primary, var(--red)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 40%, transparent); - cursor: pointer; transition: background 0.1s, border-color 0.1s, color 0.1s; -} -.email-attachment-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-open svg, -.email-attachment-open > svg, -.email-attachment-download svg, -.email-attachment-download > svg, -.email-attachment-calendar-open svg, -.email-attachment-calendar-open > svg, -.cal-event-email-source svg, -.cal-event-email-source > svg { - width: 12px; height: 12px; - opacity: 0.9; -} -.email-attachment-open:hover, -.email-attachment-download:hover, -.email-attachment-calendar-open:hover, -.cal-event-email-source:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 70%, transparent); -} -.email-attachment-download { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-calendar-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.cal-event-email-source { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-open-label { line-height: 1; } -.email-attachment-chip.is-expanded .email-attachment-open, -.email-attachment-chip.is-expanded .email-attachment-download, -.email-attachment-chip.is-expanded .email-attachment-calendar-open { - width: 24px; - min-width: 24px; - padding: 0; - margin-left: 4px; -} -.email-attachment-chip.is-expanded .email-attachment-open-label { - display: none; -} -@media (max-width: 768px) { - .doc-email-collapse-btn { - background: inherit; - } - /* Mobile: keep the pill but ensure a comfortable touch target. */ - .email-attachment-open { - height: 26px; padding: 0 10px; - min-height: 26px !important; - min-width: 58px; - } - .email-attachments, - .email-compose-atts { - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-left: 0; - padding-bottom: 2px; - } - .email-attachments::-webkit-scrollbar, - .email-compose-atts::-webkit-scrollbar { - display: none; - } - /* Attachment chip body — modest minimum height so the open icon sits - neatly without dominating. */ - .email-attachment-chip, - .email-compose-chip { - flex: 0 0 auto; - padding: 6px 8px !important; - min-height: 36px !important; - } - .email-attachment-chip.is-expanded { - flex-wrap: nowrap; - align-items: center; - max-width: min(92vw, 360px); - } - .email-attachment-chip.is-expanded > span:not(.att-size):not(.email-attachment-open):not(.email-attachment-download):not(.email-attachment-calendar-open) { - flex: 1 1 auto; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.25; - min-width: 0; - } - .email-attachment-chip.is-expanded .att-size { - margin-left: 0; - } - .email-attachment-chip.is-expanded .email-attachment-open, - .email-attachment-chip.is-expanded .email-attachment-download, - .email-attachment-chip.is-expanded .email-attachment-calendar-open { - width: 28px; - min-width: 28px; - padding: 0; - margin-left: 4px; - } - .email-compose-chip .compose-chip-name { - max-width: 190px; - } -} - -/* Compose attachment chips (when sending new email) */ -.email-compose-atts { - display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; gap: 6px; - padding: 6px 0 0 0; -} -.email-compose-chip { - display: inline-flex; align-items: center; gap: 4px; - flex: 0 1 auto; - min-width: 0; - max-width: min(240px, 100%); - height: 24px; min-height: 24px; box-sizing: border-box; - padding: 2px 3px 2px 7px; font-size: 11px; - background: var(--hover-bg, rgba(255,255,255,0.05)); - border: 1px solid var(--border); border-radius: 12px; color: var(--fg); -} -.email-compose-chip .compose-chip-name { max-width: 180px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.email-compose-chip .compose-chip-remove { - background: none; border: none; color: var(--fg); opacity: 0.5; - height: 18px; font-size: 14px; line-height: 18px; padding: 0 3px; cursor: pointer; - flex-shrink: 0; -} -.email-compose-chip .compose-chip-remove:hover { opacity: 1; color: var(--red, #e55); } - -.pdf-annotation-line-value { - width: 48px; - height: 22px; - box-sizing: border-box; - padding: 0 5px; - border: 1px solid var(--border); - border-radius: 4px; - outline: none; - background: var(--bg-elev, var(--panel)); - color: var(--fg); - font-family: inherit; - font-size: 10px; - font-weight: 500; - line-height: 1; - text-align: right; -} -.pdf-annotation-line-value:hover { border-color: color-mix(in srgb, var(--fg) 38%, var(--border)); } -.pdf-annotation-line-value:focus { border-color: var(--accent, var(--red)); } - -.email-odysseus-attach-menu { - position: fixed; - z-index: 10050; - width: 300px; - max-width: calc(100vw - 16px); - max-height: min(420px, calc(100vh - 16px)); - padding: 7px; - border: 1px solid var(--border); - border-radius: 7px; - background: var(--bg); - color: var(--fg); - box-shadow: 0 12px 30px rgba(0,0,0,0.28); - overflow: hidden; -} -.email-odysseus-attach-local { - width: 100%; - height: 30px; - display: flex; - align-items: center; - gap: 7px; - padding: 0 9px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 35%, var(--border)); - border-radius: 5px; - background: color-mix(in srgb, var(--accent-primary, var(--red)) 9%, transparent); - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-local:hover { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 16%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 55%, var(--border)); -} -.email-odysseus-attach-tabs { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 5px; - margin: 7px 0; -} -.email-odysseus-attach-tabs button { - height: 26px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border: 1px solid var(--border); - border-radius: 5px; - background: transparent; - color: var(--fg); - opacity: 0.72; - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-tabs button.active { - opacity: 1; - color: var(--accent-primary, var(--red)); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 8%, transparent); -} -.email-odysseus-attach-tabs button svg { - flex-shrink: 0; -} -.email-odysseus-attach-search-wrap { - height: 28px; - display: flex; - align-items: center; - gap: 6px; - margin: -1px 0 7px; - padding: 0 8px; - border: 1px solid var(--border); - border-radius: 5px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.email-odysseus-attach-search-wrap:focus-within { - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - color: var(--accent-primary, var(--red)); - background: color-mix(in srgb, var(--accent-primary, var(--red)) 6%, transparent); -} -.email-odysseus-attach-search { - min-width: 0; - flex: 1; - border: 0; - outline: 0; - background: transparent; - color: var(--fg); - font: inherit; - font-size: 11px; -} -.email-odysseus-attach-search::placeholder { - color: color-mix(in srgb, var(--fg) 38%, transparent); -} -.email-odysseus-attach-list { - max-height: 320px; - overflow: auto; - display: flex; - flex-direction: column; - gap: 4px; -} -.email-odysseus-attach-row { - width: 100%; - min-height: 38px; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 7px; - border: 1px solid transparent; - border-radius: 5px; - background: transparent; - color: var(--fg); - text-align: left; - font: inherit; - cursor: pointer; -} -.email-odysseus-attach-row:hover { - background: var(--hover-bg, rgba(255,255,255,0.06)); - border-color: var(--border); -} -.email-odysseus-attach-row.is-selected { - background: color-mix(in srgb, var(--accent-primary, var(--red)) 10%, transparent); - border-color: color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); -} -.email-odysseus-attach-dot { - width: 8px; - height: 8px; - flex: 0 0 auto; - display: inline-block; - border: 1px solid var(--border); - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.email-odysseus-attach-row.is-selected .email-odysseus-attach-dot { - border-color: var(--accent-primary, var(--red)); - background: var(--accent-primary, var(--red)); - box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-primary, var(--red)) 18%, transparent); -} -.email-odysseus-attach-icon, -.email-odysseus-attach-thumb { - width: 28px; - height: 28px; - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 7%, transparent); - overflow: hidden; -} -.email-odysseus-attach-main { - min-width: 0; - flex: 1; - display: flex; - flex-direction: column; - gap: 2px; -} -.email-odysseus-attach-title, -.email-odysseus-attach-meta { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-odysseus-attach-title { - font-size: 11px; - line-height: 1.2; -} -.email-odysseus-attach-meta { - font-size: 10px; - line-height: 1.15; - opacity: 0.52; -} -.email-odysseus-attach-empty { - padding: 14px 8px; - color: var(--muted, #888); - font-size: 11px; - text-align: center; -} -.email-odysseus-attach-actions { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - padding-top: 5px; - border-top: 1px solid color-mix(in srgb, var(--border) 75%, transparent); - background: var(--bg); - position: sticky; - bottom: 0; -} -.email-odysseus-attach-count { - flex: 1; - min-width: 0; - color: var(--muted, #888); - font-size: 11px; -} -.email-odysseus-attach-selected { - height: 28px; - min-width: 72px; - padding: 0 12px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--accent-primary, var(--red)) 45%, var(--border)); - background: var(--accent-primary, var(--red)); - color: #fff; - font: inherit; - font-size: 11px; - cursor: pointer; -} -.email-odysseus-attach-selected svg { - flex-shrink: 0; -} - -.email-cc-toggle { - background: none; border: none; color: var(--fg); - opacity: 0.4; font-size: 11px; cursor: pointer; - padding: 4px 8px; font-family: inherit; -} -.email-cc-toggle:hover { - opacity: 1; - color: var(--accent, #4a9eff); - background: none !important; -} - -@media (max-width: 768px) { - .doc-email-collapse-btn { - display: flex; - } -} - -.email-autocomplete { - position: absolute; top: 100%; left: 58px; right: 0; z-index: 1000; - background: var(--bg); border: 1px solid var(--border); border-radius: 6px; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); max-height: 240px; overflow-y: auto; - margin-top: 2px; -} -.contact-suggestion { - display: flex; justify-content: space-between; gap: 8px; - padding: 6px 10px; font-size: 12px; cursor: pointer; - border-bottom: 1px solid var(--border); -} -.contact-suggestion:last-child { border-bottom: none; } -.contact-suggestion:hover, -.contact-suggestion.active, -.contact-suggestion[aria-selected="true"] { - background: color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 15%, transparent); - outline: 1px solid color-mix(in srgb, var(--accent-primary, var(--accent, var(--red))) 40%, transparent); - outline-offset: -1px; -} -.contact-suggestion .contact-name { font-weight: 600; color: var(--fg); } -.contact-suggestion .contact-email { opacity: 0.6; font-size: 11px; } -.contact-suggestion-status { - padding: 7px 10px; - color: color-mix(in srgb, var(--fg) 65%, transparent); - font-size: 11px; - cursor: default; -} -.contact-suggestion-status.is-loading { opacity: 0.72; } -.contact-suggestion-status.is-unavailable { - color: color-mix(in srgb, var(--red) 82%, var(--fg)); -} -.email-ai-reply-btn { - background: none; - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - padding: 0 12px; height: 28px; font-size: 11px; cursor: pointer; - font-family: inherit; transition: all 0.15s; - display: inline-flex; align-items: center; white-space: nowrap; -} -.email-ai-reply-btn:hover { - border-color: var(--accent, #4a9eff); - color: var(--accent, #4a9eff); -} -/* Align select/new buttons in popup library toolbar */ -#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn, #email-lib-compose-btn { position: relative; top: -4px; } -/* Select + Refresh sit slightly lower than Compose on desktop. */ -#email-lib-select-btn, #email-lib-refresh-btn, #email-lib-settings-btn { top: -2px; } - -#email-lib-modal.email-settings-mode .email-accounts-row, -#email-lib-modal.email-settings-mode .admin-card > .memory-toolbar, -#email-lib-modal.email-settings-mode #email-lib-bulk, -#email-lib-modal.email-settings-mode #email-lib-grid, -#email-lib-modal.email-settings-mode #email-lib-sync-status, -#email-lib-modal.email-settings-mode #email-lib-fab { - display: none !important; -} -#email-lib-settings-page[hidden] { - display: none !important; -} -#email-lib-modal.email-settings-mode #email-lib-settings-page { - display: flex; -} -.email-settings-page { - display: flex; - flex: 1; - min-height: 0; - min-width: 0; - flex-direction: column; - gap: 6px; - overflow: hidden; - padding: 2px 0 8px; - width: 100%; - box-sizing: border-box; - -webkit-overflow-scrolling: touch; -} -.settings-email-preferences-card { - display: contents; -} -.settings-email-preferences-card .email-settings-section { - margin: 0 0 10px; - padding: 14px 12px 12px; - background: var(--panel); -} -.settings-email-preferences-card .email-settings-section:last-child { - margin-bottom: 0; -} -.settings-email-preferences-card .email-settings-style-section { - margin-bottom: 4px; -} -.settings-email-default-card #settings-email-default-host { - display: flex; - justify-content: flex-end; - min-width: 0; - margin-left: auto; -} -.settings-email-default-card .settings-email-default-title { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; -} -.settings-email-default-card .settings-email-default-title > span { - min-width: 0; -} -.settings-email-default-card .settings-email-default-title > span > svg { - color: var(--accent, var(--red)); -} -.settings-email-default-card .email-settings-account-picker { - margin-left: 0 !important; -} -.settings-email-default-card .email-settings-support-copy { - position: relative; - top: 6px; -} -.settings-email-default-spacer { - height: 12px; -} -.settings-email-default-card .cookbook-srv-default-label { - position: relative; - top: 2px; -} -.settings-email-default-card .cookbook-srv-default-icon > svg { - position: relative; - top: 7px; -} -.settings-email-settings-legacy { - display: none; -} - -/* Keep the standalone Email Settings cards aligned with the expandable - preference cards below them. */ -[data-settings-panel="email"] > .admin-card { - padding-top: 14px; -} -.email-settings-page-host { - display: flex; - flex: 0 0 auto; - min-height: 0; - padding: 0; - overflow: visible; -} -.settings-email-preferences-card > .email-settings-page-host { width: 100%; } -.email-settings-page-head { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; - min-height: 32px; - padding-bottom: 2px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.email-settings-account-picker { - min-width: 0; - max-width: 100%; -} -.email-settings-account-picker select { - min-width: 0; - max-width: 100%; -} -.email-settings-back { - position: relative; - top: -2px; -} -.email-settings-header-back { - background: transparent; - color: color-mix(in srgb, var(--fg) 58%, transparent); - border: 1px solid transparent; - width: auto; - height: 24px; - padding: 0 8px 0 6px; - align-items: center; - justify-content: center; - gap: 4px; - cursor: pointer; - border-radius: 999px; - flex-shrink: 0; - position: relative; - font-size: 11px; - font-weight: 600; - line-height: 1; -} -.email-settings-header-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); -} -.email-settings-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 8px; -} -.email-settings-title { - display: inline-flex; - align-items: center; - gap: 6px; - flex: 1 1 auto; - width: 100%; - min-width: 0; - font-size: 12px; - font-weight: 700; -} -.email-settings-title svg { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-settings-close { - width: 24px; - height: 24px; - border: 1px solid var(--border); - border-radius: 6px; - background: transparent; - color: color-mix(in srgb, var(--fg) 70%, transparent); - cursor: pointer; - font-size: 14px; - line-height: 1; -} -.email-settings-close:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, var(--border)); -} -.email-settings-body { - display: flex; - flex-direction: column; - flex: 1 1 auto; - gap: 6px; - padding-top: 4px; - box-sizing: border-box; - min-width: 0; - min-height: 0; - overflow-y: auto; - overflow-x: hidden; - overscroll-behavior: contain; -} -.email-settings-loading, -.email-settings-error { - font-size: 12px; - color: color-mix(in srgb, var(--fg) 65%, transparent); - padding: 8px 2px; -} -.email-settings-loading { - display: inline-flex; - align-items: center; - gap: 7px; -} -.email-settings-loading-css-whirlpool { - display: inline-block; - width: 14px; - height: 14px; - box-sizing: border-box; - border: 1.5px solid color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - border-top-color: var(--accent, var(--red)); - border-right-color: color-mix(in srgb, var(--accent, var(--red)) 72%, transparent); - border-radius: 50%; - animation: whirlpool-spin 0.72s linear infinite; - flex: 0 0 auto; -} -.email-settings-loading-spinner { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - color: var(--accent, var(--red)); -} -.email-settings-section { - display: flex; - flex-direction: column; - flex: 0 0 auto; - gap: 7px; - margin: 0; - padding: 14px 8px 8px; - min-width: 0; - box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - transition: opacity 0.15s ease, background 0.15s ease; -} -.email-settings-section.is-disabled { - opacity: 0.58; - background: color-mix(in srgb, var(--fg) 2%, transparent); -} -.email-settings-section-head { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.email-settings-section-head > div:first-child { - min-width: 0; - flex: 1 1 auto; -} -.email-settings-section-head > .email-settings-enabled-control, -.email-settings-section-head > button { - margin-left: auto; -} -.email-settings-section-toggle { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - width: 100%; - min-width: 0; - padding: 0; - border: 0; - background: transparent; - color: inherit; - text-align: left; - cursor: pointer; -} -.email-settings-section-toggle:hover, -.email-settings-section-toggle:active { - background: transparent; -} -.email-settings-section-toggle-copy { - display: flex; - flex-direction: column; - align-items: flex-start; - min-width: 0; - flex: 1 1 auto; -} -.email-settings-section-toggle > .email-settings-enabled-control { - margin-left: auto; - flex: 0 0 auto; -} -.email-settings-section-chevron { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); - transition: transform .14s ease, color .14s ease; -} -.email-settings-section-toggle:hover .email-settings-section-chevron, -.email-settings-section-toggle:focus-visible .email-settings-section-chevron { - color: var(--accent, var(--red)); -} -.email-settings-section-title { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 14px; - line-height: 1; - font-weight: 600; - letter-spacing: 0; - margin: 0; - padding: 0; -} -.email-settings-section-title svg { - color: var(--accent, var(--red)); - opacity: 0.6; - flex-shrink: 0; -} -.email-settings-account-scope { - margin-left: 4px; - font-size: 11px; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -.email-settings-section-body { - display: flex; - flex-direction: column; - gap: 7px; - min-width: 0; - width: 100%; -} -.email-settings-section[open] > .email-settings-section-body { - min-height: 0; - flex: 0 0 auto; - height: auto; - max-height: none; - overflow: visible; - overscroll-behavior: contain; - padding: 0 4px 4px 0; - scrollbar-width: thin; -} -.email-settings-section[open] { - min-height: 0; - flex: 0 0 auto; -} -.email-settings-auto-reply-section[open], -.email-settings-cleanup-section[open]:has(.email-unsub-card) { - flex: 1 1 auto; - display: grid; - grid-template-rows: auto minmax(0, 1fr); - align-content: stretch; -} -.email-settings-auto-reply-section[open] > .email-settings-section-body, -.email-settings-cleanup-section[open]:has(.email-unsub-card) > .email-settings-section-body { - flex: 0 0 auto; - height: 100%; - max-height: 100%; - overflow-y: auto; -} -.email-settings-section[open] > .email-settings-section-body > * { - flex: 0 0 auto; -} -.email-settings-section-body-head { - display: flex; - justify-content: flex-end; -} -.email-settings-display-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} -.email-settings-display-title { - display: inline-flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.email-settings-display-title > svg { - flex: 0 0 auto; - color: var(--accent, var(--red)); -} -.email-settings-display-title-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - position: relative; - top: 10px; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-display-title-row .email-settings-section-title { - flex: 1 1 auto; - width: auto; - padding-bottom: 0; - border-bottom: 0; -} -.email-settings-style-section .email-settings-section-title { - display: flex; - width: 100%; - position: relative; - top: 0; - padding-bottom: 8px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-style-section .email-settings-section-head, -.email-settings-style-section .email-settings-section-body { - position: static; -} -.email-settings-style-section .email-settings-section-head { margin-top: 8px; } -.email-settings-style-section .email-settings-section-body { margin-top: 12px; } -.email-settings-style-section .email-settings-section-desc { - margin-top: 16px; - top: 0; -} -.email-settings-auto-reply-section .email-settings-section-title, -.email-settings-cleanup-section .email-settings-section-title { - display: flex; - width: 100%; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-cleanup-section .email-settings-section-title { - position: relative; - top: 10px; -} -.email-settings-auto-reply-section .email-settings-section-desc, -.email-settings-cleanup-section .email-settings-section-desc { - margin-top: 10px; -} -.email-settings-display-section .email-settings-section-desc, -.email-settings-cleanup-section .email-settings-section-desc, -.email-settings-inline-images-section .email-settings-section-desc { - margin-top: 12px; - top: 12px; -} -.email-settings-auto-reply-title-row { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - position: relative; - top: 10px; - padding-bottom: 6px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); -} -.email-settings-auto-reply-section .email-settings-section-head { - margin-top: 8px; -} -.email-settings-auto-reply-section .email-settings-section-body { - margin-top: 8px; -} -.email-settings-auto-reply-section .email-settings-section-desc { - margin-top: 12px; - top: 0; -} -.email-settings-display-section .email-settings-section-desc { - margin-top: 10px; - top: 10px; -} -.email-settings-auto-reply-section .email-settings-section-body { - margin-top: 14px; -} -.email-settings-auto-reply-title-row .email-settings-section-title { - flex: 1 1 auto; - width: auto; - padding-bottom: 0; - border-bottom: 0; -} -.email-settings-support-copy { - color: color-mix(in srgb, var(--fg) 65%, transparent); - font-size: 11px; - line-height: 1.3; -} -.email-settings-clean-btn > svg { - color: var(--accent, var(--red)); -} -.email-settings-clean-btn { - height: 32px; -} -.email-unsub-rescan-btn { - height: 32px; -} -.email-settings-clean-actions { - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; - gap: 7px; - min-width: 0; - margin-left: auto; -} -.email-settings-clean-status { - display: none; - font-size: 10.5px !important; - flex: 0 1 auto; - width: auto !important; - max-width: min(52vw, 360px); - min-width: 0; - overflow: hidden; - text-align: right; - white-space: nowrap; - text-overflow: ellipsis; - position: relative; - top: 2px; -} -.email-settings-clean-status.is-busy { - order: 0; - align-self: center; - width: auto !important; - max-width: 100%; - margin-right: auto; - text-align: left; -} -.email-settings-clean-btn { - flex: 0 0 auto; -} -.email-settings-default .cookbook-srv-default-icon > svg { - position: relative; - top: 3px; -} -.email-unsub-auto-safe-btn > svg { - position: relative; - top: 2px; -} -.email-unsub-auto-safe-btn { - height: 29px; -} -.email-unsub-status, -.email-unsub-panel-status { - position: relative; - top: 2px; -} -.email-unsub-panel-status { - flex: 1 1 220px; - min-width: 0; - margin-right: auto; - overflow: hidden; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-size: 11px; - line-height: 1.3; - text-align: left; - text-overflow: ellipsis; - white-space: nowrap; -} -.email-unsub-panel-status.is-error { - color: var(--red, #e57373); -} -.email-unsub-card .email-tag { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); -} -.email-unsub-duplicate-badge { - position: relative; - top: 2px; -} -.email-unsub-accent-icon { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-unsub-card { - position: relative; - transition: opacity .16s ease; -} -.email-unsub-card-details { - display: flex; - flex-direction: column; - gap: 7px; -} -.email-unsub-card-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 24px; - height: 24px; - padding: 0; - border: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 62%, transparent); - cursor: pointer; -} -.email-unsub-card-toggle:hover, -.email-unsub-card-toggle:focus-visible { - color: var(--accent, var(--red)); -} -.email-unsub-card-toggle > svg { - transition: transform .14s ease; -} -.email-unsub-done-badge { - display: inline-flex; - align-items: center; - gap: 3px; -} -.email-unsub-done-btn { - display: inline-flex; - align-items: center; - gap: 3px; - flex: 0 0 auto; - padding: 3px 6px; - position: relative; - top: -6px; -} -.email-unsub-card-details .email-unsub-done-btn { - top: 0; -} -.email-unsub-done-btn > svg { - color: var(--accent, var(--red)); -} -.email-unsub-ignore-btn { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 22px; - height: 22px; - padding: 0; - position: absolute; - top: 5px; - right: 6px; - color: color-mix(in srgb, var(--fg) 58%, transparent); -} -.email-unsub-card-details .email-unsub-ignore-btn { - position: static; -} -.email-unsub-ignore-btn:hover { - color: var(--accent, var(--red)); -} -.email-unsub-remaining-actions { - display: inline-flex; - align-items: center; - gap: 7px; - flex-wrap: wrap; -} -.email-unsub-remaining-actions button { - display: inline-flex; - align-items: center; - gap: 4px; - position: relative; - top: -2px; -} -.email-unsub-remaining-actions svg { - flex: 0 0 auto; -} -.email-unsub-link-btn { - position: relative; - top: 2px; -} -.email-unsub-status.is-error { - color: var(--accent, var(--red)) !important; -} -.email-settings-section-desc { - position: relative; - top: 8px; - margin-top: 4px; - font-size: 10.5px; - line-height: 1.25; - color: color-mix(in srgb, var(--fg) 55%, transparent); -} -.email-settings-inline-images-section .email-settings-section-desc { - margin-bottom: 4px; -} -.email-settings-inline-images-spacer { - height: 12px; -} -.email-settings-display-spacer { - height: 10px; -} -.email-settings-display-section .email-settings-section-desc { - margin-top: 10px; - top: 10px; -} -.email-settings-display-section, -.email-settings-inline-images-section { - padding-bottom: 18px; -} -.email-settings-cleanup-section .email-settings-section-desc { - margin-top: 14px; - top: 14px; -} -.email-settings-auto-reply-section .email-settings-section-desc { - margin-top: 12px; - top: 10px; -} -.email-settings-toggle, -.email-settings-check { - display: flex; - align-items: center; - gap: 7px; - font-size: 12px; - line-height: 1.25; -} -.email-settings-toggle { - flex-shrink: 0; -} -.email-settings-enabled-control { - display: inline-flex; - align-items: center; - gap: 7px; - flex-shrink: 0; -} -.email-settings-enabled-state { - font-size: 10px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.email-settings-section.is-enabled .email-settings-enabled-state { - color: var(--accent, var(--red)); -} -.email-settings-section.is-disabled .email-settings-enabled-state { - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.email-settings-display-section.is-enabled .email-settings-enabled-state { - color: var(--accent, var(--red)); -} -.email-settings-check { - min-height: 24px; - padding: 3px 0; -} -.email-settings-check > span:first-child { - min-width: 0; - margin-right: auto; -} -.email-settings-check-spacer { - flex: 1; -} -.email-settings-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 7px; - min-width: 0; -} -.email-settings-grid label, -.email-settings-field { - display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; -} -.email-settings-grid span, -.email-settings-field > span { - font-size: 10px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0; - color: color-mix(in srgb, var(--fg) 56%, transparent); -} -.email-settings-page input[type="date"], -.email-settings-page input[type="datetime-local"], -.email-settings-page select, -.email-settings-page textarea { - width: 100%; - min-width: 0; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel, var(--bg)); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.email-settings-page input[type="date"], -.email-settings-page input[type="datetime-local"], -.email-settings-page select { - height: 28px; - padding: 0 7px; -} -.email-settings-page textarea { - resize: vertical; - min-height: 72px; - padding: 7px; - line-height: 1.35; -} -.email-settings-static-value { - min-height: 28px; - display: flex; - align-items: center; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--panel, var(--bg)) 74%, transparent); - color: color-mix(in srgb, var(--fg) 72%, transparent); - font-size: 12px; -} -.email-settings-actions { - display: flex; - align-items: center; - justify-content: flex-end; - flex-wrap: wrap; - gap: 8px; - margin-top: 2px; -} - -/* Email Settings actions use the same quiet outlined treatment everywhere: - the Settings links, the modal settings page, and unsubscribe actions. */ -[data-settings-panel="email"] #set-email-open-integrations, -[data-settings-panel="email"] #set-email-open-tasks, -[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn, -#email-lib-settings-page .email-settings-section button.memory-toolbar-btn { - background: transparent; - border: 1px solid var(--border); - color: var(--fg); - font-family: inherit; - font-weight: 400; -} -[data-settings-panel="email"] #set-email-open-integrations:hover, -[data-settings-panel="email"] #set-email-open-tasks:hover, -[data-settings-panel="email"] .email-settings-section button.memory-toolbar-btn:hover, -#email-lib-settings-page .email-settings-section button.memory-toolbar-btn:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); - color: var(--fg); -} -[data-settings-panel="email"] #set-email-open-integrations > svg, -[data-settings-panel="email"] #set-email-open-tasks > svg { - color: var(--accent, var(--red)); - opacity: 1 !important; -} -[data-settings-panel="email"] .email-style-settings-extract, -[data-settings-panel="email"] .email-style-settings-save, -#email-lib-settings-page .email-style-settings-extract, -#email-lib-settings-page .email-style-settings-save { - background: transparent; - border: 1px solid var(--border); - color: var(--fg); - font-weight: 400; -} -[data-settings-panel="email"] .email-style-settings-extract:hover, -[data-settings-panel="email"] .email-style-settings-save:hover, -#email-lib-settings-page .email-style-settings-extract:hover, -#email-lib-settings-page .email-style-settings-save:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); - color: var(--fg); -} -.email-settings-status, -.email-style-settings-status { - margin-right: auto; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.email-settings-status.is-success, -.email-style-settings-status.is-success { - color: var(--color-success, #4caf50); - position: relative; - top: 2px; -} -.email-settings-status.is-error, -.email-style-settings-status.is-error { - color: var(--accent, var(--red)); - position: relative; - top: 2px; -} -.email-settings-save, -.email-style-settings-save { - height: 28px; - min-width: 64px; - justify-content: center; - display: inline-flex; - align-items: center; - gap: 5px; -} -.email-settings-save { - height: 32px; -} -.email-style-settings-extract { - height: 32px; -} -.email-style-settings-save { - height: 32px; -} -.email-settings-save svg, -.email-style-settings-save svg { - color: var(--accent, var(--red)); - flex-shrink: 0; -} -.email-style-settings-extract svg { - color: var(--accent, var(--red)); - position: relative; - top: 2px; -} -.email-settings-task-setting { - color: color-mix(in srgb, var(--fg) 72%, transparent); -} -.email-settings-inline-link { - appearance: none; - border: 0; - padding: 0; - background: transparent; - color: var(--accent, var(--red)); - font: inherit; - text-decoration: underline; - cursor: pointer; -} -.email-style-extract-help { - width: 18px; - height: 18px; - min-width: 18px; - padding: 0; - border-radius: 999px; - border: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: color-mix(in srgb, var(--fg) 68%, transparent); - display: inline-flex; - align-items: center; - justify-content: center; - font: inherit; - font-size: 11px; - font-weight: 700; - line-height: 1; - cursor: help; -} -.email-style-extract-help:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 38%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); -} -@media (max-width: 420px) { - .email-settings-grid { - grid-template-columns: 1fr; - } -} - - -/* ── Notes ── */ -/* Notes panel — body-level flex sibling of chat-container */ -body.notes-view .chat-container { flex: 1; min-width: 0; } - -/* Mobile: notes panel takes over full screen */ -@media (max-width: 768px) { - body.notes-view .notes-pane { - position: fixed; - inset: 0; - max-width: 100%; - width: 100% !important; - /* Desktop sets height: min(80vh, 820px) which wins over inset:0's bottom:0 - and leaves a gap at the bottom of the viewport. Force full height here. */ - height: 100dvh !important; - max-height: 100dvh !important; - z-index: 170; - /* Bottom-sheet treatment like the document editor: stroked, rounded top, - slides up from the bottom. */ - border: 1px solid var(--border); - border-bottom: none; - border-radius: 14px 14px 0 0; - /* Pane itself never scrolls — its inner .notes-pane-body is the scroller. - Two nested scrollers caused the body's flex:1 to lose its height bound - on Firefox mobile and the last row got clipped off-screen. */ - overflow: hidden; - animation: sheet-enter 0.25s cubic-bezier(0.2, 0.8, 0.2, 1) both; - transform-origin: bottom center; - } - /* Required for the flex:1 body to actually constrain to remaining pane - height instead of expanding to its content (default min-height:auto on - flex items). Without this both grid and list views overflow the viewport - bottom on phones. */ - body.notes-view .notes-pane-body { - min-height: 0; - -webkit-overflow-scrolling: touch; - } - body.notes-view #notes-divider { - display: none; - } - body.notes-view .notes-mobile-grabber { - display: block; - flex-shrink: 0; - height: 18px; - position: relative; - background: var(--bg); - touch-action: none; - cursor: grab; - } - body.notes-view .notes-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; - } - body.notes-view .chat-container { - display: none; - } - body.notes-view .mobile-new-chat-btn, - body.notes-view .hamburger-btn { - display: none !important; - } -} -/* ── Mobile notes UX ──────────────────────────────────── - Tiles become read-only previews on touch ≤768px wide. - Tap opens a fullscreen overlay where editing happens; long-press - toggles drag-to-reorder. Wired up in static/js/notes.js. */ -body.notes-mobile-mode .note-card-corner, -body.notes-mobile-mode .note-card-corner-edit, -body.notes-mobile-mode .note-card-corner-archive, -body.notes-mobile-mode .note-card-corner-copy, -body.notes-mobile-mode .note-card-corner-trash, -body.notes-mobile-mode .note-card-corner-unarchive, -body.notes-mobile-mode .note-card-corner-done, -body.notes-mobile-mode .note-card-edit-corner, -body.notes-mobile-mode .note-card-done, -body.notes-mobile-mode .note-card-actions, -body.notes-mobile-mode .note-cl-quickadd, -body.notes-mobile-mode .note-card .note-checkbox-rm, -/* Panel-fullscreen toggle is redundant on mobile — the panel always - fills the viewport already. */ -body.notes-mobile-mode #notes-fullscreen-toggle { - display: none !important; -} - -/* Header toggle icons (archive view, list/grid switch) — defaults are - tuned for the dense desktop header; on mobile bump them slightly so - they read as tap targets without dominating the header. */ -body.notes-mobile-mode #notes-archive-toggle, -body.notes-mobile-mode #notes-view-toggle { - width: 30px; - height: 30px; - padding: 6px; -} -body.notes-mobile-mode #notes-archive-toggle svg, -body.notes-mobile-mode #notes-view-toggle svg { - width: 17px; - height: 17px; -} -/* The whole card is the tap target on mobile — bigger feedback */ -body.notes-mobile-mode .note-card { - cursor: pointer; - -webkit-tap-highlight-color: transparent; - transition: transform 0.12s ease, box-shadow 0.12s ease; -} - -/* Drag-to-reorder mode — desaturate the grid and sweep a subtle - shimmer across every tile so it's obvious you're in rearrange mode - (the Google Keep "active edit" cue). Also disable scroll inside the - panel so the drag works smoothly. */ -body.notes-drag-mode .notes-pane-body { - overflow: hidden; - touch-action: none; -} body.notes-drag-mode .note-card { cursor: grab; transform: scale(0.985); @@ -6010,3 +3027,1036 @@ body.notes-drag-mode .note-card { align-items: center; gap: 6px; } +.note-cl-dot { + width: 8px; + height: 8px; + border-radius: 50%; + border: 1.5px solid color-mix(in srgb, var(--fg) 30%, transparent); + flex-shrink: 0; + cursor: pointer; + transition: all 0.15s; +} +.note-cl-dot:hover { border-color: var(--fg); transform: scale(1.2); } +.note-cl-row.done .note-cl-dot { + background: var(--accent); + border-color: var(--accent); +} +.note-cl-row.done .note-cl-text { + opacity: 0.4; + background: linear-gradient(currentColor, currentColor) no-repeat; + background-size: 0 1px; + background-position: 0 calc(50% - 1px); + animation: cl-strike 0.32s ease-out forwards; + transition: opacity 0.2s ease; +} +/* Draws the strikethrough line left-to-right when the row is marked + done, instead of snapping it in full-width on the same frame. */ +@keyframes cl-strike { + to { background-size: 100% 1px; } +} +.note-cl-text { + background: transparent; + border: none; + border-bottom: 1px solid var(--border); + color: var(--fg); + font-size: 12px; + padding: 3px 0 3px 3px; + flex: 1 1 auto; + width: auto; + min-width: 0; + outline: none; +} +.note-cl-text:focus { border-color: var(--fg); } +.note-cl-rm { + background: none; + border: none; + color: var(--fg); + opacity: 0.3; + cursor: pointer; + font-size: 14px; + padding: 0; + width: 24px; + height: 14px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: flex-end; + line-height: 1; +} +.note-cl-rm:hover { opacity: 0.8; color: var(--red); } +.note-cl-add { + background: none; + border: none; + color: var(--fg); + opacity: 0.4; + cursor: pointer; + font-size: 11px; + padding: 4px 0; + text-align: left; +} + +/* ── Calendar ── */ +.cal-modal-content { width:min(680px, 94vw); max-height:88vh; overflow-x:hidden; position:relative; } +#calendar-modal .cal-modal-content { animation: none; } +/* Was overflow-y:auto with grid + day-detail flowing naturally. Now uses a + true splitter layout: the body is a flex column that doesn't scroll + itself; the grid takes the remaining space and scrolls if needed; the + day-detail has an explicit height (driven by the splitter drag via + --cal-detail-h) and shrinks the grid visually as it grows. */ +#cal-body { display:flex; flex-direction:column; gap:8px; overflow:hidden; padding:0; min-height:0; } +#cal-body > .cal-grid { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overflow-x: hidden; } +@media (max-width: 768px) { + /* Let the calendar pane shrink to nothing on mobile so the day-detail + splitter can be dragged all the way to the top, hiding the calendar + entirely. Without min-height:0 the grid (or the week-wrap below) + refuses to shrink past its built-in floor. */ + #cal-body > .cal-grid, + #cal-body > .cal-wk-wrap { + min-height: 0; + max-height: none; + } + /* Let the week grid fill the calendar pane and scroll its hours internally + instead of overflowing #cal-body (which clips it and feels cramped). */ + #cal-body > .cal-wk-wrap { + flex: 1 1 auto; + overflow: auto; + } +} +.cal-toolbar { display:flex; align-items:center; gap:6px; margin-bottom:7px; flex-wrap:wrap; line-height:1; max-width:100%; row-gap:6px; } + +/* Quick-add bar: natural-language event creation. Sits directly under + the toolbar; focused with `Q`; opens the bespoke form pre-filled. */ +.cal-quickadd-row { + display: flex; + align-items: center; + gap: 8px; + margin: 0 0 8px; + padding: 1px 10px; + border: 1px solid var(--border); + border-radius: 8px; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + transition: border-color 0.15s, background 0.15s; + position: relative; +} +/* Two-tone placeholder hint: "Quick add" in accent, the example in grey, both + at one low opacity. Overlays the (empty) input; hidden once the user types. */ +.cal-quickadd-hint { + position: absolute; + left: 10px; + right: 10px; + top: 50%; + transform: translateY(-50%); + display: flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--fg); + opacity: 0.5; + pointer-events: none; + white-space: nowrap; + overflow: hidden; +} +.cal-quickadd-hint .qa-hint-accent { color: var(--accent, var(--red)); } +/* ↵ enter glyph in the hint — makes it obvious the field submits on Enter. */ +.cal-quickadd-hint .qa-hint-enter { margin-left: auto; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.8; } +/* Cycling example text fades out before swap, then fades back in. */ +.cal-quickadd-hint .qa-hint-example { + display: block; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-style: italic; + transition: opacity 0.18s ease; +} +.cal-quickadd-hint .qa-hint-example.qa-hint-fade { opacity: 0; } +.cal-title-hint { + position: absolute; + left: 0; + top: 50%; + transform: translateY(calc(-50% - 2px)); + display: none; + align-items: center; + gap: 7px; + font-size: 18px; + line-height: 1.2; + white-space: nowrap; + pointer-events: none; + color: color-mix(in srgb, var(--fg) 42%, transparent); +} +.cal-hero-title:placeholder-shown ~ .cal-title-hint { display: inline-flex; } +.cal-title-hint .cal-title-enter-ico { color: var(--accent, var(--red)); flex-shrink: 0; } +/* Pure-CSS toggle: hide the hint as soon as the field has real text (no + per-keystroke JS, so typing never triggers a re-render / focus loss). */ +.cal-quickadd-input:not(:placeholder-shown) ~ .cal-quickadd-hint { display: none; } +.cal-quickadd-row:focus-within { + border-color: color-mix(in srgb, var(--accent, var(--fg)) 55%, var(--border)); + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); +} +.cal-quickadd-icon { + color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent); + display: inline-flex; + align-items: center; + flex-shrink: 0; + opacity: 0.7; +} +.cal-quickadd-input { + flex: 1; + min-width: 0; + background: transparent; + border: none; + outline: none; + color: var(--fg); + font-family: inherit; + font-size: 12px; + height: 22px; + padding: 0; +} +.cal-quickadd-status { + font-size: 10px; + opacity: 0.55; + font-variant-numeric: tabular-nums; +} +.cal-toolbar > *, .cal-toolbar-nav > *, .cal-toolbar-right > * { vertical-align:middle; } +.cal-toolbar-nav { display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; } +.cal-toolbar-right { + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 6px; + flex: 1 1 100%; + width: 100%; + margin-left: 0; + margin-top: 6px; + flex-wrap: wrap; +} +.cal-title { font-size:13px; font-weight:600; white-space:nowrap; height:24px; line-height:30px; padding:0 6px; display:inline-block; vertical-align:middle; box-sizing:border-box; } +.cal-toolbar-clock { + margin-left: 8px; + color: var(--accent, var(--red)); + font-size: 11px; + font-weight: 600; + font-variant-numeric: tabular-nums; +} +button.cal-nav { background:color-mix(in srgb, var(--fg) 6%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 8px; height:24px; line-height:22px; cursor:pointer; font-size:11px; font-family:inherit; box-sizing:border-box; vertical-align:middle; } +button.cal-nav:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); } +button.cal-nav.cal-toolbar-arrow { + display: none; + align-items: center; + justify-content: center; + width: 24px; + padding: 0; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + border-radius: 50%; + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + font-size: 18px; + line-height: 1; + opacity: 0.84; +} +.cal-toolbar-arrow-glyph { + position: relative; + top: -1px; + line-height: 1; +} +@media (max-width: 768px) { + button.cal-nav.cal-toolbar-arrow { display: inline-flex !important; } + .cal-toolbar-nav { + flex: 1 1 100%; + width: 100%; + min-width: 0; + } + #cal-next { margin-left: auto; } +} +.cal-week-start-toggle { + display: inline-flex; + align-items: center; + gap: 0; + width: max-content; + padding: 0; + height: 24px; + box-sizing: border-box; + border: 1px solid var(--border); + border-radius: 5px; + background: color-mix(in srgb, var(--fg) 4%, transparent); +} +.cal-week-start-btn { + box-sizing: border-box; + height: 22px !important; + min-height: 22px !important; + margin-top: 0 !important; + padding: 0 7px; + border: 1px solid transparent; + border-radius: 4px; + background: transparent; + color: color-mix(in srgb, var(--fg) 68%, transparent); + cursor: pointer; + font-family: inherit; + font-size: 10px; + line-height: 20px; + transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; +} +.cal-settings-actions { + display: flex; + justify-content: flex-end; + width: 100%; +} +#cal-settings-panel .cal-week-start-toggle { + display: flex; + margin-left: auto; +} +.cal-settings-sync-status { + position: relative; + top: 2px; + font-size: 11px; + opacity: 0.6; +} +.cal-settings-sync-status.is-error { + color: var(--red); + opacity: 1; +} +.cal-week-start-btn:hover { + color: var(--fg); + background: color-mix(in srgb, var(--fg) 8%, transparent); +} +.cal-week-start-btn.active, +.cal-week-start-btn.active:hover { + color: var(--fg); + border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); +} +.cal-side-nav { + position: absolute; + top: var(--cal-side-nav-y, 50%); + transform: translateY(-50%); + width: 32px; + height: 32px; + border-radius: 50%; + border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); + background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); + color: var(--accent, var(--red)); + box-shadow: 0 6px 18px rgba(0,0,0,0.16); + cursor: pointer; + z-index: 6; + opacity: 0; + pointer-events: none; + font-family: inherit; + font-size: 24px; + line-height: 28px; + padding: 0 0 3px; + transition: opacity 0.12s ease, background 0.12s ease, transform 0.12s ease; +} +.cal-modal-content:hover .cal-side-nav, +.cal-side-nav:focus-visible { + opacity: 0.84; + pointer-events: auto; +} +.cal-side-nav:hover { + opacity: 1; + background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); + transform: translateY(-50%) scale(1.04); +} +button.cal-today-btn { font-size:10px; opacity:0.5; } +button.cal-add-btn { background:var(--accent); color:#fff; border:none; border-radius:50%; width:24px; height:24px; line-height:22px; font-size:18px; cursor:pointer; flex-shrink:0; padding:0; box-sizing:border-box; font-family:inherit; vertical-align:middle; text-align:center; } +button.cal-add-btn.cal-add-btn-text { + width:auto; min-width:0; + background: color-mix(in srgb, var(--fg) 8%, transparent); + color: var(--fg); + border: 1px solid var(--border); + border-radius: 12px; + padding: 0 10px 0 6px; + display: inline-flex; + align-items: center; + gap: 4px; + height: 24px; + line-height: 1; + margin-top: 0; + transition: background 0.15s, border-color 0.15s; +} +button.cal-add-btn.cal-add-btn-text:hover { + background: color-mix(in srgb, var(--fg) 14%, transparent); + border-color: var(--accent); + opacity: 1; +} +.cal-add-plus { + font-size: 16px; + line-height: 1; + color: var(--accent, var(--red)); + font-weight: 500; + display: inline-block; + transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + padding-bottom: 2px; +} +button.cal-add-btn.cal-add-btn-text:hover .cal-add-plus { + transform: rotate(180deg); +} +.cal-add-label { font-size:11px; font-weight:600; line-height:1; opacity:0.85; } +/* Small variant of the toolbar's +New button. Inherits the .cal-add-btn-text + styles (pill with "+" + "New" + spring-rotate on the +), so the + animation is identical. Just shrunk a bit. */ +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { + height: 20px; + border-radius: 10px; + /* At rest: tight, equal padding around the "+" so it sits centred + with no trailing gap. On hover the right pad and the flex gap grow + to make room for the revealed "New". */ + padding: 0 5px; + gap: 0; + transition: padding 0.25s ease, gap 0.25s ease, background 0.15s, border-color 0.15s; +} +/* Don't touch .cal-add-plus on the small variant — the toolbar +New is + the reference and we want the same rotation centre / motion. The label + is shrunk and hidden until hover so the pill is compact at rest. */ +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { + font-size: 10px; + max-width: 0; + margin-left: 0; + opacity: 0; + overflow: hidden; + white-space: nowrap; + transition: max-width 0.25s ease, opacity 0.18s ease, margin-left 0.25s ease; +} +button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover .cal-add-label { + max-width: 40px; + margin-left: 3px; + opacity: 0.85; +} +.cal-add-btn:hover { opacity:0.85; } + +/* Mobile: bump the +New pill and the +/- zoom buttons up to a tap-friendly + size so they're easy to hit with a thumb. */ +@media (max-width: 768px) { + /* Toolbar +New (relocated by JS into the quickadd row on mobile) */ + button.cal-add-btn.cal-add-btn-text { + height: 36px; + border-radius: 18px; + padding: 0 14px 0 12px; + gap: 6px; + flex-shrink: 0; + } + /* Day-detail +New: just a "+" on mobile — drop the label entirely. */ + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { + width: 28px; + height: 28px; + border-radius: 50%; + padding: 0; + gap: 0; + justify-content: center; + } + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { + display: none; + } + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { + font-size: 18px; + padding-bottom: 0; + } + .cal-add-plus { font-size: 22px; padding-bottom: 1px; } + .cal-add-label { font-size: 13px; } + .cal-wk-zoom { + width: 36px; + height: 36px; + font-size: 20px; + border-radius: 8px; + opacity: 0.85; + } + /* The +New button sits as a sibling of the quickadd row inside a flex + wrapper, so it's clearly outside the input's bordered box. The row + itself keeps its own styling. */ + .cal-quickadd-wrap { + display: flex; + align-items: stretch; + gap: 8px; + margin: 0 0 8px; + } + .cal-quickadd-wrap > #cal-quickadd-row { + flex: 1; + min-width: 0; + margin: 0; + } + .cal-quickadd-wrap > .cal-add-btn-text { + align-self: stretch; + flex-shrink: 0; + } + /* Nudge the "+" glyph up one pixel on the round day-detail button so it + sits visually centred inside the circle. */ + button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { + transform: translateY(-1px); + } + /* Event form: bigger Create / Cancel buttons so they're easy to tap. */ + .cal-form-actions button.cal-btn { + height: 44px; + padding: 0 20px; + font-size: 14px; + border-radius: 8px; + min-width: 96px; + } + /* Left-align the day/month + tags row in the day-detail panel and + the calendar/category chip row so they sit flush against the + left edge on a narrow screen. */ + .cal-detail-header { + justify-content: flex-start; + gap: 12px; + } + .cal-filters { + justify-content: flex-start; + } +} + +/* Refresh button spin — driven by JS toggling .cal-syncing on the button. + transform-box + transform-origin keep the rotation pivoted at the SVG's + own centre so it spins in place instead of orbiting / drifting up. */ +#cal-sync svg, +#email-lib-refresh-btn svg { display: block; transform-box: fill-box; transform-origin: 50% 50%; } +/* Brief checkmark flash after a refresh completes. Lasts ~900ms in JS; + the small bounce + accent color give a clear "done" cue. */ +#cal-sync.cal-sync-done svg, +#email-lib-refresh-btn.email-lib-refresh-done svg { + color: var(--accent, #2ea043); + animation: refresh-check-pop 0.32s ease-out; +} +@keyframes refresh-check-pop { + 0% { transform: scale(0.55); opacity: 0.4; } + 60% { transform: scale(1.18); opacity: 1; } + 100% { transform: scale(1); opacity: 1; } +} +#cal-sync.cal-syncing svg, +#email-lib-refresh-btn.email-lib-refreshing svg { animation: spin 0.6s linear infinite; } +.email-undone-toggle.active, +.email-attach-toggle.active { + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); + color: var(--accent, var(--red)); + font-weight: 600; +} +/* Hide the gallery-style reader-btn labels on desktop — only the mobile + @media block re-displays them under each icon. Desktop keeps compact + icon-only reader buttons. */ +.reader-btn-label { + display: inline-block; + font-size: 9px; + font-weight: 500; + line-height: 1; + letter-spacing: 0.02em; + white-space: nowrap; + opacity: 0.85; +} + +/* Inline attachment-filter toggle nested inside the search input. The + input has padding-right:34px set inline so its text doesn't slide + under the button. */ +.email-search-wrap { display: flex; align-items: center; } +/* Keep the email search input and the adjacent "Select" button identical in + height regardless of base-vs-mobile overrides — pin both explicitly. */ +.email-search-row .memory-search-input { + height: 32px; + min-height: 32px; + box-sizing: border-box; +} +.email-search-row .email-search-select-btn { + height: 32px; + min-height: 32px; + box-sizing: border-box; + margin-top: 6px; + flex-shrink: 0; +} +/* Select moved into the dropdown row — dock it to the right and match the + selects' vertical nudge (.memory-sort-select has top:3px). */ +.memory-category-filters .email-filter-select-btn, +.memory-category-filters .email-filter-refresh-btn { + position: relative; + top: 3px; + flex-shrink: 0; +} +.memory-category-filters .email-filter-refresh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 8px; +} +.memory-category-filters .email-reminders-clear-btn { + position: relative; + top: -2px; + flex-shrink: 0; +} +.email-attach-toggle-inline { + position: absolute !important; + right: 4px !important; + top: 50%; + transform: translateY(calc(-50% - 5px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-attach-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-attach-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +/* Inline undone-toggle nested inside the search input, sits LEFT of the + attach toggle. Same visual treatment. */ +.email-undone-toggle-inline { + position: absolute !important; + right: 34px !important; + top: 50%; + transform: translateY(calc(-50% - 3px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-reminder-toggle-inline { + position: absolute !important; + right: 64px !important; + top: 50%; + transform: translateY(calc(-50% - 3px)); + width: 26px !important; + height: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 6px !important; + background: transparent !important; + border: none !important; + opacity: 0.55; +} +.email-reminder-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-reminder-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +.email-undone-toggle-inline:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 10%, transparent) !important; +} +.email-undone-toggle-inline.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +/* Round (circular) toggles in the search bar, matching the app's default icon + buttons. Always fully visible (not dimmed until hover). */ +.email-attach-toggle-inline, +.email-undone-toggle-inline, +.email-reminder-toggle-inline { border-radius: 50% !important; opacity: 1 !important; } +.email-search-options-btn { + position: absolute !important; + right: 5px !important; + top: 50% !important; + transform: translateY(calc(-50% - 5px)); + width: 26px !important; + height: 26px !important; + min-width: 26px !important; + padding: 0 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + border-radius: 999px !important; + background: transparent !important; + border: 0 !important; + color: inherit !important; + opacity: 0.7; + z-index: 2; +} +.email-search-options-btn:hover, +.email-search-options-btn.active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; + color: var(--accent, var(--red)) !important; +} +.email-search-options-menu { + position: absolute; + top: calc(100% + 2px); + right: 0; + z-index: 75; + min-width: 140px; + padding: 4px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--panel, var(--bg)); + box-shadow: 0 8px 24px rgba(0,0,0,0.3); +} +.email-search-options-title { + padding: 5px 9px 4px; + color: var(--fg-muted, var(--fg)); + font-size: 10px; + font-weight: 600; + opacity: 0.65; + white-space: nowrap; +} +.email-search-options-menu button { + width: 100%; + border: 0; + background: transparent; + color: var(--fg); + display: flex; + align-items: center; + gap: 10px; + height: auto; + min-height: 0; + padding: 6px 8px; + border-radius: 8px; + font-family: inherit; + font-size: 11px; + font-weight: 400; + line-height: normal; + letter-spacing: normal; + text-align: left; + cursor: pointer; +} +.email-search-options-menu button:hover, +.email-search-options-menu button.active { + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + color: var(--accent, var(--red)); +} +.email-search-option-icon { + width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + opacity: 0.5; +} +.email-search-option-icon svg { width: 14px; height: 14px; } +.email-search-date-panel { + width: 220px; + padding: 6px 7px 5px; + border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); +} +.email-search-date-panel input { + width: 100%; + height: 26px; + box-sizing: border-box; + padding: 2px 6px; + border: 1px solid var(--border); + border-radius: 5px; + outline: none; + background: var(--bg); + color: var(--fg); + font: inherit; +} +.email-search-date-panel input:focus { border-color: var(--accent, var(--red)); } +.email-search-date-actions { display: flex; justify-content: flex-end; gap: 5px; } +.email-search-options-menu .email-search-date-actions [data-email-date-apply] { + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); +} +@media (max-width: 768px) { + .email-search-options-menu button { + min-height: 44px; + padding: 12px 12px !important; + font-size: 14px !important; + gap: 10px !important; + } + .email-search-options-menu .email-search-option-icon { + width: 18px; + height: 18px; + } + .email-search-options-menu .email-search-option-icon svg { + width: 16px; + height: 16px; + } +} +/* Mobile: enlarge the icons inside the inline search-bar toggles + (done / attachment / reminders) — buttons themselves stay the same, + only the SVG glyph scales up so it's tappable + visible. */ +@media (max-width: 768px) { + .email-filter-btn { + height: 30px; + min-height: 30px; + font-size: 12px; + top: -6px; + padding: 0 9px; + } + .email-folder-btn { + top: -4px; + } + + #email-lib-folder, + .memory-category-filters .email-filter-select-btn, + .memory-category-filters .email-filter-refresh-btn, + .memory-category-filters .email-filter-settings-btn { + height: 30px !important; + min-height: 30px !important; + box-sizing: border-box; + } + + .memory-category-filters .email-filter-refresh-btn, + .memory-category-filters .email-filter-settings-btn { + width: 30px; + padding: 0 !important; + } + .memory-category-filters .email-filter-settings-btn > svg { + width: 15px; + height: 15px; + } + + .email-search-row .memory-search-input { + height: 38px; + min-height: 38px; + } + + #email-lib-chip-bar { + padding-right: 42px !important; + padding-left: 30px !important; + gap: 5px !important; + } + + .email-lib-chip-bar-icon { + width: 14px !important; + height: 14px !important; + left: 9px !important; + } + + #email-lib-search { + font-size: 14px !important; + min-width: 88px !important; + top: 0 !important; + } + + #email-lib-search::placeholder { + font-size: 14px !important; + } + + #email-lib-chip-bar.has-email-lib-pills #email-lib-search { + min-width: 24px !important; + flex: 1 1 24px !important; + } + + .email-lib-pill { + min-height: 24px !important; + height: 24px !important; + line-height: 24px !important; + font-size: 12px !important; + padding: 0 6px 0 8px !important; + max-width: 180px !important; + } + + .email-lib-pill svg { + width: 14px !important; + height: 14px !important; + } + + .email-lib-pill-x { + font-size: 13px !important; + top: -4.5px !important; + } + + .email-lib-filter-pill { + min-height: 24px !important; + height: 24px !important; + line-height: 24px !important; + gap: 4px !important; + padding: 0 6px 0 8px !important; + } + + .email-lib-filter-pill .email-lib-pill-icon { + width: 17px !important; + height: 17px !important; + } + + .email-lib-filter-pill svg { + width: 18px !important; + height: 18px !important; + } + + .email-lib-filter-pill .email-lib-pill-x { + font-size: 13px !important; + top: -4.5px !important; + } + + .email-tags-toggle-inline { + right: 6px; + height: 28px !important; + min-width: 42px; + padding: 0 8px !important; + font-size: 11px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-attach-toggle-inline { + right: 56px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-undone-toggle-inline { + right: 92px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-reminder-toggle-inline { + right: 128px !important; + width: 32px !important; + height: 32px !important; + transform: translateY(calc(-50% - 3px)); + } + + .email-attach-toggle-inline svg, + .email-undone-toggle-inline svg, + .email-reminder-toggle-inline svg, + .email-filter-refresh-btn svg { + width: 16px !important; + height: 16px !important; + } +} +.email-accounts-row { + display: flex; + align-items: center; + gap: 10px; + padding: 0 0 6px; +} +.email-accounts-row .doclib-desc { flex-shrink: 0; } +/* Only the direct-child compose button gets pushed right; nested chips + inside #email-lib-accounts pack to the left as normal flex items. */ +.email-accounts-row > .memory-toolbar-btn { flex-shrink: 0; margin-left: auto; } +.email-account-chip { + display: inline-flex; + align-items: center; + gap: 5px; +} +.email-account-chip.email-account-has-unread { + border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); + background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); + color: color-mix(in srgb, var(--accent, var(--red)) 86%, var(--fg)); +} +.email-account-unread-dot { + width: 6px; + height: 6px; + border-radius: 999px; + background: var(--accent, var(--red)); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); + flex: 0 0 auto; +} +.email-account-chip-label { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; +} +.email-account-unread-count { + font-size: 9px; + line-height: 1; + font-weight: 700; + color: var(--accent, var(--red)); + opacity: 0.9; + position: relative; + top: 0; +} +.email-account-away-label { + color: var(--accent, var(--red)); + font-size: 9px; + font-weight: 700; + line-height: 1; + flex: 0 0 auto; +} +.email-accounts-loading-whirlpool { + width: 14px; + height: 14px; + margin: 3px 4px 0 1px; + display: inline-flex; + flex: 0 0 auto; +} +.email-accounts-loading-label { + font-size: 10px; + opacity: 0.55; + position: relative; + top: 2px; + white-space: nowrap; +} +.email-loading-with-label { + /* Fill the email list grid so the spinner lands in the middle of the + email window rather than the top of the empty list section. */ + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + text-align: center; + pointer-events: none; +} +.date-section-header { + display: flex; + align-items: center; + gap: 7px; + padding: 8px 9px 4px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + font-size: 10px; + font-weight: 700; + letter-spacing: 0; + line-height: 1.2; + text-transform: uppercase; + user-select: none; +} +.date-section-header::after { + content: ''; + flex: 1 1 auto; + height: 1px; + background: color-mix(in srgb, var(--border) 72%, transparent); +} +#session-list .date-section-header { + padding-left: 10px; + padding-right: 8px; + cursor: pointer; +} +#session-list .date-section-header.collapsed { + color: color-mix(in srgb, var(--fg) 36%, transparent); +} +#session-list .date-section-header.collapsed::after { + opacity: 0.45; +} +#session-list .date-section-header:focus-visible { + outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); + outline-offset: 2px; + border-radius: 5px; +} +/* Bootstrap row shown while the session list hydrates, and on load failure. + Reads as a normal list row but is not selectable. */ +.session-list-bootstrap { + cursor: default; + pointer-events: none; +} +#email-lib-grid .date-section-header { + padding: 10px 5px 3px; +} +#email-lib-modal .email-lib-sync-status { + flex: 0 0 auto; + min-height: 14px; + padding: 1px 4px 0; + font-size: 10.5px; + line-height: 1.2; + text-align: center; + opacity: 0.58; + color: var(--muted-text, currentColor); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + top: 4px; +} diff --git a/static/css/16-calendar.css b/static/css/16-calendar.css new file mode 100644 index 000000000..d4112f705 --- /dev/null +++ b/static/css/16-calendar.css @@ -0,0 +1,1175 @@ +/* 16-calendar - 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. + */ +@media (max-width: 768px) { + #email-lib-modal .email-lib-sync-status { + min-height: 13px; + padding-right: 5px; + font-size: 10px; + } +} +/* Nudge the subject + sender/date meta line 4px right inside each + email card so they don't sit flush against the read/unread/done + markers on the left. Includes #email-lib-modal selector to + override the expanded card's `padding: 4px 0 6px` shorthand + which was zeroing out the title's padding-left. */ +#email-lib-grid .email-card-titlerow, +#email-lib-grid .memory-item-meta, +#email-lib-modal .doclib-card-expanded .email-card-titlerow { + padding-left: 4px; +} +.email-loading-label { + font-size: 11px; + opacity: 0.6; +} + +/* Refresh button now lives top-right in the modal header next to the close X. + Borderless (matches the close X), and a fixed square box so the spin and the + refresh→checkmark icon swap never change its size or nudge neighbours. */ +.email-lib-header-actions #email-lib-refresh-btn { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + border: none; + background: none; + padding: 0; + width: 24px; + height: 24px; +} +/* Bump the icon up on mobile for a bigger visual — but keep the button BOX at + 24px so it never exceeds the header's natural height (the title/close set + that). A taller box would grow the sticky header and push the list down. */ +@media (max-width: 768px) { + .email-lib-header-actions #email-lib-refresh-btn svg { + width: 17px; + height: 17px; + } +} +.cal-view-toggle { + display: inline-flex; + align-items: stretch; + border: 1px solid var(--border); + border-radius: 5px; + overflow: hidden; + vertical-align: middle; + box-sizing: border-box; + padding: 0; + margin: 0; + line-height: 1; +} +.cal-view-btn + .cal-view-btn { border-left: 1px solid var(--border); } +.cal-view-btn:hover { opacity: 0.75; } +.cal-view-btn.active { + background: color-mix(in srgb, var(--fg) 12%, transparent); + color: var(--accent, var(--red)); + opacity: 1; + font-weight: 600; +} +/* Toolbar holds only the toggle button; the chip row renders below the + toolbar on its own line and wraps freely. */ +.cal-filters { + display:flex; + gap:6px; + margin:6px 0 8px; + flex-wrap:wrap; + padding:6px 8px; + border:1px solid color-mix(in srgb, var(--fg) 8%, transparent); + border-radius:8px; + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 4%, transparent), transparent), + color-mix(in srgb, var(--fg) 2.5%, var(--bg)); + box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 4%, transparent); +} +/* Round the native color-input swatch in calendar settings. The outer + wraps a colored fill drawn by the browser; we need to round both + the wrapper and the fill so it actually appears circular. */ +.cal-s-color { border-radius: 50%; overflow: hidden; } +.cal-filter-item { display:inline-flex; align-items:center; gap:4px; cursor:pointer; font-size:10px; padding:2px 8px; line-height:1.4; border-radius:10px; background:color-mix(in srgb, var(--bg) 72%, transparent); border:1px solid color-mix(in srgb, var(--fg) 12%, transparent); transition:opacity 0.15s, background 0.15s, border-color 0.15s, transform 0.15s; } +.cal-filter-item:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); } +.cal-filter-item.cal-filter-important { font-weight:700; color:#e5a33a; border-color:color-mix(in srgb, #e5a33a 40%, transparent); } +/* Match the toolbar's other buttons (cal-nav, cal-view-btn, cal-add-btn-text): + 24px tall, 11px font, 5px corners. */ +.cal-filter-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + background: color-mix(in srgb, var(--fg) 6%, transparent); + border: 1px solid var(--border); + color: var(--fg); + cursor: pointer; + font-size: 11px; + font-family: inherit; + font-weight: 500; + padding: 0 10px; + height: 24px; + line-height: 1; + border-radius: 5px; + box-sizing: border-box; + vertical-align: middle; + opacity: 0.65; + position: relative; + top: -3px; +} +.cal-filter-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } +.cal-filter-toggle-glyph { + display: inline-block; + position: relative; + top: 0; + margin-right: 3px; + color: var(--accent, var(--red)); +} +.cal-filter-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; } +/* The grid is now a flex column: one row of weekday headers + six + .cal-week-row rows. Each row holds the 7 day cells in its own + internal grid plus the absolute-positioned multi-day overlays. + --bars on a week-row is the number of multi-day events overlapping + it; cells in that row use it to reserve top padding so the bar + overlays don't cover the day numbers or single-event rows. */ +.cal-grid { display:flex; flex-direction:column; gap:1px; background:color-mix(in srgb, var(--fg) 8%, transparent); border-radius:6px; overflow:hidden; } +.cal-week-headers { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } +.cal-week-row { position:relative; display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } +.cal-weekday { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); text-align:center; font-size:10px; font-weight:600; opacity:0.4; padding:5px 0; } +.cal-day { + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 34px), + var(--bg); + min-height:78px; + padding:3px; + cursor:pointer; + position:relative; + transition:background 0.12s, box-shadow 0.12s; + overflow:hidden; +} +.cal-day:hover { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); } +.cal-day.cal-today { + box-shadow: inset 0 0 0 2px var(--accent, var(--red)); + background: var(--bg); + border-radius: 8px; +} +.cal-day.cal-selected:not(.cal-today) .cal-day-num { + color: var(--accent, var(--fg)); + opacity: 1; + font-weight: 700; +} +.cal-day.cal-drag-over { background:color-mix(in srgb, var(--accent) 18%, var(--bg)); } +.cal-day.cal-range-select { background:color-mix(in srgb, var(--accent, var(--red)) 25%, var(--bg)); box-shadow:inset 0 0 0 1px var(--accent, var(--red)); } +.cal-day-num { font-size:10px; font-weight:600; display:block; margin-bottom:1px; opacity:0.7; line-height:1.2; } +.cal-dots { display:flex; flex-direction:row; align-items:center; gap:2px; flex-wrap:nowrap; margin-bottom:1px; height:6px; } +.cal-dot { width:5px; height:5px; border-radius:50%; display:inline-block; cursor:grab; flex-shrink:0; } +.cal-dot-more { font-size:7px; opacity:0.4; } +.cal-event-row { + display:flex; + align-items:center; + gap:4px; + padding:2px 4px; + border-radius:4px; + cursor:grab; + line-height:1.2; + margin-bottom:2px; + min-height:17px; + background:color-mix(in srgb, var(--fg) 5%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); + animation:cal-event-enter 0.16s ease-out both; + transition:background 0.15s, box-shadow 0.15s, transform 0.15s; +} +.cal-event-row:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); transform:translateY(-1px); } +.cal-event-row-dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } +.cal-event-row-time { font-size:10px; opacity:0.72; font-variant-numeric:tabular-nums; flex-shrink:0; font-weight:600; } +.cal-event-row-name { font-size:10px; opacity:0.96; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; font-weight:600; } +.cal-event-reminder { + display: inline-flex; + width: 12px; + height: 12px; + margin-left: 5px; + vertical-align: -2px; + color: color-mix(in srgb, var(--fg) 58%, transparent); + opacity: 0.9; + flex: 0 0 auto; +} +.cal-event-reminder svg { + width: 100%; + height: 100%; + display: block; +} +.cal-event-row .cal-event-reminder { + width: 10px; + height: 10px; + margin-left: 4px; +} +.cal-event-source { + display:inline-flex; + align-items:center; + justify-content:center; + width:12px; + height:12px; + margin-left:5px; + border-radius:50%; + font-size:7px; + font-weight:800; + line-height:1; + color:var(--cal-event-fg, var(--fg)); + background:color-mix(in srgb, currentColor 16%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); + vertical-align:1px; +} +.cal-event-source svg { + width: 10px; + height: 10px; + display: block; +} +.cal-event-row .cal-event-source { + width:11px; + height:11px; + font-size:7px; + margin-left:4px; + color:color-mix(in srgb, var(--fg) 72%, transparent); +} +.cal-multiday .cal-event-reminder, +.cal-wk-allday-event .cal-event-reminder { + color: var(--cal-event-fg, currentColor); + opacity: 0.88; +} +.cal-multiday .cal-event-source, +.cal-wk-allday-event .cal-event-source { + color: var(--cal-event-fg, currentColor); +} +.cal-event-more { font-size:9px; opacity:0.55; padding-left:6px; } +/* Multi-day bar — positioned as an absolute overlay inside its + .cal-week-row so it spans uninterrupted across every column it + covers. --col is the starting column (0–6), --span is the number + of days it covers within the row, --slot stacks parallel bars. */ +.cal-multiday { + position: absolute; + /* Fractional offsets let timed events that cross midnight render at + true proportional width. start-frac shifts the left edge into the + first day; end-frac trims the right edge inside the last day. + All-day events default to (0, 1) and still fill whole columns. */ + left: calc((var(--col, 0) + var(--start-frac, 0)) * (100% / 7)); + width: calc( + (var(--span, 1) - var(--start-frac, 0) - (1 - var(--end-frac, 1))) + * (100% / 7) + ); + top: calc(2px + var(--slot, 0) * 12px); + height: 12px; + font-size: 9px; + line-height: 12px; + padding: 0 5px; + border-radius: 4px; + color: var(--cal-event-fg, #fff); + font-weight: 700; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: grab; + opacity: 0.98; + z-index: 2; + pointer-events: auto; + box-shadow: 0 1px 2px color-mix(in srgb, #000 18%, transparent); +} +/* Splitter between the month/week grid and the day-tasks panel — drag + vertically to give the day panel more room. Height clamped by the + `--cal-detail-h` variable set on #cal-body by the splitter drag JS. */ +.cal-splitter { + height: 8px; + margin: 6px -10px 0; + cursor: row-resize; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + user-select: none; + touch-action: none; +} +/* Mobile: fat hitbox so the splitter is easy to grab with a thumb, + without changing how the grip itself looks. */ +@media (max-width: 768px) { + .cal-splitter { + height: 24px; + margin-top: 0; + margin-bottom: 0; + } + .cal-splitter-grip { + width: 56px; + height: 4px; + } +} +.cal-splitter-grip { + width: 42px; + height: 3px; + border-radius: 2px; + background: color-mix(in srgb, var(--fg) 25%, transparent); + transition: background 0.12s; +} +.cal-splitter:hover .cal-splitter-grip, +.cal-splitter-dragging .cal-splitter-grip { + background: var(--accent, var(--red)); +} +.cal-day-detail { + margin-top: 4px; + border-top: 1px solid var(--border); + padding-top: 8px; + height: var(--cal-detail-h, 240px); + overflow-y: auto; + overscroll-behavior: contain; + flex-shrink: 0; +} +.cal-detail-header { display:flex; justify-content:space-between; align-items:center; font-size:12px; font-weight:600; margin-bottom:6px; padding-right:8px; } +.cal-empty { font-size:11px; opacity:0.3; padding:4px 0; } +.cal-event-item { display:flex; gap:9px; padding:8px 9px; border-radius:6px; cursor:pointer; align-items:flex-start; background:color-mix(in srgb, var(--fg) 3.5%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); margin-bottom:4px; } +.cal-event-item:hover { background:color-mix(in srgb, var(--fg) 8%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); } +.cal-event-dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:4px; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } +.cal-event-info { flex:1; min-width:0; } +.cal-event-name { font-size:13px; font-weight:700; line-height:1.25; color:color-mix(in srgb, var(--fg) 96%, transparent); overflow-wrap:anywhere; } +.cal-event-tag { + display: inline-block; + font-size: 10px; + font-weight: 500; + padding: 1px 6px; + border-radius: 8px; + border: 1px solid; + margin-left: 6px; + vertical-align: 1px; + opacity: 0.85; + white-space: nowrap; +} +.cal-event-time { font-size:11px; opacity:0.68; line-height:1.35; margin-top:2px; font-weight:500; } +.cal-event-loc { font-size:11px; opacity:0.58; line-height:1.35; margin-top:1px; } +/* ── Week view: hour grid ────────────────────────────────────────── + Vertical hour rail on the left, 7 day columns on the right with their + own all-day strip and an absolute-positioned hour grid below it. */ +.cal-wk-wrap { + display: flex; + flex-direction: row; + gap: 0; + background: + linear-gradient(180deg, color-mix(in srgb, var(--fg) 3%, transparent), transparent 120px), + var(--bg); + border: 1px solid var(--border); + border-radius: 8px; + /* The wrap itself scrolls so the hour rail and day columns move + together; column headers and the rail spacer are sticky-pinned. */ + overflow: auto; + max-height: calc(100vh - 220px); + min-height: 360px; + position: relative; +} +.cal-wk-now-below-hint { + position: absolute; + height: 2px; + border-radius: 2px 2px 0 0; + background: var(--accent, var(--red)); + pointer-events: none; + z-index: 38; +} +@media (max-width: 768px) { + .cal-wk-wrap { + -webkit-overflow-scrolling: touch; + overscroll-behavior-y: contain; + } + .cal-wk-wrap > .cal-wk-rail, + .cal-wk-wrap > .cal-wk-cols { + transform: translateY(var(--wk-edge-pull, 0)); + } + .cal-wk-wrap.cal-wk-edge-release > .cal-wk-rail, + .cal-wk-wrap.cal-wk-edge-release > .cal-wk-cols { + transition: transform 0.14s cubic-bezier(0.2, 0.75, 0.25, 1); + } + .cal-wk-wrap.cal-wk-pull-top { + border-top-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); + } + .cal-wk-wrap.cal-wk-pull-bottom { + border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); + } +} +.cal-wk-rail { + flex: 0 0 56px; + border-right: 1px solid var(--border); + display: flex; + flex-direction: column; + background: color-mix(in srgb, var(--fg) 2%, var(--bg)); + position: sticky; + left: 0; + z-index: 5; +} +.cal-wk-rail-spacer { + /* Match the 32px day header. The 24px all-day row gets its own spacer + below, so the zoom controls sit high without shifting the hour grid. */ + height: 32px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 0; + background: color-mix(in srgb, var(--fg) 2%, var(--bg)); + z-index: 9; + display: flex; + align-items: center; + justify-content: center; + gap: 2px; + padding: 0 4px; +} +.cal-wk-rail-allday-spacer { + height: 24px; + border-bottom: 1px solid var(--border); + flex-shrink: 0; + position: sticky; + top: 32px; + z-index: 8; + background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); +} +.cal-wk-zoom { + position: relative; + top: -3px; + width: 22px; + height: 22px; + border: 1px solid var(--border); + background: var(--bg); + color: var(--fg); + border-radius: 50%; + font-family: inherit; + font-size: 14px; + font-weight: 600; + line-height: 1; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0; + opacity: 0.55; + transition: opacity 0.15s, background 0.15s, border-color 0.15s; +} +.cal-wk-zoom:hover { + opacity: 1; + background: color-mix(in srgb, var(--fg) 8%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--fg)) 50%, var(--border)); +} +.cal-wk-rail-cell { + font-size: 11px; + color: color-mix(in srgb, var(--fg) 55%, transparent); + padding: 4px 8px 0; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); + flex-shrink: 0; + display: flex; + align-items: flex-start; + justify-content: flex-end; + font-variant-numeric: tabular-nums; +} +.cal-wk-cols { + flex: 1; + display: grid; + grid-template-columns: repeat(7, 1fr); + position: relative; +} +.cal-wk-col { + border-left: 1px solid var(--border); + display: flex; + flex-direction: column; + min-width: 0; +} +.cal-wk-col:first-child { border-left: none; } +.cal-wk-col-head { + height: 32px; + padding: 0 6px; + display: flex; + align-items: center; + gap: 6px; + font-size: 11px; + flex-shrink: 0; + background: color-mix(in srgb, var(--fg) 3%, var(--bg)); + border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); + position: sticky; + top: 0; + z-index: 40; +} +/* Today on a Sunday still wins the accent treatment. */ +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); } +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dn, +.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dt { color: var(--accent, var(--fg)); } +.cal-wk-dn { font-weight: 600; opacity: 0.6; } +.cal-wk-allday { + height: 24px; + padding: 2px 4px; + display: flex; + flex-direction: row; + gap: 2px; + align-items: center; + background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); + border-bottom: 1px solid var(--border); + flex-shrink: 0; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; + position: sticky; + top: 32px; + z-index: 39; +} +.cal-wk-allday-event { + font-size: 10.5px; + font-weight: 700; + padding: 3px 6px; + border-radius: 4px; + color: var(--cal-event-fg, var(--fg)); + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +/* Hour grid body */ +.cal-wk-grid { + position: relative; + flex: 0 0 auto; + cursor: cell; + user-select: none; + background: + repeating-linear-gradient( + to bottom, + transparent 0, + transparent calc(var(--wk-hour-px, 52px) / 2 - 1px), + color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--wk-hour-px, 52px) / 2), + transparent calc(var(--wk-hour-px, 52px) / 2 + 1px) + ); +} +.cal-wk-cell { + border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); + box-sizing: border-box; +} +.cal-wk-cell:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); } +/* "Now" line */ +.cal-wk-now { + position: absolute; + left: 0; right: 0; + height: 0; + border-top: 2px solid color-mix(in srgb, var(--accent, var(--red, #f87171)) 90%, transparent); + pointer-events: none; + z-index: 7; + filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent)); +} +/* Event blocks */ +.cal-wk-block { + position: absolute; + left: calc((var(--lane, 0) * (100% / var(--lane-count, 1))) + 2px); + width: calc((100% / var(--lane-count, 1)) - 4px); + right:auto; + border-left: 4px solid var(--fg); + border-radius: 5px; + padding: 5px 7px 7px; + font-size: 12px; + line-height: 1.3; + cursor: pointer; + /* `clip` preserves the rounded-card boundary without creating an overflow + container, allowing the label inside to stick to the week scroller. */ + overflow: clip; + z-index: 2; + box-shadow: 0 2px 6px color-mix(in srgb, #000 14%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); + animation:cal-event-enter 0.18s ease-out both; + transition: width 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), height 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), filter 0.12s, transform 0.12s, box-shadow 0.16s; + box-sizing: border-box; +} +.cal-wk-block:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); } +.cal-wk-block-label { + position: sticky; + top: 37px; + z-index: 1; + min-width: 0; + pointer-events: none; +} +.cal-wk-has-allday .cal-wk-block-label { + top: 61px; +} +.cal-wk-block-name { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color:color-mix(in srgb, var(--fg) 96%, transparent); } +.cal-wk-block-time { font-size: 10.5px; opacity: 0.82; font-variant-numeric: tabular-nums; margin-top: 2px; font-weight:600; } +@media (hover: hover) and (pointer: fine) { + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left { + left: auto; + right: calc(100% - var(--lane-right) + 2px); + } + .cal-wk-block.cal-wk-block-crowded:hover { + width: var(--hover-width, 170px); + height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; + overflow: clip; + z-index: 30; + filter: brightness(1.08); + transform: translateY(var(--hover-shift-y, -2px)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-hover-width-only:hover { + height: var(--event-height) !important; + transform: translateY(-1px); + } + .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-label { + position: absolute; + top: 5px; + width: calc(var(--hover-width, 170px) - 18px); + animation: cal-wk-hover-label-reveal 0.2s ease-out both; + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left:hover .cal-wk-block-label { + left: auto; + right: 7px; + } + .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-right:hover .cal-wk-block-label { + left: 7px; + right: auto; + } + .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-name { + display: block; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + } +} +@media (max-width: 768px) { + /* Mobile week view: the hour rail already shows the time, so drop it from the + card and let the title wrap to fill the freed space (more readable text). */ + .cal-wk-block-time { display: none; } + .cal-wk-block-name { + white-space: normal; + line-height: 1.15; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 4; + -webkit-box-orient: vertical; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left { + left: auto; + right: calc(100% - var(--lane-right) + 2px); + } + .cal-wk-block.cal-wk-mobile-expanded { + width: var(--hover-width, 170px); + height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; + overflow: clip; + z-index: 30; + filter: brightness(1.08); + transform: translateY(var(--hover-shift-y, 0)); + box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); + } + .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-label { + position: absolute; + top: 5px; + width: calc(var(--hover-width, 170px) - 18px); + animation: cal-wk-hover-label-reveal 0.2s ease-out both; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left .cal-wk-block-label { + left: auto; + right: 7px; + } + .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-right .cal-wk-block-label { + left: 7px; + right: auto; + } + .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-name { + display: block; + white-space: normal; + overflow: visible; + text-overflow: clip; + overflow-wrap: anywhere; + -webkit-line-clamp: unset; + } +} +/* Resize handle: a 6 px hit zone along the block's bottom edge. The + visible 2-px stripe only shows on hover so blocks at rest stay clean. */ +.cal-wk-block-resize { + position: absolute; + left: 4px; + right: 4px; + bottom: 0; + height: 6px; + cursor: ns-resize; + z-index: 3; +} +/* Drag-to-create ghost */ +.cal-wk-ghost { + position: absolute; + left: 2px; + right: 2px; + background: color-mix(in srgb, var(--accent, var(--fg)) 28%, transparent); + border: 1px dashed color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent); + border-radius: 4px; + pointer-events: none; + z-index: 3; + font-size: 10px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + color: var(--accent, var(--fg)); + font-variant-numeric: tabular-nums; +} +.cal-wk-slot-selected { + background: color-mix(in srgb, var(--accent, var(--fg)) 20%, var(--bg)); + border-style: solid; + border-color: var(--accent, var(--fg)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--fg)) 20%, transparent); + overflow: visible; + pointer-events: auto; + cursor: pointer; +} +.cal-wk-slot-resize { + position: absolute; + left: 14%; + right: 14%; + bottom: -7px; + height: 14px; + z-index: 4; + cursor: ns-resize; + touch-action: none; + pointer-events: auto; +} +.cal-year { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; } +.cal-year-month { background:color-mix(in srgb, var(--fg) 3%, var(--bg)); border:1px solid var(--border); border-radius:6px; padding:6px; cursor:pointer; transition:background 0.15s, border-color 0.15s, transform 0.15s; } +.cal-year-month:hover { background:color-mix(in srgb, var(--fg) 8%, var(--bg)); border-color:var(--accent); transform:translateY(-1px); } +.cal-year-month-title { font-size:11px; font-weight:600; text-align:center; margin-bottom:4px; opacity:0.7; } +.cal-year-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; } +.cal-year-wd { font-size:7px; text-align:center; opacity:0.3; padding:1px 0; } +.cal-year-cell { font-size:8px; text-align:center; padding:2px 0; min-height:14px; border-radius:2px; transition:transform 0.12s, background 0.12s; } +.cal-year-day:hover { background:var(--accent, var(--red)); color:#fff; opacity:1; transform:scale(1.15); font-weight:700; z-index:2; position:relative; } +.cal-year-today { color:var(--accent, var(--red)); font-weight:700; opacity:1; } +.cal-year-has { opacity:1; background:color-mix(in srgb, var(--accent) 15%, transparent); } +.cal-year-has.cal-year-today { background:color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } +.cal-loading { display:flex; justify-content:center; padding:40px 0; } +.cal-badge { background:var(--accent); border-radius:50%; width:6px; height:6px; margin-left:auto; display:inline-block; flex-shrink:0; } +/* Search input — in-panel variant lives inside the day-detail panel and + spans its width; no width animation since growing/shrinking on every + keystroke would jitter beside the events list. */ +.cal-search-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:0 8px; height:24px; color:var(--fg); font-size:11px; font-family:inherit; width:120px; box-sizing:border-box; transition:width 0.15s; } +.cal-search-input:focus { outline:none; border-color:var(--accent); width:180px; } +.cal-search-input.cal-day-search { width:100%; margin-bottom:0; transition:none; padding-left: 28px; } +/* Wrap for the search input so the magnifying-glass icon can be absolute- + positioned inside the field. */ +.cal-search-wrap { + position: relative; + display: block; + margin-bottom: 6px; +} +.cal-search-wrap .cal-search-icon { + position: absolute; + left: 8px; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + color: var(--accent, var(--red)); +} +.cal-day-search-meta { font-size:10px; opacity:0.5; margin:0 2px 6px; } +.cal-search-results { display:flex; flex-direction:column; gap:4px; } +.cal-search-count { font-size:10px; opacity:0.5; padding:4px 2px 8px; } +/* Agenda view */ +.cal-agenda { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-left:4px; } +.cal-agenda-day { display:flex; flex-direction:column; gap:5px; position:relative; padding-left:14px; } +.cal-agenda-date { font-size:12px; font-weight:800; opacity:0.72; padding:5px 2px 3px; border-bottom:1px solid var(--border); } +.cal-agenda-today-badge { + display: inline-block; + margin-left: 6px; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + padding: 1px 6px; + border-radius: 8px; + background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); + color: var(--accent, var(--red)); + vertical-align: 1px; +} +.cal-agenda-empty { font-size:11px; opacity:0.4; padding:8px; font-style:italic; } +.cal-agenda-event { + display:flex; + gap:9px; + padding:9px 10px; + border-radius:6px; + cursor:pointer; + align-items:center; + position:relative; + background:color-mix(in srgb, var(--fg) 3.5%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); + animation:cal-event-enter 0.18s ease-out both; + transition:background 0.15s, box-shadow 0.15s, transform 0.15s; +} +.cal-agenda-event:hover { + background:color-mix(in srgb, var(--fg) 8%, transparent); + box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); + transform:translateX(2px); +} +.cal-agenda-event .cal-event-more, +.cal-event-item .cal-event-more { opacity:0; transition:opacity 0.15s; } +.cal-event-item { position:relative; } + +/* ── Personal Assistant ── */ +.sidebar-assistant-entry { gap: 6px; min-height: 29px; box-sizing: border-box; align-items: center; } +.sidebar-assistant-entry .sidebar-action-icon { position: relative; left: -2px; } +#sidebar-assistant-gear:hover { opacity: 0.8 !important; } +.assistant-settings-form { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; } +.assistant-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: color-mix(in srgb, var(--fg) 70%, transparent); } +.assistant-field > span { font-size: 11px; opacity: 0.6; } +.assistant-field input[type="text"], +.assistant-field input[type="time"], +.assistant-field select, +.assistant-field textarea { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 5px; + padding: 7px 10px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + box-sizing: border-box; +} +.assistant-field textarea { resize: vertical; min-height: 60px; } +.assistant-field input:focus, +.assistant-field select:focus, +.assistant-field textarea:focus { outline: none; border-color: var(--accent, var(--red)); } +.assistant-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } +.assistant-field-row > .assistant-field { flex: 1 1 180px; } +.assistant-field-check { flex-direction: row !important; align-items: center; gap: 6px; padding-bottom: 7px; } +.assistant-checkins { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); } +.assistant-checkins h5 { margin: 0 0 2px; font-size: 12px; font-weight: 600; opacity: 0.8; } +.assistant-checkin-row { + display: flex; + flex-direction: column; + gap: 4px; + padding: 8px; + border: 1px solid var(--border); + border-radius: 6px; + background: color-mix(in srgb, var(--fg) 3%, transparent); +} +.assistant-checkin-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } +.assistant-checkin-head input[type="text"], +.assistant-checkin-head input[type="time"] { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 4px; + padding: 4px 8px; + color: var(--fg); + font-size: 12px; + font-family: inherit; +} +.assistant-checkin-head input[type="text"] { flex: 1; min-width: 120px; } +.assistant-checkin-head input[type="time"] { width: 90px; } +.assistant-checkin-run { + background: none; + border: 1px solid var(--border); + color: var(--fg); + font-family: inherit; + font-size: 11px; + padding: 4px 8px; + border-radius: 4px; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.15s; +} +.assistant-checkin-run:hover { opacity: 1; } +.assistant-checkin-prompt { + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + border: 1px solid var(--border); + border-radius: 4px; + padding: 6px 8px; + color: var(--fg); + font-size: 12px; + font-family: inherit; + resize: vertical; + min-height: 48px; +} +.assistant-checkin-prompt:focus { outline: none; border-color: var(--accent, var(--red)); } +.assistant-checkin-meta { font-size: 10px; opacity: 0.45; } +.assistant-settings-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--border); +} +.assistant-tools-grid { + display: flex; + flex-direction: column; + gap: 6px; + max-height: 240px; + overflow-y: auto; + padding: 6px; + border: 1px solid var(--border); + border-radius: 6px; + margin-top: 4px; +} +.assistant-tool-group { + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; +} +.assistant-tool-group-label { + font-size: 10px; + font-weight: 600; + opacity: 0.5; + text-transform: uppercase; + letter-spacing: 0.03em; + width: 100%; + margin-bottom: 1px; +} +.assistant-tool-item { + display: inline-flex; + align-items: center; + gap: 3px; + font-size: 11px; + padding: 2px 6px; + border: 1px solid var(--border); + border-radius: 4px; + cursor: pointer; + transition: background 0.1s, border-color 0.1s; + user-select: none; +} +.assistant-tool-item:hover { + background: color-mix(in srgb, var(--fg) 5%, transparent); +} +.assistant-tool-item:has(input:checked) { + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 10%, transparent); +} +.assistant-tool-item input[type="checkbox"] { + width: 12px; + height: 12px; + margin: 0; +} + +/* Empty state */ +.cal-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px 20px; text-align:center; gap:10px; } +.cal-empty-title { font-size:14px; font-weight:600; opacity:0.8; } +.cal-empty-msg { font-size:12px; opacity:0.5; max-width:320px; line-height:1.5; margin-bottom:8px; } +.cal-allday-switch { margin:0; flex-shrink:0; } +/* Stack the "All day" label above its toggle so it doesn't get squeezed / + truncated next to the date inputs in the row. */ +.cal-allday-ctrl { display:flex; flex-direction:column; align-items:center; gap:3px; flex-shrink:0; } +.cal-allday-label { font-size:10px; opacity:0.5; white-space:nowrap; line-height:1; } +.cal-form { display:flex; flex-direction:column; gap:8px; } +.cal-form-title { font-size:13px; font-weight:600; margin-bottom:2px; } +.cal-title-wrap { position: relative; } +/* Calendar-picker select sits 2px higher; its border-left/background tint is + set in JS to the chosen calendar's colour. */ +.cal-f-cal-select { position: relative; top: -4px; } + +/* ── Bespoke event form ──────────────────────────────────────────── + Big clock-face date/time hero with the title under it. Details panel + stays collapsed until the title gets focus or "Add details" is clicked. +*/ +.cal-form-bespoke { + position: relative; + width: min(720px, calc(100% - 24px)); + max-width: none; + margin: 12px auto; + padding: 18px 28px 14px; + box-sizing: border-box; + background: var(--bg); + /* Optional per-event tint set by JS via --ev-color. When unset, falls + back to the theme's neutral border so the card looks like normal. */ + border: 1px solid var(--border); + border-radius: 14px; + box-shadow: 0 8px 24px color-mix(in srgb, var(--fg) 6%, transparent); + transition: border-color 0.18s, box-shadow 0.18s; +} +/* When a colour is picked, lift the card with a soft tinted border + glow + so the choice is unmistakable. */ +.cal-form-bespoke[style*="--ev-color"] { + border-color: color-mix(in srgb, var(--ev-color) 55%, var(--border)); + box-shadow: 0 8px 28px color-mix(in srgb, var(--ev-color) 22%, transparent); +} +.cal-form-bespoke[style*="--ev-color"] .cal-btn-primary { + background: var(--ev-color); + border-color: var(--ev-color); +} +.cal-form-bespoke.cal-form-bg-image .cal-btn-primary { + background: var(--accent, var(--red)); + border-color: var(--accent, var(--red)); + color: var(--bg); +} +.cal-form-close { + position: absolute; top: 10px; right: 12px; + background: none; border: none; color: var(--fg); + opacity: 0.35; cursor: pointer; font-size: 20px; line-height: 1; + padding: 4px 8px; border-radius: 6px; +} +.cal-form-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 8%, transparent); } +.cal-form-mobile-cancel { display: none; } + +/* "Today is …" header pinned at the top of the form. Keeps you oriented + even when the event date you're picking is way off in the future. */ +.cal-form-today { + text-align: center; + font-size: 11px; + letter-spacing: 0.08em; + text-transform: uppercase; + opacity: 0.45; + margin-bottom: 0; +} + +/* Hero — clock face + date label. Spacing between the two lives entirely + on the clock's bottom margin so it's predictable. */ +.cal-hero { + display: flex; flex-direction: column; align-items: center; + gap: 0; margin-bottom: 12px; +} +.cal-hero-time { + display: inline-flex; align-items: baseline; gap: 8px; + font-family: ui-monospace, SFMono-Regular, "Menlo", "Consolas", monospace; + font-variant-numeric: tabular-nums; + font-size: 56px; + letter-spacing: 0.02em; + font-weight: 200; + color: var(--fg); + line-height: 1; + /* Behaves like a button — strip native chrome but keep it tappable. */ + background: none; + border: none; + /* Pill that reads as a real hit target for a 56-px clock face. + Top padding kept tight so the clock sits close to the "Today is" line. */ + padding: 6px 38px 18px; + margin: 0 0 24px; + border-radius: 18px; + cursor: pointer; + line-height: 1; + transition: background 0.15s, color 0.15s; +} +/* The clock is a `` and a global `button:hover` rule paints + every button's background on hover. Suppress it explicitly. */ +.cal-hero-time:hover { background: transparent; border-color: transparent; } +.cal-hero-clock { font-feature-settings: "tnum"; display: inline-flex; align-items: baseline; } +/* Per-segment hover so it's clear hh vs mm are individually clickable. */ +.cal-hero-clock-hh, .cal-hero-clock-mm { + display: inline-block; + border-radius: 6px; + padding: 0 2px; + transition: background 0.12s; +} +.cal-hero-clock-hh:hover, .cal-hero-clock-mm:hover { + background: color-mix(in srgb, var(--fg) 9%, transparent); +} +.cal-hero-sep { padding: 0 2px; opacity: 0.55; pointer-events: none; } +.cal-hero-ampm { + font-size: 14px; + letter-spacing: 0.12em; + opacity: 0.55; + font-weight: 500; + text-transform: uppercase; +} +.cal-hero-date { + font-size: 16px; + opacity: 0.6; + letter-spacing: 0.04em; + background: none; + border: none; + color: inherit; + font-family: inherit; + padding: 4px 10px; + border-radius: 6px; + cursor: pointer; + margin: 7px 0 0; /* extra nudge below the clock */ + transition: background 0.15s, opacity 0.15s; +} +.cal-hero-date:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); opacity: 0.85; } + +/* Title input — flat, large, no chrome until focused */ +.cal-input.cal-hero-title { + font-size: 18px; + text-align: left; + padding: 10px 12px 10px 0; + background: transparent; + border: 1px solid transparent; + border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); + border-radius: 0; + margin-bottom: 4px; +} + +/* Details panel — animated reveal via max-height + opacity. */ +.cal-form-bespoke .cal-form-details { + display: block; + max-height: 0; + opacity: 0; + overflow: hidden; + transition: max-height 240ms ease, opacity 180ms ease, margin 220ms ease; + margin-top: 0; +} +/* The detail children themselves keep the existing flex/gap rules from + the regular .cal-form, so we don't restyle inputs/rows here. */ +.cal-form-bespoke .cal-form-details > * + * { margin-top: 8px; } + + +/* Location row: input + Apple Maps pin link. Pin only lights up when the + field has content; clicking opens maps.apple.com (native Maps app on + Apple devices, web fallback elsewhere). All theme vars. */ +.cal-loc-row { + display: flex; + align-items: stretch; + gap: 6px; +} +.cal-loc-input-wrap { + position: relative; + flex: 1; + min-width: 0; +} +.cal-loc-hint { + position: absolute; + inset: 0 auto 0 10px; + display: none; + align-items: center; + gap: 5px; + pointer-events: none; + color: color-mix(in srgb, var(--fg) 42%, transparent); + font-size: 12px; +} +.cal-loc-map { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + flex-shrink: 0; + border: 1px solid var(--border); + border-radius: 5px; + color: var(--accent, var(--red)); + background: color-mix(in srgb, var(--fg) 5%, var(--bg)); + text-decoration: none; + transition: background 0.15s, border-color 0.15s, opacity 0.15s; +} +.cal-loc-map:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); border-color: var(--accent, var(--red)); } + +@media (max-width: 520px) { + .cal-hero-time { font-size: 44px; } + .cal-form-bespoke { margin: 16px 12px; padding: 32px 16px 14px; } + .cal-form-mobile-cancel { + display: inline-flex; + position: absolute; + top: 8px; + right: 8px; + width: 30px; + height: 30px; + align-items: center; + justify-content: center; + padding: 0; + border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); + border-radius: 999px; + background: color-mix(in srgb, var(--bg) 82%, transparent); + color: var(--fg); + opacity: 0.72; + cursor: pointer; + z-index: 3; + -webkit-tap-highlight-color: transparent; + } + .cal-form-mobile-cancel:active { + opacity: 1; + background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); + border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); + } +} +.cal-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:7px 10px; color:var(--fg); font-size:12px; width:100%; box-sizing:border-box; } +.cal-input:focus { outline:none; border-color:var(--accent); } +.cal-input-time { width:auto; flex:1; } +.cal-form-row { display:flex; gap:8px; align-items:center; } +.cal-form-actions { display:flex; gap:8px; justify-content:flex-end; margin-top:4px; } diff --git a/static/css/style-part-08.css b/static/css/17-research.css similarity index 57% rename from static/css/style-part-08.css rename to static/css/17-research.css index df8418a28..d2fa2b1d1 100644 --- a/static/css/style-part-08.css +++ b/static/css/17-research.css @@ -1,2206 +1,19 @@ -/* Fragment 9/9 of the former static/style.css. +/* 17-research - part of the former static/style.css. * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. + * 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. * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. + * 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. */ -.note-cl-dot { - width: 8px; - height: 8px; - border-radius: 50%; - border: 1.5px solid color-mix(in srgb, var(--fg) 30%, transparent); - flex-shrink: 0; - cursor: pointer; - transition: all 0.15s; -} -.note-cl-dot:hover { border-color: var(--fg); transform: scale(1.2); } -.note-cl-row.done .note-cl-dot { - background: var(--accent); - border-color: var(--accent); -} -.note-cl-row.done .note-cl-text { - opacity: 0.4; - background: linear-gradient(currentColor, currentColor) no-repeat; - background-size: 0 1px; - background-position: 0 calc(50% - 1px); - animation: cl-strike 0.32s ease-out forwards; - transition: opacity 0.2s ease; -} -/* Draws the strikethrough line left-to-right when the row is marked - done, instead of snapping it in full-width on the same frame. */ -@keyframes cl-strike { - to { background-size: 100% 1px; } -} -.note-cl-text { - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - color: var(--fg); - font-size: 12px; - padding: 3px 0 3px 3px; - flex: 1 1 auto; - width: auto; - min-width: 0; - outline: none; -} -.note-cl-text:focus { border-color: var(--fg); } -.note-cl-rm { - background: none; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - font-size: 14px; - padding: 0; - width: 24px; - height: 14px; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: flex-end; - line-height: 1; -} -.note-cl-rm:hover { opacity: 0.8; color: var(--red); } -.note-cl-add { - background: none; - border: none; - color: var(--fg); - opacity: 0.4; - cursor: pointer; - font-size: 11px; - padding: 4px 0; - text-align: left; -} - -/* ── Calendar ── */ -.cal-modal-content { width:min(680px, 94vw); max-height:88vh; overflow-x:hidden; position:relative; } -#calendar-modal .cal-modal-content { animation: none; } -/* Was overflow-y:auto with grid + day-detail flowing naturally. Now uses a - true splitter layout: the body is a flex column that doesn't scroll - itself; the grid takes the remaining space and scrolls if needed; the - day-detail has an explicit height (driven by the splitter drag via - --cal-detail-h) and shrinks the grid visually as it grows. */ -#cal-body { display:flex; flex-direction:column; gap:8px; overflow:hidden; padding:0; min-height:0; } -#cal-body > .cal-grid { flex: 1 1 auto; min-height: 120px; overflow-y: auto; overflow-x: hidden; } -@media (max-width: 768px) { - /* Let the calendar pane shrink to nothing on mobile so the day-detail - splitter can be dragged all the way to the top, hiding the calendar - entirely. Without min-height:0 the grid (or the week-wrap below) - refuses to shrink past its built-in floor. */ - #cal-body > .cal-grid, - #cal-body > .cal-wk-wrap { - min-height: 0; - max-height: none; - } - /* Let the week grid fill the calendar pane and scroll its hours internally - instead of overflowing #cal-body (which clips it and feels cramped). */ - #cal-body > .cal-wk-wrap { - flex: 1 1 auto; - overflow: auto; - } -} -.cal-toolbar { display:flex; align-items:center; gap:6px; margin-bottom:7px; flex-wrap:wrap; line-height:1; max-width:100%; row-gap:6px; } - -/* Quick-add bar: natural-language event creation. Sits directly under - the toolbar; focused with `Q`; opens the bespoke form pre-filled. */ -.cal-quickadd-row { - display: flex; - align-items: center; - gap: 8px; - margin: 0 0 8px; - padding: 1px 10px; - border: 1px solid var(--border); - border-radius: 8px; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - transition: border-color 0.15s, background 0.15s; - position: relative; -} -/* Two-tone placeholder hint: "Quick add" in accent, the example in grey, both - at one low opacity. Overlays the (empty) input; hidden once the user types. */ -.cal-quickadd-hint { - position: absolute; - left: 10px; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: flex; - align-items: center; - gap: 4px; - font-size: 12px; - color: var(--fg); - opacity: 0.5; - pointer-events: none; - white-space: nowrap; - overflow: hidden; -} -.cal-quickadd-hint .qa-hint-accent { color: var(--accent, var(--red)); } -/* ↵ enter glyph in the hint — makes it obvious the field submits on Enter. */ -.cal-quickadd-hint .qa-hint-enter { margin-left: auto; flex: 0 0 auto; color: var(--accent, var(--red)); opacity: 0.8; } -/* Cycling example text fades out before swap, then fades back in. */ -.cal-quickadd-hint .qa-hint-example { - display: block; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - font-style: italic; - transition: opacity 0.18s ease; -} -.cal-quickadd-hint .qa-hint-example.qa-hint-fade { opacity: 0; } -.cal-title-hint { - position: absolute; - left: 0; - top: 50%; - transform: translateY(calc(-50% - 2px)); - display: none; - align-items: center; - gap: 7px; - font-size: 18px; - line-height: 1.2; - white-space: nowrap; - pointer-events: none; - color: color-mix(in srgb, var(--fg) 42%, transparent); -} -.cal-hero-title:placeholder-shown ~ .cal-title-hint { display: inline-flex; } -.cal-title-hint .cal-title-enter-ico { color: var(--accent, var(--red)); flex-shrink: 0; } -/* Pure-CSS toggle: hide the hint as soon as the field has real text (no - per-keystroke JS, so typing never triggers a re-render / focus loss). */ -.cal-quickadd-input:not(:placeholder-shown) ~ .cal-quickadd-hint { display: none; } -.cal-quickadd-row:focus-within { - border-color: color-mix(in srgb, var(--accent, var(--fg)) 55%, var(--border)); - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); -} -.cal-quickadd-icon { - color: color-mix(in srgb, var(--accent, var(--fg)) 80%, transparent); - display: inline-flex; - align-items: center; - flex-shrink: 0; - opacity: 0.7; -} -.cal-quickadd-input { - flex: 1; - min-width: 0; - background: transparent; - border: none; - outline: none; - color: var(--fg); - font-family: inherit; - font-size: 12px; - height: 22px; - padding: 0; -} -.cal-quickadd-status { - font-size: 10px; - opacity: 0.55; - font-variant-numeric: tabular-nums; -} -.cal-toolbar > *, .cal-toolbar-nav > *, .cal-toolbar-right > * { vertical-align:middle; } -.cal-toolbar-nav { display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; } -.cal-toolbar-right { - display: inline-flex; - align-items: center; - justify-content: flex-start; - gap: 6px; - flex: 1 1 100%; - width: 100%; - margin-left: 0; - margin-top: 6px; - flex-wrap: wrap; -} -.cal-title { font-size:13px; font-weight:600; white-space:nowrap; height:24px; line-height:30px; padding:0 6px; display:inline-block; vertical-align:middle; box-sizing:border-box; } -.cal-toolbar-clock { - margin-left: 8px; - color: var(--accent, var(--red)); - font-size: 11px; - font-weight: 600; - font-variant-numeric: tabular-nums; -} -button.cal-nav { background:color-mix(in srgb, var(--fg) 6%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 8px; height:24px; line-height:22px; cursor:pointer; font-size:11px; font-family:inherit; box-sizing:border-box; vertical-align:middle; } -button.cal-nav:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); } -button.cal-nav.cal-toolbar-arrow { - display: none; - align-items: center; - justify-content: center; - width: 24px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 1; - opacity: 0.84; -} -.cal-toolbar-arrow-glyph { - position: relative; - top: -1px; - line-height: 1; -} -@media (max-width: 768px) { - button.cal-nav.cal-toolbar-arrow { display: inline-flex !important; } - .cal-toolbar-nav { - flex: 1 1 100%; - width: 100%; - min-width: 0; - } - #cal-next { margin-left: auto; } -} -.cal-week-start-toggle { - display: inline-flex; - align-items: center; - gap: 0; - width: max-content; - padding: 0; - height: 24px; - box-sizing: border-box; - border: 1px solid var(--border); - border-radius: 5px; - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.cal-week-start-btn { - box-sizing: border-box; - height: 22px !important; - min-height: 22px !important; - margin-top: 0 !important; - padding: 0 7px; - border: 1px solid transparent; - border-radius: 4px; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - cursor: pointer; - font-family: inherit; - font-size: 10px; - line-height: 20px; - transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease; -} -.cal-settings-actions { - display: flex; - justify-content: flex-end; - width: 100%; -} -#cal-settings-panel .cal-week-start-toggle { - display: flex; - margin-left: auto; -} -.cal-settings-sync-status { - position: relative; - top: 2px; - font-size: 11px; - opacity: 0.6; -} -.cal-settings-sync-status.is-error { - color: var(--red); - opacity: 1; -} -.cal-week-start-btn:hover { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.cal-week-start-btn.active, -.cal-week-start-btn.active:hover { - color: var(--fg); - border-color: color-mix(in srgb, var(--accent, var(--red)) 40%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent); -} -.cal-side-nav { - position: absolute; - top: var(--cal-side-nav-y, 50%); - transform: translateY(-50%); - width: 32px; - height: 32px; - border-radius: 50%; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - cursor: pointer; - z-index: 6; - opacity: 0; - pointer-events: none; - font-family: inherit; - font-size: 24px; - line-height: 28px; - padding: 0 0 3px; - transition: opacity 0.12s ease, background 0.12s ease, transform 0.12s ease; -} -.cal-modal-content:hover .cal-side-nav, -.cal-side-nav:focus-visible { - opacity: 0.84; - pointer-events: auto; -} -.cal-side-nav:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); - transform: translateY(-50%) scale(1.04); -} -button.cal-today-btn { font-size:10px; opacity:0.5; } -button.cal-add-btn { background:var(--accent); color:#fff; border:none; border-radius:50%; width:24px; height:24px; line-height:22px; font-size:18px; cursor:pointer; flex-shrink:0; padding:0; box-sizing:border-box; font-family:inherit; vertical-align:middle; text-align:center; } -button.cal-add-btn.cal-add-btn-text { - width:auto; min-width:0; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 12px; - padding: 0 10px 0 6px; - display: inline-flex; - align-items: center; - gap: 4px; - height: 24px; - line-height: 1; - margin-top: 0; - transition: background 0.15s, border-color 0.15s; -} -button.cal-add-btn.cal-add-btn-text:hover { - background: color-mix(in srgb, var(--fg) 14%, transparent); - border-color: var(--accent); - opacity: 1; -} -.cal-add-plus { - font-size: 16px; - line-height: 1; - color: var(--accent, var(--red)); - font-weight: 500; - display: inline-block; - transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); - padding-bottom: 2px; -} -button.cal-add-btn.cal-add-btn-text:hover .cal-add-plus { - transform: rotate(180deg); -} -.cal-add-label { font-size:11px; font-weight:600; line-height:1; opacity:0.85; } -/* Small variant of the toolbar's +New button. Inherits the .cal-add-btn-text - styles (pill with "+" + "New" + spring-rotate on the +), so the - animation is identical. Just shrunk a bit. */ -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { - height: 20px; - border-radius: 10px; - /* At rest: tight, equal padding around the "+" so it sits centred - with no trailing gap. On hover the right pad and the flex gap grow - to make room for the revealed "New". */ - padding: 0 5px; - gap: 0; - transition: padding 0.25s ease, gap 0.25s ease, background 0.15s, border-color 0.15s; -} -/* Don't touch .cal-add-plus on the small variant — the toolbar +New is - the reference and we want the same rotation centre / motion. The label - is shrunk and hidden until hover so the pill is compact at rest. */ -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { - font-size: 10px; - max-width: 0; - margin-left: 0; - opacity: 0; - overflow: hidden; - white-space: nowrap; - transition: max-width 0.25s ease, opacity 0.18s ease, margin-left 0.25s ease; -} -button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm:hover .cal-add-label { - max-width: 40px; - margin-left: 3px; - opacity: 0.85; -} -.cal-add-btn:hover { opacity:0.85; } - -/* Mobile: bump the +New pill and the +/- zoom buttons up to a tap-friendly - size so they're easy to hit with a thumb. */ -@media (max-width: 768px) { - /* Toolbar +New (relocated by JS into the quickadd row on mobile) */ - button.cal-add-btn.cal-add-btn-text { - height: 36px; - border-radius: 18px; - padding: 0 14px 0 12px; - gap: 6px; - flex-shrink: 0; - } - /* Day-detail +New: just a "+" on mobile — drop the label entirely. */ - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm { - width: 28px; - height: 28px; - border-radius: 50%; - padding: 0; - gap: 0; - justify-content: center; - } - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-label { - display: none; - } - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { - font-size: 18px; - padding-bottom: 0; - } - .cal-add-plus { font-size: 22px; padding-bottom: 1px; } - .cal-add-label { font-size: 13px; } - .cal-wk-zoom { - width: 36px; - height: 36px; - font-size: 20px; - border-radius: 8px; - opacity: 0.85; - } - /* The +New button sits as a sibling of the quickadd row inside a flex - wrapper, so it's clearly outside the input's bordered box. The row - itself keeps its own styling. */ - .cal-quickadd-wrap { - display: flex; - align-items: stretch; - gap: 8px; - margin: 0 0 8px; - } - .cal-quickadd-wrap > #cal-quickadd-row { - flex: 1; - min-width: 0; - margin: 0; - } - .cal-quickadd-wrap > .cal-add-btn-text { - align-self: stretch; - flex-shrink: 0; - } - /* Nudge the "+" glyph up one pixel on the round day-detail button so it - sits visually centred inside the circle. */ - button.cal-add-btn.cal-add-btn-text.cal-add-btn-sm .cal-add-plus { - transform: translateY(-1px); - } - /* Event form: bigger Create / Cancel buttons so they're easy to tap. */ - .cal-form-actions button.cal-btn { - height: 44px; - padding: 0 20px; - font-size: 14px; - border-radius: 8px; - min-width: 96px; - } - /* Left-align the day/month + tags row in the day-detail panel and - the calendar/category chip row so they sit flush against the - left edge on a narrow screen. */ - .cal-detail-header { - justify-content: flex-start; - gap: 12px; - } - .cal-filters { - justify-content: flex-start; - } -} - -/* Refresh button spin — driven by JS toggling .cal-syncing on the button. - transform-box + transform-origin keep the rotation pivoted at the SVG's - own centre so it spins in place instead of orbiting / drifting up. */ -#cal-sync svg, -#email-lib-refresh-btn svg { display: block; transform-box: fill-box; transform-origin: 50% 50%; } -/* Brief checkmark flash after a refresh completes. Lasts ~900ms in JS; - the small bounce + accent color give a clear "done" cue. */ -#cal-sync.cal-sync-done svg, -#email-lib-refresh-btn.email-lib-refresh-done svg { - color: var(--accent, #2ea043); - animation: refresh-check-pop 0.32s ease-out; -} -@keyframes refresh-check-pop { - 0% { transform: scale(0.55); opacity: 0.4; } - 60% { transform: scale(1.18); opacity: 1; } - 100% { transform: scale(1); opacity: 1; } -} -#cal-sync.cal-syncing svg, -#email-lib-refresh-btn.email-lib-refreshing svg { animation: spin 0.6s linear infinite; } -.email-undone-toggle.active, -.email-attach-toggle.active { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); - color: var(--accent, var(--red)); - font-weight: 600; -} -/* Hide the gallery-style reader-btn labels on desktop — only the mobile - @media block re-displays them under each icon. Desktop keeps compact - icon-only reader buttons. */ -.reader-btn-label { - display: inline-block; - font-size: 9px; - font-weight: 500; - line-height: 1; - letter-spacing: 0.02em; - white-space: nowrap; - opacity: 0.85; -} - -/* Inline attachment-filter toggle nested inside the search input. The - input has padding-right:34px set inline so its text doesn't slide - under the button. */ -.email-search-wrap { display: flex; align-items: center; } -/* Keep the email search input and the adjacent "Select" button identical in - height regardless of base-vs-mobile overrides — pin both explicitly. */ -.email-search-row .memory-search-input { - height: 32px; - min-height: 32px; - box-sizing: border-box; -} -.email-search-row .email-search-select-btn { - height: 32px; - min-height: 32px; - box-sizing: border-box; - margin-top: 6px; - flex-shrink: 0; -} -/* Select moved into the dropdown row — dock it to the right and match the - selects' vertical nudge (.memory-sort-select has top:3px). */ -.memory-category-filters .email-filter-select-btn, -.memory-category-filters .email-filter-refresh-btn { - position: relative; - top: 3px; - flex-shrink: 0; -} -.memory-category-filters .email-filter-refresh-btn { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0 8px; -} -.memory-category-filters .email-reminders-clear-btn { - position: relative; - top: -2px; - flex-shrink: 0; -} -.email-attach-toggle-inline { - position: absolute !important; - right: 4px !important; - top: 50%; - transform: translateY(calc(-50% - 5px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-attach-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-attach-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -/* Inline undone-toggle nested inside the search input, sits LEFT of the - attach toggle. Same visual treatment. */ -.email-undone-toggle-inline { - position: absolute !important; - right: 34px !important; - top: 50%; - transform: translateY(calc(-50% - 3px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-reminder-toggle-inline { - position: absolute !important; - right: 64px !important; - top: 50%; - transform: translateY(calc(-50% - 3px)); - width: 26px !important; - height: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 6px !important; - background: transparent !important; - border: none !important; - opacity: 0.55; -} -.email-reminder-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-reminder-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -.email-undone-toggle-inline:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; -} -.email-undone-toggle-inline.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 16%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -/* Round (circular) toggles in the search bar, matching the app's default icon - buttons. Always fully visible (not dimmed until hover). */ -.email-attach-toggle-inline, -.email-undone-toggle-inline, -.email-reminder-toggle-inline { border-radius: 50% !important; opacity: 1 !important; } -.email-search-options-btn { - position: absolute !important; - right: 5px !important; - top: 50% !important; - transform: translateY(calc(-50% - 5px)); - width: 26px !important; - height: 26px !important; - min-width: 26px !important; - padding: 0 !important; - display: inline-flex !important; - align-items: center !important; - justify-content: center !important; - border-radius: 999px !important; - background: transparent !important; - border: 0 !important; - color: inherit !important; - opacity: 0.7; - z-index: 2; -} -.email-search-options-btn:hover, -.email-search-options-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; - color: var(--accent, var(--red)) !important; -} -.email-search-options-menu { - position: absolute; - top: calc(100% + 2px); - right: 0; - z-index: 75; - min-width: 140px; - padding: 4px; - border: 1px solid var(--border); - border-radius: 8px; - background: var(--panel, var(--bg)); - box-shadow: 0 8px 24px rgba(0,0,0,0.3); -} -.email-search-options-title { - padding: 5px 9px 4px; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - font-weight: 600; - opacity: 0.65; - white-space: nowrap; -} -.email-search-options-menu button { - width: 100%; - border: 0; - background: transparent; - color: var(--fg); - display: flex; - align-items: center; - gap: 10px; - height: auto; - min-height: 0; - padding: 6px 8px; - border-radius: 8px; - font-family: inherit; - font-size: 11px; - font-weight: 400; - line-height: normal; - letter-spacing: normal; - text-align: left; - cursor: pointer; -} -.email-search-options-menu button:hover, -.email-search-options-menu button.active { - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - color: var(--accent, var(--red)); -} -.email-search-option-icon { - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - opacity: 0.5; -} -.email-search-option-icon svg { width: 14px; height: 14px; } -.email-search-date-panel { - width: 220px; - padding: 6px 7px 5px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.email-search-date-panel input { - width: 100%; - height: 26px; - box-sizing: border-box; - padding: 2px 6px; - border: 1px solid var(--border); - border-radius: 5px; - outline: none; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.email-search-date-panel input:focus { border-color: var(--accent, var(--red)); } -.email-search-date-actions { display: flex; justify-content: flex-end; gap: 5px; } -.email-search-options-menu .email-search-date-actions [data-email-date-apply] { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} -@media (max-width: 768px) { - .email-search-options-menu button { - min-height: 44px; - padding: 12px 12px !important; - font-size: 14px !important; - gap: 10px !important; - } - .email-search-options-menu .email-search-option-icon { - width: 18px; - height: 18px; - } - .email-search-options-menu .email-search-option-icon svg { - width: 16px; - height: 16px; - } -} -/* Mobile: enlarge the icons inside the inline search-bar toggles - (done / attachment / reminders) — buttons themselves stay the same, - only the SVG glyph scales up so it's tappable + visible. */ -@media (max-width: 768px) { - .email-filter-btn { - height: 30px; - min-height: 30px; - font-size: 12px; - top: -6px; - padding: 0 9px; - } - .email-folder-btn { - top: -4px; - } - - #email-lib-folder, - .memory-category-filters .email-filter-select-btn, - .memory-category-filters .email-filter-refresh-btn, - .memory-category-filters .email-filter-settings-btn { - height: 30px !important; - min-height: 30px !important; - box-sizing: border-box; - } - - .memory-category-filters .email-filter-refresh-btn, - .memory-category-filters .email-filter-settings-btn { - width: 30px; - padding: 0 !important; - } - .memory-category-filters .email-filter-settings-btn > svg { - width: 15px; - height: 15px; - } - - .email-search-row .memory-search-input { - height: 38px; - min-height: 38px; - } - - #email-lib-chip-bar { - padding-right: 42px !important; - padding-left: 30px !important; - gap: 5px !important; - } - - .email-lib-chip-bar-icon { - width: 14px !important; - height: 14px !important; - left: 9px !important; - } - - #email-lib-search { - font-size: 14px !important; - min-width: 88px !important; - top: 0 !important; - } - - #email-lib-search::placeholder { - font-size: 14px !important; - } - - #email-lib-chip-bar.has-email-lib-pills #email-lib-search { - min-width: 24px !important; - flex: 1 1 24px !important; - } - - .email-lib-pill { - min-height: 24px !important; - height: 24px !important; - line-height: 24px !important; - font-size: 12px !important; - padding: 0 6px 0 8px !important; - max-width: 180px !important; - } - - .email-lib-pill svg { - width: 14px !important; - height: 14px !important; - } - - .email-lib-pill-x { - font-size: 13px !important; - top: -4.5px !important; - } - - .email-lib-filter-pill { - min-height: 24px !important; - height: 24px !important; - line-height: 24px !important; - gap: 4px !important; - padding: 0 6px 0 8px !important; - } - - .email-lib-filter-pill .email-lib-pill-icon { - width: 17px !important; - height: 17px !important; - } - - .email-lib-filter-pill svg { - width: 18px !important; - height: 18px !important; - } - - .email-lib-filter-pill .email-lib-pill-x { - font-size: 13px !important; - top: -4.5px !important; - } - - .email-tags-toggle-inline { - right: 6px; - height: 28px !important; - min-width: 42px; - padding: 0 8px !important; - font-size: 11px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-attach-toggle-inline { - right: 56px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-undone-toggle-inline { - right: 92px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-reminder-toggle-inline { - right: 128px !important; - width: 32px !important; - height: 32px !important; - transform: translateY(calc(-50% - 3px)); - } - - .email-attach-toggle-inline svg, - .email-undone-toggle-inline svg, - .email-reminder-toggle-inline svg, - .email-filter-refresh-btn svg { - width: 16px !important; - height: 16px !important; - } -} -.email-accounts-row { - display: flex; - align-items: center; - gap: 10px; - padding: 0 0 6px; -} -.email-accounts-row .doclib-desc { flex-shrink: 0; } -/* Only the direct-child compose button gets pushed right; nested chips - inside #email-lib-accounts pack to the left as normal flex items. */ -.email-accounts-row > .memory-toolbar-btn { flex-shrink: 0; margin-left: auto; } -.email-account-chip { - display: inline-flex; - align-items: center; - gap: 5px; -} -.email-account-chip.email-account-has-unread { - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - color: color-mix(in srgb, var(--accent, var(--red)) 86%, var(--fg)); -} -.email-account-unread-dot { - width: 6px; - height: 6px; - border-radius: 999px; - background: var(--accent, var(--red)); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - flex: 0 0 auto; -} -.email-account-chip-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} -.email-account-unread-count { - font-size: 9px; - line-height: 1; - font-weight: 700; - color: var(--accent, var(--red)); - opacity: 0.9; - position: relative; - top: 0; -} -.email-account-away-label { - color: var(--accent, var(--red)); - font-size: 9px; - font-weight: 700; - line-height: 1; - flex: 0 0 auto; -} -.email-accounts-loading-whirlpool { - width: 14px; - height: 14px; - margin: 3px 4px 0 1px; - display: inline-flex; - flex: 0 0 auto; -} -.email-accounts-loading-label { - font-size: 10px; - opacity: 0.55; - position: relative; - top: 2px; - white-space: nowrap; -} -.email-loading-with-label { - /* Fill the email list grid so the spinner lands in the middle of the - email window rather than the top of the empty list section. */ - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - text-align: center; - pointer-events: none; -} -.date-section-header { - display: flex; - align-items: center; - gap: 7px; - padding: 8px 9px 4px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 10px; - font-weight: 700; - letter-spacing: 0; - line-height: 1.2; - text-transform: uppercase; - user-select: none; -} -.date-section-header::after { - content: ''; - flex: 1 1 auto; - height: 1px; - background: color-mix(in srgb, var(--border) 72%, transparent); -} -#session-list .date-section-header { - padding-left: 10px; - padding-right: 8px; - cursor: pointer; -} -#session-list .date-section-header.collapsed { - color: color-mix(in srgb, var(--fg) 36%, transparent); -} -#session-list .date-section-header.collapsed::after { - opacity: 0.45; -} -#session-list .date-section-header:focus-visible { - outline: 1px solid color-mix(in srgb, var(--accent) 45%, transparent); - outline-offset: 2px; - border-radius: 5px; -} -/* Bootstrap row shown while the session list hydrates, and on load failure. - Reads as a normal list row but is not selectable. */ -.session-list-bootstrap { - cursor: default; - pointer-events: none; -} -#email-lib-grid .date-section-header { - padding: 10px 5px 3px; -} -#email-lib-modal .email-lib-sync-status { - flex: 0 0 auto; - min-height: 14px; - padding: 1px 4px 0; - font-size: 10.5px; - line-height: 1.2; - text-align: center; - opacity: 0.58; - color: var(--muted-text, currentColor); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - position: relative; - top: 4px; -} -@media (max-width: 768px) { - #email-lib-modal .email-lib-sync-status { - min-height: 13px; - padding-right: 5px; - font-size: 10px; - } -} -/* Nudge the subject + sender/date meta line 4px right inside each - email card so they don't sit flush against the read/unread/done - markers on the left. Includes #email-lib-modal selector to - override the expanded card's `padding: 4px 0 6px` shorthand - which was zeroing out the title's padding-left. */ -#email-lib-grid .email-card-titlerow, -#email-lib-grid .memory-item-meta, -#email-lib-modal .doclib-card-expanded .email-card-titlerow { - padding-left: 4px; -} -.email-loading-label { - font-size: 11px; - opacity: 0.6; -} - -/* Refresh button now lives top-right in the modal header next to the close X. - Borderless (matches the close X), and a fixed square box so the spin and the - refresh→checkmark icon swap never change its size or nudge neighbours. */ -.email-lib-header-actions #email-lib-refresh-btn { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - border: none; - background: none; - padding: 0; - width: 24px; - height: 24px; -} -/* Bump the icon up on mobile for a bigger visual — but keep the button BOX at - 24px so it never exceeds the header's natural height (the title/close set - that). A taller box would grow the sticky header and push the list down. */ -@media (max-width: 768px) { - .email-lib-header-actions #email-lib-refresh-btn svg { - width: 17px; - height: 17px; - } -} -.cal-view-toggle { - display: inline-flex; - align-items: stretch; - border: 1px solid var(--border); - border-radius: 5px; - overflow: hidden; - vertical-align: middle; - box-sizing: border-box; - padding: 0; - margin: 0; - line-height: 1; -} -.cal-view-btn + .cal-view-btn { border-left: 1px solid var(--border); } -.cal-view-btn:hover { opacity: 0.75; } -.cal-view-btn.active { - background: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--accent, var(--red)); - opacity: 1; - font-weight: 600; -} -/* Toolbar holds only the toggle button; the chip row renders below the - toolbar on its own line and wraps freely. */ -.cal-filters { - display:flex; - gap:6px; - margin:6px 0 8px; - flex-wrap:wrap; - padding:6px 8px; - border:1px solid color-mix(in srgb, var(--fg) 8%, transparent); - border-radius:8px; - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 4%, transparent), transparent), - color-mix(in srgb, var(--fg) 2.5%, var(--bg)); - box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 4%, transparent); -} -/* Round the native color-input swatch in calendar settings. The outer - wraps a colored fill drawn by the browser; we need to round both - the wrapper and the fill so it actually appears circular. */ -.cal-s-color { border-radius: 50%; overflow: hidden; } -.cal-filter-item { display:inline-flex; align-items:center; gap:4px; cursor:pointer; font-size:10px; padding:2px 8px; line-height:1.4; border-radius:10px; background:color-mix(in srgb, var(--bg) 72%, transparent); border:1px solid color-mix(in srgb, var(--fg) 12%, transparent); transition:opacity 0.15s, background 0.15s, border-color 0.15s, transform 0.15s; } -.cal-filter-item:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); } -.cal-filter-item.cal-filter-important { font-weight:700; color:#e5a33a; border-color:color-mix(in srgb, #e5a33a 40%, transparent); } -/* Match the toolbar's other buttons (cal-nav, cal-view-btn, cal-add-btn-text): - 24px tall, 11px font, 5px corners. */ -.cal-filter-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font-size: 11px; - font-family: inherit; - font-weight: 500; - padding: 0 10px; - height: 24px; - line-height: 1; - border-radius: 5px; - box-sizing: border-box; - vertical-align: middle; - opacity: 0.65; - position: relative; - top: -3px; -} -.cal-filter-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 12%, transparent); } -.cal-filter-toggle-glyph { - display: inline-block; - position: relative; - top: 0; - margin-right: 3px; - color: var(--accent, var(--red)); -} -.cal-filter-dot { width:7px; height:7px; border-radius:50%; flex-shrink:0; } -/* The grid is now a flex column: one row of weekday headers + six - .cal-week-row rows. Each row holds the 7 day cells in its own - internal grid plus the absolute-positioned multi-day overlays. - --bars on a week-row is the number of multi-day events overlapping - it; cells in that row use it to reserve top padding so the bar - overlays don't cover the day numbers or single-event rows. */ -.cal-grid { display:flex; flex-direction:column; gap:1px; background:color-mix(in srgb, var(--fg) 8%, transparent); border-radius:6px; overflow:hidden; } -.cal-week-headers { display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } -.cal-week-row { position:relative; display:grid; grid-template-columns:repeat(7,1fr); gap:1px; } -.cal-weekday { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); text-align:center; font-size:10px; font-weight:600; opacity:0.4; padding:5px 0; } -.cal-day { - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 2%, transparent), transparent 34px), - var(--bg); - min-height:78px; - padding:3px; - cursor:pointer; - position:relative; - transition:background 0.12s, box-shadow 0.12s; - overflow:hidden; -} -.cal-day:hover { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); } -.cal-day.cal-today { - box-shadow: inset 0 0 0 2px var(--accent, var(--red)); - background: var(--bg); - border-radius: 8px; -} -.cal-day.cal-selected:not(.cal-today) .cal-day-num { - color: var(--accent, var(--fg)); - opacity: 1; - font-weight: 700; -} -.cal-day.cal-drag-over { background:color-mix(in srgb, var(--accent) 18%, var(--bg)); } -.cal-day.cal-range-select { background:color-mix(in srgb, var(--accent, var(--red)) 25%, var(--bg)); box-shadow:inset 0 0 0 1px var(--accent, var(--red)); } -.cal-day-num { font-size:10px; font-weight:600; display:block; margin-bottom:1px; opacity:0.7; line-height:1.2; } -.cal-dots { display:flex; flex-direction:row; align-items:center; gap:2px; flex-wrap:nowrap; margin-bottom:1px; height:6px; } -.cal-dot { width:5px; height:5px; border-radius:50%; display:inline-block; cursor:grab; flex-shrink:0; } -.cal-dot-more { font-size:7px; opacity:0.4; } -.cal-event-row { - display:flex; - align-items:center; - gap:4px; - padding:2px 4px; - border-radius:4px; - cursor:grab; - line-height:1.2; - margin-bottom:2px; - min-height:17px; - background:color-mix(in srgb, var(--fg) 5%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); - animation:cal-event-enter 0.16s ease-out both; - transition:background 0.15s, box-shadow 0.15s, transform 0.15s; -} -.cal-event-row:hover { background:color-mix(in srgb, var(--fg) 10%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); transform:translateY(-1px); } -.cal-event-row-dot { width:6px; height:6px; border-radius:50%; flex-shrink:0; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } -.cal-event-row-time { font-size:10px; opacity:0.72; font-variant-numeric:tabular-nums; flex-shrink:0; font-weight:600; } -.cal-event-row-name { font-size:10px; opacity:0.96; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; flex:1; min-width:0; font-weight:600; } -.cal-event-reminder { - display: inline-flex; - width: 12px; - height: 12px; - margin-left: 5px; - vertical-align: -2px; - color: color-mix(in srgb, var(--fg) 58%, transparent); - opacity: 0.9; - flex: 0 0 auto; -} -.cal-event-reminder svg { - width: 100%; - height: 100%; - display: block; -} -.cal-event-row .cal-event-reminder { - width: 10px; - height: 10px; - margin-left: 4px; -} -.cal-event-source { - display:inline-flex; - align-items:center; - justify-content:center; - width:12px; - height:12px; - margin-left:5px; - border-radius:50%; - font-size:7px; - font-weight:800; - line-height:1; - color:var(--cal-event-fg, var(--fg)); - background:color-mix(in srgb, currentColor 16%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, currentColor 20%, transparent); - vertical-align:1px; -} -.cal-event-source svg { - width: 10px; - height: 10px; - display: block; -} -.cal-event-row .cal-event-source { - width:11px; - height:11px; - font-size:7px; - margin-left:4px; - color:color-mix(in srgb, var(--fg) 72%, transparent); -} -.cal-multiday .cal-event-reminder, -.cal-wk-allday-event .cal-event-reminder { - color: var(--cal-event-fg, currentColor); - opacity: 0.88; -} -.cal-multiday .cal-event-source, -.cal-wk-allday-event .cal-event-source { - color: var(--cal-event-fg, currentColor); -} -.cal-event-more { font-size:9px; opacity:0.55; padding-left:6px; } -/* Multi-day bar — positioned as an absolute overlay inside its - .cal-week-row so it spans uninterrupted across every column it - covers. --col is the starting column (0–6), --span is the number - of days it covers within the row, --slot stacks parallel bars. */ -.cal-multiday { - position: absolute; - /* Fractional offsets let timed events that cross midnight render at - true proportional width. start-frac shifts the left edge into the - first day; end-frac trims the right edge inside the last day. - All-day events default to (0, 1) and still fill whole columns. */ - left: calc((var(--col, 0) + var(--start-frac, 0)) * (100% / 7)); - width: calc( - (var(--span, 1) - var(--start-frac, 0) - (1 - var(--end-frac, 1))) - * (100% / 7) - ); - top: calc(2px + var(--slot, 0) * 12px); - height: 12px; - font-size: 9px; - line-height: 12px; - padding: 0 5px; - border-radius: 4px; - color: var(--cal-event-fg, #fff); - font-weight: 700; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: grab; - opacity: 0.98; - z-index: 2; - pointer-events: auto; - box-shadow: 0 1px 2px color-mix(in srgb, #000 18%, transparent); -} -/* Splitter between the month/week grid and the day-tasks panel — drag - vertically to give the day panel more room. Height clamped by the - `--cal-detail-h` variable set on #cal-body by the splitter drag JS. */ -.cal-splitter { - height: 8px; - margin: 6px -10px 0; - cursor: row-resize; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - user-select: none; - touch-action: none; -} -/* Mobile: fat hitbox so the splitter is easy to grab with a thumb, - without changing how the grip itself looks. */ -@media (max-width: 768px) { - .cal-splitter { - height: 24px; - margin-top: 0; - margin-bottom: 0; - } - .cal-splitter-grip { - width: 56px; - height: 4px; - } -} -.cal-splitter-grip { - width: 42px; - height: 3px; - border-radius: 2px; - background: color-mix(in srgb, var(--fg) 25%, transparent); - transition: background 0.12s; -} -.cal-splitter:hover .cal-splitter-grip, -.cal-splitter-dragging .cal-splitter-grip { - background: var(--accent, var(--red)); -} -.cal-day-detail { - margin-top: 4px; - border-top: 1px solid var(--border); - padding-top: 8px; - height: var(--cal-detail-h, 240px); - overflow-y: auto; - overscroll-behavior: contain; - flex-shrink: 0; -} -.cal-detail-header { display:flex; justify-content:space-between; align-items:center; font-size:12px; font-weight:600; margin-bottom:6px; padding-right:8px; } -.cal-empty { font-size:11px; opacity:0.3; padding:4px 0; } -.cal-event-item { display:flex; gap:9px; padding:8px 9px; border-radius:6px; cursor:pointer; align-items:flex-start; background:color-mix(in srgb, var(--fg) 3.5%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); margin-bottom:4px; } -.cal-event-item:hover { background:color-mix(in srgb, var(--fg) 8%, transparent); box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); } -.cal-event-dot { width:9px; height:9px; border-radius:50%; flex-shrink:0; margin-top:4px; box-shadow:0 0 0 1px color-mix(in srgb, var(--bg) 80%, transparent); } -.cal-event-info { flex:1; min-width:0; } -.cal-event-name { font-size:13px; font-weight:700; line-height:1.25; color:color-mix(in srgb, var(--fg) 96%, transparent); overflow-wrap:anywhere; } -.cal-event-tag { - display: inline-block; - font-size: 10px; - font-weight: 500; - padding: 1px 6px; - border-radius: 8px; - border: 1px solid; - margin-left: 6px; - vertical-align: 1px; - opacity: 0.85; - white-space: nowrap; -} -.cal-event-time { font-size:11px; opacity:0.68; line-height:1.35; margin-top:2px; font-weight:500; } -.cal-event-loc { font-size:11px; opacity:0.58; line-height:1.35; margin-top:1px; } -/* ── Week view: hour grid ────────────────────────────────────────── - Vertical hour rail on the left, 7 day columns on the right with their - own all-day strip and an absolute-positioned hour grid below it. */ -.cal-wk-wrap { - display: flex; - flex-direction: row; - gap: 0; - background: - linear-gradient(180deg, color-mix(in srgb, var(--fg) 3%, transparent), transparent 120px), - var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - /* The wrap itself scrolls so the hour rail and day columns move - together; column headers and the rail spacer are sticky-pinned. */ - overflow: auto; - max-height: calc(100vh - 220px); - min-height: 360px; - position: relative; -} -.cal-wk-now-below-hint { - position: absolute; - height: 2px; - border-radius: 2px 2px 0 0; - background: var(--accent, var(--red)); - pointer-events: none; - z-index: 38; -} -@media (max-width: 768px) { - .cal-wk-wrap { - -webkit-overflow-scrolling: touch; - overscroll-behavior-y: contain; - } - .cal-wk-wrap > .cal-wk-rail, - .cal-wk-wrap > .cal-wk-cols { - transform: translateY(var(--wk-edge-pull, 0)); - } - .cal-wk-wrap.cal-wk-edge-release > .cal-wk-rail, - .cal-wk-wrap.cal-wk-edge-release > .cal-wk-cols { - transition: transform 0.14s cubic-bezier(0.2, 0.75, 0.25, 1); - } - .cal-wk-wrap.cal-wk-pull-top { - border-top-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); - } - .cal-wk-wrap.cal-wk-pull-bottom { - border-bottom-color: color-mix(in srgb, var(--accent, var(--red)) 65%, var(--border)); - } -} -.cal-wk-rail { - flex: 0 0 56px; - border-right: 1px solid var(--border); - display: flex; - flex-direction: column; - background: color-mix(in srgb, var(--fg) 2%, var(--bg)); - position: sticky; - left: 0; - z-index: 5; -} -.cal-wk-rail-spacer { - /* Match the 32px day header. The 24px all-day row gets its own spacer - below, so the zoom controls sit high without shifting the hour grid. */ - height: 32px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 0; - background: color-mix(in srgb, var(--fg) 2%, var(--bg)); - z-index: 9; - display: flex; - align-items: center; - justify-content: center; - gap: 2px; - padding: 0 4px; -} -.cal-wk-rail-allday-spacer { - height: 24px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - position: sticky; - top: 32px; - z-index: 8; - background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); -} -.cal-wk-zoom { - position: relative; - top: -3px; - width: 22px; - height: 22px; - border: 1px solid var(--border); - background: var(--bg); - color: var(--fg); - border-radius: 50%; - font-family: inherit; - font-size: 14px; - font-weight: 600; - line-height: 1; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0; - opacity: 0.55; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.cal-wk-zoom:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--fg)) 50%, var(--border)); -} -.cal-wk-rail-cell { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 55%, transparent); - padding: 4px 8px 0; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); - flex-shrink: 0; - display: flex; - align-items: flex-start; - justify-content: flex-end; - font-variant-numeric: tabular-nums; -} -.cal-wk-cols { - flex: 1; - display: grid; - grid-template-columns: repeat(7, 1fr); - position: relative; -} -.cal-wk-col { - border-left: 1px solid var(--border); - display: flex; - flex-direction: column; - min-width: 0; -} -.cal-wk-col:first-child { border-left: none; } -.cal-wk-col-head { - height: 32px; - padding: 0 6px; - display: flex; - align-items: center; - gap: 6px; - font-size: 11px; - flex-shrink: 0; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - border-bottom: 1px solid color-mix(in srgb, var(--fg) 5%, transparent); - position: sticky; - top: 0; - z-index: 40; -} -/* Today on a Sunday still wins the accent treatment. */ -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-col-head { background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); } -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dn, -.cal-wk-col.cal-wk-today.cal-wk-sun .cal-wk-dt { color: var(--accent, var(--fg)); } -.cal-wk-dn { font-weight: 600; opacity: 0.6; } -.cal-wk-allday { - height: 24px; - padding: 2px 4px; - display: flex; - flex-direction: row; - gap: 2px; - align-items: center; - background: color-mix(in srgb, var(--fg) 1.5%, var(--bg)); - border-bottom: 1px solid var(--border); - flex-shrink: 0; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: none; - position: sticky; - top: 32px; - z-index: 39; -} -.cal-wk-allday-event { - font-size: 10.5px; - font-weight: 700; - padding: 3px 6px; - border-radius: 4px; - color: var(--cal-event-fg, var(--fg)); - cursor: pointer; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Hour grid body */ -.cal-wk-grid { - position: relative; - flex: 0 0 auto; - cursor: cell; - user-select: none; - background: - repeating-linear-gradient( - to bottom, - transparent 0, - transparent calc(var(--wk-hour-px, 52px) / 2 - 1px), - color-mix(in srgb, var(--fg) 4%, transparent) calc(var(--wk-hour-px, 52px) / 2), - transparent calc(var(--wk-hour-px, 52px) / 2 + 1px) - ); -} -.cal-wk-cell { - border-bottom: 1px solid color-mix(in srgb, var(--fg) 6%, transparent); - box-sizing: border-box; -} -.cal-wk-cell:hover { background: color-mix(in srgb, var(--fg) 3%, transparent); } -/* "Now" line */ -.cal-wk-now { - position: absolute; - left: 0; right: 0; - height: 0; - border-top: 2px solid color-mix(in srgb, var(--accent, var(--red, #f87171)) 90%, transparent); - pointer-events: none; - z-index: 7; - filter: drop-shadow(0 1px 3px color-mix(in srgb, var(--accent, var(--red)) 35%, transparent)); -} -/* Event blocks */ -.cal-wk-block { - position: absolute; - left: calc((var(--lane, 0) * (100% / var(--lane-count, 1))) + 2px); - width: calc((100% / var(--lane-count, 1)) - 4px); - right:auto; - border-left: 4px solid var(--fg); - border-radius: 5px; - padding: 5px 7px 7px; - font-size: 12px; - line-height: 1.3; - cursor: pointer; - /* `clip` preserves the rounded-card boundary without creating an overflow - container, allowing the label inside to stick to the week scroller. */ - overflow: clip; - z-index: 2; - box-shadow: 0 2px 6px color-mix(in srgb, #000 14%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 7%, transparent); - animation:cal-event-enter 0.18s ease-out both; - transition: width 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), height 0.2s cubic-bezier(0.2, 0.75, 0.25, 1), filter 0.12s, transform 0.12s, box-shadow 0.16s; - box-sizing: border-box; -} -.cal-wk-block:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 5px 14px color-mix(in srgb, #000 20%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 14%, transparent); } -.cal-wk-block-label { - position: sticky; - top: 37px; - z-index: 1; - min-width: 0; - pointer-events: none; -} -.cal-wk-has-allday .cal-wk-block-label { - top: 61px; -} -.cal-wk-block-name { font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; color:color-mix(in srgb, var(--fg) 96%, transparent); } -.cal-wk-block-time { font-size: 10.5px; opacity: 0.82; font-variant-numeric: tabular-nums; margin-top: 2px; font-weight:600; } -@media (hover: hover) and (pointer: fine) { - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left { - left: auto; - right: calc(100% - var(--lane-right) + 2px); - } - .cal-wk-block.cal-wk-block-crowded:hover { - width: var(--hover-width, 170px); - height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; - overflow: clip; - z-index: 30; - filter: brightness(1.08); - transform: translateY(var(--hover-shift-y, -2px)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-hover-width-only:hover { - height: var(--event-height) !important; - transform: translateY(-1px); - } - .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-label { - position: absolute; - top: 5px; - width: calc(var(--hover-width, 170px) - 18px); - animation: cal-wk-hover-label-reveal 0.2s ease-out both; - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-left:hover .cal-wk-block-label { - left: auto; - right: 7px; - } - .cal-wk-block.cal-wk-block-crowded.cal-wk-expand-right:hover .cal-wk-block-label { - left: 7px; - right: auto; - } - .cal-wk-block.cal-wk-block-crowded:hover .cal-wk-block-name { - display: block; - white-space: normal; - overflow: visible; - text-overflow: clip; - overflow-wrap: anywhere; - } -} -@media (max-width: 768px) { - /* Mobile week view: the hour rail already shows the time, so drop it from the - card and let the title wrap to fill the freed space (more readable text). */ - .cal-wk-block-time { display: none; } - .cal-wk-block-name { - white-space: normal; - line-height: 1.15; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 4; - -webkit-box-orient: vertical; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left { - left: auto; - right: calc(100% - var(--lane-right) + 2px); - } - .cal-wk-block.cal-wk-mobile-expanded { - width: var(--hover-width, 170px); - height: var(--hover-fit-height, var(--hover-height, var(--event-height))) !important; - overflow: clip; - z-index: 30; - filter: brightness(1.08); - transform: translateY(var(--hover-shift-y, 0)); - box-shadow: 0 8px 22px color-mix(in srgb, #000 34%, transparent), inset 0 0 0 1px color-mix(in srgb, var(--fg) 18%, transparent); - } - .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-label { - position: absolute; - top: 5px; - width: calc(var(--hover-width, 170px) - 18px); - animation: cal-wk-hover-label-reveal 0.2s ease-out both; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-left .cal-wk-block-label { - left: auto; - right: 7px; - } - .cal-wk-block.cal-wk-mobile-expanded.cal-wk-expand-right .cal-wk-block-label { - left: 7px; - right: auto; - } - .cal-wk-block.cal-wk-mobile-expanded .cal-wk-block-name { - display: block; - white-space: normal; - overflow: visible; - text-overflow: clip; - overflow-wrap: anywhere; - -webkit-line-clamp: unset; - } -} -/* Resize handle: a 6 px hit zone along the block's bottom edge. The - visible 2-px stripe only shows on hover so blocks at rest stay clean. */ -.cal-wk-block-resize { - position: absolute; - left: 4px; - right: 4px; - bottom: 0; - height: 6px; - cursor: ns-resize; - z-index: 3; -} -/* Drag-to-create ghost */ -.cal-wk-ghost { - position: absolute; - left: 2px; - right: 2px; - background: color-mix(in srgb, var(--accent, var(--fg)) 28%, transparent); - border: 1px dashed color-mix(in srgb, var(--accent, var(--fg)) 70%, transparent); - border-radius: 4px; - pointer-events: none; - z-index: 3; - font-size: 10px; - font-weight: 600; - display: flex; - align-items: center; - justify-content: center; - color: var(--accent, var(--fg)); - font-variant-numeric: tabular-nums; -} -.cal-wk-slot-selected { - background: color-mix(in srgb, var(--accent, var(--fg)) 20%, var(--bg)); - border-style: solid; - border-color: var(--accent, var(--fg)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--fg)) 20%, transparent); - overflow: visible; - pointer-events: auto; - cursor: pointer; -} -.cal-wk-slot-resize { - position: absolute; - left: 14%; - right: 14%; - bottom: -7px; - height: 14px; - z-index: 4; - cursor: ns-resize; - touch-action: none; - pointer-events: auto; -} -.cal-year { display:grid; grid-template-columns:repeat(4, 1fr); gap:10px; } -.cal-year-month { background:color-mix(in srgb, var(--fg) 3%, var(--bg)); border:1px solid var(--border); border-radius:6px; padding:6px; cursor:pointer; transition:background 0.15s, border-color 0.15s, transform 0.15s; } -.cal-year-month:hover { background:color-mix(in srgb, var(--fg) 8%, var(--bg)); border-color:var(--accent); transform:translateY(-1px); } -.cal-year-month-title { font-size:11px; font-weight:600; text-align:center; margin-bottom:4px; opacity:0.7; } -.cal-year-grid { display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; } -.cal-year-wd { font-size:7px; text-align:center; opacity:0.3; padding:1px 0; } -.cal-year-cell { font-size:8px; text-align:center; padding:2px 0; min-height:14px; border-radius:2px; transition:transform 0.12s, background 0.12s; } -.cal-year-day:hover { background:var(--accent, var(--red)); color:#fff; opacity:1; transform:scale(1.15); font-weight:700; z-index:2; position:relative; } -.cal-year-today { color:var(--accent, var(--red)); font-weight:700; opacity:1; } -.cal-year-has { opacity:1; background:color-mix(in srgb, var(--accent) 15%, transparent); } -.cal-year-has.cal-year-today { background:color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); } -.cal-loading { display:flex; justify-content:center; padding:40px 0; } -.cal-badge { background:var(--accent); border-radius:50%; width:6px; height:6px; margin-left:auto; display:inline-block; flex-shrink:0; } -/* Search input — in-panel variant lives inside the day-detail panel and - spans its width; no width animation since growing/shrinking on every - keystroke would jitter beside the events list. */ -.cal-search-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:0 8px; height:24px; color:var(--fg); font-size:11px; font-family:inherit; width:120px; box-sizing:border-box; transition:width 0.15s; } -.cal-search-input:focus { outline:none; border-color:var(--accent); width:180px; } -.cal-search-input.cal-day-search { width:100%; margin-bottom:0; transition:none; padding-left: 28px; } -/* Wrap for the search input so the magnifying-glass icon can be absolute- - positioned inside the field. */ -.cal-search-wrap { - position: relative; - display: block; - margin-bottom: 6px; -} -.cal-search-wrap .cal-search-icon { - position: absolute; - left: 8px; - top: 50%; - transform: translateY(-50%); - pointer-events: none; - color: var(--accent, var(--red)); -} -.cal-day-search-meta { font-size:10px; opacity:0.5; margin:0 2px 6px; } -.cal-search-results { display:flex; flex-direction:column; gap:4px; } -.cal-search-count { font-size:10px; opacity:0.5; padding:4px 2px 8px; } -/* Agenda view */ -.cal-agenda { display:flex; flex-direction:column; gap:12px; flex:1 1 auto; min-height:0; overflow-y:auto; overscroll-behavior:contain; padding-left:4px; } -.cal-agenda-day { display:flex; flex-direction:column; gap:5px; position:relative; padding-left:14px; } -.cal-agenda-date { font-size:12px; font-weight:800; opacity:0.72; padding:5px 2px 3px; border-bottom:1px solid var(--border); } -.cal-agenda-today-badge { - display: inline-block; - margin-left: 6px; - font-size: 9px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - vertical-align: 1px; -} -.cal-agenda-empty { font-size:11px; opacity:0.4; padding:8px; font-style:italic; } -.cal-agenda-event { - display:flex; - gap:9px; - padding:9px 10px; - border-radius:6px; - cursor:pointer; - align-items:center; - position:relative; - background:color-mix(in srgb, var(--fg) 3.5%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); - animation:cal-event-enter 0.18s ease-out both; - transition:background 0.15s, box-shadow 0.15s, transform 0.15s; -} -.cal-agenda-event:hover { - background:color-mix(in srgb, var(--fg) 8%, transparent); - box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 12%, transparent); - transform:translateX(2px); -} -.cal-agenda-event .cal-event-more, -.cal-event-item .cal-event-more { opacity:0; transition:opacity 0.15s; } -.cal-event-item { position:relative; } - -/* ── Personal Assistant ── */ -.sidebar-assistant-entry { gap: 6px; min-height: 29px; box-sizing: border-box; align-items: center; } -.sidebar-assistant-entry .sidebar-action-icon { position: relative; left: -2px; } -#sidebar-assistant-gear:hover { opacity: 0.8 !important; } -.assistant-settings-form { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 2px; } -.assistant-field { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: color-mix(in srgb, var(--fg) 70%, transparent); } -.assistant-field > span { font-size: 11px; opacity: 0.6; } -.assistant-field input[type="text"], -.assistant-field input[type="time"], -.assistant-field select, -.assistant-field textarea { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 5px; - padding: 7px 10px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - box-sizing: border-box; -} -.assistant-field textarea { resize: vertical; min-height: 60px; } -.assistant-field input:focus, -.assistant-field select:focus, -.assistant-field textarea:focus { outline: none; border-color: var(--accent, var(--red)); } -.assistant-field-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; } -.assistant-field-row > .assistant-field { flex: 1 1 180px; } -.assistant-field-check { flex-direction: row !important; align-items: center; gap: 6px; padding-bottom: 7px; } -.assistant-checkins { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; padding-top: 8px; border-top: 1px solid var(--border); } -.assistant-checkins h5 { margin: 0 0 2px; font-size: 12px; font-weight: 600; opacity: 0.8; } -.assistant-checkin-row { - display: flex; - flex-direction: column; - gap: 4px; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.assistant-checkin-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; } -.assistant-checkin-head input[type="text"], -.assistant-checkin-head input[type="time"] { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 4px; - padding: 4px 8px; - color: var(--fg); - font-size: 12px; - font-family: inherit; -} -.assistant-checkin-head input[type="text"] { flex: 1; min-width: 120px; } -.assistant-checkin-head input[type="time"] { width: 90px; } -.assistant-checkin-run { - background: none; - border: 1px solid var(--border); - color: var(--fg); - font-family: inherit; - font-size: 11px; - padding: 4px 8px; - border-radius: 4px; - cursor: pointer; - opacity: 0.7; - transition: opacity 0.15s; -} -.assistant-checkin-run:hover { opacity: 1; } -.assistant-checkin-prompt { - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - border: 1px solid var(--border); - border-radius: 4px; - padding: 6px 8px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - resize: vertical; - min-height: 48px; -} -.assistant-checkin-prompt:focus { outline: none; border-color: var(--accent, var(--red)); } -.assistant-checkin-meta { font-size: 10px; opacity: 0.45; } -.assistant-settings-actions { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 8px; - padding-top: 8px; - border-top: 1px solid var(--border); -} -.assistant-tools-grid { - display: flex; - flex-direction: column; - gap: 6px; - max-height: 240px; - overflow-y: auto; - padding: 6px; - border: 1px solid var(--border); - border-radius: 6px; - margin-top: 4px; -} -.assistant-tool-group { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.assistant-tool-group-label { - font-size: 10px; - font-weight: 600; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.03em; - width: 100%; - margin-bottom: 1px; -} -.assistant-tool-item { - display: inline-flex; - align-items: center; - gap: 3px; - font-size: 11px; - padding: 2px 6px; - border: 1px solid var(--border); - border-radius: 4px; - cursor: pointer; - transition: background 0.1s, border-color 0.1s; - user-select: none; -} -.assistant-tool-item:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.assistant-tool-item:has(input:checked) { - border-color: var(--accent); - background: color-mix(in srgb, var(--accent) 10%, transparent); -} -.assistant-tool-item input[type="checkbox"] { - width: 12px; - height: 12px; - margin: 0; -} - -/* Empty state */ -.cal-empty-state { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:60px 20px; text-align:center; gap:10px; } -.cal-empty-title { font-size:14px; font-weight:600; opacity:0.8; } -.cal-empty-msg { font-size:12px; opacity:0.5; max-width:320px; line-height:1.5; margin-bottom:8px; } -.cal-allday-switch { margin:0; flex-shrink:0; } -/* Stack the "All day" label above its toggle so it doesn't get squeezed / - truncated next to the date inputs in the row. */ -.cal-allday-ctrl { display:flex; flex-direction:column; align-items:center; gap:3px; flex-shrink:0; } -.cal-allday-label { font-size:10px; opacity:0.5; white-space:nowrap; line-height:1; } -.cal-form { display:flex; flex-direction:column; gap:8px; } -.cal-form-title { font-size:13px; font-weight:600; margin-bottom:2px; } -.cal-title-wrap { position: relative; } -/* Calendar-picker select sits 2px higher; its border-left/background tint is - set in JS to the chosen calendar's colour. */ -.cal-f-cal-select { position: relative; top: -4px; } - -/* ── Bespoke event form ──────────────────────────────────────────── - Big clock-face date/time hero with the title under it. Details panel - stays collapsed until the title gets focus or "Add details" is clicked. -*/ -.cal-form-bespoke { - position: relative; - width: min(720px, calc(100% - 24px)); - max-width: none; - margin: 12px auto; - padding: 18px 28px 14px; - box-sizing: border-box; - background: var(--bg); - /* Optional per-event tint set by JS via --ev-color. When unset, falls - back to the theme's neutral border so the card looks like normal. */ - border: 1px solid var(--border); - border-radius: 14px; - box-shadow: 0 8px 24px color-mix(in srgb, var(--fg) 6%, transparent); - transition: border-color 0.18s, box-shadow 0.18s; -} -/* When a colour is picked, lift the card with a soft tinted border + glow - so the choice is unmistakable. */ -.cal-form-bespoke[style*="--ev-color"] { - border-color: color-mix(in srgb, var(--ev-color) 55%, var(--border)); - box-shadow: 0 8px 28px color-mix(in srgb, var(--ev-color) 22%, transparent); -} -.cal-form-bespoke[style*="--ev-color"] .cal-btn-primary { - background: var(--ev-color); - border-color: var(--ev-color); -} -.cal-form-bespoke.cal-form-bg-image .cal-btn-primary { - background: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); - color: var(--bg); -} -.cal-form-close { - position: absolute; top: 10px; right: 12px; - background: none; border: none; color: var(--fg); - opacity: 0.35; cursor: pointer; font-size: 20px; line-height: 1; - padding: 4px 8px; border-radius: 6px; -} -.cal-form-close:hover { opacity: 0.9; background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cal-form-mobile-cancel { display: none; } - -/* "Today is …" header pinned at the top of the form. Keeps you oriented - even when the event date you're picking is way off in the future. */ -.cal-form-today { - text-align: center; - font-size: 11px; - letter-spacing: 0.08em; - text-transform: uppercase; - opacity: 0.45; - margin-bottom: 0; -} - -/* Hero — clock face + date label. Spacing between the two lives entirely - on the clock's bottom margin so it's predictable. */ -.cal-hero { - display: flex; flex-direction: column; align-items: center; - gap: 0; margin-bottom: 12px; -} -.cal-hero-time { - display: inline-flex; align-items: baseline; gap: 8px; - font-family: ui-monospace, SFMono-Regular, "Menlo", "Consolas", monospace; - font-variant-numeric: tabular-nums; - font-size: 56px; - letter-spacing: 0.02em; - font-weight: 200; - color: var(--fg); - line-height: 1; - /* Behaves like a button — strip native chrome but keep it tappable. */ - background: none; - border: none; - /* Pill that reads as a real hit target for a 56-px clock face. - Top padding kept tight so the clock sits close to the "Today is" line. */ - padding: 6px 38px 18px; - margin: 0 0 24px; - border-radius: 18px; - cursor: pointer; - line-height: 1; - transition: background 0.15s, color 0.15s; -} -/* The clock is a `` and a global `button:hover` rule paints - every button's background on hover. Suppress it explicitly. */ -.cal-hero-time:hover { background: transparent; border-color: transparent; } -.cal-hero-clock { font-feature-settings: "tnum"; display: inline-flex; align-items: baseline; } -/* Per-segment hover so it's clear hh vs mm are individually clickable. */ -.cal-hero-clock-hh, .cal-hero-clock-mm { - display: inline-block; - border-radius: 6px; - padding: 0 2px; - transition: background 0.12s; -} -.cal-hero-clock-hh:hover, .cal-hero-clock-mm:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); -} -.cal-hero-sep { padding: 0 2px; opacity: 0.55; pointer-events: none; } -.cal-hero-ampm { - font-size: 14px; - letter-spacing: 0.12em; - opacity: 0.55; - font-weight: 500; - text-transform: uppercase; -} -.cal-hero-date { - font-size: 16px; - opacity: 0.6; - letter-spacing: 0.04em; - background: none; - border: none; - color: inherit; - font-family: inherit; - padding: 4px 10px; - border-radius: 6px; - cursor: pointer; - margin: 7px 0 0; /* extra nudge below the clock */ - transition: background 0.15s, opacity 0.15s; -} -.cal-hero-date:hover { background: color-mix(in srgb, var(--fg) 6%, transparent); opacity: 0.85; } - -/* Title input — flat, large, no chrome until focused */ -.cal-input.cal-hero-title { - font-size: 18px; - text-align: left; - padding: 10px 12px 10px 0; - background: transparent; - border: 1px solid transparent; - border-bottom: 1px solid color-mix(in srgb, var(--fg) 18%, transparent); - border-radius: 0; - margin-bottom: 4px; -} - -/* Details panel — animated reveal via max-height + opacity. */ -.cal-form-bespoke .cal-form-details { - display: block; - max-height: 0; - opacity: 0; - overflow: hidden; - transition: max-height 240ms ease, opacity 180ms ease, margin 220ms ease; - margin-top: 0; -} -/* The detail children themselves keep the existing flex/gap rules from - the regular .cal-form, so we don't restyle inputs/rows here. */ -.cal-form-bespoke .cal-form-details > * + * { margin-top: 8px; } - - -/* Location row: input + Apple Maps pin link. Pin only lights up when the - field has content; clicking opens maps.apple.com (native Maps app on - Apple devices, web fallback elsewhere). All theme vars. */ -.cal-loc-row { - display: flex; - align-items: stretch; - gap: 6px; -} -.cal-loc-input-wrap { - position: relative; - flex: 1; - min-width: 0; -} -.cal-loc-hint { - position: absolute; - inset: 0 auto 0 10px; - display: none; - align-items: center; - gap: 5px; - pointer-events: none; - color: color-mix(in srgb, var(--fg) 42%, transparent); - font-size: 12px; -} -.cal-loc-map { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - flex-shrink: 0; - border: 1px solid var(--border); - border-radius: 5px; - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--fg) 5%, var(--bg)); - text-decoration: none; - transition: background 0.15s, border-color 0.15s, opacity 0.15s; -} -.cal-loc-map:hover { background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); border-color: var(--accent, var(--red)); } - -@media (max-width: 520px) { - .cal-hero-time { font-size: 44px; } - .cal-form-bespoke { margin: 16px 12px; padding: 32px 16px 14px; } - .cal-form-mobile-cancel { - display: inline-flex; - position: absolute; - top: 8px; - right: 8px; - width: 30px; - height: 30px; - align-items: center; - justify-content: center; - padding: 0; - border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--bg) 82%, transparent); - color: var(--fg); - opacity: 0.72; - cursor: pointer; - z-index: 3; - -webkit-tap-highlight-color: transparent; - } - .cal-form-mobile-cancel:active { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); - } -} -.cal-input { background:color-mix(in srgb, var(--fg) 5%, var(--bg)); border:1px solid var(--border); border-radius:5px; padding:7px 10px; color:var(--fg); font-size:12px; width:100%; box-sizing:border-box; } -.cal-input:focus { outline:none; border-color:var(--accent); } -.cal-input-time { width:auto; flex:1; } -.cal-form-row { display:flex; gap:8px; align-items:center; } -.cal-form-actions { display:flex; gap:8px; justify-content:flex-end; margin-top:4px; } button.cal-btn { background:color-mix(in srgb, var(--fg) 8%, transparent); border:1px solid var(--border); color:var(--fg); border-radius:5px; padding:0 14px; height:28px; font-size:11px; font-weight:500; cursor:pointer; font-family:inherit; display:inline-flex; align-items:center; justify-content:center; box-sizing:border-box; transition:background 0.15s, border-color 0.15s; } button.cal-btn:hover { background:color-mix(in srgb, var(--fg) 14%, transparent); } button.cal-btn.cal-btn-primary { background:var(--accent, var(--red)); color:var(--bg); border-color:var(--accent, var(--red)); } diff --git a/static/css/style-part-02.css b/static/css/style-part-02.css deleted file mode 100644 index a3ceff612..000000000 --- a/static/css/style-part-02.css +++ /dev/null @@ -1,6011 +0,0 @@ -/* Fragment 3/9 of the former static/style.css. - * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. - * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. - */ - .loading-dot:nth-child(1) { - animation: loading-bounce 1.4s infinite ease-in-out both; - } - .loading-dot:nth-child(2) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.32s; - } - .loading-dot:nth-child(3) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.64s; - } - .session-loading-state { - min-height: 100%; - display: flex; - flex-direction: column; - justify-content: flex-start; - gap: 12px; - color: var(--fg); - font-size: 12px; - pointer-events: none; - position: relative; - padding: 16px 14px 24px; - box-sizing: border-box; - overflow: hidden; - } - .session-loading-state::before { content: none; } - .session-skeleton-bubble { - width: min(78%, 680px); - border: 1px solid color-mix(in srgb, var(--border) 74%, transparent); - border-radius: 10px; - background: color-mix(in srgb, var(--panel, var(--bg)) 72%, transparent); - padding: 10px 12px 12px; - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.14); - } - .session-skeleton-bubble.is-user { - align-self: flex-end; - width: min(64%, 520px); - } - .session-skeleton-bubble.is-ai { - align-self: flex-start; - } - .session-skeleton-line { - position: relative; - overflow: hidden; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - border-radius: 999px; - } - .session-skeleton-line { - height: 10px; - margin-top: 8px; - opacity: 0.62; - } - .session-skeleton-line::after { - content: ''; - position: absolute; - inset: -1px; - transform: translateX(-120%); - background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--fg) 14%, transparent), transparent); - animation: session-skeleton-shimmer 1.25s ease-in-out infinite; - } - .session-skeleton-bubble:nth-child(2) .session-skeleton-line::after { animation-delay: 0.12s; } - .session-skeleton-bubble:nth-child(3) .session-skeleton-line::after { animation-delay: 0.24s; } - .session-skeleton-bubble:nth-child(4) .session-skeleton-line::after { animation-delay: 0.36s; } - @keyframes session-skeleton-shimmer { - 100% { transform: translateX(120%); } - } - @media (max-width: 768px) { - .session-loading-state { padding: 12px 10px 18px; gap: 10px; } - .session-skeleton-bubble { width: 86%; padding: 9px 10px 11px; border-radius: 9px; } - .session-skeleton-bubble.is-user { width: 72%; } - .session-skeleton-line { height: 9px; margin-top: 7px; } - } - @keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } - } - /* Modal styling */ - .modal { - position:fixed; - top:0; left:0; width:100%; height:100%; - background:none; - display:flex; align-items:center; justify-content:center; - z-index:250; - backdrop-filter:none; - pointer-events:none; - } - /* Cookbook always sits above Gallery so the "Serve a model in - Cookbook…" flow (and any other "open Cookbook from inside another - modal") is visible no matter which modal was opened first. */ - #cookbook-modal { z-index: 260; } - .modal.hidden { display:none; } - - /* Tool windows open centered in the CHAT AREA (the space right of the - sidebar + icon rail), rather than the full viewport. - We narrow the overlay to the chat area so its flex-centering lands the - window there; fullscreen / docked states use position:fixed so they - escape this narrowed overlay and still fill the screen. The - --sidebar-w / --icon-rail-w vars track collapse state live, so when a - window (e.g. Cookbook) hides the sidebar this naturally re-centers. - Desktop only — on mobile these are full-screen sheets. */ - @media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal { - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)); - width: calc(100% - (var(--icon-rail-w, 48px) + var(--sidebar-w, 0px))); - box-sizing: border-box; - /* Slide in sync with the sidebar's 0.25s collapse/expand so the - centered window glides instead of jumping when the nav toggles. */ - transition: left 0.25s ease, width 0.25s ease; - } - } - .modal-content { - background:var(--panel); - border:1px solid var(--border); - width:min(520px, 92vw); max-height:85vh; padding:10px; - box-sizing:border-box; font-size:14px; - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - letter-spacing: -0.015em; - display:flex; flex-direction:column; - position:relative; - overflow-y:auto; - border-radius:10px; - box-shadow:0 8px 32px rgba(0,0,0,0.45); - pointer-events:auto; - animation: modal-enter 0.25s ease-out both; - } - .memory-modal-content, - .tasks-modal-content, - .preset-modal-content, - #cookbook-modal .modal-content, - #theme-popup, - .doclib-modal-content, - .gallery-modal-content { - container-type: inline-size; - } - .modal-header { - display:flex; justify-content:space-between; align-items:center; margin-bottom:6px; - cursor:grab; user-select:none; - /* Pin the header (with its close button) to the top of the - scrollable modal-content so users can always dismiss the modal - even after scrolling far down — especially important on mobile - where the modal can be taller than the viewport. */ - position: sticky; - top: 0; - z-index: 5; - /* Inherit the modal-content's background so the header matches the - panel body. Many tool modals set their content to var(--bg) inline - while this header was hard-coded to var(--panel) — making the title - strip a different shade in every theme. `inherit` tracks whatever - the content uses (var(--bg) or the default var(--panel)) and stays - opaque, so the sticky header still masks scrolled content. */ - background-color: inherit; - } - .modal-header:active { cursor:grabbing; } - /* Edge/corner window resize (windowResize.js). While a resize is in - progress, suppress text selection and force the active resize cursor - across the whole document so it does not flicker as the pointer passes - over child elements mid-drag. */ - body.window-resizing-active { user-select:none !important; } - body.window-resizing-active * { cursor:inherit !important; } - /* Suppress only TRANSITIONS while resizing so the edge tracks the cursor - crisply. We deliberately do NOT toggle `animation` here: toggling - animation off→on re-triggers the modal open-animation (a scale-in) on - mouseup, which both mis-measures the final size and visibly "pops" the - window. windowResize.js instead kills the one-shot open animation inline - once, in begin(). */ - .window-resizing { - transition:none !important; - } - /* Cookbook opts out of the sticky header on mobile; the title bar scrolls - away with the content instead of following. Background is inherited from - the shared .modal-header rule so frosted/glass themes stay consistent. */ - #cookbook-modal .modal-header { - position: static; - top: auto; - } - .modal-header h4 { - margin:0; - /* Push every header control (opacity slider, minimize, close) into one - group on the right — works whether or not optional controls like the - opacity slider are present, so the minimize button never floats to - the centre when a sibling is hidden. */ - margin-right:auto; - font-size:1rem; - font-weight:600; - letter-spacing:-0.03em; - color:var(--red); - } - /* Headers with an optional Peek control already have the title's - auto-margin grouping all right-side controls. A second auto-margin on - the injected minimize button splits the group and makes it look - different from the other modal headers. */ - .modal-header .theme-opacity-wrap + .modal-minimize-btn { - margin-left: 0 !important; - } - .close-btn, - .modal-close { - background:transparent; - color:color-mix(in srgb, var(--fg) 58%, transparent); - border:1px solid transparent; - font-size:0; - width:24px; - height:24px; - padding:0; - display:inline-flex; - align-items:center; - justify-content:center; - line-height:1; - text-indent:0; - cursor:pointer; - border-radius:999px; - line-height:1; - flex-shrink:0; - position:relative; - } - .close-btn > svg, - .modal-close > svg { - display:none; - } - .close-btn::before, - .modal-close::before { - content:""; - width:10px; - height:10px; - display:block; - background:currentColor; - -webkit-mask: - url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") - center / contain no-repeat; - mask: - url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 2L8 8M8 2L2 8' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") - center / contain no-repeat; - } - .close-btn:hover, - .modal-close:hover { - background:color-mix(in srgb, var(--fg) 10%, transparent); - border-color:color-mix(in srgb, var(--fg) 12%, transparent); - color:var(--fg); - } - /* Minimize button — sits beside the close button on every modal */ - .minimize-btn { - background:transparent; - color:color-mix(in srgb, var(--fg) 58%, transparent); - border:1px solid transparent; - font-size:0; - font-weight:700; - width:24px; - height:24px; - padding:0; - display:inline-flex; - align-items:center; - justify-content:center; - line-height:1; - cursor:pointer; - border-radius:999px; - flex-shrink:0; - margin-left:auto; /* push to the right so it docks next to .close-btn */ - margin-right:4px; - } - .minimize-btn::before { - content:""; - width:9px; - height:2px; - border-radius:999px; - background:currentColor; - display:block; - transform:translateY(2px); - } - .modal-minimize-btn.minimize-btn::before { - content:none; - display:none; - } - .minimize-btn:hover { - background:color-mix(in srgb, var(--fg) 10%, transparent); - border-color:color-mix(in srgb, var(--fg) 12%, transparent); - color:var(--fg); - } - @media (max-width: 768px) { - .minimize-btn { display: none !important; } - #modal-dock { display: none !important; } - } - /* Minimized modals are hidden but stay in the DOM with their state intact */ - .modal.minimized { display:none !important; } - /* Bottom dock for minimized modals */ - #modal-dock { - position:fixed; - bottom:var(--composer-clearance, 0px); - left:0; - right:0; - display:flex; - flex-wrap:wrap; - gap:4px; - padding:4px 8px; - z-index:240; - pointer-events:none; - justify-content:flex-start; - } - #modal-dock:empty { display:none; } - .modal-dock-item { - background:var(--panel); - border:1px solid var(--border); - border-bottom:none; - border-radius:6px 6px 0 0; - padding:4px 4px 4px 10px; - display:inline-flex; - align-items:center; - gap:6px; - cursor:pointer; - pointer-events:auto; - font-size:12px; - color:var(--fg); - max-width:220px; - box-shadow:0 -2px 8px rgba(0,0,0,0.25); - transition:background 0.15s; - } - .modal-dock-item:hover { background:var(--bg); } - .modal-dock-label { - white-space:nowrap; - overflow:hidden; - text-overflow:ellipsis; - max-width:180px; - } - .modal-dock-close { - background:transparent; - border:none; - color:var(--fg); - cursor:pointer; - font-size:14px; - padding:0 4px; - line-height:1; - opacity:0.6; - } - .modal-dock-close:hover { color:var(--red); opacity:1; } - .modal-body { flex:1; overflow-y:auto; } - .modal-body button { margin-top:6px; } - /* Styled confirm dialog — keeps backdrop */ - #styled-confirm-overlay { - background:rgba(0,0,0,0.5); - backdrop-filter:blur(4px); - pointer-events:auto !important; - z-index: 99999 !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - top: 0; left: 0; width: 100%; height: 100%; - } - #styled-confirm-overlay .modal-content { - position: relative; - z-index: 10001; - } - .email-attachment-warning-modal { - z-index: 99999 !important; - } - .email-attachment-warning-modal .modal-content { - position: relative; - z-index: 100001; - } - .styled-confirm-box { - width:360px; max-width:90vw; - max-height:none; /* override modal-content's 85vh */ - padding:14px 18px; - } - .styled-confirm-box .modal-header { margin-bottom:4px; } - .styled-confirm-box .modal-body p { - margin:8px 0 12px; color:var(--fg); font-size:0.92rem; line-height:1.45; - white-space:pre-line; - } - .styled-confirm-box .modal-footer { - display:flex; justify-content:flex-end; gap:8px; padding-top:6px; - border-top:1px solid var(--border); margin-top:4px; - } - @media (max-width:768px) { - .styled-confirm-box { - width: 85vw; - padding: 12px 16px; - font-size: 0.88rem; - border-radius: 12px; - } - .styled-confirm-box .modal-header h4 { font-size: 0.9rem; } - .styled-confirm-box .modal-body p { font-size: 0.85rem; margin: 6px 0 10px; } - .styled-confirm-box .modal-footer { gap: 10px; } - .styled-confirm-box .confirm-btn { - flex: 1; - /* More bottom than top padding nudges the label up ~2px so it isn't - sitting low in the taller mobile buttons. */ - padding: 8px 12px 12px; - font-size: 0.85rem; - border-radius: 8px; - text-align: center; - } - } - .confirm-btn { - /* Asymmetric padding nudges the label UP ~2px from where it was (more - bottom than top padding), so the confirm-dialog text isn't sitting low. */ - padding:3px 16px 5px; border-radius:4px; font-size:0.85rem; - cursor:pointer; border:1px solid var(--border); - font-family:inherit; - } - @media (max-width: 820px) { - /* Mobile: flip the asymmetry to shift the text 1 px UP from - centre instead (the bigger touch targets on mobile read better - with the label sitting slightly high). */ - .confirm-btn { padding:2px 16px 4px; } - } - .confirm-btn-secondary { background:var(--bg); color:var(--fg); } - .confirm-btn-secondary:hover { background:var(--border); } - #styled-confirm-cancel .styled-confirm-cancel-label { - position:relative; - top:2px; - } - .confirm-btn-primary { background:var(--accent-primary, var(--red, #4a9eff)); color:#fff; border-color:transparent; } - .confirm-btn-primary:hover { filter:brightness(1.15); } - .confirm-btn-danger { background:var(--color-danger); color:#fff; border-color:transparent; } - .confirm-btn-danger:hover { background:var(--color-error); } - .cookbook-gguf-delete-box { - width:560px; - max-width:92vw; - } - .cookbook-gguf-delete-list { - display:flex; - flex-direction:column; - gap:6px; - max-height:42vh; - overflow:auto; - padding:2px 2px 4px; - } - .cookbook-gguf-delete-row { - display:grid; - grid-template-columns:18px minmax(0,1fr); - gap:7px 8px; - align-items:start; - padding:7px 8px; - border:1px solid var(--border); - border-radius:7px; - background:color-mix(in srgb, var(--panel, var(--bg)) 92%, var(--fg) 8%); - cursor:pointer; - } - .cookbook-gguf-delete-row:hover { - border-color:color-mix(in srgb, var(--accent-primary, var(--fg)) 45%, var(--border)); - } - .cookbook-gguf-delete-cb { - -webkit-appearance:none; - appearance:none; - width:8px !important; - height:8px !important; - min-width:8px; - min-height:8px; - padding:0; - margin:4px 0 0; - border:1px solid var(--border); - border-radius:50%; - background:transparent; - box-sizing:content-box; - cursor:pointer; - transition:background 0.15s, border-color 0.15s, transform 0.12s; - } - .cookbook-gguf-delete-cb:hover { - border-color:var(--accent, var(--red)); - transform:scale(1.12); - } - .cookbook-gguf-delete-cb:checked { - background:var(--accent, var(--red)); - border-color:var(--accent, var(--red)); - } - .cookbook-gguf-delete-main, - .cookbook-gguf-delete-path { - min-width:0; - overflow:hidden; - text-overflow:ellipsis; - white-space:nowrap; - } - .cookbook-gguf-delete-main { - font-size:0.86rem; - color:var(--fg); - } - .cookbook-gguf-delete-path { - grid-column:2; - margin-top:-2px; - font-size:0.74rem; - opacity:0.58; - } - /* Styled prompt — text-input dialog (used in place of window.prompt) */ - #styled-prompt-overlay { - background:rgba(0,0,0,0.5); - backdrop-filter:blur(4px); - pointer-events:auto !important; - z-index: 99999 !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - } - #styled-prompt-overlay .modal-content { - position: relative; - z-index: 10001; - } - .styled-prompt-box { width: min(400px, 92vw); max-width: 100%; box-sizing: border-box; } - .styled-prompt-box .modal-body { padding-top: 4px; } - .styled-prompt-input { - width:100%; - box-sizing:border-box; - margin-top:8px; - padding:9px 12px; - border:1px solid var(--border); - border-radius:6px; - background:var(--bg); - color:var(--fg); - font:inherit; - font-size:0.95rem; - outline:none; - transition: border-color 0.15s, box-shadow 0.15s; - } - .styled-prompt-input:focus { - border-color: var(--accent-primary, var(--red, #4a9eff)); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-primary, var(--red, #4a9eff)) 25%, transparent); - } - @media (max-width:768px) { - .styled-prompt-box { width: 85vw; } - .styled-prompt-input { font-size: 0.9rem; padding: 10px 12px; } - } - /* Scroll navigation buttons */ - .scroll-nav-btn { - position:fixed; - background:var(--panel); - color: var(--accent); - border:none; - border-radius:10px; - width:38px; height:38px; - padding:0; - display:flex; align-items:center; justify-content:center; - font-size:14px; - line-height:1; - font-family:inherit; - cursor:pointer; - opacity:0; - pointer-events:none; - transition: opacity .2s, transform .3s cubic-bezier(0.25, 1, 0.5, 1), background .15s; - z-index:100; - transform: translateY(0); - } - .scroll-nav-btn::before { - content: ''; - position: absolute; - inset: 0; - border-radius: 10px; - padding: 1px; - background: linear-gradient(to bottom, var(--border), color-mix(in srgb, var(--border) 30%, transparent)); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - pointer-events: none; - } - .scroll-nav-btn.agent-working::after, - body.chat-agent-working #scroll-bottom-btn.show::after { - content: ''; - position: absolute; - inset: 0; - border-radius: 10px; - padding: 1px; - background: conic-gradient( - from 0deg, - transparent 0deg, - transparent 245deg, - color-mix(in srgb, var(--accent, var(--red)) 78%, transparent) 315deg, - transparent 360deg - ); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - opacity: 0.65; - pointer-events: none; - animation: scrollAgentWorkingRing 1.05s linear infinite; - } - @keyframes scrollAgentWorkingRing { - to { transform: rotate(360deg); } - } - #scroll-bottom-btn.show { opacity:1; pointer-events:auto; } - .scroll-nav-caret { - display: inline-block; - font-family: Arial, Helvetica, sans-serif; - font-size: 16px; - line-height: 1; - transform: translateY(-1px); - pointer-events: none; - } - @media (hover: hover) and (pointer: fine) { - #scroll-bottom-btn.show:hover { - border-color: color-mix(in srgb, var(--fg) 25%, transparent); - } - } - #scroll-bottom-btn.slide-out { - transform: translateY(20px); - opacity: 0 !important; - pointer-events: none; - } - /* Focus outline for accessibility */ - :focus-visible { - outline: 2px solid var(--red); - outline-offset: 2px; - } - /* Hamburger menu button */ - .hamburger { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - background: none; - border: 1px solid transparent; - border-radius: 6px; - padding: 0; - cursor: pointer; - transition: background 0.15s, border-color 0.15s; - } - .hamburger:hover { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: var(--border); - } - .hamburger span { - display: block; - width: 16px; - height: 2px; - background: var(--fg); - border-radius: 1px; - transition: transform 0.2s, opacity 0.2s; - } - .hamburger span + span { margin-top: 3px; } - /* Agent indicator */ - #agent-indicator { - position: fixed; - top: 20px; - right: 20px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - display: none; - z-index: 100; - cursor: pointer; - transition: all 0.2s ease; - } - #agent-indicator.active { - display: block; - border-color: var(--color-agent-active); - box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); - } - #agent-indicator:hover { - border-color: var(--color-agent-active); - background: var(--panel); - } - #research-toggle-btn:disabled { - opacity: 0.6; - cursor: not-allowed; - } - -/* ── Drag & Drop ── */ - -/* ---------- Color palette (dark / light) ---------- */ - - /* Drag and drop styling */ - .section[dnd-active="true"] { - background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: var(--red) !important; - } - - .section[dnd-over="true"] { - background: color-mix(in srgb, var(--red) 20%, transparent) !important; - border-color: var(--red) !important; - transform: scale(1.02); - } - - .drag-handle { - cursor: grab; - opacity: 0.5; - padding: 0 6px; - user-select: none; - } - - .drag-handle:hover { - opacity: 0.8; - } - - .drag-handle:active { - cursor: grabbing; - } - -/* ── UI Controls (Radio, Presets, Toolbar, Settings) ── */ - - - /* Custom radio button styling */ - .radio-option { - display: flex; - align-items: center; - gap: 10px; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - cursor: pointer; - transition: all 0.2s ease; - } - - .radio-option:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: var(--fg); - } - - .radio-option input[type="radio"] { - appearance: none; - -webkit-appearance: none; - width: 18px; - height: 18px; - border: 2px solid var(--border); - border-radius: 50%; - outline: none; - margin: 0; - background: var(--bg); - transition: all 0.2s ease; - position: relative; - flex-shrink: 0; - } - - .radio-option input[type="radio"]:checked { - border-color: var(--red); - background: var(--red); - } - - .radio-option input[type="radio"]:focus { - box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 30%, transparent); - } - - .radio-label { - color: var(--fg); - font-size: 14px; - user-select: none; - } - - /* Preset buttons */ - .preset-btn { - height: 27.2px; /* 15% smaller than 32px */ - padding: 0 8.5px; /* 15% smaller than 10px */ - margin-left: 4px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font-family: 'Fira Code', monospace; - font-size: 10.2px; /* 15% smaller than 12px */ - cursor: pointer; - transition: all 0.2s ease; - } - - .preset-btn:hover { - background: var(--panel); - border-color: var(--fg); - } - - .preset-btn.active { - background: var(--panel); - border-color: var(--fg); - box-shadow: 0 0 0 1px var(--fg), 0 0 8px color-mix(in srgb, var(--fg) 16%, transparent); - font-weight: 600; - } - - /* All preset buttons use the same blue color */ - .preset-btn { - border-color: var(--red); /* Blue color */ - } - - .preset-btn.active { - border-color: var(--red); /* Blue color when active */ - box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); - } - - /* Custom preset modal: the base .preset-modal-content sets overflow:hidden - (for desktop rounded-corner clipping), which on the mobile sheet clipped - the footer (Start/Cancel) off the bottom with no way to reach it. Let the - whole sheet scroll — same as every other mobile modal (.modal-content is - overflow-y:auto on mobile) — so the footer is always reachable. No flex - changes, so the body can't collapse. */ - #custom-preset-modal .preset-modal-content { - overflow-y: auto !important; - } - #custom-preset-modal .modal-body label { - font-size: 13px; - font-weight: 500; - color: var(--fg); - margin-top: 8px; - margin-bottom: 4px; - display: block; - } - - #custom-preset-modal .modal-body input, - #custom-preset-modal .modal-body textarea { - width: 100%; - margin-bottom: 8px; - box-sizing: border-box; - } - #custom-preset-modal .modal-body textarea, - #custom-preset-modal .modal-body input[type="text"] { - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - padding: 8px 10px; - font-size: 13px; - font-family: inherit; - transition: border-color 0.15s; - } - #custom-preset-modal .modal-body textarea { - resize: vertical; - } - #custom-preset-modal .modal-body textarea:focus, - #custom-preset-modal .modal-body input[type="text"]:focus { - outline: none; - border-color: var(--red); - } - - #custom-preset-modal .modal-footer { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 12px; - padding-top: 10px; - border-top: 1px solid var(--border); - } - - #custom-preset-modal .modal-footer button { - padding: 7px 14px; - border-radius: 6px; - font-size: 12px; - font-weight: 500; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: all 0.15s; - } - #custom-preset-modal .modal-footer button:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - } - - #custom-preset-modal .modal-footer button#save-custom-preset { - /* The theme's accent is stored in --red (theme.js sets --red = accentHex) - and --accent is undefined, so the canonical accent expression is - var(--accent, var(--red)). The old bare var(--accent) resolved to nothing - which, with color:var(--bg), made the button invisible. */ - background: var(--accent, var(--red)); - color: var(--bg); - border-color: var(--accent, var(--red)); - } - #custom-preset-modal .modal-footer button#save-custom-preset:hover { - opacity: 0.85; - } - /* Toolbar visibility tab */ - .toolbar-hint { - font-size: 12px; - color: color-mix(in srgb, var(--fg) 55%, transparent); - margin-bottom: 12px; - } - /* ── Appearance visibility toggles ── */ - .vis-toggles { - display: flex; - flex-direction: column; - } - .vis-row { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - padding: 5px 6px; - border-radius: 4px; - transition: background 0.12s; - } - .vis-row:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); - } - .vis-row input[type="checkbox"] { - display: none; - } - .vis-icon { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - color: color-mix(in srgb, var(--fg) 40%, transparent); - } - .vis-icon-text { - font-size: 10px; - font-weight: 700; - font-family: inherit; - letter-spacing: -0.5px; - } - .vis-label { - flex: 1; - font-size: 12px; - color: var(--fg); - user-select: none; - } - .vis-switch { - position: relative; - width: 30px; - height: 16px; - background: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 8px; - transition: background 0.2s; - flex-shrink: 0; - } - .vis-switch::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 12px; - height: 12px; - background: var(--panel); - border-radius: 50%; - transition: transform 0.2s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); - } - .vis-row input:checked + .vis-switch { - background: var(--red); - } - .vis-row input:checked + .vis-switch::after { - transform: translateX(14px); - } - .vis-row input:checked ~ .vis-icon, - .vis-row:has(input:checked) .vis-icon { - color: var(--fg); - } - .persona-display-name-row .vis-label { - position: relative; - top: -4px; - } - .persona-display-name-row .vis-switch { - position: relative; - left: 4px; - } - .md-attach-paperclip-icon { - position: relative; - top: 1px; - } - /* Compare model selector — match the calendar modal's clean header - (no border underline). */ - #compare-model-overlay .modal-header h4 { - pointer-events: none; - } - /* Compare model selector: keep manually-resized/tiny windows contained. - Picker dropdowns are appended to document.body, so the card itself can - clip and scroll without cropping the dropdown list. */ - #compare-model-overlay .modal-content { - display: flex; - flex-direction: column; - max-height: min(720px, calc(100dvh - 48px)); - overflow: hidden; - min-height: 180px; - } - #compare-model-overlay .modal-body { - overflow: auto; - flex: 1 1 auto; - min-height: 0; - } - .vis-hint { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 30%, transparent); - font-weight: 400; - margin-left: 2px; - } - /* Settings toggle — admin-only lock indicator */ - .ui-vis-lock { - display: none; - } - /* (legacy toolbar-toggle styles removed — now using .vis-* classes) */ - - /* Demo highlight pulse */ - .odysseus-highlight { - outline: 2px solid var(--accent, var(--red)) !important; - outline-offset: 1px; - box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - animation: ody-pulse 1.5s ease-in-out infinite; - z-index: 100; - position: relative; - } - @keyframes ody-pulse { - 0%, 100% { outline-color: var(--accent, var(--red)); box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); } - 50% { outline-color: color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); } - } - /* Floating breathing halo. Rendered as a body-level div positioned over - the target, so we don't fight the target's own outline / box-shadow / - overflow chain. JS keeps it in sync with the target's bounding rect. */ - .tour-halo { - position: fixed; - pointer-events: none; - border: 3px solid var(--accent, var(--red)); - border-radius: 10px; - z-index: 10000; - animation: ody-breathe 1.4s ease-in-out infinite; - opacity: 0; - transform: scale(0.94); - transition: opacity 0.35s ease-out, transform 0.35s ease-out; - } - .tour-halo.tour-fade-in { - opacity: 1; - transform: scale(1); - } - @keyframes ody-breathe { - 0%, 100% { - box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent, var(--red)) 55%, transparent), - 0 0 22px 4px color-mix(in srgb, var(--accent, var(--red)) 40%, transparent); - border-color: var(--accent, var(--red)); - } - 50% { - box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 30%, transparent), - 0 0 40px 14px color-mix(in srgb, var(--accent, var(--red)) 70%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 80%, transparent); - } - } - /* While the tour is active, lift overflow:hidden on common clipping - ancestors so the halo around a highlighted child isn't cropped. */ - body.tour-active .sidebar, - body.tour-active .sidebar-inner, - body.tour-active .chat-input-bar, - body.tour-active .chat-input-top, - body.tour-active .chat-input-wrap, - body.tour-active .chat-input-right, - body.tour-active .mode-toggle, - body.tour-active .model-picker-wrap { - overflow: visible !important; - } - - /* ── Secret tour hint (drag-to-snap on first modal open) ── */ - .tour-hint { - position: fixed; - z-index: 10002; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 12px 14px 10px; - width: 240px; - font-size: 0.78rem; - line-height: 1.5; - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.32); - opacity: 0; - transform: translateY(-4px); - transition: opacity 0.28s ease-out, transform 0.28s ease-out; - pointer-events: auto; - } - .tour-hint.tour-hint-in { opacity: 1; transform: translateY(0); } - .tour-hint.tour-hint-out { opacity: 0; transform: translateY(-4px); } - .tour-hint-visual { - display: flex; - justify-content: center; - margin-bottom: 8px; - color: var(--accent, var(--red)); - } - .tour-hint-visual svg { display: block; } - .tour-hint-text { margin-bottom: 10px; opacity: 0.92; } - .tour-hint-text b { color: var(--accent, var(--red)); font-weight: 600; } - .tour-hint-dismiss { - display: block; - margin: 0 0 0 auto; - background: none; - border: 1px solid var(--border); - color: var(--fg); - border-radius: 6px; - padding: 3px 12px; - cursor: pointer; - font-family: inherit; - font-size: 0.72rem; - opacity: 0.85; - transition: opacity 0.15s, background 0.15s; - } - .tour-hint-dismiss:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 8%, transparent); } - - /* SVG dance: cursor approaches title bar, drags right, modal snaps to a - right-half zone, holds, returns. 3.2s loop. */ - .th-cursor { animation: th-cursor 3.2s ease-in-out infinite; transform-origin: 0 0; } - .th-modal-group { animation: th-modal-slide 3.2s ease-in-out infinite; transform-origin: 39px 31px; } - .th-zone { animation: th-zone-flash 3.2s ease-in-out infinite; } - @keyframes th-cursor { - 0%, 5% { transform: translate(35px, 32px); } - 18% { transform: translate(35px, 22px); } - 48% { transform: translate(80px, 22px); } - 72% { transform: translate(80px, 22px); } - 90%, 100% { transform: translate(35px, 32px); } - } - @keyframes th-modal-slide { - 0%, 18% { transform: translate(0, 0) scale(1, 1); } - 48% { transform: translate(45px, 0) scale(1, 1); } - 58% { transform: translate(30px, -19px) scale(1.4, 2.4); } - 72% { transform: translate(30px, -19px) scale(1.4, 2.4); } - 90%, 100% { transform: translate(0, 0) scale(1, 1); } - } - @keyframes th-zone-flash { - 0%, 38% { opacity: 0; } - 48% { opacity: 0.22; } - 58%, 72% { opacity: 0; } - 100% { opacity: 0; } - } - @media (prefers-reduced-motion: reduce) { - .th-cursor, .th-modal-group, .th-zone { animation: none !important; } - } - .odysseus-hl-label { - position: absolute; - top: -22px; - left: 8px; - background: var(--red); - color: var(--bg); - font-size: 0.7rem; - padding: 2px 8px; - border-radius: 4px; - white-space: nowrap; - z-index: 101; - pointer-events: none; - } - - /* Generated images inside chat bubbles */ - .msg.generated-image-wrap .body { text-align: center; } - .generated-image { - max-width: 100%; - max-height: 512px; - border-radius: 8px; - cursor: pointer; - transition: transform 0.2s; - display: inline-block; - margin: 0 auto; - } - .generated-image:hover { transform: scale(1.02); } - .generated-image-caption { - font-size: 0.8rem; - opacity: 0.5; - margin-top: 6px; - font-style: italic; - text-align: center; - } - - /* Setup wizard */ - .setup-wizard { padding: 16px; max-width: 500px; } - .setup-wizard .setup-title { font-size: 1.1rem; font-weight: 600; margin-bottom: 12px; color: var(--fg); } - .setup-wizard .setup-label { font-size: 0.85rem; color: var(--fg); opacity: 0.7; margin-bottom: 8px; } - .setup-wizard .setup-presets { display: flex; flex-wrap: wrap; gap: 8px; } - .setup-wizard .setup-preset-btn { - padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; - font-size: 0.85rem; transition: all 0.2s; - } - .setup-wizard .setup-preset-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .setup-wizard .setup-input { - display: block; width: 100%; padding: 8px 12px; margin-bottom: 8px; - background: var(--panel); border: 1px solid var(--border); - border-radius: 6px; color: var(--fg); font-size: 0.85rem; box-sizing: border-box; - } - .setup-wizard .setup-input:focus { border-color: var(--red); outline: none; } - .setup-wizard .setup-connect-btn { - padding: 8px 20px; background: var(--red); color: var(--bg); - border: none; border-radius: 6px; cursor: pointer; font-weight: 600; margin-top: 4px; - } - .setup-wizard .setup-connect-btn:hover { opacity: 0.9; } - .setup-wizard .setup-status { font-size: 0.8rem; margin-top: 8px; color: var(--fg); opacity: 0.7; } - .setup-wizard .setup-model-list { display: flex; flex-wrap: wrap; gap: 8px; } - .setup-wizard .setup-model-btn { - padding: 8px 14px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; - font-size: 0.85rem; transition: all 0.2s; - } - .setup-wizard .setup-model-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .setup-wizard .setup-step.hidden { display: none; } - - /* Dropdown menu styles */ - .dropdown { - position: absolute; - top: calc(100% + 6px); - right: 0; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 10px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); - z-index: 1000; - display: none; - min-width: 220px; - padding: 6px; - backdrop-filter: blur(12px); - } - - .dropdown.show { - display: block; - animation: dropdown-in 0.15s ease-out; - } - @keyframes dropdown-in { - from { opacity:0; transform:translateY(-6px) scale(0.97); } - to { opacity:1; transform:translateY(0) scale(1); } - } - - .dropdown-item { - padding: 8px 10px; - border-radius: 6px; - cursor: pointer; - transition: background 0.12s ease; - display: flex; - align-items: center; - gap: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - } - - .dropdown-item:last-child { - border-bottom: none; - } - - .dropdown-item .menu-icon { - width: 28px; - height: 28px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 5%, transparent); - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - flex-shrink: 0; - } - - .dropdown-item .menu-text h4 { - margin: 0; - font-size: 12.5px; - font-weight: 500; - color: var(--fg); - line-height: 1.3; - } - - .dropdown-item .menu-text p { - margin: 0; - font-size: 10.5px; - color: var(--color-subheader); - line-height: 1.3; - } - - .dropdown-item:hover { - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .dropdown-item:hover .menu-icon { - background: color-mix(in srgb, var(--red) 15%, transparent); - } - - /* Compact dropdown items (session/model context menus) */ - .dropdown-item-compact { - cursor: pointer; - padding: 6px 8px; - font-size: 11px; - border-radius: 8px; - display: flex; - align-items: center; - gap: 10px; - color: var(--fg); - transition: background 0.1s; - } - .dropdown-item-compact:hover { - background: color-mix(in srgb, var(--accent) 10%, transparent); - } - .dropdown-item-compact .dropdown-icon { - width: 14px; - height: 14px; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - opacity: 0.5; - } - .dropdown-item-compact .dropdown-icon svg { - width: 14px; - height: 14px; - } - .dropdown-item-compact .dropdown-shortcut { - margin-left: auto; - font-size: 9.5px; - opacity: 0.35; - font-weight: 400; - } - /* Keyboard shortcut hints (⌘+Alt+D etc.) are meaningless on touch — - hide them in the per-chat actions menu on mobile. */ - @media (max-width: 768px) { - .session-dropdown .dropdown-shortcut { display: none; } - } - .dropdown-item-danger { - color: var(--red) !important; - } - .dropdown-item-danger .dropdown-icon { - opacity: 0.7; - } - - /* Inline rename input for sessions */ - .session-rename-input { - width: 100%; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--accent, var(--accent-primary)); - border-radius: 4px; - padding: 2px 6px; - font-family: inherit; - font-size: 12px; - line-height: 1.3; - outline: none; - } - - /* Session dropdown container */ - .session-dropdown-menu { - position: fixed; - z-index: 1000; - display: none; - min-width: auto; - width: max-content; - padding: 4px; - animation: dropdown-in 0.15s ease-out; - } - - /* Folder move submenu */ - .session-folder-submenu { - position: fixed; - z-index: 1001; - display: none; - min-width: auto; - width: max-content; - padding: 4px; - } - - .dropdown-divider { - height: 1px; - background: var(--border); - margin: 4px 8px; - } - - /* Search toggle styles */ - .search-toggle { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 0; - border-bottom: 1px solid var(--border); - margin-bottom: 8px; - } - - .search-provider-label { - font-size: 14px; - color: var(--fg); - } - -/* ── Voice, Search, Themes, Comparison, Censor, Print ── */ - - /* Voice recording styles */ - #mic-btn { - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - transition: all 0.2s ease; - } - - #mic-btn:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: var(--fg); - } - - #mic-btn.recording { - background: var(--color-recording); - border-color: var(--color-recording); - animation: pulse 1.5s infinite; - } - - @keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } - } - - #recording-indicator { - position: fixed; - top: 10px; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin: 10px; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - } - - #recording-indicator.hidden { - display: none !important; - } - - .recording-content { - display: flex; - align-items: center; - gap: 12px; - color: white; - } - - .recording-icon { - color: var(--color-recording); - font-size: 20px; - animation: pulse 1.5s infinite; - } - - .recording-text { - font-size: 16px; - font-weight: 500; - } - - #stop-recording { - background: var(--color-recording); - color: white; - border: none; - border-radius: 6px; - padding: 6px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s ease; - } - - #stop-recording:hover { - background: var(--color-recording-hover); - } - - /* Error state for recording */ - #recording-indicator.error { - background: rgba(173, 26, 26, 0.9); - } - - .recording-error { - color: var(--color-recording); - font-size: 14px; - margin-top: 4px; - } - - @media (max-width: 768px) { - #recording-indicator { - margin: 8px; - padding: 10px; - } - - .recording-text { - font-size: 14px; - } - - #stop-recording { - padding: 6px 10px; - font-size: 12px; - } - } -/* SYNTAX HIGHLIGHTING — uses theme vars from style.css, no hardcoded overrides */ -.hljs { color: var(--hl-fg, #9cdef2); background: none !important; } -pre { background: var(--code-bg, var(--hl-bg, #282c34)) !important; } - - /* ---------- Search overlay (Ctrl+K command palette) ---------- */ - .search-overlay { - position: fixed; - top: 0; left: 0; width: 100%; height: 100%; - background: rgba(0, 0, 0, 0.6); - display: flex; - align-items: flex-start; - justify-content: center; - padding-top: var(--search-overlay-top, 15vh); - z-index: 300; - backdrop-filter: blur(6px); - } - .search-overlay.hidden { display: none; } - .search-popup { - width: 520px; - max-width: 90vw; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 12px; - box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5), 0 0 0 1px color-mix(in srgb, var(--fg) 6%, transparent); - overflow: hidden; - display: flex; - flex-direction: column; - max-height: 60vh; - } - .search-popup input#search-input { - width: 100%; - background: transparent; - border: none; - border-bottom: 1px solid var(--border); - outline: none; - color: var(--fg); - font-size: 16px; - font-family: 'Fira Code', monospace; - padding: 14px 16px; - box-sizing: border-box; - } - .search-popup input#search-input::placeholder { - color: color-mix(in srgb, var(--fg) 30%, transparent); - } - .search-results { - overflow-y: auto; - flex: 1; - padding: 4px; - } - .search-results:empty { - display: none; - } - .search-group-header { - font-size: 10px; - font-weight: 600; - color: var(--color-subheader); - text-transform: uppercase; - letter-spacing: 0.5px; - padding: 10px 12px 4px; - } - .search-result-item { - display: flex; - align-items: baseline; - gap: 8px; - padding: 8px 12px; - border-radius: 6px; - cursor: pointer; - transition: background 0.1s; - } - .search-result-item:hover, - .search-result-item.selected { - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .search-result-role { - font-size: 10px; - font-weight: 600; - color: var(--color-subheader); - flex-shrink: 0; - min-width: 24px; - } - .search-result-snippet { - flex: 1; - font-size: 12px; - color: var(--fg); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .search-result-time { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 35%, transparent); - flex-shrink: 0; - white-space: nowrap; - } - mark.search-highlight { - /* Was orange-on-orange (0.35 alpha bg + orange text) — too low-contrast - to read. Solid accent fill with bg-colored text reads clearly. */ - background: var(--accent, #e8a830); - color: var(--bg, #1a1a1a); - border-radius: 2px; - padding: 0 2px; - font-weight: 600; - } - .search-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 24px; - font-size: 13px; - } - - /* ---------- Theme popup (uses .modal > .modal-content frame) ---------- */ - #theme-modal { z-index: 260; } - #theme-popup .theme-popup-sub { color: color-mix(in srgb, var(--fg) 50%, transparent); font-size: 11px; margin-bottom: 10px; } - /* `max-height` instead of a fixed height so the popup shrinks to fit its - content (avoids the leftover whitespace after the time-based switching - card was removed). The inner .theme-tab-panel keeps overflow:auto, so - any taller content still scrolls inside. */ - #theme-popup { overflow-y: hidden; max-height: min(85vh, 600px); } - #theme-popup .modal-header { flex-shrink: 0; } - #theme-popup .admin-tabs { margin: 0 -10px 8px; padding: 0 10px; flex-shrink: 0; } - .theme-tab-panel { overflow-y: auto; min-height: 0; flex: 1; padding-bottom: 10px; } - - .theme-grid { - display: grid; grid-template-columns: repeat(auto-fill, minmax(66px, 1fr)); - gap: 6px; margin-bottom: 12px; - } - .theme-swatch { - border: 2px solid var(--border); border-radius: 8px; cursor: pointer; - padding: 5px; text-align: center; font-size: 0.65rem; color: var(--fg); - transition: border-color 0.15s, transform 0.15s; - } - .theme-swatch:hover { transform: scale(1.06); } - .theme-swatch.active { border-color: var(--red); box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 33%, transparent); } - .theme-swatch-colors { - display: flex; justify-content: center; margin-bottom: 3px; - } - .theme-swatch-colors span { - width: 15px; height: 15px; border-radius: 50%; - margin-left: -5px; - border: 1.5px solid color-mix(in srgb, var(--fg) 12%, transparent); - } - .theme-swatch-colors span:first-child { margin-left: 0; } - .theme-custom-label { font-size: 11px; font-weight: 600; color: color-mix(in srgb, var(--fg) 50%, transparent); text-transform: uppercase; letter-spacing: 0.06em; margin: 10px 0 6px; } - .theme-custom { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; } - .color-row { display: flex; align-items: center; gap: 4px; position: relative; } - .color-row label { font-size: 13px; font-weight: 500; color: var(--fg); opacity: 0.7; flex: 1; } - .color-row input[type="color"], - .color-row input.cp-swatch-input { - width: 24px; height: 24px; border: 1px solid var(--border); border-radius: 50%; - background: none; cursor: pointer; padding: 0; flex-shrink: 0; - overflow: hidden; - -webkit-appearance: none; - appearance: none; - } - .color-row input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; } - .color-row input[type="color"]::-webkit-color-swatch { border: none; border-radius: 50%; } - .color-row input[type="color"]::-moz-color-swatch { border: none; border-radius: 50%; } - #theme-bg-effect-color { position: relative; top: 2px; } - .color-row input.cp-swatch-input { - color: transparent; - text-shadow: none; - caret-color: transparent; - font-size: 0; - user-select: none; - } - .color-row input.cp-swatch-input::selection { background: transparent; } - .color-row input.cp-swatch-input:focus { outline: 1px solid var(--red); outline-offset: 1px; } - .color-reset-btn { - width: 24px; height: 24px; border: none; background: none; cursor: pointer; - color: var(--fg); opacity: 0; font-size: 1.15rem; padding: 0; line-height: 1; - transition: opacity 0.15s, color 0.15s; flex-shrink: 0; pointer-events: none; - position: absolute; right: 6px; top: 50%; transform: translateY(-50%); - } - .color-row input[type="color"], - .color-row input.cp-swatch-input { margin-left: auto; } - /* Keep the harmony hex readout on the left, with the swatch dot anchored - to the right like the other color controls. */ - #theme-harmony-card #harmony-accent-hex { - order: -1; - margin-right: auto; - } - .color-row:has(.color-reset-btn.changed) input[type="color"], - .color-row:has(.color-reset-btn.changed) input.cp-swatch-input { margin-right: 28px; } - .color-reset-btn.changed { opacity: 0.4; pointer-events: auto; } - .color-reset-btn.changed:hover { opacity: 1; color: var(--red); } - .theme-custom-divider { - grid-column: 1 / -1; font-size: 11px; color: var(--fg); opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.04em; margin: 6px 0 2px; - border-top: 1px solid var(--border); padding-top: 6px; - } - .theme-swatch[data-custom] { position: relative; overflow: visible; } - /* Accent-coloured circular X — always visible (mobile-friendly), sits - INSIDE the swatch's top-right corner so it never crops into a - neighbouring swatch. */ - .theme-delete-btn { - position: absolute; - top: -2px; - right: -2px; - width: 20px; - height: 20px; - padding: 0; - border: none; - border-radius: 50%; - background: var(--accent, var(--red, #d92534)); - color: #fff; - cursor: pointer; - z-index: 2; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35); - transition: transform 0.12s, background 0.12s; - } - .theme-delete-btn:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 80%, white); - transform: scale(1.12); - } - .theme-delete-btn:active { transform: scale(0.95); } - .theme-delete-btn svg { - display: block; - /* Optical nudge — the X reads off-center inside the circle without it. */ - position: relative; - left: 1px; - top: -1px; - } - .theme-save-row { - display: flex; gap: 6px; margin-top: 8px; - } - .theme-save-row input { - flex: 1; padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 12px; font-family: inherit; - } - .theme-save-row input:focus { outline: none; border-color: var(--red); } - .theme-save-row input::placeholder { color: color-mix(in srgb, var(--fg) 35%, transparent); } - .theme-save-row button { - padding: 6px 12px; border: 1px solid var(--red); border-radius: 6px; - background: transparent; color: var(--red); cursor: pointer; - font-size: 12px; font-family: inherit; white-space: nowrap; transition: all 0.15s; - } - .theme-save-row button:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } - .theme-save-error { - font-size: 11px; color: var(--red); margin-top: 2px; display: none; - } - /* Import/Export */ - .theme-io-row { display: flex; gap: 6px; margin-top: 6px; } - .theme-io-btn { - flex: 1; padding: 5px 10px; border: 1px solid var(--border); border-radius: 6px; - background: transparent; color: var(--fg); cursor: pointer; - font-size: 12px; opacity: 0.7; transition: all 0.15s; font-family: inherit; - } - .theme-io-btn:hover { opacity: 1; border-color: var(--fg); background: color-mix(in srgb, var(--fg) 5%, transparent); } - - #memory-import-btn svg, - #memory-export-btn svg { - color: var(--accent, var(--red)); - } - .theme-import-area { - width: 100%; margin-top: 6px; padding: 6px 8px; - border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 0.7rem; - font-family: inherit; resize: vertical; min-height: 48px; - } - .theme-import-area:focus { outline: none; border-color: var(--red); } - .theme-import-area.hidden { display: none; } - .theme-import-actions { display: flex; gap: 6px; margin-top: 4px; } - .theme-import-actions.hidden { display: none; } - /* Font & Density */ - .theme-fd-row { display: flex; gap: 8px; margin-bottom: 8px; } - .theme-fd-group { flex: 1; display: flex; flex-direction: column; gap: 3px; } - .theme-fd-label { font-size: 12px; font-weight: 500; color: var(--fg); opacity: 0.6; } - .theme-fd-select { - padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; - background: var(--bg); color: var(--fg); font-size: 12px; - font-family: inherit; cursor: pointer; - } - .theme-fd-select:focus { outline: none; border-color: var(--red); } - @media (max-width: 768px) { - /* The switch is too cramped beside the Font and Density selects on - narrow screens; give Frosted a dedicated row instead. */ - .theme-fd-row:first-of-type { - flex-wrap: wrap; - } - .theme-fd-row:first-of-type #theme-frosted-group { - flex: 1 1 100%; - flex-direction: row; - align-items: center; - justify-content: space-between; - min-width: 0; - } - } - .theme-fd-range { - width: 100%; - max-width: 100%; - box-sizing: border-box; - margin: 0; - padding: 0; - height: 24px; - background: transparent; - cursor: pointer; - -webkit-appearance: none; - appearance: none; - accent-color: var(--red); - } - .theme-fd-range::-webkit-slider-runnable-track { - height: 4px; background: var(--border); border-radius: 2px; - } - .theme-fd-range::-moz-range-track { - height: 4px; background: var(--border); border-radius: 2px; - } - .theme-fd-range::-webkit-slider-thumb { - -webkit-appearance: none; appearance: none; - width: 14px; height: 14px; border-radius: 50%; - background: var(--red); border: none; margin-top: -5px; cursor: pointer; - } - .theme-fd-range::-moz-range-thumb { - width: 14px; height: 14px; border-radius: 50%; - background: var(--red); border: none; cursor: pointer; - } - .theme-fd-range:focus { outline: none; } - /* Color Harmony Generator */ - .theme-harmony-row { display: flex; gap: 8px; align-items: flex-end; } - .harmony-generate-btn { - padding: 5px 14px; border: 1px solid var(--red); border-radius: 6px; - background: transparent; color: var(--red); cursor: pointer; - font-size: 12px; white-space: nowrap; transition: all 0.15s; - font-family: inherit; width: 100%; - } - .harmony-generate-btn:hover { background: color-mix(in srgb, var(--red) 11%, transparent); } - .harmony-preview { - display: flex; height: 20px; border-radius: 6px; overflow: hidden; - margin-top: 8px; border: 1px solid var(--border); - } - .harmony-preview:empty { display: none; border: none; } - .harmony-preview span { flex: 1; } - #theme-reset-btn { - margin-top: 6px; width: 100%; padding: 6px; border: 1px solid var(--border); - border-radius: 6px; background: var(--bg); color: var(--fg); cursor: pointer; - font-size: 12px; font-family: inherit; opacity: 0.7; transition: opacity 0.15s; - } - #theme-reset-btn:hover { opacity: 1; } - .theme-adv-toggle { - margin-top: 10px; margin-bottom: 10px; - padding: 6px 8px; cursor: pointer; - font-size: 11px; color: var(--red); opacity: 0.8; - border: 1px solid var(--border); border-radius: 6px; - transition: opacity 0.15s, background 0.15s; - user-select: none; - } - .theme-adv-toggle:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 4%, transparent); } - .theme-adv-toggle .theme-adv-arrow { - display: inline-block; transition: transform 0.2s; font-size: 10px; - margin-right: 4px; - } - .theme-adv-toggle.open .theme-adv-arrow { transform: rotate(90deg); } - .theme-adv-section { margin-top: 8px; margin-bottom: 10px; } - .theme-adv-section.hidden { display: none; } - .theme-adv-group { margin-bottom: 8px; } - .theme-adv-group-label { - font-size: 10px; color: var(--fg); opacity: 0.5; - text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 4px; - } - .theme-adv-clear-btn { - margin-top: 6px; width: 100%; padding: 5px; border: 1px solid var(--border); - border-radius: 6px; background: transparent; color: var(--fg); opacity: 0.5; - cursor: pointer; font-size: 12px; font-family: inherit; transition: opacity 0.15s; - } - .theme-adv-clear-btn:hover { opacity: 1; } - - /* Customization is an editor, so keep its sections visually related while - preserving the existing controls and their order. */ - #theme-tab-customize { - display: flex; - flex-direction: column; - gap: 10px; - } - #theme-tab-customize > .admin-card, - #theme-tab-customize > .theme-adv-toggle, - #theme-tab-customize > .theme-adv-section { - margin-top: 0; - margin-bottom: 0; - } - #theme-tab-customize > .admin-card { - padding: 12px; - } - #theme-tab-customize > .admin-card > h2 { - display: flex; - align-items: center; - min-height: 20px; - margin: 0 0 10px; - padding-bottom: 7px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 65%, transparent); - font-size: 13px; - } - #theme-tab-customize .theme-custom { - gap: 6px 14px; - } - #theme-tab-customize .color-row { - min-height: 30px; - padding: 3px 6px; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--border) 55%, transparent); - background: color-mix(in srgb, var(--fg) 2%, transparent); - } - #theme-tab-customize .color-row label { - font-size: 12px; - } - #theme-tab-customize .theme-adv-toggle { - flex-shrink: 0; - margin: 0; - background: color-mix(in srgb, var(--fg) 2%, transparent); - } - #theme-tab-customize .theme-adv-section { - padding: 10px 12px 2px; - border: 1px solid color-mix(in srgb, var(--border) 75%, transparent); - border-radius: 7px; - background: color-mix(in srgb, var(--panel) 55%, transparent); - } - #theme-tab-customize .theme-adv-group { - margin: 0; - padding: 0 0 9px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 48%, transparent); - } - #theme-tab-customize .theme-adv-group + .theme-adv-group { - padding-top: 9px; - } - #theme-tab-customize .theme-adv-group:last-of-type { - border-bottom: 0; - } - #theme-tab-customize .theme-adv-group-label { - margin-bottom: 6px; - color: var(--red); - opacity: 0.8; - font-weight: 600; - } - #theme-tab-customize .theme-fd-row, - #theme-tab-customize .theme-harmony-row { - gap: 12px; - margin-bottom: 10px; - } - #theme-tab-customize .theme-fd-row:last-child, - #theme-tab-customize .theme-harmony-row:last-child { - margin-bottom: 0; - } - #theme-tab-customize .theme-fd-group { - min-width: 0; - } - #theme-tab-customize .theme-fd-select { - min-height: 30px; - } - #theme-tab-customize #theme-harmony-card .theme-fd-label { - font-size: 11px; - } - #theme-tab-customize .theme-save-row, - #theme-tab-customize .theme-io-row { - gap: 8px; - } - #theme-tab-customize #theme-reset-btn { - margin-top: 8px; - } - @media (max-width: 560px) { - #theme-tab-customize .theme-custom { - grid-template-columns: 1fr; - } - #theme-tab-customize .theme-fd-row, - #theme-tab-customize .theme-harmony-row { - flex-direction: column; - align-items: stretch; - } - #theme-tab-customize .theme-fd-group, - #theme-tab-customize .theme-harmony-row .theme-fd-group { - width: 100%; - } - } - - - /* Mobile: bottom sheet modals — slide up from bottom */ - @media (max-width: 768px) { - .modal { - align-items: flex-end; - background: rgba(0,0,0,0.4); - pointer-events: auto; - /* Anchor the bottom sheet to the DYNAMIC viewport. The base overlay is - position:fixed; height:100% = the layout viewport, whose bottom sits - UNDER Firefox Android's bottom URL bar — so flex-end parked the sheet - (and its footer / Start button) beneath the bar. dvh excludes the bar - so the footer lands above it. Extra safe-area pad for iOS home bar. */ - height: 100dvh; - padding-bottom: env(safe-area-inset-bottom, 0px); - } - /* Confirm dialog stays centered, not a bottom sheet */ - #styled-confirm-overlay { - align-items: center; - } - .styled-confirm-box { - border-radius: 12px !important; - border: 1px solid var(--border) !important; - animation: modal-enter 0.25s ease-out both !important; - max-height: none !important; - } - .styled-confirm-box.modal-closing { - animation: modal-exit 0.18s ease-in both !important; - } - .styled-confirm-box::before { - display: none !important; - } - .styled-confirm-box .close-btn { - display: none !important; - } - #theme-popup { - width: 100% !important; - height: 65vh !important; - max-height: 65vh !important; - top: auto !important; left: 0 !important; right: 0 !important; bottom: 0 !important; - position: fixed !important; - border-radius: 14px 14px 0 0; - border: none; - border-top: 1px solid var(--border); - padding: 6px 12px 12px; - animation: sheet-enter 0.2s ease-out forwards; - overflow-y: hidden !important; - } - #theme-popup .theme-tab-panel { - touch-action: pan-y; - overscroll-behavior: contain; - -webkit-overflow-scrolling: touch; - } - #theme-popup.sheet-ready { - animation: none; - } - #theme-popup.modal-closing { - animation: sheet-exit 0.15s ease-in both !important; - } - .modal-content, - .memory-modal-content, - .settings-modal-content, - #compare-model-overlay .modal-content { - width: 100% !important; - /* 85dvh leaves comfortable headroom above the sheet so the user can - still see the chat behind it and the drag handle has breathing - room. Was 65vh (too short, content clipped) → 90vh (too tall, top - hugged the status bar). */ - max-height: 85dvh !important; - max-height: 85vh !important; /* fallback for browsers without dvh */ - height: auto !important; - border-radius: 14px 14px 0 0; - border: none; - border-top: 1px solid var(--border); - padding-top: 6px !important; - /* Clip children to the rounded top corners — otherwise the sticky - modal-header's var(--panel) background paints a darker - rectangle past the radius and the corners look square again. */ - overflow: hidden; - animation: sheet-enter 0.2s ease-out forwards; - } - /* Keep the tall tool drawers on a shared mobile height ceiling. The - final mobile drawer rule below makes this content-sized so shorter - tools remain compact while tall ones leave a visible top gap. */ - #cookbook-modal .modal-content, - #tasks-modal .modal-content, - #calendar-modal .modal-content, - #memory-modal .memory-modal-content, - #settings-modal .settings-modal-content, - #email-lib-modal .modal-content, - #doclib-modal .doclib-modal-content { - max-height: 90vh !important; - max-height: 90dvh !important; - height: auto !important; - /* Reserve the iOS home-indicator / bottom-bar strip so an expanded - skill card's footer (Delete / Edit / Run) isn't hidden under it. - dvh already excludes the URL bar; this handles the safe area. */ - padding-bottom: env(safe-area-inset-bottom, 0px) !important; - } - /* Grab handle pill on the non-standard content classes too. Memory's - desktop rule defines a radial-glow ::before later in the file — - these body-prefixed selectors win the specificity battle on mobile. */ - body .memory-modal-content::before, - body .settings-modal-content::before { - content: ''; - display: block; - position: static; - inset: auto; - width: 36px; height: 4px; - background: var(--fg); - opacity: 0.25; - border-radius: 2px; - margin: 0 auto 4px; - flex-shrink: 0; - padding: 0; - border-top: 10px solid transparent; - border-bottom: 6px solid transparent; - background-clip: padding-box; - animation: none; - } - .memory-modal-content .close-btn, - .memory-modal-content .modal-close, - .settings-modal-content .close-btn, - .settings-modal-content .modal-close { - display: none !important; - } - .memory-modal-content, - .settings-modal-content { - touch-action: pan-y; - overscroll-behavior: contain; - } - /* Library modals use the same bottom-sheet sizing as the other tools. - The shared mobile drawer rule below provides the top breathing room. */ - #cookbook-modal .modal-content, - #calendar-modal .modal-content, - #email-lib-modal .modal-content, - #doclib-modal .modal-content, - #gallery-modal .modal-content, - #tasks-modal .modal-content { - /* vh-first as fallback for very old browsers, then dvh wins so - the modal shrinks/grows with the mobile URL bar. Wrong order - previously made the expanded chat preview extend below the - visible viewport on Chrome/Safari mobile (URL bar covered the - action buttons row). */ - max-height: 90vh !important; - max-height: 90dvh !important; - height: auto !important; - } - /* Anchor those modals to the top so the full height is usable */ - #cookbook-modal, - #calendar-modal, - #email-lib-modal, - #doclib-modal, - #gallery-modal, - #tasks-modal { - padding-top: 0 !important; - align-items: stretch !important; - } - /* Deep Research already gets the swipe grab-handle pill from the - shared `.modal-content::before` rule (the pane carries that class). - The previous header-level pill was redundant and rendered as a - SECOND pill stacked above the real one — removed. */ - /* The inner body must flex to fill the new full height, and the - tasks list inside it must scroll independently — otherwise the - list crops at whatever pre-mobile height the desktop layout had. */ - #tasks-modal .modal-content { - display: flex !important; - flex-direction: column !important; - } - #tasks-modal .modal-body { - flex: 1 1 auto !important; - min-height: 0 !important; - } - /* Memory/Skills: the body lacks flex:1, so on the fixed-height mobile - sheet (overflow:hidden) it grew past the viewport and clipped the - bottom of the skills list + its row action buttons with no way to - scroll there. Bound the body so the inner list scrolls internally. */ - #memory-modal .memory-modal-content { - display: flex !important; - flex-direction: column !important; - } - /* flex-basis MUST be 0 (not auto) — matches the working #doclib-modal - .modal-body. With basis:auto, Firefox (incl. mobile) sizes the body - to its content (~half height) instead of filling, while Chromium - fills it regardless — which is exactly why the skill expand worked - on desktop/Chromium but stuck at ~50% on Firefox mobile. */ - #memory-modal .memory-modal-body { - flex: 1 1 0 !important; - min-height: 0 !important; - overflow: hidden !important; - } - /* Same basis:0 fix down the rest of the skills chain so every layer - fills instead of sizing to content under Firefox. */ - #memory-modal .memory-tab-panel[data-memory-panel="skills"] { - flex: 1 1 0 !important; - min-height: 0 !important; - } - #memory-modal .memory-tab-panel[data-memory-panel="skills"] > .admin-card { - flex: 1 1 0 !important; - min-height: 0 !important; - } - /* The skills modal carries an extra toolbar row (search/sort/select + - bulk bar) the doc/email libraries don't, so the shared 82dvh preview - min-height overflowed here and pushed the expanded footer (Delete / - Edit / Run) off the bottom of the screen. Let flexbox size it from - the resolved card height instead, so the footer always pins inside - the visible area. */ - #memory-modal .doclib-card.doclib-card-expanded .doclib-card-preview { - min-height: 0 !important; - } - /* Once enter animation finishes, clear it so inline transform works for dragging */ - .modal-content.sheet-ready { - animation: none; - } - .modal-content.modal-closing { - animation: sheet-exit 0.15s ease-in both !important; - } - /* Grab handle — large touch target, visible pill */ - #theme-popup::before, - .modal-content::before { - content: ''; - display: block; - width: 36px; height: 4px; - background: var(--fg); opacity: 0.25; - border-radius: 2px; - margin: 0 auto 4px; - flex-shrink: 0; - padding: 0; - /* Expand touch target without changing visual size */ - border-top: 10px solid transparent; - border-bottom: 6px solid transparent; - background-clip: padding-box; - } - /* Hide close X on mobile — swipe down to dismiss */ - .modal-content .close-btn, - .modal-content .modal-close, - #theme-popup .close-btn { - display: none !important; - } - /* Hide the auto-injected minimize (_) button on mobile — the dock - chip already represents the minimized state, and the swipe-down - gesture is the canonical minimize action. */ - .modal-minimize-btn, - .minimize-btn, - [data-minimize] { - display: none !important; - } - /* Lock modals to vertical touch only, prevent horizontal dragging */ - .modal-content { - touch-action: pan-y; - overscroll-behavior: contain; - } - .modal-content .cookbook-body, - .modal-content .modal-body { - touch-action: pan-y; - overscroll-behavior: contain; - } - .modal-header { - cursor: default; - touch-action: none; - } - @keyframes sheet-enter { - from { transform: translateY(100%); } - to { transform: translateY(0); } - } - @keyframes sheet-exit { - from { transform: translateY(0); } - to { transform: translateY(100%); } - } - } - - /* ── Model A/B Comparison ── */ - - /* -- Extracted inline-style classes -- */ - .cmp-header-action-btn { - background: none; border: 1px solid var(--border); color: var(--fg); - cursor: pointer; padding: 3px 10px; font-size: 11px; font-weight: 600; - opacity: 0.7; transition: all 0.15s; line-height: 1; border-radius: 4px; - display: inline-flex; align-items: center; font-family: inherit; - } - .cmp-form-control { - padding: 8px; background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; font-size: 0.85em; - } - .cmp-btn-secondary { - background: transparent; color: var(--fg); border: 1px solid var(--border); - border-radius: 6px; cursor: pointer; - } - .cmp-btn-primary { - background: var(--fg); color: var(--bg); border: none; - border-radius: 6px; cursor: pointer; font-weight: 600; - transition: filter 0.12s, background 0.12s, color 0.12s; - } - /* Override global button:hover (which switches bg to var(--panel) = - very dark) — keep the bright primary look and just brighten slightly. */ - .cmp-btn-primary:hover:not(:disabled) { - background: var(--fg); color: var(--bg); - filter: brightness(1.1); - } - .cmp-btn-primary:active:not(:disabled) { filter: brightness(0.95); } - .cmp-btn-secondary:hover:not(:disabled) { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: var(--fg); color: var(--fg); - } - .cmp-model-row { - display: flex; align-items: center; gap: 8px; margin-bottom: 8px; - flex-wrap: wrap; - padding: 2px 0; - transition: margin-left 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), background 0.15s, border-color 0.15s; - } - .cmp-model-row-duplicate { - margin-left: -4px; - margin-right: -4px; - padding: 4px; - border-radius: 6px; - background: color-mix(in srgb, var(--color-error) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--color-error) 20%, transparent); - } - .cmp-model-row-swap-target { - margin-left: -4px; - margin-right: -4px; - padding: 4px; - border-radius: 6px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 42%, transparent); - } - .cmp-model-row-swap-target .cmp-row-label { - color: var(--accent, var(--red)); - opacity: 1; - } - .cmp-row-label { - color: var(--fg); font-size: 0.82em; font-weight: 600; min-width: 20px; - opacity: 0.4; text-align: center; flex-shrink: 0; - } - .cmp-model-meta { - order: 3; - flex: 1 0 calc(100% - 28px); - margin-left: 28px; - margin-top: -4px; - display: flex; - align-items: center; - gap: 5px; - min-width: 0; - font-size: 10px; - line-height: 1.2; - } - .cmp-model-meta-chip, - .cmp-model-meta-warning, - .cmp-model-meta-muted { - display: inline-flex; - align-items: center; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - border-radius: 999px; - padding: 1px 6px; - letter-spacing: 0; - } - .cmp-model-meta-chip { - color: color-mix(in srgb, var(--fg) 60%, transparent); - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - } - .cmp-model-meta-warning { - color: var(--color-error); - background: color-mix(in srgb, var(--color-error) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--color-error) 24%, transparent); - font-weight: 600; - } - .cmp-model-meta-muted { - color: color-mix(in srgb, var(--fg) 36%, transparent); - } - .cmp-rm-btn { - background: none; border: none; color: var(--accent, var(--red)); cursor: pointer; - min-width: 20px; font-size: 16px; font-weight: 600; opacity: 0.3; - transition: all 0.15s; padding: 0; line-height: 1; text-align: center; - position: relative; top: -2px; - order: 2; - margin-left: auto; - align-self: center; - } - .cmp-prov-select { - flex: 0 0 auto; width: 120px; font-size: 0.8em; - } - /* Eval-prompts picker — only shown during compare; absolute top-right - inside .chat-input-top, matching .model-picker-wrap's slot. */ - .chat-input-top > .cmp-eval-wrap { - position: relative; - grid-column: 2; - grid-row: 1; - top: -2px; right: 0; - z-index: 2; - } - .cmp-eval-btn { - display: inline-flex; align-items: center; gap: 4px; - padding: 4px 8px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 11px; font-weight: 500; - font-family: inherit; - cursor: pointer; opacity: 0.75; - transition: opacity 0.15s, border-color 0.15s; - } - .cmp-eval-btn:hover { opacity: 1; border-color: var(--fg); } - .cmp-eval-clear { - display: inline-flex; align-items: center; gap: 4px; margin-left: 4px; - border: 0; background: transparent; color: var(--fg); opacity: .55; - padding: 3px 5px; font: inherit; font-size: 10px; cursor: pointer; - } - .cmp-eval-clear:hover { opacity: 1; } - .cmp-eval-caret { opacity: 0.7; transform: rotate(180deg); } - .cmp-eval-menu { - position: absolute; bottom: calc(100% + 4px); right: 0; - min-width: 220px; max-width: 280px; - max-height: 360px; overflow-y: auto; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 -4px 16px rgba(0,0,0,0.3); - padding: 4px; - z-index: 1000; - } - .cmp-eval-menu.hidden { display: none; } - .cmp-eval-group-label { - font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px; - opacity: 0.45; font-weight: 600; - padding: 6px 8px 2px; - } - .cmp-eval-item { - display: block; width: 100%; - text-align: left; - padding: 5px 8px; - background: none; border: none; - color: var(--fg); font-size: 11px; - font-family: inherit; - border-radius: 4px; - cursor: pointer; - } - .cmp-eval-item:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - } - .cmp-eval-empty { - padding: 10px; text-align: center; - font-size: 11px; opacity: 0.5; - } - /* Tick on items that ship a known expected answer */ - .cmp-eval-item-tick { - float: right; - margin-left: 6px; - font-size: 10px; - color: var(--color-success, #4caf50); - opacity: 0.8; - } - /* Expected-answer panel — floats as its own little window above the - chat-input-bar. Distinct surface, padded, drop-shadow, slightly - lifted so it reads as a separate UI element, not part of the input. */ - .chat-input-bar:has(.cmp-eval-expected) { position: relative; } - .cmp-eval-expected { - position: absolute; - bottom: calc(100% + 8px); - right: 0; - display: inline-flex; align-items: center; gap: 8px; - padding: 8px 12px; - font-size: 11px; - background: var(--panel); - border: 1px solid color-mix(in srgb, var(--color-success, #4caf50) 50%, transparent); - border-radius: 8px; - box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2); - color: var(--fg); - width: fit-content; - z-index: 5; - pointer-events: auto; - } - .cmp-eval-expected.hidden { display: none; } - .cmp-eval-expected-label { - opacity: 0.6; - text-transform: uppercase; - font-size: 9px; - letter-spacing: 0.5px; - font-weight: 600; - } - .cmp-eval-expected-value { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 11px; - } - .cmp-eval-expected-close { - background: none; border: none; color: var(--fg); - font-size: 14px; line-height: 1; padding: 0 0 0 4px; - opacity: 0.5; cursor: pointer; font-family: inherit; - } - .cmp-eval-expected-close:hover { opacity: 1; } - /* Auto-grade badge — stamped on a pane after stream completes when an - eval prompt with a known expected answer was used. */ - .pane-grade-badge { - display: inline-flex; align-items: center; justify-content: center; - width: 18px; height: 18px; - margin: 0 4px; - font-size: 12px; font-weight: 700; - border-radius: 50%; - border: 1px solid currentColor; - flex-shrink: 0; - } - .pane-grade-badge.pass { - color: var(--color-success, #4caf50); - background: color-mix(in srgb, var(--color-success, #4caf50) 12%, transparent); - } - .pane-grade-badge.fail { - color: var(--color-error, #e55); - background: color-mix(in srgb, var(--color-error, #e55) 12%, transparent); - } - - /* Compare probe overlay */ - .compare-probe-overlay { - position: fixed; inset: 0; z-index: 300; - background: rgba(0,0,0,0.5); display: flex; - align-items: center; justify-content: center; - padding: 12px; - box-sizing: border-box; - } - .compare-probe-card { - width: min(460px, calc(100% - 16px)); - max-height: min(620px, calc(100dvh - 24px)); - box-sizing: border-box; - background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px; - display: flex; flex-direction: column; align-items: stretch; gap: 10px; - min-width: 280px; box-shadow: 0 8px 28px rgba(0,0,0,0.28); - overflow: hidden; - font-family: inherit; - } - .compare-probe-title { - font-size: 13px; font-weight: 400; color: var(--fg); - } - .compare-probe-title-label { font-weight: 600; } - .compare-probe-title-status { font-weight: 400; } - .compare-probe-list { - display: grid; grid-template-columns: 1fr 1fr; gap: 6px; min-width: 0; width: 100%; - overflow-y: auto; - } - .compare-probe-row { - display: flex; align-items: center; gap: 6px; padding: 6px 10px; - border-radius: 6px; background: color-mix(in srgb, var(--fg) 4%, transparent); - font-size: 12px; transition: background 0.2s; overflow: hidden; - } - .compare-probe-row.fail { - background: color-mix(in srgb, var(--color-error, #f44) 8%, transparent); - } - .compare-probe-spinner { - width: 24px; text-align: center; font-size: 10px; flex-shrink: 0; - letter-spacing: -1px; opacity: 0.6; - } - .compare-probe-spinner.ok { color: var(--color-success); animation: none; } - .compare-probe-spinner.fail { color: var(--color-error, #f44); animation: none; } - .compare-probe-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .compare-probe-status { font-size: 11px; opacity: 0.5; flex-shrink: 0; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } - .compare-probe-status.ok { color: var(--color-success); opacity: 1; } - .compare-probe-status.fail { color: var(--color-error, #f44); opacity: 1; } - .compare-probe-action-btn { - display: inline-flex; align-items: center; gap: 5px; - min-height: 25px; padding: 3px 8px; background: transparent; color: var(--fg); border: 1px solid var(--border); - border-radius: 5px; cursor: pointer; font-size: 10px; font-family: inherit; - opacity: 0.78; transition: opacity 0.15s, border-color 0.15s, background 0.15s; white-space: nowrap; flex-shrink: 0; - } - .compare-probe-action-btn svg, - .compare-probe-footer-btn svg { width: 12px; height: 12px; flex: 0 0 auto; color: var(--accent, var(--red)); } - .compare-probe-action-btn:hover { opacity: 1; border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--fg) 5%, transparent); } - .compare-probe-feedback { display: flex; flex-direction: column; gap: 5px; } - .compare-probe-feedback:empty { display: none; } - .compare-probe-detail { - display: flex; align-items: center; gap: 6px; min-width: 0; - padding: 6px 8px; font-size: 10px; - background: color-mix(in srgb, var(--color-error, #f44) 6%, transparent); - border-left: 2px solid color-mix(in srgb, var(--color-error, #f44) 72%, transparent); - border-radius: 4px; - } - .compare-probe-detail-icon { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - width: 14px; - height: 14px; - color: var(--accent, var(--red)); - font-family: inherit; - font-size: 11px; - font-weight: 700; - line-height: 1; - } - .compare-probe-detail-message { flex: 1 1 auto; min-width: 0; line-height: 1.35; color: color-mix(in srgb, var(--fg) 72%, transparent); overflow-wrap: anywhere; } - .compare-probe-footer { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; margin-top: 2px; } - .compare-probe-footer-btn { - display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 5px 10px; - font-family: inherit; font-size: 11px; font-weight: 600; line-height: 1; - } - .compare-probe-start-anyway, - .compare-probe-start-anyway:hover:not(:disabled) { - background: var(--accent, var(--red)); - border: 1px solid var(--accent, var(--red)); - color: var(--bg); - } - .compare-probe-start-anyway svg { color: currentColor; } - .compare-probe-start-anyway span { font-weight: 700; } - @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } - @keyframes pane-shake { - 0%, 100% { transform: translateX(0); } - 15% { transform: translateX(-3px) rotate(-0.5deg); } - 30% { transform: translateX(3px) rotate(0.5deg); } - 45% { transform: translateX(-2px); } - 60% { transform: translateX(2px); } - 75% { transform: translateX(-1px); } - } - - .chat-container.compare-active { - display: flex; - flex-direction: column; - padding: 0; - overflow: hidden; - animation: compare-enter 0.3s ease-out; - } - @keyframes compare-enter { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } - } - .chat-container.compare-active .chat-input-bar { - margin-bottom: 0; - } - .chat-container.compare-active #model-picker-wrap { - display: none !important; - } - .compare-grid { - display: grid; - gap: 4px; - flex: 1 1 0; - min-height: 0; - overflow: hidden; - position: relative; - } - .compare-mobile-tabs { display: none; } - .compare-grid[data-cols="2"] { grid-template-columns: 1fr 1fr; } - .compare-grid[data-cols="3"] { grid-template-columns: 1fr 1fr 1fr; } - .compare-grid[data-cols="4"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="5"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="6"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="7"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid[data-cols="8"] { grid-template-columns: repeat(4, 1fr); } - .compare-grid { grid-auto-rows: 1fr; } - .compare-shuffle-notice { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%) scale(0.94); - z-index: 30; - display: inline-flex; - align-items: center; - gap: 8px; - padding: 10px 14px; - border: 1px solid color-mix(in srgb, var(--red) 28%, var(--border)); - border-radius: 8px; - background: color-mix(in srgb, var(--panel, var(--bg)) 92%, transparent); - color: var(--fg); - box-shadow: 0 10px 30px color-mix(in srgb, #000 24%, transparent); - font-size: 12px; - font-weight: 700; - line-height: 1; - opacity: 0; - pointer-events: none; - transition: opacity 0.14s ease, transform 0.14s ease; - } - .compare-shuffle-notice.show { - opacity: 1; - transform: translate(-50%, -50%) scale(1); - } - .compare-shuffle-notice-icon { - display: inline-flex; - color: var(--red); - animation: compare-shuffle-dice 0.56s ease both; - } - .compare-shuffle-notice-icon svg { - display: block; - width: 18px; - height: 18px; - } - @keyframes compare-shuffle-dice { - 0% { transform: rotate(-18deg) scale(0.9); } - 55% { transform: rotate(380deg) scale(1.08); } - 100% { transform: rotate(360deg) scale(1); } - } - /* Sequential waterfall layout — stacked rows, staggered left, flush right */ - .compare-grid.sequential-layout { - display: flex !important; - flex-direction: column !important; - grid-template-columns: none !important; - gap: 4px; - overflow-y: auto; - } - .compare-grid.sequential-layout .compare-pane { - flex-shrink: 0; - min-height: 200px; - transition: margin-left 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease; - } - /* Herringbone diagonal cut on left side of sequential pane headers */ - .compare-grid.sequential-layout .compare-pane .pane-header { - clip-path: polygon(20px 0, 100% 0, 100% 100%, 0 100%); - padding-left: 26px; - } - .compare-pane { - display: flex; - flex-direction: column; - border: 1px solid var(--border); - border-radius: 6px; - overflow: hidden; - min-height: 0; - min-width: 0; - position: relative; - transition: border-color 0.18s ease, box-shadow 0.18s ease, opacity 0.18s ease; - } - .compare-pane::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 2px; - z-index: 2; - pointer-events: none; - background: transparent; - transform-origin: left center; - } - .compare-pane.is-streaming { - border-color: color-mix(in srgb, var(--accent, var(--red)) 34%, var(--border)); - box-shadow: inset 0 1px 0 color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - } - .compare-pane.is-streaming::before { - background: linear-gradient(90deg, transparent 0%, var(--accent, var(--red)) 35%, var(--red) 50%, var(--accent, var(--red)) 65%, transparent 100%); - animation: compare-pane-progress 1.25s linear infinite; - } - .compare-pane.is-awaiting-input { - border-color: color-mix(in srgb, var(--red) 42%, var(--border)); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--red) 12%, transparent); - } - .compare-pane.is-awaiting-input::before { - background: var(--red); - animation: none; - } - .compare-pane.is-done { - border-color: color-mix(in srgb, var(--green, #50fa7b) 20%, var(--border)); - } - .compare-pane.is-failed { - border-color: color-mix(in srgb, var(--color-error) 42%, var(--border)); - } - .compare-pane.is-failed::before { - background: var(--color-error); - } - @keyframes compare-pane-progress { - from { transform: translateX(-100%); } - to { transform: translateX(100%); } - } - .compare-pane .pane-header { - display: flex; - flex-direction: column; - align-items: stretch; - gap: 4px; - padding: 4px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-bottom: 1px solid var(--border); - font-size: 0.82em; - font-weight: 600; - color: var(--fg); - transition: background 0.4s; - flex-shrink: 0; - overflow: visible; - min-width: 0; - flex-wrap: nowrap; - } - .pane-header-row { display: flex; align-items: center; width: 100%; min-width: 0; } - .pane-header-primary { min-height: 24px; } - .pane-header-secondary { min-height: 20px; } - .pane-primary-actions, .pane-stats { display: flex; align-items: center; gap: 4px; min-width: 0; } - .pane-primary-actions { margin-left: auto; flex: 0 0 auto; } - .pane-stats { flex: 1 1 auto; overflow: hidden; } - .compare-header-bar button:hover, - .compare-header-bar button:focus-visible { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent) !important; - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)) !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-header-bar button:active { - background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent) !important; - transform: scale(0.96); - } - .compare-more-wrap { position: relative; display: inline-flex; } - .compare-more-btn { - display: inline-flex; align-items: center; justify-content: center; - width: 28px; height: 24px; padding: 0 !important; - border: 1px solid transparent; border-radius: 5px; - background: none; color: var(--fg); cursor: pointer; - opacity: 0.5; transition: opacity .15s, background .15s; - } - .compare-header-bar .compare-close-btn { - width: 24px; height: 24px; min-width: 24px; padding: 0; - border-radius: 999px; font-size: 0; display: inline-flex; - align-items: center; justify-content: center; - } - .compare-header-bar .compare-close-btn:hover, - .compare-header-bar .compare-close-btn:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-header-bar .compare-more-btn:hover, - .compare-header-bar .compare-more-btn:focus-visible, - .compare-header-bar .compare-more-btn[aria-expanded="true"] { - background: color-mix(in srgb, var(--fg) 10%, transparent) !important; - border-color: transparent !important; - color: var(--fg) !important; - opacity: 1 !important; - outline: none; - } - .compare-more-menu { - position: absolute; top: calc(100% + 5px); right: 0; z-index: 10002; - display: none; flex-direction: column; gap: 3px; min-width: 132px; - padding: 5px; border: 1px solid var(--border); border-radius: 8px; - background: var(--panel, var(--bg)); box-shadow: 0 8px 24px rgba(0,0,0,.3); - } - .compare-more-menu.is-open { display: flex; } - .compare-more-menu > .compare-export-wrap, - .compare-more-menu > button { - width: 100%; display: flex; align-items: center; - } - .compare-more-menu #compare-export-btn, - .compare-more-menu #compare-shuffle-btn, - .compare-more-menu #compare-check-btn, - .compare-more-menu #compare-add-menu-btn { - width: 100%; min-height: 32px; height: 32px; - box-sizing: border-box; justify-content: flex-start; gap: 7px; - padding: 7px 9px !important; - border: 1px solid transparent !important; border-radius: 6px; - background: transparent !important; color: var(--fg) !important; - font-size: 11px; font-weight: 500; opacity: .72; - transition: background .12s ease, opacity .12s ease, color .12s ease; - } - .compare-more-menu #compare-export-btn:hover, - .compare-more-menu #compare-shuffle-btn:hover, - .compare-more-menu #compare-check-btn:hover, - .compare-more-menu #compare-add-menu-btn:hover, - .compare-more-menu #compare-export-btn:focus-visible, - .compare-more-menu #compare-shuffle-btn:focus-visible, - .compare-more-menu #compare-check-btn:focus-visible, - .compare-more-menu #compare-add-menu-btn:focus-visible { - opacity: 1; background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: transparent !important; color: var(--fg) !important; outline: none; - } - .compare-more-menu #compare-export-btn svg, - .compare-more-menu #compare-shuffle-btn svg, - .compare-more-menu #compare-check-btn svg, - .compare-more-menu #compare-add-menu-btn svg { flex: 0 0 auto; opacity: .65; } - .compare-more-menu #compare-export-btn span, - .compare-more-menu #compare-shuffle-btn span, - .compare-more-menu #compare-check-btn span, - .compare-more-menu #compare-add-menu-btn span { - font-size: 11px !important; margin-left: 0 !important; - color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; - } - .compare-more-menu #compare-export-btn, - .compare-more-menu #compare-shuffle-btn, - .compare-more-menu #compare-check-btn, - .compare-more-menu #compare-add-menu-btn { - color: var(--fg) !important; opacity: 1 !important; font-weight: 500 !important; - } - .compare-more-menu #compare-export-btn svg, - .compare-more-menu #compare-shuffle-btn svg, - .compare-more-menu #compare-check-btn svg, - .compare-more-menu #compare-add-menu-btn svg { color: var(--fg) !important; opacity: .65 !important; } - .compare-export-item:hover, - .compare-export-item:focus-visible { - background: color-mix(in srgb, var(--red) 10%, transparent) !important; - border-color: transparent !important; outline: none; - } - .compare-export-item { display: flex; align-items: center; gap: 8px; } - .compare-export-item > svg { flex: 0 0 auto; opacity: .65; } - .compare-export-menu .compare-export-item, - .compare-export-menu .compare-export-item > span { - color: var(--fg) !important; - opacity: 1 !important; - } - .compare-add-flap { - position: absolute; z-index: 20; right: -8px; top: 50%; - width: 34px; min-width: 34px; height: 30px; box-sizing: border-box; - padding: 0 !important; - display: inline-flex; flex-direction: row; align-items: center; justify-content: center; - gap: 0; border: 1px solid var(--border); - border-radius: 0 7px 7px 0; background: var(--panel, var(--bg)); - color: var(--fg); cursor: pointer; opacity: .7; - box-shadow: 3px 2px 9px color-mix(in srgb, var(--fg) 12%, transparent); - transform: translate(0, -50%); transition: width .18s ease, opacity .15s, background .15s, box-shadow .15s; - } - .compare-add-flap:hover, .compare-add-flap:focus-visible { - width: 70px; opacity: 1; background: color-mix(in srgb, var(--fg) 8%, var(--panel)); - box-shadow: 4px 3px 12px color-mix(in srgb, var(--fg) 18%, transparent); - outline: none; - } - .compare-add-flap span { - max-width: 0; overflow: hidden; white-space: nowrap; margin-left: 0 !important; - font-size: 10px; opacity: 0; transition: max-width .18s ease, margin-left .18s ease, opacity .12s; - } - .compare-add-flap:hover span, .compare-add-flap:focus-visible span { - max-width: 32px; margin-left: 3px !important; opacity: 1; - } - .compare-add-flap svg { flex-shrink: 0; position: relative; left: -3px; } - @media (max-width: 768px) { - .compare-add-flap { - top: 8px; right: 8px; width: auto; min-width: 32px; height: 32px; - padding: 5px 8px !important; flex-direction: row; transform: none; - border-right: 1px solid var(--border); border-radius: 6px; - } - .compare-add-flap span { opacity: 1; } - } - .pane-actions { - display: flex; gap: 4px; align-items: center; margin-left: auto; flex-shrink: 0; - } - .compare-pane-footer { - font-size: 0.72em; opacity: 0.4; padding: 4px 10px; - border-top: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; flex-shrink: 0; - } - /* Per-pane vote button. Sits at the bottom of each compare pane so - the action lives right under the response it judges. */ - .pane-vote-footer { - padding: 6px 8px; - border-top: 1px solid color-mix(in srgb, var(--border) 60%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex-shrink: 0; - } - .pane-vote-btn { - width: 100%; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 6px 10px; - font-family: inherit; - font-size: 12px; - font-weight: 500; - cursor: pointer; - transition: background 0.15s, border-color 0.15s, opacity 0.15s; - } - .pane-vote-btn:hover:not(:disabled) { - background: color-mix(in srgb, var(--accent, var(--fg)) 12%, var(--bg)); - border-color: var(--accent, var(--fg)); - } - .pane-vote-btn:disabled { cursor: not-allowed; } - .pane-vote-label { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; - } - .pane-action-btn { - background: none; border: none; color: var(--fg); cursor: pointer; - opacity: 0.3; padding: 2px; border-radius: 4px; - display: flex; align-items: center; transition: all 0.15s; - } - .pane-action-btn:hover { opacity: 0.8; background: color-mix(in srgb, var(--fg) 6%, transparent); } - /* Pane title as clickable model-swap button */ - .pane-title-btn { - background: none; border: none; cursor: pointer; - font-size: 10px; font-weight: 400; font-family: inherit; - color: var(--fg); padding: 0; - text-align: left; display: flex; align-items: center; gap: 4px; - overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - transition: opacity 0.15s; - min-width: 0; flex: 1 1 0; - } - .pane-title-btn:hover { opacity: 0.7; } - .pane-title-caret { font-size: 0.6em; opacity: 0.35; flex-shrink: 0; position: relative; top: 2px; } - .pane-title-btn:hover .pane-title-caret { opacity: 0.7; } - .pane-mode-badge { - display: inline-flex; - align-items: center; - gap: 4px; - flex: 0 0 auto; - height: 18px; - padding: 0 6px; - border: 1px solid color-mix(in srgb, var(--border) 70%, transparent); - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - color: color-mix(in srgb, var(--fg) 76%, transparent); - font-size: 9px; - font-weight: 600; - line-height: 1; - letter-spacing: 0; - white-space: nowrap; - } - .pane-mode-dot { - width: 5px; - height: 5px; - border-radius: 50%; - background: currentColor; - opacity: 0.75; - flex: 0 0 auto; - } - .pane-mode-detail { - opacity: 0.52; - font-weight: 500; - } - .pane-mode-badge.pane-mode-icon-only { - width: 20px; - padding: 0; - justify-content: center; - } - .pane-mode-settings { - opacity: 0.65; - cursor: pointer; - } - .pane-settings-btn { width: 20px; height: 20px; justify-content: center; } - .pane-settings-btn[aria-expanded="true"] { opacity: 1; background: color-mix(in srgb, var(--fg) 9%, transparent); } - .response-metrics[data-action="settings"] { cursor: pointer; } - .response-thinking-state { color: color-mix(in srgb, var(--fg) 55%, transparent); } - .pane-mode-settings:hover, - .pane-mode-settings:focus-visible, - .pane-mode-settings[aria-expanded="true"] { - opacity: 1; - color: var(--fg); - background: color-mix(in srgb, var(--fg) 9%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--fg)) 45%, var(--border)); - outline: none; - } - .pane-mode-icon, - .pane-mode-icon svg { - display: block; - width: 12px; - height: 12px; - } - .pane-mode-agent { - color: color-mix(in srgb, var(--fg) 76%, transparent); - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - border-color: color-mix(in srgb, var(--border) 70%, transparent); - } - .pane-mode-search { - color: color-mix(in srgb, var(--accent, var(--red)) 82%, var(--fg)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, var(--bg)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, var(--border)); - } - .pane-mode-research { - color: color-mix(in srgb, #36c48f 82%, var(--fg)); - background: color-mix(in srgb, #36c48f 10%, var(--bg)); - border-color: color-mix(in srgb, #36c48f 28%, var(--border)); - } - .compare-check-label { - position: relative; - top: -2px; - font-size: 11px; - margin-left: 3px; - } - .compare-check-icon { - position: relative; - top: 1px; - } - #compare-check-btn > .spinner-whirlpool { - position: relative; - top: -2px; - transform: none; - } - .compare-pane .pane-close-btn { opacity: 0.3; } - .compare-pane .pane-close-btn:hover { opacity: 1; color: var(--color-error); } - /* Model swap dropdown under pane title */ - .pane-model-dropdown { - position: absolute; - z-index: 1000; - min-width: 220px; - max-height: 300px; - overflow-y: auto; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 16px rgba(0,0,0,0.3); - padding: 4px; - } - .pane-model-item { - display: block; width: 100%; - box-sizing: border-box; - padding: 8px 10px; font-size: 11px; line-height: 1.3; - text-align: left; background: none; border: none; border-radius: 6px; - color: var(--fg); cursor: pointer; transition: background 0.12s ease; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - } - .pane-model-item:hover, - .pane-model-item:focus-visible { background: color-mix(in srgb, var(--red) 10%, transparent); outline: none; } - .pane-model-item.current { color: var(--red); font-weight: 600; } - .pane-timer { - font-size: 10px; font-weight: 400; opacity: 0.45; font-variant-numeric: tabular-nums; - white-space: nowrap; padding-right: 4px; - } - .pane-summary { - display: inline-flex; - align-items: center; - position: relative; - top: 1px; - min-height: 16px; - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding: 0 5px; - border-radius: 999px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 9px; - font-weight: 500; - line-height: 1; - font-variant-numeric: tabular-nums; - cursor: pointer; - transition: max-width .15s ease, white-space .15s ease; - } - .pane-summary:empty { display: none; } - .pane-stats-popup { - position: fixed; z-index: 10040; width: 210px; padding: 6px; - background: var(--panel, var(--bg)); color: var(--fg); - border: 1px solid var(--border); border-radius: 7px; - box-shadow: 0 8px 24px rgba(0,0,0,.3); font-size: 11px; - } - .pane-stats-popup-title { padding: 4px 6px 7px; font-weight: 600; border-bottom: 1px solid var(--border); } - .pane-stats-popup-row { display: flex; justify-content: space-between; gap: 12px; padding: 6px; } - .pane-stats-popup-row span { color: color-mix(in srgb, var(--fg) 55%, transparent); } - .pane-stats-popup-row strong { font-weight: 500; font-variant-numeric: tabular-nums; text-align: right; } - /* Metrics stay on the lower row even in dense comparisons. */ - .compare-grid[data-cols="4"] .pane-timer, - .compare-grid[data-cols="5"] .pane-timer, - .compare-grid[data-cols="6"] .pane-timer { - width: auto; order: initial; margin-top: 0; padding-bottom: 0; padding-left: 0; - } - .pane-finish-badge { - font-weight: 600; color: var(--green, #50fa7b); - position: relative; - top: 2px; - } - .compare-pane.winner .pane-header { - background: color-mix(in srgb, var(--green, #50fa7b) 12%, transparent); - border-bottom-color: color-mix(in srgb, var(--green, #50fa7b) 30%, var(--border)); - } - .compare-pane.winner .pane-title { - color: var(--green, #50fa7b); - } - .compare-pane.loser .pane-header { - opacity: 0.5; - } - .confetti-piece { - position: fixed; - pointer-events: none; - z-index: 1000000; - } - .compare-pane.expanded { grid-column: 1 / -1; } - .compare-pane .chat-history { - flex: 1 1 0; - min-height: 0; - overflow-y: auto !important; - overflow-x: hidden; - padding: 8px; - display: flex; - flex-direction: column; - } - .compare-pane .chat-history .msg { - flex-shrink: 0; - } - .compare-gen-image { - max-width: 100%; - max-height: 100%; - object-fit: contain; - border-radius: 4px; - cursor: pointer; - } - .compare-section { - margin-bottom: 14px; - } - .compare-section:last-child { - margin-bottom: 0; - } - .compare-section-label { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.6px; - opacity: 0.5; - margin-bottom: 5px; - font-weight: 600; - } - /* Active-type/mode readout next to "Type:"/"Mode:" — only needed on mobile - (where the tab/toggle text labels are hidden); hidden on desktop. */ - .compare-type-current, - .compare-mode-current { display: none; } - /* Contextual one-liner under the mode toggles describing what you just - changed — empty until the first toggle, then a subtle hint. */ - .compare-mode-hint { - font-size: 11px; - opacity: 0.55; - margin-top: 6px; - min-height: 0; - line-height: 1.3; - } - .compare-mode-hint:empty { display: none; } - .compare-type-tabs .compare-mode-tab > svg, - .compare-type-current > svg { - color: var(--accent, var(--red)); - } - - .compare-mode-tabs { - display: flex; - gap: 4px; - flex-wrap: wrap; - min-width: 0; - } - /* Type tabs match Mode toggles 1:1 (same flex column layout, same metrics) */ - .compare-mode-tab { - display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 56px; height: 38px; flex: 1 1 0; - padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; - cursor: pointer; user-select: none; transition: all 0.15s; - background: none; color: var(--fg); opacity: 0.35; - flex-shrink: 0; gap: 0; - font-family: inherit; - } - .compare-mode-tab > svg { - width: 13px; height: 13px; flex: 0 0 13px; - } - .compare-mode-tab:hover { - opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); - } - .compare-mode-tab.active { - opacity: 1; border-color: var(--fg); - background: color-mix(in srgb, var(--fg) 10%, transparent); - } - .compare-sources-box { - display: flex; align-items: center; gap: 6px; - padding: 6px 10px; margin-bottom: 8px; - border-radius: 6px; font-size: 0.78em; - background: color-mix(in srgb, var(--red) 8%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - cursor: default; - } - .compare-sources-box .sources-label { font-weight: 600; } - /* Compact tool blocks inside compare panes */ - .compare-pane .agent-thread-node { - margin: 4px 0; font-size: 0.85em; - } - .compare-pane .agent-thread-cmd { - max-height: 80px; overflow-y: auto; - } - .compare-pane .agent-tool-output pre { - max-height: 120px; overflow-y: auto; - } - .compare-vote-bar { - display: flex; justify-content: center; gap: 8px; - padding: 8px; border-top: 1px solid var(--border); - flex-shrink: 0; flex-wrap: wrap; - } - .compare-vote-bar.hidden { display: none; } - .compare-vote-btn { - padding: 6px 13px; border: 1px solid var(--border); border-radius: 6px; - background: var(--panel); color: var(--fg); cursor: pointer; font-size: 0.8em; - transition: all 0.15s; white-space: nowrap; - } - .compare-score-label { - position: relative; - top: -2px; - display: inline-block; - } - .compare-vote-btn:hover { border-color: var(--red); background: color-mix(in srgb, var(--red) 11%, transparent); } - .compare-vote-tie { opacity: 0.7; } - .compare-rematch-btn { - display: flex; align-items: center; gap: 6px; margin-left: 8px; - border-color: color-mix(in srgb, var(--accent, var(--red)) 60%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - color: var(--fg); opacity: 1; font-weight: 600; - } - .compare-rematch-btn:hover { border-color: var(--accent, var(--red)); background: color-mix(in srgb, var(--accent, var(--red)) 24%, transparent); } - /* Preview button accent in pane header */ - .pane-preview-btn.active { color: var(--red); } - /* Full-pane iframe for HTML preview */ - .compare-pane-iframe { - flex: 1; - width: 100%; - border: none; - border-radius: 0 0 8px 8px; - background: #fff; - } - /* ---- Add-pane "+" button in pane header (last pane only) ---- */ - .pane-add-btn { display: none !important; font-size: 16px; font-weight: 600; } - .compare-pane:last-child .pane-add-btn { display: flex !important; } - /* Dropdown for adding a pane */ - .add-pane-dropdown { - position: absolute; - right: 0; - z-index: 100; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 10px; - max-height: 300px; - overflow-y: auto; - min-width: 220px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3), 0 0 0 1px color-mix(in srgb, var(--fg) 5%, transparent); - padding: 6px; - backdrop-filter: blur(12px); - animation: dropdown-in 0.15s ease-out; - } - .add-pane-search { - width: 100%; - padding: 6px 8px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font-size: 0.85em; - box-sizing: border-box; - margin-bottom: 4px; - position: sticky; - top: 0; - z-index: 1; - } - /* Compare toggle buttons — icon + label stacked */ - .compare-toggle-label { - display: block; - font-size: 9px; - line-height: 1; - margin-top: 2px; - font-weight: 500; - } - .compare-blind-toggle, - .compare-save-toggle, - .compare-dice-toggle, - .compare-parallel-toggle, - .compare-reset-toggle { - display: flex; flex-direction: column; align-items: center; justify-content: center; - width: 56px; height: auto; flex: 1 1 0; - padding: 5px 4px 4px; border: 1px solid var(--border); border-radius: 6px; - cursor: pointer; user-select: none; transition: all 0.15s; - background: none; color: var(--fg); opacity: 0.35; - flex-shrink: 0; gap: 0; - } - .compare-blind-toggle:hover, - .compare-save-toggle:hover, - .compare-dice-toggle:hover, - .compare-parallel-toggle:hover, - .compare-reset-toggle:hover { - opacity: 0.7; background: color-mix(in srgb, var(--fg) 6%, transparent); - } - .compare-blind-toggle.active { - opacity: 1; color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - } - .compare-blind-toggle.active > svg { - color: var(--accent, var(--red)); - } - .compare-save-toggle.active { - opacity: 1; color: var(--color-save-green); border-color: var(--color-save-green); - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .compare-dice-toggle.active { - opacity: 1; color: var(--red); border-color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - } - .compare-parallel-toggle { - opacity: 1; color: #e0a050; border-color: #e0a050; - background: rgba(224, 160, 80, 0.1); - } - .compare-parallel-toggle.active { - color: var(--accent, var(--red)); border-color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 10%, transparent); - } - @media (max-width: 520px) { - .compare-toggle-label { display: none; } - /* Tab text labels are hidden on mobile, so spell out the active type next - to "Type:" with its icon. */ - .compare-type-current { - display: inline-flex; - align-items: center; - gap: 4px; - margin-left: 5px; - font-weight: 600; - vertical-align: -3px; - } - .compare-type-current svg { width: 14px; height: 14px; } - /* Mode list — comma-separated, each name already coloured inline. */ - .compare-mode-current { - display: inline; - margin-left: 5px; - font-weight: 600; - } - .compare-blind-toggle, - .compare-save-toggle, - .compare-dice-toggle, - .compare-parallel-toggle, - .compare-reset-toggle { - width: 32px; height: 32px; min-width: 32px; padding: 0; - } - /* The Group tab's seq/parallel toggle is a single full-width button (not - a row of compact icons like the Compare header), so keep its label and - make it a comfortable touch target with the text beside the icon. */ - #group-mode-btn { - flex-direction: row !important; - width: auto !important; - height: auto !important; - min-height: 44px; - padding: 8px 14px !important; - gap: 8px !important; - font-size: 13px; - } - #group-mode-btn .compare-toggle-label { - display: inline !important; - font-size: 13px; - margin-top: 0; - } - #group-mode-btn svg { width: 18px; height: 18px; } - /* Compare header: hide labels + close button, show icons only */ - #compare-shuffle-btn span { - display: none; - } - #compare-shuffle-btn, - #compare-check-btn, - #compare-add-btn { - padding: 3px 6px; - } - /* Save space so the header buttons fit on one row: tighter padding + - smaller labels (icons kept full size). (Score now lives in the vote bar.) */ - .compare-header-bar button { padding: 3px 5px !important; } - .compare-header-bar #compare-check-btn > span, - .compare-header-bar #compare-add-btn > span { - font-size: 10px; margin-left: 2px; - } - /* Compare mobile: keep the close X visible — it's the only way out - now that the hamburger is hidden during compare mode. */ - .compare-header-bar { - padding: 14px 8px 10px 8px !important; - min-height: 44px; - } - .compare-pane .pane-header { - align-items: stretch; - row-gap: 3px; - } - .compare-pane .pane-title-btn { - flex: 1 1 auto; - width: auto; - padding-right: 2px; - } - /* Compare is already visibly in chat mode; the Chat speech-bubble - badge adds a redundant second row on narrow screens. */ - .compare-pane .pane-mode-chat { display: none; } - .compare-pane .pane-timer, - .compare-pane .pane-summary, - .compare-pane .pane-finish-badge, - .compare-pane .pane-mode-badge, - .compare-pane .pane-actions { - order: 1; - align-self: center; - } - .compare-pane .pane-finish-badge { top: 0; } - .compare-pane .pane-summary { - max-width: 110px; - } - .compare-pane .pane-mode-detail { - display: none; - } - .compare-grid[data-cols="4"] .compare-pane .pane-timer, - .compare-grid[data-cols="5"] .compare-pane .pane-timer, - .compare-grid[data-cols="6"] .compare-pane .pane-timer { - width: auto; - order: 1; - margin-top: 0; - padding-bottom: 0; - padding-left: 0; - } - .compare-pane .pane-actions { - margin-left: auto; - } - /* Mode tabs: icons only, centered */ - .compare-mode-tab span { display: none; } - .compare-mode-tabs { justify-content: center; } - /* Header action buttons: hide text labels on Export / Shuffle / - Model on mobile so the close X fits on the right. Score keeps - its "Score" label because its icon (4-square grid) reads too - similar to Shuffle's icon without text. */ - .compare-header-bar #compare-export-btn > span, - .compare-header-bar #compare-shuffle-btn > span { - display: none; - } - /* Override the desktop override: on mobile the model picker overlay - MUST cap its height to the viewport so the dropdown doesn't run - past the bottom of the screen. */ - #compare-model-overlay .modal-content { - max-height: 85dvh !important; - max-height: 85vh !important; - overflow: hidden !important; - } - #compare-model-overlay .modal-body { - overflow: auto !important; - flex: 1 1 auto !important; - min-height: 0 !important; - } - } - /* Hide number input spinners */ - input[type="number"]::-webkit-inner-spin-button, - input[type="number"]::-webkit-outer-spin-button { - -webkit-appearance: none; margin: 0; - } - - /* ── Sensitive info censor ── */ - .censored-item { - filter: blur(5px); - cursor: pointer; - transition: filter 0.2s; - border-radius: 2px; - padding: 0 2px; - background: rgba(255,100,100,0.08); - user-select: none; - } - .censored-item:hover { filter: blur(3px); background: rgba(255,100,100,0.15); } - .censored-item.revealed { - filter: none; - background: rgba(100,255,100,0.08); - user-select: auto; - cursor: text; - } - - #settings-menu-list .list-item.active { - background: color-mix(in srgb, var(--accent) 10%, transparent); - border-color: var(--accent); - } - - /* ── Print / PDF Export ── */ - @media print { - body { background: #fff !important; color: #000 !important; } - #sidebar, .sidebar, #icon-rail, .hamburger-btn, #sidebar-backdrop, .chat-input-bar, .input-bar-wrapper, - #welcome-screen, .chat-top-bar, .chat-meta-overlay, .msg-footer, - .modal, .toast, .overflow-wrapper, .mode-toggle, .incognito-btn, - button, .dropdown, .session-dropdown, - .agent-tool-spinner, .agent-thread-node.running { display: none !important; } - main.chat-container { width: 100% !important; margin: 0 !important; padding: 0 !important; max-height: none !important; overflow: visible !important; } - #chat-history { max-height: none !important; overflow: visible !important; height: auto !important; padding: 0 !important; } - .msg { break-inside: avoid; page-break-inside: avoid; border: none !important; box-shadow: none !important; } - .msg-ai { background: #f5f5f5 !important; color: #000 !important; } - .msg-user { background: #e8e8e8 !important; color: #000 !important; } - .msg .role { color: #333 !important; font-weight: bold; } - .msg .body { color: #000 !important; } - pre, code { background: #f0f0f0 !important; color: #000 !important; border: 1px solid #ccc !important; } - details { display: block !important; } - details[open] summary ~ * { display: block !important; } - details > summary { list-style: none; } - details > summary::before { content: "" !important; } - #chat-history::before { content: attr(data-print-title); display: block; font-size: 1.3em; font-weight: bold; margin-bottom: 1em; color: #000; } - a { color: #000 !important; text-decoration: underline; } - } - - -/* ── Components (from style.css) ── */ - -/* Self-hosted Fira Code font */ -@font-face { font-family: 'Fira Code'; font-weight: 300; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Light.woff2') format('woff2'); } -@font-face { font-family: 'Fira Code'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-Regular.woff2') format('woff2'); } -@font-face { font-family: 'Fira Code'; font-weight: 600; font-style: normal; font-display: swap; src: url('/static/fonts/FiraCode-SemiBold.woff2') format('woff2'); } - -/* Scrollbar styling */ - -/* Code block styling */ -pre, code, .hljs { - font-size: 11px; - line-height: 1.5; -} - -/* WebKit (Chrome, Edge, Safari) */ -/* Utility class for red text */ -.red-text { - color: var(--red); -} - -/* Internal chat links (search results, session references) */ -a.chat-link { - color: var(--hl-function); - text-decoration: none; - border-bottom: 1px dotted var(--hl-function); - cursor: pointer; -} -a.chat-link:hover { - opacity: 0.8; - border-bottom-style: solid; -} - -a.chat-link[href^="#email-"] { - display: inline; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 34%, transparent); - border-radius: 5px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - color: var(--fg); - font-weight: 600; - border-bottom-style: solid; -} -a.chat-link[href^="#email-"]:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -a.chat-link[href^="#notes-more-"], -a.chat-link[href^="#skills-more-"], -a.chat-link[href^="#memories-more-"], -a.chat-link[href^="#events-more-"], -a.chat-link[href^="#sessions-more-"] { - color: var(--muted); - border-bottom: 1px dotted color-mix(in srgb, var(--muted) 55%, transparent); - font-size: 11px; - font-weight: 500; -} -a.chat-link[href^="#notes-more-"]:hover, -a.chat-link[href^="#skills-more-"]:hover, -a.chat-link[href^="#memories-more-"]:hover, -a.chat-link[href^="#events-more-"]:hover, -a.chat-link[href^="#sessions-more-"]:hover { - color: var(--fg); - opacity: 1; - border-bottom-style: solid; -} -.more-list-expanded { - display: block; - max-height: 0; - overflow: hidden; - opacity: 0; - color: inherit; - font: inherit; - line-height: inherit; - transition: max-height 220ms ease, opacity 160ms ease; -} -.more-list-expanded.is-open { - opacity: 1; -} -.more-list-expanded-content { - color: inherit; - font: inherit; - line-height: inherit; -} - -/* Session items */ -.session-item { position: relative; } -.text-ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.session-menu-btn { padding: 0 2px !important; min-width: 20px; height: 20px; display: inline-flex !important; align-items: center; justify-content: center; background: none !important; border-color: transparent !important; } -.session-menu-btn:hover { background: none !important; border-color: transparent !important; } -@media (max-width: 768px) { - .session-menu-btn { display: none !important; } - .item-drag-handle { display: none !important; } -} -.session-menu-btn svg { transition: transform 0.2s ease; } - -/* First-time swipe hint */ -.swipe-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - font-size: 0.7rem; - color: var(--color-error, #f44); - opacity: 0.8; - transition: opacity 0.5s ease; - pointer-events: none; - display: flex; - align-items: center; - gap: 4px; - z-index: 2; -} -.swipe-hint-arrow { - animation: swipe-nudge 1s ease-in-out infinite; -} -@keyframes swipe-nudge { - 0%, 100% { transform: translateX(0); } - 50% { transform: translateX(-6px); } -} - -/* Utility classes */ -.muted { opacity: 0.5; } -.muted-sm { opacity: 0.35; font-size: 0.8em; } -.accent-link { color: var(--accent-primary, var(--color-accent)); cursor: pointer; font-size: 0.85em; } -.models-empty-state { text-align: center; padding: 12px 8px; line-height: 1.6; } - -/* Provider logo inside favorite dot */ -.provider-logo { - border: none !important; - background: none !important; - width: 14px !important; height: 14px !important; - display: inline-flex; align-items: center; justify-content: center; - transition: opacity 0.15s; -} -.provider-logo svg { width: 14px; height: 14px; display: block; } -.provider-logo:hover { opacity: 1 !important; transform: scale(1.2); } - -/* Hide session menu button until hover — use width:0 so it doesn't steal space from text */ -.list-item .hamburger { opacity: 0; width: 0; min-width: 0; overflow: hidden; padding: 0 !important; transition: opacity 0.15s, width 0.15s, padding 0.15s; flex-shrink: 0; display: flex; align-items: center; justify-content: center; } -.list-item:hover .hamburger { opacity: 1; width: 24px; min-width: 24px; padding: 0 4px !important; } -@media (max-width: 768px) { - .list-item .hamburger { opacity: 0.5; width: 28px; min-width: 28px; padding: 0 4px !important; } - .list-item .hamburger:active { opacity: 1; } -} - -/* Hamburger menu button styling (overrides default button appearance) */ -button.hamburger { - background: none; - border: none; - padding: 0; - cursor: pointer; -} - -/* ============================================ */ -/* HEADER SIZING FOR CHAT MESSAGES */ -/* ============================================ */ -/* Markdown in chat messages — colorful, scannable */ -.msg h1, .msg h2, .msg h3, .msg h4, .msg h5, .msg h6 { - margin: 0.6em 0 0.3em 0; - line-height: 1.3; - border-bottom: none; - padding-bottom: 0; -} - -.msg h1 { - font-size: 1.15em; - font-weight: 700; - color: var(--hl-keyword, #c678dd); -} - -.msg h2 { - font-size: 1.1em; - font-weight: 600; - color: var(--hl-function, #5b8def); -} - -.msg h3 { - font-size: 1.05em; - font-weight: 600; - color: var(--hl-string, #98c379); -} - -.msg h4 { - font-size: 1.02em; - font-weight: 600; - color: var(--hl-builtin, #e5c07b); -} - -.msg h5 { - font-size: 1em; - font-weight: 600; - color: var(--hl-variable, #61afef); -} - -.msg h6 { - font-size: 0.95em; - font-weight: 600; - color: var(--hl-number, #d19a66); -} - -/* Bold text — subtle accent color */ -.msg strong, .msg b { - color: var(--hl-builtin, #e5c07b); - font-weight: 600; -} - -/* Italic — softer highlight */ -.msg em, .msg i { - color: var(--hl-params, #abb2bf); - font-style: italic; -} - -/* Bold + italic */ -.msg strong em, .msg em strong, -.msg b i, .msg i b, -.msg b em, .msg em b, -.msg strong i, .msg i strong { - color: var(--hl-keyword, #c678dd); -} - -/* Strikethrough */ -.msg del { - color: color-mix(in srgb, var(--fg) 45%, transparent); - text-decoration: line-through; -} - -/* Inline code */ -.msg code:not(pre code) { - color: var(--hl-string, #98c379); - background: color-mix(in srgb, var(--fg) 6%, transparent); - padding: 1px 5px; - border-radius: 4px; - font-size: 0.9em; -} - -/* Blockquotes */ -.msg blockquote { - border-left: 3px solid var(--hl-function, #5b8def); - padding: 4px 12px; - margin: 0.5em 0; - color: color-mix(in srgb, var(--fg) 75%, transparent); - background: var(--bg); - border-radius: 0 6px 6px 0; -} -.msg blockquote p { margin: 0.3em 0; } - -/* Horizontal rules */ -.msg hr { - border: none; - height: 1px; - background: linear-gradient(90deg, transparent, var(--border), transparent); - margin: 0.8em 0; -} - -/* Lists */ -.msg ul, .msg ol { - margin: 0.3em 0 0.3em 1.2em; - padding: 0; -} -.msg li { - margin: 0.15em 0; -} -.msg li::marker { - color: var(--hl-function, #5b8def); -} - -/* Links */ -.msg a { - color: var(--hl-function, #5b8def); - text-decoration: none; - border-bottom: 1px solid rgba(91, 141, 239, 0.3); - transition: border-color 0.15s; -} -.msg a:hover { - border-bottom-color: var(--hl-function, #5b8def); -} -.msg a.is-loading { - opacity: 0.65; - border-bottom-color: var(--accent, #bd93f9); - cursor: progress; -} - -/* Tables */ -.msg table { - border-collapse: collapse; - margin: 0.5em 0; - font-size: 0.9em; - display: block; - width: max-content; - min-width: 100%; - max-width: 100%; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - table-layout: auto; -} -.msg th { - background: color-mix(in srgb, var(--fg) 7%, transparent); - color: var(--hl-keyword, #c678dd); - font-weight: 600; - padding: 6px 12px; - border: 1px solid var(--border); - text-align: left; - min-width: 9ch; - word-break: normal; - overflow-wrap: break-word; -} -.msg td { - padding: 5px 12px; - border: 1px solid var(--border); - min-width: 9ch; - word-break: normal; - overflow-wrap: break-word; -} - -/* Agent UI Styling */ -.agent-controls { - background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); - border: 1px solid #dee2e6; - border-radius: 8px; - padding: 12px; - margin-bottom: 12px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.agent-toggle label { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-weight: 500; - margin-bottom: 8px; -} - -#workflow-selector { - margin-top: 8px; -} - -#workflow-type { - width: 100%; - padding: 6px 12px; - border: 1px solid #ced4da; - border-radius: 4px; - background: white; - font-size: 14px; -} - -.agent-progress { - background: #ffebee; - border: 1px solid #ef9a9a; - border-radius: 6px; - padding: 12px; - margin: 8px 0; - text-align: center; -} - -.agent-working { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - font-style: italic; - color: var(--red); -} - -.loading-dots::after { - content: '...'; - animation: dots 1.5s infinite; -} - -@keyframes dots { - 0%, 20% { opacity: 0; } - 40% { opacity: 0.5; } - 60%, 100% { opacity: 1; } -} - -.workflow-info { - background: #f8f9fa; - border: 1px solid var(--red); - border-radius: 6px; - padding: 8px 12px; - margin: 4px 0; - font-size: 0.9em; - color: var(--red); - text-align: center; -} - -/* Scrollbar uses --red from :root (set at top of file) */ - -/* Loading spinner */ -@keyframes spin { - to { transform: rotate(360deg); } -} -.spinner { - width: 24px; - height: 24px; - margin: 8px auto; - border: 3px solid var(--border); - border-top-color: var(--red); - border-radius: 50%; - animation: spin 0.9s linear infinite; -} - -/* Inline spinner for buttons */ -.btn-spinner { - display: inline-block; - width: 12px; - height: 12px; - border: 2px solid transparent; - border-top-color: currentColor; - border-radius: 50%; - animation: spin 1s linear infinite; - margin-right: 6px; -} - -/* Loading indicator for messages */ -.loading-indicator { - display: flex; - align-items: center; - justify-content: center; - padding: 10px; -} -.loading-dots { - display: flex; - gap: 4px; -} -.loading-dot { - width: 6px; - height: 6px; - background-color: var(--fg); - border-radius: 50%; - opacity: 0.6; -} -.loading-dot:nth-child(1) { - animation: loading-bounce 1.4s infinite ease-in-out both; -} -.loading-dot:nth-child(2) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.32s; -} -.loading-dot:nth-child(3) { - animation: loading-bounce 1.4s infinite ease-in-out both; - animation-delay: -0.64s; -} -@keyframes loading-bounce { - 0%, 80%, 100% { - transform: scale(0); - } - 40% { - transform: scale(1); - } -} - -/* Hamburger menu button */ -.hamburger { - display: inline-flex; - flex-direction: column; - justify-content: space-between; - width: 24px; - height: 18px !important; - background: none; - border: none; - padding: 0; - cursor: pointer; -} -.hamburger span { - display: block; - width: 100%; - height: 3px; - background: var(--fg); - border-radius: 2px; -} - -/* Agent indicator */ -#agent-indicator { - position: fixed; - top: 20px; - right: 20px; - background: var(--sidebar-bg, var(--panel)); - color: var(--fg); - border: 1px solid var(--border); - padding: 6px 12px; - border-radius: 6px; - font-size: 12px; - display: none; - z-index: 100; - cursor: pointer; - transition: all 0.2s ease; -} -#agent-indicator.active { - display: block; - border-color: var(--color-agent-active); - box-shadow: 0 0 10px rgba(0, 255, 0, 0.3); -} -#agent-indicator:hover { - border-color: var(--color-agent-active); - background: var(--panel); -} - -/* Preset buttons */ -.preset-btn { - height: 27.2px; - padding: 0 8.5px; - margin-left: 4px; - border: 1px solid var(--red); - border-radius: 4px; - background: var(--sidebar-bg, var(--panel)); - color: var(--fg); - font-family: inherit; - font-size: 10.2px; - cursor: pointer; - transition: all 0.2s ease; -} - -.preset-btn:hover { - background: var(--panel); - border-color: var(--fg); -} - -.preset-btn.active { - background: var(--panel); - border-color: var(--red); - box-shadow: 0 0 0 1px var(--red), 0 0 8px color-mix(in srgb, var(--red) 30%, transparent); - font-weight: 600; -} - -/* Custom preset modal — inherits from .modal base class */ - -/* Unified chat input area */ -.chat-input-area { - display: flex; - flex-direction: column; - gap: 8px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin-top: 12px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.chat-controls-row { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; -} - -.chat-controls-left { - display: flex; - align-items: center; - gap: 8px; -} - -.chat-controls-right { - display: flex; - align-items: center; - gap: 8px; -} - -.control-group { - display: flex; - align-items: center; - gap: 4px; -} - -.control-label { - font-size: 11px; - color: var(--fg); - opacity: 0.8; -} - -.toggle-switch { - position: relative; - display: inline-block; - width: 30px; - height: 16px; -} - -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; -} - -.toggle-slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: color-mix(in srgb, var(--fg) 15%, transparent); - border-radius: 8px; - transition: background 0.08s; -} - -.toggle-slider:before { - position: absolute; - content: ""; - height: 12px; - width: 12px; - left: 4px; - top: 2px; - background-color: var(--panel); - border-radius: 50%; - transition: transform 0.08s; - box-shadow: 0 1px 2px rgba(0,0,0,0.25); -} - -.toggle-switch input:checked + .toggle-slider { - background-color: var(--toggle-active, var(--red)); -} - -.toggle-switch input:checked + .toggle-slider:before { - transform: translateX(14px); -} - -.preset-buttons-row { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: wrap; -} - -.chat-input-form { - display: flex; - gap: 8px; - align-items: flex-end; -} - -#message { - flex: 1; - min-height: 34px; - max-height: 120px; - resize: none; - font-size: 13px !important; - overflow-y: auto !important; - line-height: 1.4 !important; - font-family: inherit !important; -} - -.action-button { - width: 34px; - height: 34px; - display: flex; - align-items: center; - justify-content: center; - padding: 0; - margin: 0; - background: none; - border: 1px solid var(--border); - border-radius: 4px; - cursor: pointer; - color: var(--fg); - transition: all 0.2s ease; -} - -.action-button:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: var(--fg); -} - -.action-button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.action-button.recording { - background: var(--color-recording); - border-color: var(--color-recording); - animation: pulse 1.5s infinite; -} - -@keyframes pulse { - 0% { opacity: 1; } - 50% { opacity: 0.7; } - 100% { opacity: 1; } -} - -#stop-icon { - display: none; - width: 14px; - height: 14px; - background: var(--color-recording); - border-radius: 2px; -} - -/* Attachment strip — centered + max-width to match the chat-input-bar below, - otherwise the chip floats flush-left while the input is centered (visible on - desktop where the chat area is wider than 800px). */ -.attach-strip { - display: flex; - gap: 6px; - flex-wrap: wrap; - padding: 2px 8px; - max-width: 800px; - width: 100%; - margin-left: auto; - margin-right: auto; - box-sizing: border-box; -} -.attach-strip:empty { display: none; } - -/* Upload-in-progress feedback: the message bubble shows immediately, so while - the files are still uploading we put a whirlpool ON each attachment chip and - dim the chip's content — making it obvious that file is being sent, not stuck. */ -.attach-strip.attach-uploading .thumb { - position: relative; - pointer-events: none; -} -.attach-strip.attach-uploading .thumb > :not(.thumb-upload-spinner) { - opacity: 0.4; -} -.thumb-upload-spinner { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; - z-index: 3; -} - -.thumb { - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 11%, transparent); - padding: 3px 6px; - font-size: 12px; - display: flex; - gap: 6px; - align-items: center; - border-radius: 4px; - transition: all 0.2s ease; - max-width: 180px; -} -.thumb-img { - max-width: 60px; - max-height: 40px; - border-radius: 3px; - object-fit: cover; -} -.attach-image-preview { - margin: 4px 0; -} -.attach-image-preview img { - box-shadow: 0 1px 4px rgba(0,0,0,0.2); - /* Same border as the chat bubbles. */ - border: 1px solid var(--bubble-border, var(--border)); -} -/* Image chips: image fills the chip, X overlays as a corner accent badge. - Same on desktop and mobile — doc/text chips keep the beside-X layout. */ -.thumb.thumb-image { - position: relative; - padding: 0; -} -.thumb.thumb-image .thumb-img { - max-height: 56px; - display: block; -} -@media (max-width: 768px) { - .thumb.thumb-image .thumb-img { - cursor: pointer; - touch-action: manipulation; - } -} -.thumb.thumb-image button { - position: absolute; - /* Sit on the top-right corner edge as an accent badge. */ - top: -7px; - right: -7px; - width: 24px; - height: 24px; - min-width: 0; - padding: 0; - border: 2px solid var(--bg); - border-radius: 50%; - background: var(--accent-primary, var(--red)); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 15px; - line-height: 1; - z-index: 3; - transition: transform 0.12s ease, filter 0.12s ease, box-shadow 0.12s ease; -} -.thumb.thumb-image button:hover { - transform: scale(1.12); - filter: brightness(1.12); - box-shadow: 0 2px 8px rgba(0,0,0,0.25); -} -.thumb.thumb-image button:active { - transform: scale(0.96); -} - -.attach-crop-overlay { - position: fixed; - inset: 0; - z-index: var(--attach-crop-z, 100001); - background: rgba(0, 0, 0, 0.72); - display: flex; - align-items: center; - justify-content: center; - padding: 14px; - box-sizing: border-box; -} -.attach-crop-panel { - width: min(94vw, 520px); - max-height: min(86vh, 720px); - background: var(--panel, #111); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 18px 50px rgba(0,0,0,0.45); - overflow: hidden; - display: flex; - flex-direction: column; -} -.attach-crop-stage { - position: relative; - min-height: 280px; - height: min(66vh, 560px); - background: #050505; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - touch-action: none; -} -.attach-crop-img { - max-width: 100%; - max-height: 100%; - object-fit: contain; - user-select: none; - pointer-events: none; -} -.attach-crop-box { - position: absolute; - border: 2px solid var(--accent, var(--red)); - box-shadow: 0 0 0 9999px rgba(0,0,0,0.42); - cursor: move; - touch-action: none; - box-sizing: border-box; -} -.attach-crop-box::before, -.attach-crop-box::after { - content: ''; - position: absolute; - inset: 33.333% 0 auto; - border-top: 1px solid rgba(255,255,255,0.38); -} -.attach-crop-box::after { - inset: 66.666% 0 auto; -} -.attach-crop-handle { - position: absolute; - right: -9px; - bottom: -9px; - width: 20px; - height: 20px; - border-radius: 50%; - background: var(--accent, var(--red)); - border: 2px solid var(--bg); - box-sizing: border-box; - cursor: nwse-resize; -} -.attach-crop-actions { - display: flex; - gap: 8px; - padding: 10px; - background: color-mix(in srgb, var(--bg) 78%, transparent); -} -.attach-crop-btn { - flex: 1; - height: 34px; - border: 1px solid var(--border); - border-radius: 5px; - background: var(--bg); - color: var(--fg); - font: inherit; - font-size: 12px; -} -.attach-crop-primary { - background: var(--accent, var(--red)); - color: #fff; - border-color: var(--accent, var(--red)); -} -@media (max-width: 768px) { - /* Collapsed "N files" badge: use the same corner-X accent badge as image thumbs. */ - .thumb-collapsed { position: relative; } - .thumb-collapsed .thumb-collapsed-x { - position: absolute; - top: -7px; - right: -7px; - width: 24px; - height: 24px; - min-width: 0; - padding: 0; - border: 2px solid var(--bg); - border-radius: 50%; - background: var(--accent-primary, var(--red)); - color: #fff; - display: flex; - align-items: center; - justify-content: center; - font-size: 15px; - line-height: 1; - z-index: 3; - opacity: 1; - } - /* Bigger remove-X tap target for non-image (doc/text) chips on mobile too. */ - .thumb button { - height: 28px; - min-width: 28px; - font-size: 15px; - } -} -.thumb span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.thumb:hover { - background: color-mix(in srgb, var(--fg) 16%, transparent); - border-color: var(--fg); -} - -.thumb button { - height: 24px; - padding: 0 5px; - font-size: 0.95em; - border-radius: 4px; - color: var(--accent-primary, var(--red)); -} - -.thumb-collapsed { - cursor: pointer; - color: var(--red); - border-color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - font-weight: 600; - gap: 8px; - border-radius: 999px; /* pill — rounder than the square file chips */ - padding-left: 12px; -} -.thumb-collapsed:hover { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.thumb-collapsed-label { white-space: nowrap; } -.thumb-collapsed-x { - height: 24px; padding: 0 7px; font-size: 13px; border-radius: 4px; - color: var(--accent-primary, var(--red)); - background: none; border: none; cursor: pointer; opacity: 0.6; -} -.thumb-collapsed-x:hover { opacity: 1; } - -/* Recording indicator */ -#recording-indicator { - position: fixed; - top: 10px; - left: 0; - right: 0; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - margin: 10px; - z-index: 1000; - display: flex; - align-items: center; - justify-content: space-between; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); -} - -#recording-indicator.hidden { - display: none !important; -} - -.recording-content { - display: flex; - align-items: center; - gap: 12px; - color: white; -} - -.recording-icon { - color: var(--color-recording); - font-size: 20px; - animation: pulse 1.5s infinite; -} - -.recording-text { - font-size: 16px; - font-weight: 500; -} - -.stop-recording-btn { - background: var(--color-recording); - color: white; - border: none; - border-radius: 6px; - padding: 6px 12px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: background 0.2s ease; -} - -.stop-recording-btn:hover { - background: var(--color-recording-hover); -} - -#recording-indicator.error { - background: rgba(173, 26, 26, 0.9); -} - -.recording-error { - color: var(--color-recording); - font-size: 14px; - margin-top: 4px; -} - -/* Mermaid diagram containers */ -.mermaid-container { - margin: 12px 0; - padding: 16px; - background: color-mix(in srgb, var(--bg) 95%, var(--fg)); - border: 1px solid var(--border); - border-radius: 8px; - overflow-x: auto; - text-align: center; -} -.mermaid-container svg { max-width: 100%; height: auto; } - -/* KaTeX math overrides */ -.katex-display { margin: 0.8em 0; overflow-x: auto; overflow-y: hidden; } -.katex { font-size: 1.1em; } - -/* Hide thinking sections globally via settings toggle */ -body.hide-thinking .thinking-section { display: none !important; } - -/* Widen chat area via settings toggle */ -body.fullwidth-chat .chat-history { - padding-left: 0 !important; - padding-right: 12px !important; -} - -/* Thinking process styles — colors follow theme accent */ -.msg .body .stream-content { - width: 100%; -} -.thinking-section { - margin: 12px 0; - width: 100%; - max-width: 100%; - box-sizing: border-box; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - overflow: hidden; - transition: all 0.3s ease; -} - -.thinking-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 6px 12px; - cursor: pointer; - user-select: none; - background: color-mix(in srgb, var(--red) 8%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - transition: background 0.2s ease; -} - -.thinking-header > .token-new { - display: none; -} - -.thinking-header > div:last-child { - flex-shrink: 0; -} - -.thinking-header:hover { - background: color-mix(in srgb, var(--red) 12%, transparent); -} - -.thinking-header-left { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9em; - color: var(--red); - font-weight: 500; - overflow: hidden; - min-width: 0; -} -.thinking-header-left span { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - transition: opacity 0.2s ease; -} - -.thinking-icon { - font-size: 1.1em; -} - -.thinking-toggle { - font-size: 0.9em; - color: var(--red); - transition: transform 0.3s ease; -} -.thinking-toggle::after { - content: '\25BC'; /* ▼ */ -} - -.thinking-toggle.expanded { - transform: rotate(180deg); -} - -.thinking-content { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; - padding: 0 12px; -} - -.thinking-content.expanded { - max-height: 300px; - overflow-y: auto; - padding: 12px; -} - -.thinking-content-inner { - font-size: 0.85em; - color: var(--fg); - opacity: 0.9; - line-height: 1.5; -} -.live-reply-content { - animation: fadeSlideIn 0.3s ease-out; -} -@keyframes fadeSlideIn { - from { opacity: 0; transform: translateY(4px); } - to { opacity: 1; transform: translateY(0); } -} - -/* Thinking indicator animation */ -.thinking-indicator { - display: flex; - align-items: center; - gap: 4px; - color: var(--red); - font-style: italic; - padding: 8px 0; -} - -.thinking-dots::after { - content: '...'; - animation: thinking-dots 1.5s infinite; - display: inline-block; - width: 20px; - text-align: left; -} - -@keyframes thinking-dots { - 0%, 20% { content: '.'; } - 40% { content: '..'; } - 60%, 100% { content: '...'; } -} - -.thinking-complete { - color: var(--red); - font-size: 0.9em; - padding: 4px 0; - opacity: 0.8; -} - -.research-context-continue-hint { - display: inline-flex; - align-items: center; - gap: 4px; - margin: 5px 2px 0; - color: var(--fg-muted, var(--fg)); - font-size: 10px; - opacity: 0.55; -} -.research-context-continue-hint svg { - flex: 0 0 auto; - opacity: 0.8; -} - -/* ── Sources section — collapsible source citations ── */ -.sources-section { - margin: 8px 0 12px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 8px; - overflow: hidden; - transition: all 0.3s ease; -} -.sources-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 12px; - cursor: pointer; - background: color-mix(in srgb, var(--red) 8%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - transition: background 0.2s ease; - user-select: none; -} -.sources-header:hover { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.sources-header-left { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.85em; - font-weight: 500; - color: var(--red); -} -.sources-header-left svg { - width: 14px; - height: 14px; - flex-shrink: 0; - opacity: 0.7; -} -.sources-toggle { - font-size: 0.8em; - color: var(--red); - opacity: 0.7; - transition: none; -} -.sources-toggle::after { - content: '\25B6'; /* ▶ right arrow */ -} -.sources-toggle[data-arrow="down"]::after { - content: '\25BC'; /* ▼ down arrow */ -} -.sources-content { - max-height: 0; - overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; - padding: 0 10px; -} -.sources-content.expanded { - max-height: 3000px; - padding: 8px 10px; -} -.sources-content-inner { - display: flex; - flex-direction: column; - gap: 4px; -} -.source-link { - display: flex; - align-items: center; - gap: 8px; - padding: 5px 8px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - text-decoration: none; - color: var(--fg); - transition: background 0.15s ease; -} -.source-link:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.source-num { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 20px; - height: 20px; - border-radius: 50%; - background: color-mix(in srgb, var(--fg) 15%, transparent); - color: var(--fg); - font-size: 0.7em; - font-weight: 600; - flex-shrink: 0; -} -.source-title { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 0.82em; -} -.source-domain { - font-size: 0.72em; - opacity: 0.45; - flex-shrink: 0; -} - -.ai-spinner { - color: var(--red); -} -/* Nudge the Tidy button 2px left. */ -#memory-tidy-btn { position: relative; left: -2px; } -/* Tidy button's whirlpool nudge — sits 1px lower so it visually centers on - the Tidy label baseline. */ -#memory-tidy-btn .ai-spinner-whirlpool, -#memory-tidy-btn .spinner-whirlpool { - position: relative; - top: 1px; -} -.list-item.stream-complete { - animation: none; -} -.session-run-state { - flex: 0 0 auto; - margin-left: 5px; - font-size: 10px; - line-height: 1; - color: var(--fg-dim, #888); - opacity: 0.9; -} -.session-run-state.is-working { - color: var(--accent, var(--red)); -} -.session-run-state .session-run-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 !important; - display: inline-flex; - align-items: center; -} -.session-run-state .session-run-whirlpool .ai-spinner-whirlpool { - display: inline-flex; - width: 12px; - height: 12px; -} -.session-run-state.is-done { - color: var(--color-success, #4caf50); - font-weight: 600; -} -.cookbook-notif-active svg { opacity: 1 !important; } - -/* Rail notification dot — pulsing indicator on icon-rail buttons */ -.icon-rail-btn.rail-notify { - opacity: 1 !important; - position: relative; -} -.icon-rail-btn.rail-notify::before { - content: ''; - position: absolute; - top: 5px; - right: 4px; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--accent, var(--red)); - animation: rail-notif-pulse 2s ease-in-out infinite; - z-index: 1; -} -.icon-rail-btn.rail-notify.rail-notify-success::before { - background: var(--color-success, #4caf50); -} -@keyframes rail-notif-pulse { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.4; transform: scale(0.8); } -} -@keyframes stream-complete-pulse { - 0%, 100% { box-shadow: none; } - 50% { box-shadow: inset 0 0 0 1.5px var(--accent); } -} - -/* ===== SLASH COMMAND RESPONSES ===== */ -.msg.msg-system { - padding: 6px 12px; margin: 4px auto 4px 8px; - max-width: 85%; background: none; border-left: 2px solid var(--border); -} -.msg.msg-system .body { padding: 0; } -.msg.msg-system pre { margin: 4px 0; white-space: pre-wrap; font-size: 0.85em; } -.dropdown-divider { - height: 1px; - margin: 3px 6px; - background: color-mix(in srgb, var(--border) 60%, transparent); -} -.msg.stream-done-toast { - cursor: pointer; - border-left-color: var(--accent, var(--border)); - border-radius: 6px; - background: color-mix(in srgb, var(--accent) 6%, transparent); - transition: background 0.15s; -} -.msg.stream-done-toast:hover { - background: color-mix(in srgb, var(--accent) 12%, transparent); -} -.msg.stream-done-toast .body { - display: flex; - align-items: center; - gap: 8px; -} -.stream-done-indicator { - font-family: monospace; - font-size: 1.1em; - line-height: 1; - color: var(--accent); - flex-shrink: 0; - animation: bar-pulse 1.2s ease-in-out infinite; -} -@keyframes bar-pulse { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } -} - -/* ===== AGENT MULTI-BUBBLE ===== */ -.msg.msg-tool { - display: none; /* legacy — hidden, replaced by agent-thread */ -} -.msg.msg-continuation { - margin-top: 2px; -} - -/* ===== AGENT THREAD TIMELINE ===== */ -.agent-thread { - position: relative; - margin: 2px 0 2px 28px; - padding: 4px 0 4px 22px; - max-width: calc(85% - 20px); - box-sizing: border-box; -} -.agent-thread::before { - content: ''; - position: absolute; - left: 5px; - top: 14px; - bottom: 14px; - width: 2px; - background: color-mix(in srgb, var(--red) 18%, transparent); - border-radius: 1px; -} -/* Extend line to connect to chat bubble above/below */ -.agent-thread.has-top::before { - top: -6.5px; -} -.agent-thread.has-bottom::before { - bottom: -5px; -} -.agent-thread.has-ask-user-bottom::before { - bottom: -12px; -} -/* Terminating dot at bottom when no bubble below and last node is expanded */ -.agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { - content: ''; - position: absolute; - /* Centre the 6px dot on the rail: the rail sits at left:5px + 2px wide, - so its centre is 6px from the thread's left edge and the dot's left - edge belongs at 3px — i.e. 3px - padding-left. Keep this in step with - .agent-thread-dot and with the mobile padding override below. */ - left: -19px; - bottom: 5px; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--red); - box-shadow: 0 0 4px 1px color-mix(in srgb, var(--red) 55%, transparent), - 0 0 0 3px color-mix(in srgb, var(--red) 18%, transparent); -} - -/* Synapse pulse — a bright dot traveling down the line */ -.agent-thread::after { - content: ''; - position: absolute; - left: 4px; - width: 4px; - height: 4px; - border-radius: 50%; - background: var(--red); - box-shadow: 0 0 3px 1px color-mix(in srgb, var(--red) 50%, transparent); - pointer-events: none; - top: 0%; - opacity: 0; -} -.agent-thread.streaming::after { - animation: synapse-capped-short 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-top::after { - animation: synapse-capped 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-bottom::after { - animation: synapse-travel-short 0.8s ease-in-out infinite; -} -.agent-thread.streaming.has-top.has-bottom::after { - animation: synapse-travel 0.8s ease-in-out infinite; -} -@keyframes synapse-travel { - 0% { top: 0%; opacity: 0; } - 5% { opacity: 0.5; } - 85% { opacity: 0.35; } - 100% { top: 100%; opacity: 0; } -} -@keyframes synapse-capped { - 0% { top: 0%; opacity: 0; } - 5% { opacity: 0.5; } - 70% { opacity: 0.35; top: calc(100% - 20px); } - 100% { opacity: 0; top: calc(100% - 20px); } -} -@keyframes synapse-travel-short { - 0% { top: 14px; opacity: 0; } - 5% { opacity: 0.5; } - 85% { opacity: 0.35; } - 100% { top: 100%; opacity: 0; } -} -@keyframes synapse-capped-short { - 0% { top: 14px; opacity: 0; } - 5% { opacity: 0.5; } - 70% { opacity: 0.35; top: calc(100% - 20px); } - 100% { opacity: 0; top: calc(100% - 20px); } -} -.agent-thread-node { - position: relative; - padding: 5px 0; -} -.agent-thread-node + .agent-thread-node { - margin-top: 2px; -} -/* Supervisor ladder cards — same chrome as tool cards but tinted so the - user can tell at a glance "this is the agent recovering" vs "this is - the agent doing work". Stop rung gets the red accent. */ -.agent-thread-node.supervisor-step .agent-thread-tool { - color: color-mix(in srgb, var(--accent, #c08a3e) 80%, var(--fg)); - font-style: italic; -} -.agent-thread-node.supervisor-step .agent-thread-dot { - background: color-mix(in srgb, var(--accent, #c08a3e) 60%, transparent); -} -.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-tool { - color: var(--red, #d65a5a); -} -.agent-thread-node.supervisor-step[data-rung="stop"] .agent-thread-dot { - background: color-mix(in srgb, var(--red, #d65a5a) 60%, transparent); -} -.agent-thread-dot { - position: absolute; - left: -20px; - top: 10px; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--red); - border: 2px solid var(--bg); - z-index: 1; -} -.agent-thread-node.running .agent-thread-dot { - background: var(--red); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 25%, transparent); - animation: thread-pulse 1.5s ease-in-out infinite; - top: 10px; -} -@keyframes thread-pulse { - 0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, var(--red) 20%, transparent); } - 50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--red) 10%, transparent); } -} -.agent-thread-node.error .agent-thread-dot { - background: var(--color-error); -} -.agent-thread-header { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 70%, transparent); - user-select: none; - padding: 2px 0; -} -.agent-thread-header:hover { - color: var(--fg); -} -.agent-thread-icon { - font-size: 0.9em; - color: var(--red); -} -.agent-thread-tool-icon { - width: 14px; - height: 14px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 14px; - color: color-mix(in srgb, var(--fg) 64%, transparent); - opacity: 0.85; -} -.agent-thread-tool-icon svg { - display: block; - width: 12px; - height: 12px; -} -.agent-thread-tool-icon-link { - color: inherit; - text-decoration: none; - cursor: pointer; -} -.agent-thread-tool-icon-link:hover { - color: var(--red); - opacity: 1; -} -.agent-thread-node.error .agent-thread-icon { - color: var(--color-error); -} -.agent-thread-tool { - font-weight: 600; - color: var(--red); - text-transform: uppercase; - letter-spacing: 0.3px; - font-size: 0.9em; -} -.agent-thread-header-link { - width: 17px; - height: 17px; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 17px; - margin-left: -3px; - color: var(--red); - opacity: 0.72; - text-decoration: none; - border: 0; - background: transparent; -} -.agent-thread-header-link:hover { - opacity: 1; - color: var(--red); -} -.agent-thread-header-link svg { - display: block; -} -.agent-thread-header-link-calendar { - transform: translateY(-2px); -} -.msg-ai .body a[href^="#event-"], -.msg-ai .body a[href^="#email-"], -.msg-ai .body a[href^="#document-"], -.msg-ai .body a[href^="#note-"], -.msg-ai .body a[href^="#task-"], -.msg-ai .body a[href^="#skill-"], -.msg-ai .body a[href^="#memory-"], -.msg-ai .body a[href^="#session-"] { - display: inline-flex; - align-items: baseline; - max-width: 100%; - padding: 1px 5px; - border-radius: 5px; - background: color-mix(in srgb, var(--red) 12%, transparent); - color: var(--red); - font-weight: 600; - text-decoration: none; - box-decoration-break: clone; - -webkit-box-decoration-break: clone; -} -.msg-ai .body a[href^="#event-"], -.msg-ai .body a[href^="#note-"], -.msg-ai .body a[href^="#skill-"], -.msg-ai .body a[href^="#memory-"], -.msg-ai .body a[href^="#session-"] { - font-size: 11px; - line-height: 1.35; -} -.msg-ai .body p:has(> .emoji:first-child):has(> a.chat-link[href^="#note-"]) { - margin-top: 0.15em; - margin-bottom: 0.15em; - line-height: 1.35; -} -.msg-ai .body a[href^="#event-"]:hover, -.msg-ai .body a[href^="#email-"]:hover, -.msg-ai .body a[href^="#document-"]:hover, -.msg-ai .body a[href^="#note-"]:hover, -.msg-ai .body a[href^="#task-"]:hover, -.msg-ai .body a[href^="#skill-"]:hover, -.msg-ai .body a[href^="#memory-"]:hover, -.msg-ai .body a[href^="#session-"]:hover { - background: color-mix(in srgb, var(--red) 18%, transparent); - text-decoration: underline; -} -.agent-thread-status { - font-size: 0.85em; - opacity: 0.5; -} -.agent-thread-chevron { - width: 12px; - height: 12px; - flex: 0 0 12px; - display: inline-grid; - place-items: center; - opacity: 0.4; -} -.agent-thread-chevron::before { - content: ""; - width: 0; - height: 0; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 6px solid currentColor; - transform-origin: 50% 50%; - transition: transform 0.2s ease; -} -.agent-thread-node.open .agent-thread-chevron::before { - transform: rotate(90deg); -} -.agent-thread-node.browser-preview-node .agent-thread-chevron { - display: none; -} -.agent-thread-wave { - font-family: monospace; - font-size: 0.85em; - color: var(--red); - letter-spacing: -1px; -} -/* Live "cooking" timer on a running tool — prominent (accent, tabular) so a - long-running command always reads as alive, not frozen. */ -.agent-thread-elapsed { - margin: 0 6px 0 4px; - font-size: 11px; - font-weight: 600; - font-variant-numeric: tabular-nums; - color: var(--accent, var(--red)); -} -/* Stall watchdog banner — shown when the stream has been silent for ~1min. */ -.stall-banner { - display: flex; - align-items: center; - gap: 8px; - margin: 8px auto; - padding: 8px 12px; - max-width: 90%; - font-size: 12px; - border-radius: 8px; - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, var(--bg)); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 40%, transparent); -} -.stall-banner-txt { flex: 1; opacity: 0.85; } -.stall-banner-btn { - font-size: 11px; - font-weight: 600; - padding: 4px 10px; - border-radius: 6px; - border: none; - background: var(--accent, var(--red)); - color: #fff; - cursor: pointer; - flex-shrink: 0; -} -.stall-banner-stop { - background: none; - color: var(--fg); - border: 1px solid var(--border); -} -.agent-thread-content { - display: none; - padding: 4px 0 2px 0; - overflow-x: auto; - overflow-y: hidden; -} -.agent-thread-node.open .agent-thread-content { - display: block; -} -.agent-thread-cmd { - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - padding: 8px 12px; - margin: 4px 0; - color: var(--fg); - font-size: 0.85em; - white-space: pre-wrap; - word-break: break-word; - line-height: 1.4; - overflow-x: auto; -} -.agent-thread-summary { - display: inline-flex; - align-items: center; - gap: 6px; - max-width: 100%; - margin: 4px 0; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 26%, var(--border)); - border-radius: 6px; - background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); - color: color-mix(in srgb, var(--fg) 84%, var(--accent, var(--red))); - font-size: 12px; - font-variant-numeric: tabular-nums; - line-height: 1.35; -} -.agent-thread-summary-link { - cursor: pointer; - text-decoration: none; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-thread-summary-link:hover { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--border)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); -} - -.agent-image-progress { - margin: 6px 0 0; -} -.agent-image-progress-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 10px; - font-size: 11px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 72%, transparent); -} -.agent-image-progress-label { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-image-progress-value { - flex-shrink: 0; - font-variant-numeric: tabular-nums; - color: color-mix(in srgb, var(--accent, var(--red)) 75%, var(--fg)); -} -.agent-image-progress-track { - display: none !important; -} - -.private-browser-preview { - width: 100%; - max-height: min(300px, 36vh); - margin: 6px 0 2px; - display: flex; - flex-direction: column; - overflow: hidden; - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - color: var(--fg); - box-shadow: none; -} -.private-browser-preview-header { - height: 32px; - min-height: 32px; - display: flex; - align-items: center; - gap: 8px; - padding: 0 6px 0 10px; - border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); - background: color-mix(in srgb, var(--red) 10%, transparent); - font-size: 0.85em; - font-weight: 500; - cursor: pointer; -} -.private-browser-preview-title { - flex-shrink: 0; - color: var(--red); - font-size: inherit; - font-weight: inherit; - line-height: 1; - letter-spacing: 0; -} -.private-browser-preview-status { - min-width: 0; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 68%, transparent); - font-size: inherit; - line-height: 1; -} -.private-browser-preview-fold { - width: 18px; - height: 18px; - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - left: 2px; - flex-shrink: 0; - padding: 0; - border: 0; - border-radius: 0; - background: transparent; - color: var(--red); - cursor: pointer; - font-size: 0.95em; - font-family: inherit; - font-weight: 500; - line-height: 1; - text-shadow: none; - -webkit-text-stroke: 0; -} -.private-browser-preview-fold:hover { - background: transparent; - color: var(--red); -} -.private-browser-preview-frame { - position: relative; - min-height: 130px; - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - background: color-mix(in srgb, #000 72%, var(--bg)); - box-shadow: - inset 0 1px 0 color-mix(in srgb, #fff 8%, transparent), - inset 0 0 0 1px color-mix(in srgb, #000 35%, transparent), - 0 8px 18px color-mix(in srgb, #000 18%, transparent); -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame { - cursor: zoom-in; -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame::after { - content: ""; - position: absolute; - inset: 0; - pointer-events: none; - opacity: 0.55; - transition: opacity 150ms ease; - background: - radial-gradient(circle at center, transparent 42%, color-mix(in srgb, #000 30%, transparent) 100%), - color-mix(in srgb, var(--bg) 10%, transparent); -} -.private-browser-preview[data-browser-url] .private-browser-preview-frame:hover::after { - opacity: 0.9; -} -.private-browser-preview.folded .private-browser-preview-frame { - display: none; -} -.private-browser-preview.folded .private-browser-preview-header { - border-bottom: 0; -} -.private-browser-preview-frame img { - width: 100%; - height: auto; - max-height: calc(min(300px, 36vh) - 32px); - display: block; - object-fit: contain; - transform: scale(1.1); - transform-origin: center center; - animation: private-browser-image-in 180ms ease-out both; -} -.private-browser-preview-frame img:not([src]), -.private-browser-preview-frame img[src=""] { - display: none; -} -.private-browser-preview-empty { - padding: 22px 14px; - width: auto; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 7px; - color: color-mix(in srgb, var(--fg) 48%, transparent); - font-size: 11px; - line-height: 1.35; -} -.private-browser-preview-spinner { - width: 13px; - height: 13px; - flex: 0 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.private-browser-preview-spinner .spinner-whirlpool, -.private-browser-preview-spinner .ai-spinner-whirlpool { - margin: 0 !important; -} -@keyframes private-browser-image-in { - from { opacity: 0; transform: scale(1.08); } - to { opacity: 1; transform: scale(1.1); } -} - -@media (max-width: 700px) { - .private-browser-preview-frame img { - max-height: calc(36vh - 32px); - } -} - -/* Mobile: constrain thread content */ -@media (max-width: 768px) { - .agent-thread { - margin-left: 16px; - padding-left: 18px; - max-width: calc(90% - 16px); - } - .agent-thread-cmd { - font-size: 0.78em; - padding: 6px 8px; - max-width: 100%; - overflow-x: auto; - } - .agent-thread-content { - max-width: calc(100vw - 90px); - } - .agent-tool-output pre { - font-size: 0.8em; - max-width: 100%; - overflow-x: auto; - } - .agent-thread-header { - font-size: 0.8em; - } - .agent-thread-dot { - left: -16px; - top: 10px; - } - /* padding-left drops to 18px here, so the terminating dot needs its own - offset (3px - 18px) to stay centred on the rail. */ - .agent-thread:not(.has-bottom) .agent-thread-node.open:last-child::after { - left: -15px; - } -} - -/* ===== AGENT TOOL OUTPUT (inside thread nodes) ===== */ -.agent-tool-output { - margin-top: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 8px; - overflow: hidden; -} -.agent-tool-output summary { - color: var(--red); - background: color-mix(in srgb, var(--red) 10%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--red) 15%, transparent); - cursor: pointer; - font-size: 0.85em; - user-select: none; - padding: 6px 10px; - font-weight: 500; - /* Chevron on the right (like the thinking section) instead of the default - left-side disclosure triangle. */ - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - list-style: none; -} -.agent-tool-output summary::-webkit-details-marker { display: none; } -/* File-write diff — neutral chrome (not the red error tint) + colored lines */ -.agent-tool-diff { - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.agent-tool-diff summary { - color: var(--fg); - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-bottom-color: color-mix(in srgb, var(--fg) 12%, transparent); -} -.agent-tool-diff .diff-stat { - font-weight: 600; - opacity: 0.7; - font-family: var(--mono, monospace); -} -/* Collapsed diff summary: filename + +adds/−dels (theme green/red). */ -.agent-tool-diff summary { - display: flex; - align-items: center; - gap: 8px; -} -.agent-tool-diff .diff-file { - font-family: var(--mono, monospace); - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.agent-tool-diff .diff-summary-stats { - margin-left: auto; - font-family: var(--mono, monospace); - font-weight: 600; - flex-shrink: 0; -} -.agent-tool-diff .diff-summary-stats .diff-stat-add { color: var(--green, #2ecc71); } -.agent-tool-diff .diff-summary-stats .diff-stat-del { color: var(--red, #e74c3c); } -.agent-tool-diff .diff-summary-stats .diff-stat-new { color: var(--accent, var(--red)); opacity: 0.85; } -.diff-pre { - margin: 0; - padding: 8px 10px; - overflow-x: auto; - font-family: var(--mono, monospace); - font-size: 0.82em; - line-height: 1.45; -} -.diff-pre span { display: block; white-space: pre; } -.diff-pre .diff-add { background: color-mix(in srgb, #2ecc71 22%, transparent); } -.diff-pre .diff-del { background: color-mix(in srgb, #e74c3c 22%, transparent); } -.diff-pre .diff-hunk { color: var(--accent); opacity: 0.85; } -.diff-pre .diff-meta { opacity: 0.55; } -.diff-pre .diff-ctx { opacity: 0.8; } -/* Suppress the global `summary::before { content: '▶' }` left arrow — this - section uses a right-side chevron instead. */ -.agent-tool-output summary::before { content: none; } -.agent-tool-output summary::after { - content: '\25BC'; /* ▼ */ - color: var(--red); - font-size: 0.9em; - transition: transform 0.3s ease; -} -.agent-tool-output[open] > summary::after { - transform: rotate(180deg); -} -.agent-tool-output summary:hover { - background: color-mix(in srgb, var(--red) 15%, transparent); -} -.agent-thinking-dots .ai-spinner { - font-size: 12px; - letter-spacing: 0.5px; -} - -.agent-tool-output[open] { - background: color-mix(in srgb, var(--red) 6%, transparent); -} -.agent-tool-output[open] > :not(summary) { - animation: detail-reveal 0.25s ease-out both; -} -@keyframes detail-reveal { - from { opacity: 0; transform: translateY(-4px); } - to { opacity: 1; transform: translateY(0); } -} -.agent-tool-output pre { - background: transparent; - border: none; - border-radius: 0; - padding: 10px 14px; - margin-top: 0; - max-height: 300px; - overflow-y: auto; - font-size: 0.95em; - color: var(--fg); - opacity: 0.85; - white-space: pre-wrap; - word-break: break-all; - line-height: 1.5; -} - -/* Mobile responsive styles */ -@media (max-width: 768px) { - #recording-indicator { - margin: 8px; - padding: 10px; - } - - .recording-text { - font-size: 14px; - } - - .stop-recording-btn { - padding: 6px 10px; - font-size: 12px; - } -} -/* ===== PANE STYLES (shared by compare) ===== */ - -.pane-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 0 4px; - border-bottom: 1px solid var(--border); -} -.close-split-btn, -.pane-close-btn { - background: none; - border: none; - color: var(--color-error); - font-size: 18px; - cursor: pointer; - padding: 0 4px; - line-height: 1; - opacity: 0; - transition: opacity 0.15s; -} -.pane-header:hover .close-split-btn, -.pane-header:hover .pane-close-btn { - opacity: 1; -} -.close-split-btn:hover, -.pane-close-btn:hover { - color: var(--color-error-light); -} - -/* Compare pane close controls use the same shared circular close treatment - as modal/title close buttons. */ -.compare-pane .pane-close-btn.close-btn { - width: 24px; - height: 24px; - min-width: 24px; - padding: 0; - border: 1px solid transparent; - border-radius: 999px; - background: transparent; - color: color-mix(in srgb, var(--fg) 58%, transparent); - font-size: 0; - opacity: 0.5; - display: inline-flex; - align-items: center; - justify-content: center; - transition: background 0.15s, color 0.15s, opacity 0.15s; -} -.compare-pane .pane-close-btn.close-btn:hover, -.compare-pane .pane-close-btn.close-btn:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); - opacity: 1; - outline: none; -} -.compare-pane .pane-action-btn[data-action="expand"] { - width: 24px; - height: 24px; - min-width: 24px; - padding: 0; - border: 1px solid transparent; - border-radius: 999px; - justify-content: center; - transition: background 0.15s, color 0.15s, opacity 0.15s; -} -.compare-pane .pane-action-btn[data-action="expand"]:hover, -.compare-pane .pane-action-btn[data-action="expand"]:focus-visible { - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 12%, transparent); - color: var(--fg); - opacity: 1; - outline: none; -} - - -/* ============================================ */ -/* RESEARCH DETAILS EXPANDABLE SECTION - UPDATED */ -/* ============================================ */ - -/* Style the details element */ -details { - background: color-mix(in srgb, var(--hl-string) 5%, transparent); - border: 1px solid color-mix(in srgb, var(--hl-string) 30%, transparent); - border-radius: 8px; - margin: 12px 0; - padding: 0; - overflow: hidden; - transition: all 0.3s ease; -} - -details[open] { - background: color-mix(in srgb, var(--hl-string) 8%, transparent); -} -details[open] > :not(summary) { - animation: detail-reveal 0.25s ease-out both; -} - -/* Style the summary (clickable header) - NO CURSIVE, NORMAL SIZE */ -summary { - cursor: pointer; - padding: 10px 14px; - background: color-mix(in srgb, var(--hl-string) 10%, transparent); - border-bottom: 1px solid color-mix(in srgb, var(--hl-string) 20%, transparent); - font-weight: 500; - font-size: 0.95em; - font-style: normal; - font-family: inherit; - color: var(--hl-string); - user-select: none; - list-style: none; - display: flex; - align-items: center; - gap: 8px; - transition: background 0.2s ease; -} - -summary:hover { - background: color-mix(in srgb, var(--hl-string) 15%, transparent); -} - -/* Add custom arrow */ -summary::before { - content: '▶'; - display: inline-block; - transition: transform 0.3s ease; - font-size: 0.75em; /* Smaller arrow */ -} - -details[open] summary::before { - transform: rotate(90deg); -} - -/* Hide default marker in webkit browsers */ -summary::-webkit-details-marker { - display: none; -} - -/* Style the content inside details - SMALLER, MORE COMPACT */ -details > div, -details > p, -details > ul, -details > ol { - padding: 12px 14px; /* Less padding */ - animation: fadeIn 0.3s ease; - font-size: 0.9em; /* Smaller text */ - line-height: 1.5; -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Style research findings inside details - SMALLER HEADINGS */ -details h3 { - margin-top: 12px; - margin-bottom: 6px; - color: var(--hl-string); - font-size: 0.95em; - font-weight: 500; -} - -details h4 { - margin-top: 10px; - margin-bottom: 5px; - color: var(--hl-string); - font-size: 0.9em; - font-weight: 500; -} - -details ul { - margin-left: 18px; - margin-bottom: 10px; -} - -details li { - margin-bottom: 5px; - line-height: 1.4; - font-size: 0.85em; /* Smaller list items */ -} - -details strong { - color: var(--hl-string); - font-weight: 500; -} - -details a { - color: var(--accent); - text-decoration: none; - transition: color 0.2s ease; -} - -details a:hover { - color: var(--color-link-hover); - text-decoration: underline; -} - -/* Research metadata - SMALLER */ -details .research-meta { - font-size: 0.8em; - color: var(--fg); - opacity: 0.8; - margin-top: 4px; -} - -/* Source links - SMALLER */ -details .source-link { - display: inline-block; - margin-top: 3px; - padding: 2px 5px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); - border-radius: 4px; - font-size: 0.68em; - color: var(--accent); -} - -details .source-link:hover { - background: color-mix(in srgb, var(--accent) 20%, transparent); - border-color: var(--accent); -} - -/* Research report links - clean and subtle */ -details a { - color: var(--accent); - text-decoration: none; - font-weight: normal; /* Remove bold */ - font-size: 0.85em; - opacity: 0.9; - transition: all 0.2s ease; - word-break: break-all; /* Break long URLs nicely */ -} - -details a:hover { - color: var(--color-link-hover); - text-decoration: underline; - opacity: 1; -} - -/* ============================================ */ -/* CUSTOM SYNTAX HIGHLIGHTING - Theme-Reactive */ -/* ============================================ */ -.hljs { - background: var(--code-bg, var(--hl-bg, var(--panel))); - color: var(--code-fg, var(--hl-fg, var(--fg))); - padding: 8px; - border-radius: 4px; -} - -/* Keywords & control flow — purple/magenta */ -.hljs-keyword, -.hljs-selector-tag { color: var(--hl-keyword); } - -/* Strings & regex — warm yellow/orange */ -.hljs-string, -.hljs-regexp, -.hljs-addition { color: var(--hl-string); } - -/* Comments & docs — muted. font-style intentionally omitted: italics shift - glyph widths in the highlight overlay relative to the transparent textarea - above it, which makes the caret drift away from the visible character. */ -.hljs-comment, -.hljs-quote, -.hljs-meta { color: var(--hl-comment); } - -/* Functions & method names — blue */ -.hljs-function, -.hljs-title, -.hljs-title.function_, -.hljs-section { color: var(--hl-function); } - -/* Numbers & constants — distinct from strings */ -.hljs-number, -.hljs-literal { color: var(--hl-number, var(--hl-string)); } - -/* Built-ins & types — teal/cyan tint */ -.hljs-built_in, -.hljs-type, -.hljs-class, -.hljs-title.class_ { color: var(--hl-builtin, var(--hl-function)); } - -/* Variables & identifiers — fg with slight distinction */ -.hljs-variable, -.hljs-template-variable, -.hljs-attr { color: var(--hl-variable, var(--hl-fg, var(--fg))); } - -/* Operators & punctuation — slightly dimmed fg */ -.hljs-operator, -.hljs-punctuation { color: var(--hl-fg, var(--fg)); opacity: 0.8; } - -/* Parameters */ -.hljs-params { color: var(--hl-params, var(--hl-fg, var(--fg))); } - -/* Property access, attributes in HTML/XML */ -.hljs-property, -.hljs-selector-class, -.hljs-selector-id { color: var(--hl-variable, var(--hl-function)); } - -/* Tags (HTML) */ -.hljs-tag { color: var(--hl-keyword); } -.hljs-name { color: var(--hl-keyword); } - -/* Deletion/diff */ -.hljs-deletion { color: var(--red); } - -/* Symbol, special */ -.hljs-symbol { color: var(--hl-string); } -.hljs-link { color: var(--hl-function); text-decoration: underline; } - -/* Emphasis — applied globally (chat messages, rendered markdown in docs - preview, etc.) but explicitly NEUTRALIZED in the doc editor's overlay so - the textarea-and-overlay caret alignment stays bit-perfect. Bold / italic - shift glyph widths and that drifts the click-to-row mapping. */ -.hljs-emphasis { font-style: italic; } -.hljs-strong { font-weight: bold; } -.doc-editor-highlight .hljs-emphasis, -.doc-editor-highlight .hljs-strong { - font-style: normal !important; - font-weight: inherit !important; -} diff --git a/static/css/style-part-04.css b/static/css/style-part-04.css deleted file mode 100644 index ce6a9add4..000000000 --- a/static/css/style-part-04.css +++ /dev/null @@ -1,6011 +0,0 @@ -/* Fragment 5/9 of the former static/style.css. - * - * This is a mechanical, order-preserving slice: rules were moved verbatim - * and this file's position in the order of static/index.html is what - * keeps the cascade identical. Reordering the links, or moving a rule between - * fragments, can change which of two competing declarations wins. - * - * static/index.html and the PRECACHE list in static/sw.js must list every - * fragment in the same order. tests/test_static_stylesheet_manifest.py and - * tests/test_css_computed_style_snapshot.py both fail if that drifts. - */ -.admin-ep-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} - -/* Status messages */ -.admin-error { color: var(--color-error); font-size: 11px; margin-top: 4px; } -.admin-success { color: var(--color-success); font-size: 11px; margin-top: 4px; } -.admin-empty { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 12px; - padding: 10px 0; - text-align: center; -} -/* Endpoint-list empty states ("No endpoints configured" / "None") get a - left-aligned, accent-colored treatment so they read as "this row is your - placeholder, click Add" rather than a centered "nothing here" grey blob. */ -#adm-epList-local .admin-empty, -#adm-epList-api .admin-empty { - text-align: left; - padding: 8px 4px; - color: var(--accent-primary, var(--accent, var(--red))); -} - -/* RAG upload zone */ -.admin-rag-upload-zone { - border: 2px dashed var(--border); - border-radius: 8px; - padding: 14px; - text-align: center; - cursor: pointer; - transition: border-color 0.2s, background 0.2s; - margin-bottom: 8px; -} -.admin-rag-upload-zone:hover, -.admin-rag-upload-zone.dragover { - border-color: var(--red); - background: color-mix(in srgb, var(--red) 7%, transparent); -} -.admin-rag-upload-zone p { - color: color-mix(in srgb, var(--fg) 50%, transparent); - font-size: 11px; - margin-top: 4px; -} -.admin-rag-icon { font-size: 18px; } -.admin-rag-dir-row { - display: flex; - gap: 6px; - margin-bottom: 8px; -} -.admin-rag-dir-row input { - flex: 1; - padding: 5px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-rag-dir-row input:focus { outline: none; border-color: var(--red); } -.admin-rag-dir-row button { - padding: 5px 8px; - border: none; - border-radius: 6px; - background: var(--red); - color: #fff; - cursor: pointer; - font-size: 12px; - white-space: nowrap; -} -.admin-rag-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: 6px; -} -.admin-rag-section-label { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin: 8px 0 4px; -} -.admin-rag-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 8px; - background: var(--bg); - border-radius: 6px; - margin-bottom: 4px; - font-size: 12px; -} -.admin-rag-item-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--fg); -} -.admin-rag-item-meta { - color: color-mix(in srgb, var(--fg) 40%, transparent); - font-size: 10px; - margin: 0 6px; - flex-shrink: 0; -} -.admin-rag-status { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-top: 6px; -} - -/* Token reveal */ -.admin-token-reveal { - margin-top: 8px; - padding: 8px; - background: color-mix(in srgb, var(--red) 8%, var(--bg)); - border: 1px solid color-mix(in srgb, var(--red) 25%, var(--border)); - border-radius: 8px; -} - -/* Selects in admin cards */ -.admin-card select { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; -} -.admin-card input { - font-family: inherit; - font-size: 12px; -} -.admin-card label { - font-size: 12px; -} - -/* ---- Document Library ---- */ - -.doclib-modal-content { - width: min(600px, 92vw); - max-height: 85vh; - font-size: 12px; - background: var(--bg); -} -/* Anchor doclib modal to top so only the bottom edge moves on resize */ -#doclib-modal { - align-items: flex-start; - padding-top: 8vh; -} -.doclib-modal-content .modal-header h4 { - font-size: 1rem; -} -/* The "open in new tab" email modal inherited the 1rem doclib header, which - read way bigger than the email subject in the regular (inline) reader. Pin - it smaller so the two views are consistent. */ -.email-reader-tab-modal .modal-header h4 { - font-size: 13px; - font-weight: 600; -} -/* Match the sticky modal-header to the doclib modal-content body color - (var(--bg)) instead of the global var(--panel) default — otherwise the - header reads as a darker stripe above the email list. */ -.doclib-modal-content > .modal-header { - background: var(--bg); -} -.doclib-stats { - font-size: 11px; - color: var(--fg-muted); - margin-bottom: 8px; -} -.doclib-toolbar { - display: flex; - gap: 8px; - margin-bottom: 8px; - align-items: center; -} -.doclib-search { - flex: 1; - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - outline: none; - transition: border-color 0.15s; -} -#archive-select-btn { height: auto; padding: 5px 8px; } -.doclib-search:focus { - border-color: var(--red); -} -.doclib-sort { - padding: 6px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font-size: 12px; - font-family: inherit; - outline: none; - cursor: pointer; -} -.doclib-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 8px; -} -.doclib-chip { - padding: 2px 10px; - border-radius: 12px; - font-size: 10px; - border: 1px solid var(--border); - background: transparent; - color: var(--fg-muted); - cursor: pointer; - user-select: none; - transition: background 0.15s, border-color 0.15s; -} -.doclib-chip:hover { - border-color: var(--red); -} -.doclib-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Document library — language chip row */ -.doclib-lang-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - padding: 2px 0; - max-height: 52px; - overflow-y: auto; -} -.doclib-lang-chips:empty { display: none; } -/* Mobile: keep tag chips on ONE row and scroll horizontally instead of wrapping - into a tall multi-line block (Serve, library — anywhere .doclib-lang-chips is used). */ -@media (max-width: 768px) { - .doclib-lang-chips { - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - max-height: none; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .doclib-lang-chips::-webkit-scrollbar { display: none; } - .doclib-lang-chips > * { flex-shrink: 0; } -} -#doclib-tidy-btn, #doclib-select-btn, #doclib-chats-tidy-btn { - position: relative; - top: -3px; -} -/* Document library — list layout */ -.doclib-grid { - display: flex; - flex-direction: column; - gap: 4px; - max-height: 400px; - overflow-y: auto; - padding: 0; - position: relative; -} - -/* Gallery grid domino-in cascade on open — same recipe as the document/email - libraries. Applied to #gallery-grid in gallery.js on the first render after - each open, removed after the longest delay so re-renders feel instant. */ -.gallery-just-opened > .gallery-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.gallery-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.gallery-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.gallery-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.gallery-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.gallery-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.gallery-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.gallery-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.gallery-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.gallery-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.gallery-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.gallery-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.gallery-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.gallery-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.gallery-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.gallery-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.gallery-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.gallery-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.gallery-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.gallery-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.gallery-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.gallery-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Tasks list domino-in cascade on open — mirrors gallery / doclib. Applied to - #tasks-list in tasks.js on the first render after each open, removed after - the longest delay so re-renders feel instant. */ -.tasks-just-opened > .task-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.tasks-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.tasks-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.tasks-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.tasks-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.tasks-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.tasks-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.tasks-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.tasks-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.tasks-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.tasks-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.tasks-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.tasks-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.tasks-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.tasks-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.tasks-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.tasks-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.tasks-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.tasks-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.tasks-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.tasks-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.tasks-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Document library cascade — same recipe as email library, applied per-tab - the first time content loads (chats / archive / research / documents). - Module-level Set in documentLibrary.js prevents re-firing on tab swaps - or filter/sort re-renders within the same page session. */ -.doclib-just-opened > .memory-item, -.doclib-just-opened > .doclib-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.doclib-just-opened > :nth-child(1) { animation-delay: 0.02s; } -.doclib-just-opened > :nth-child(2) { animation-delay: 0.04s; } -.doclib-just-opened > :nth-child(3) { animation-delay: 0.06s; } -.doclib-just-opened > :nth-child(4) { animation-delay: 0.08s; } -.doclib-just-opened > :nth-child(5) { animation-delay: 0.10s; } -.doclib-just-opened > :nth-child(6) { animation-delay: 0.12s; } -.doclib-just-opened > :nth-child(7) { animation-delay: 0.14s; } -.doclib-just-opened > :nth-child(8) { animation-delay: 0.16s; } -.doclib-just-opened > :nth-child(9) { animation-delay: 0.18s; } -.doclib-just-opened > :nth-child(10) { animation-delay: 0.20s; } -.doclib-just-opened > :nth-child(11) { animation-delay: 0.22s; } -.doclib-just-opened > :nth-child(12) { animation-delay: 0.24s; } -.doclib-just-opened > :nth-child(13) { animation-delay: 0.26s; } -.doclib-just-opened > :nth-child(14) { animation-delay: 0.28s; } -.doclib-just-opened > :nth-child(15) { animation-delay: 0.30s; } -.doclib-just-opened > :nth-child(16) { animation-delay: 0.32s; } -.doclib-just-opened > :nth-child(17) { animation-delay: 0.34s; } -.doclib-just-opened > :nth-child(18) { animation-delay: 0.36s; } -.doclib-just-opened > :nth-child(19) { animation-delay: 0.38s; } -.doclib-just-opened > :nth-child(20) { animation-delay: 0.40s; } -.doclib-just-opened > :nth-child(n+21) { animation-delay: 0.42s; } - -/* Domino cascade on first open of the email library — mirrors the sidebar - .section-just-expanded recipe so list items spring in staggered. Class - is applied to #email-lib-grid in emailLibrary.js on the first render - only, then removed after the longest delay so re-renders feel instant. */ -.email-lib-just-opened :is(.doclib-card, .date-section-header) { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(1) { animation-delay: 0.02s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(2) { animation-delay: 0.04s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(3) { animation-delay: 0.06s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(4) { animation-delay: 0.08s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(5) { animation-delay: 0.10s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(6) { animation-delay: 0.12s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(7) { animation-delay: 0.14s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(8) { animation-delay: 0.16s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(9) { animation-delay: 0.18s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(10) { animation-delay: 0.20s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(11) { animation-delay: 0.22s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(12) { animation-delay: 0.24s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(13) { animation-delay: 0.26s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(14) { animation-delay: 0.28s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(15) { animation-delay: 0.30s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(16) { animation-delay: 0.32s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(17) { animation-delay: 0.34s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(18) { animation-delay: 0.36s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(19) { animation-delay: 0.38s; } -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(20) { animation-delay: 0.40s; } -/* Cap the cascade at 20 — beyond that they animate together so opening a - long inbox doesn't take forever to settle. */ -.email-lib-just-opened :is(.doclib-card, .date-section-header):nth-child(n+21) { animation-delay: 0.42s; } - -/* Inside the email library modal, the grid needs to grow with the modal — - but `flex: 1 1 0` collapses to 0 when the parent isn't height-constrained - (which is the non-fullscreen default). Use `auto` basis + a sensible - `min-height` floor so it shows ~400px naturally and absorbs more when - the modal is resized / fullscreened. */ -#email-lib-modal .doclib-grid { - max-height: none; - height: auto; - flex: 1 1 auto; - min-height: 400px; -} - -/* ── Mobile compose FAB (email) ── - Replaces the top-right "New" button on phones with a round mail-icon button - bottom-right that collapses to a circle while the list scrolls and springs - back out to "New" when scrolling stops. Desktop is unchanged. */ -.email-lib-fab { display: none; } -@media (max-width: 768px) { - /* The absolute FAB anchors to the email panel card. */ - #email-lib-modal .admin-card { position: relative; } - /* Hide the toolbar "New" button — the FAB is the mobile entry point. */ - #email-lib-compose-btn { display: none; } - - #email-lib-modal .email-lib-fab { - --fab-size: 56px; - display: flex; - align-items: center; - gap: 9px; - /* Hidden until the email list has rendered; JS adds .fab-revealed to pop - it in (scale-from-center). Avoids the button flashing at the top and - sliding down before _positionFab() places it. */ - transform: scale(0); - opacity: 0; - transform-origin: center center; - position: absolute; - right: calc(16px + env(safe-area-inset-right, 0px)); - bottom: calc(18px + env(safe-area-inset-bottom, 0px)); - height: var(--fab-size); - min-width: var(--fab-size); - padding: 0 20px 0 18px; - border: none; - border-radius: 999px; - background: var(--accent-primary, var(--red)); - color: #fff; - font-family: inherit; - font-size: 15px; - font-weight: 600; - line-height: 1; - cursor: pointer; - box-shadow: 0 6px 18px rgba(0,0,0,.38), 0 2px 6px rgba(0,0,0,.28); - z-index: 30; - pointer-events: auto; - /* This (base) transition governs the EXPAND — slower + graceful. */ - transition: - padding 420ms cubic-bezier(0.22, 1, 0.36, 1), - box-shadow 240ms ease, - transform 140ms ease, - background 140ms ease; - will-change: padding, transform; - } - #email-lib-modal .email-lib-fab svg { flex: 0 0 auto; display: block; } - #email-lib-modal .email-lib-fab .email-lib-fab-label { - overflow: hidden; - white-space: nowrap; - max-width: 120px; - opacity: 1; - /* EXPAND timing — label eases out a touch after the width opens up. */ - transition: - max-width 420ms cubic-bezier(0.22, 1, 0.36, 1), - opacity 300ms 60ms cubic-bezier(0.22, 1, 0.36, 1), - margin 420ms cubic-bezier(0.22, 1, 0.36, 1); - } - /* Collapsed (while scrolling) → pure circle, icon only. We DON'T set an - explicit width: the container is auto-width and shrinks smoothly as the - padding + label max-width animate (min-width keeps it circular). Setting a - fixed width here made the collapse snap, since auto↔px width can't tween. - This (.collapsed) transition governs the COLLAPSE — kept snappy. */ - #email-lib-modal .email-lib-fab.collapsed { - padding: 0; - justify-content: center; - transition: - padding 230ms cubic-bezier(0.4, 0, 0.2, 1), - box-shadow 200ms ease, - transform 140ms ease; - } - #email-lib-modal .email-lib-fab.collapsed .email-lib-fab-label { - max-width: 0; - opacity: 0; - margin-left: -9px; - transition: - max-width 230ms cubic-bezier(0.4, 0, 0.2, 1), - opacity 150ms cubic-bezier(0.4, 0, 0.2, 1), - margin 230ms cubic-bezier(0.4, 0, 0.2, 1); - } - /* Entrance: circle-expand-from-middle pop, played once when revealed. No - `forwards` fill so the resting transform (scale 1) comes from this rule — - that keeps the :active press transform working afterwards. */ - #email-lib-modal .email-lib-fab.fab-revealed { - transform: scale(1); - opacity: 1; - animation: emailFabPop 380ms cubic-bezier(0.34, 1.56, 0.64, 1); - } - #email-lib-modal .email-lib-fab:active { - transform: scale(0.93); - filter: brightness(0.92); - box-shadow: 0 3px 10px rgba(0,0,0,.4); - } -} -@keyframes emailFabPop { - from { transform: scale(0); opacity: 0; } - 60% { transform: scale(1.06); opacity: 1; } - to { transform: scale(1); opacity: 1; } -} -@media (prefers-reduced-motion: reduce) { - #email-lib-modal .email-lib-fab, - #email-lib-modal .email-lib-fab .email-lib-fab-label { transition-duration: 1ms; } - #email-lib-modal .email-lib-fab.fab-revealed { animation: none; } -} -/* When the modal-content has explicit height (fullscreen or user-resized), - drop the floor so the grid sizes purely from flex. */ -#email-lib-modal.email-lib-fullscreen .doclib-grid, -#email-lib-modal .modal-content[style*="height"] .doclib-grid { - min-height: 0; -} -/* Document library: same fix as the email library (#74). When fullscreened - the modal-content gets an inline `height: 100vh`, but the inner - .doclib-grid stays capped at the 400px default so the list looks like a - tiny strip floating in a giant empty modal. Mirror the email recipe: - make the modal a flex column, give the body/admin-card claim of the - remaining height, and let the grid take the rest. Scoped to the - fullscreen class so windowed-mode layout is unchanged. */ -/* Inner flex chain that lets the chats/documents grid claim the full - remaining height of the modal. Applies in BOTH fullscreen AND - right-docked states — without the docked state included, dragging a - fullscreen doclib to the right snap zone breaks the flex layout - because exitFullscreen removes .doclib-fullscreen, and the grid - falls back to its base max-height: 400px showing only ~8 items. - The CSS variable :is() selector lets both states share one rule. */ -#doclib-modal.doclib-fullscreen .doclib-modal-content, -#doclib-modal.modal-right-docked .doclib-modal-content, -#doclib-modal.modal-left-docked .doclib-modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -/* Fullscreen positioning — scoped to NOT apply when also right-docked. - Without this exclusion, dragging a fullscreen doclib to the right snap - zone keeps .doclib-fullscreen on the modal and these !important rules - override the dock's inline styles, leaving the modal stuck fullscreen - instead of becoming a side panel. */ -#doclib-modal.doclib-fullscreen:not(.modal-right-docked) .doclib-modal-content { - position: fixed !important; - top: 0 !important; - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; - right: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; -} -#doclib-modal.doclib-fullscreen .modal-header, -#doclib-modal.doclib-fullscreen .lib-tabs, -#doclib-modal.modal-right-docked .modal-header, -#doclib-modal.modal-right-docked .lib-tabs, -#doclib-modal.modal-left-docked .modal-header, -#doclib-modal.modal-left-docked .lib-tabs { - flex: 0 0 auto; -} -#doclib-modal.doclib-fullscreen .modal-body, -#doclib-modal.modal-right-docked .modal-body, -#doclib-modal.modal-left-docked .modal-body { - flex: 1 1 0; - min-height: 0; - overflow: hidden; - display: flex; - flex-direction: column; -} -#doclib-modal.doclib-fullscreen .admin-card, -#doclib-modal.modal-right-docked .admin-card, -#doclib-modal.modal-left-docked .admin-card { - flex: 1 1 0; - min-height: 0; - display: flex; - flex-direction: column; -} -#doclib-modal.doclib-fullscreen .doclib-grid:not(:has(.doclib-card-expanded)), -#doclib-modal.modal-right-docked .doclib-grid:not(:has(.doclib-card-expanded)), -#doclib-modal.modal-left-docked .doclib-grid:not(:has(.doclib-card-expanded)) { - max-height: none; - height: auto; - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* "Reply" docks the open email modal to the left as a sidebar so the doc - panel (which slides in from the right of the chat area) is visible - side-by-side. The .modal backdrop is already pointer-events:none for - email modals — only the content takes pointer events — so the chat / - doc panel underneath stays interactive. */ -.modal.email-snap-left { - align-items: stretch; - justify-content: flex-start; - left: 0 !important; - width: 100% !important; -} -.modal.email-snap-left .modal-content { - left: var(--email-doc-split-left-x, 0px) !important; - width: var(--email-doc-split-email-w, 420px) !important; - max-width: var(--email-doc-split-email-w, 420px) !important; - border-right: 1px solid var(--border); - box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; -} -@media (min-width: 769px) { - body.email-doc-split-active.doc-view #email-lib-modal, - body.email-doc-split-active.doc-view.email-front #email-lib-modal, - body.email-doc-split-active.doc-view .modal[id^="email-reader-"], - body.email-doc-split-active.doc-view.email-front .modal[id^="email-reader-"] { - z-index: 150 !important; - } - body.email-doc-split-active #email-lib-modal.email-snap-left, - body.email-doc-split-active #email-lib-modal.modal-left-docked { - left: var(--email-doc-split-left-x, 0px) !important; - width: var(--email-doc-split-email-w, 420px) !important; - overflow: hidden !important; - z-index: 155 !important; - } - body.email-doc-split-active #email-lib-modal.email-snap-left:not(.modal-dragging) .modal-content, - body.email-doc-split-active #email-lib-modal.modal-left-docked:not(.modal-dragging) .modal-content, - body.email-doc-split-active .modal[id^="email-reader-"].email-snap-left:not(.modal-dragging) .modal-content, - body.email-doc-split-active .modal[id^="email-reader-"].modal-left-docked:not(.modal-dragging) .modal-content { - position: absolute !important; - left: 0 !important; - top: 0 !important; - bottom: 0 !important; - right: auto !important; - width: var(--email-doc-split-email-w, 420px) !important; - max-width: var(--email-doc-split-email-w, 420px) !important; - height: 100vh !important; - max-height: 100vh !important; - transform: none !important; - margin: 0 !important; - } - body.email-doc-split-active.doc-view .doc-divider { - display: none !important; - } - body.email-doc-split-active.doc-view .doc-editor-pane { - position: fixed !important; - left: var(--email-doc-split-right-x, 420px) !important; - right: var(--right-dock-w, 0px) !important; - top: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - z-index: 260 !important; - margin-top: 0 !important; - transform: none !important; - border-left: none !important; - } -} - -/* Edge docking. Docked panels are fixed to the viewport edge; the workspace - panes reserve room with margins so left + right docks can be active at the - same time without skewing the entire body box. */ -body.right-dock-active { - padding-right: 0; -} -body.left-dock-active { - padding-left: 0; -} -body.left-dock-active:not(.email-doc-split-active) .chat-container { - margin-left: var(--left-dock-w, 0px); -} -body.right-dock-active .chat-container, -body.right-dock-active:not(.email-doc-split-active) .doc-editor-pane { - margin-right: var(--right-dock-w, 0px); -} -.modal.modal-right-docked { - align-items: stretch; - justify-content: flex-end; -} -.modal.modal-right-docked .modal-content { - border-left: 1px solid var(--border); - box-shadow: -4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; -} -.modal.modal-left-docked { - align-items: stretch; - justify-content: flex-start; -} -.modal.modal-left-docked .modal-content { - border-right: 1px solid var(--border); - box-shadow: 4px 0 14px rgba(0, 0, 0, 0.18); - border-radius: 0; - /* Pin transitions OFF on the dock's positioning properties. The wide - sidebar collapse/expand changes --sidebar-w instantly, which means - the docked modal's `left: calc(...)` jumps by ~240px. Any CSS - transition on `left` (inherited or otherwise) would animate that - jump as a fly-across. Same defense for right-docked / fullscreen - panels in case future themes add a generic transition. */ - transition: none !important; -} -.modal.modal-right-docked .modal-content, -#email-lib-modal.email-lib-fullscreen .modal-content, -#doclib-modal.doclib-fullscreen .doclib-modal-content { - transition: none !important; -} -.modal.modal-right-docked .email-reader-header, -.modal.modal-left-docked .email-reader-header { - flex-direction: column; - /* Stretch the meta to the full header width so the absolute - action cluster's right:0 lands at the right edge (was sizing - meta to chip content width and centering it). */ - align-items: stretch; - gap: 6px; -} -/* Docked panes: 4px between From→To and 4px between To→Cc. - Meta has its own 2px gap (From→details) so an extra 2px on - details margin-top gives a 4px From→To distance, and gap:4 on - the details flex column gives 4px between To and Cc. */ -.modal.modal-right-docked .email-reader-meta-details, -.modal.modal-left-docked .email-reader-meta-details { - margin-top: 2px; - gap: 4px; -} -/* Docked: always show To/Cc — there's vertical room reserved for - the action cluster already and the chevron toggle just adds - friction. Force the hidden attribute open and hide the chevron. */ -.modal.modal-right-docked .email-reader-meta-details[hidden], -.modal.modal-left-docked .email-reader-meta-details[hidden] { - display: flex !important; -} -.modal.modal-right-docked .email-reader-meta-toggle, -.modal.modal-left-docked .email-reader-meta-toggle { - display: none; -} -.modal.modal-right-docked .email-reader-header > .email-reader-actions, -.modal.modal-left-docked .email-reader-header > .email-reader-actions { - position: static; - align-self: flex-end; - background: none; - padding-left: 0; -} -/* Docked modal: keep all meta rows as flex so From / To / Cc - labels align at the same x. Chips already overflow-scroll - inside each row when there are too many. No per-row margin-top - here — the spacing between rows comes entirely from - .email-reader-meta-details gap so From→To and To→Cc match. */ -.modal.modal-right-docked .email-reader-meta-row:not(.email-reader-meta-from), -.modal.modal-left-docked .email-reader-meta-row:not(.email-reader-meta-from) { - padding-right: 0; -} -.modal.modal-right-docked .email-reader-meta-row strong, -.modal.modal-left-docked .email-reader-meta-row strong { - min-width: 0; -} -.modal.modal-right-docked .recipient-chip, -.modal.modal-left-docked .recipient-chip { - max-width: 100%; -} -.archive-list { - margin-top: 8px; - border-top: 1px solid var(--border); - padding-top: 8px; -} -#archive-modal .memory-sort-select, -#archive-modal .memory-toolbar-btn { - height: 32px; -} -.archive-menu-btn { - position: relative; - top: 2px; -} -#archive-chips .doclib-chip { - height: 22px; - display: inline-flex; - align-items: center; - font-size: 10px; - padding: 0 8px; -} -/* Library tab panels — Chats / Archive / Research / Documents share the - same toolbar dimensions so the sort dropdown + Select button line up - identically across tabs. */ -#doclib-panel-chats .memory-sort-select, -#doclib-panel-archive .memory-sort-select, -#doclib-panel-research .memory-sort-select, -[data-doclib-panel="documents"] .memory-sort-select { - height: 24px; - font-size: 11px; - width: 86px; -} -#doclib-panel-chats .memory-toolbar-btn, -#doclib-panel-archive .memory-toolbar-btn, -#doclib-panel-research .memory-toolbar-btn, -[data-doclib-panel="documents"] .memory-toolbar-btn { - height: 24px; - font-size: 11px; - position: relative; - top: -3px; -} -/* Research's Recent (sort) + Select sat 1px lower than the other tabs — nudge up. */ -#doclib-panel-research .memory-sort-select { position: relative; top: 1.5px; } -#doclib-panel-research .memory-toolbar-btn { top: -4.5px; } -.library-sort-picker { - width: 108px; - flex: 0 0 108px; - top: 1px; -} -.library-sort-picker .library-sort-btn { - top: 0; - width: 100%; -} -.library-sort-picker .library-sort-menu { - top: calc(100% + 4px); - width: max(100%, 154px); -} -.library-sort-picker .library-sort-item[aria-selected="true"] { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); -} -#doclib-panel-chats #doclib-chats-tidy-btn, -#doclib-panel-chats #doclib-chats-select-btn, -#doclib-panel-archive #doclib-arc-select-btn { - top: 1px; -} -#doclib-panel-chats .doclib-chip-scroll-arrow { - top: calc(50% - 9px) !important; -} -[data-doclib-panel="documents"] .doclib-chip-scroll-arrow { - top: calc(50% - 9px) !important; -} -#doclib-modal .doclib-chip-scroll-arrow { - top: calc(50% - 19px) !important; -} -[data-doclib-panel="documents"] #doclib-tidy-btn, -[data-doclib-panel="documents"] #doclib-select-btn { - top: 1px; -} -#doclib-panel-research #doclib-research-tidy-btn, -#doclib-panel-research #doclib-research-select-btn { - top: -0.5px; -} -#doclib-panel-research .library-sort-picker { top: -0.5px; } -[data-doclib-panel] { padding-top: 6px !important; } -#doclib-panel-chats, #doclib-panel-archive { padding-top: 14px !important; } -#doclib-panel-chats .memory-desc, #doclib-panel-archive .memory-desc, #doclib-panel-research .memory-desc { margin-top: 7px; } -#doclib-panel-chats .memory-search-input, -#doclib-panel-archive .memory-search-input { - height: 30px !important; - min-height: 30px !important; - flex-shrink: 0; - font-size: 11px; -} -/* Unified search bar across Library tabs + Memory modal — same height, - same magnifying-glass icon at the start, consistent padding. */ -#doclib-panel-chats .memory-search-input, -#doclib-panel-archive .memory-search-input, -#doclib-panel-research .memory-search-input, -[data-doclib-panel="documents"] .memory-search-input, -#memory-modal .memory-search-input, -#tasks-modal .memory-search-input { - height: 30px; - min-height: 30px; - font-size: 11px; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: 9px center; - padding-left: 28px; -} -#doclib-panel-chats .doclib-chip { - height: 28px; -} -#doclib-panel-archive .doclib-chip { - height: 28px; -} -#doclib-panel-chats .doclib-chip, -#doclib-panel-archive .doclib-chip { - display: inline-flex; - align-items: center; - font-size: 9px; - padding: 0 8px; - border-radius: 10px; -} -.doclib-grid:has(.doclib-card-expanded) > .doclib-card:not(.doclib-card-expanded) { - display: none; -} -/* Hide everything except the grid when a card is expanded */ -.admin-card:has(.doclib-card-expanded) > *:not(.doclib-grid):not(.hwfit-cached-list) { - opacity: 0; - max-height: 0; - overflow: hidden; - pointer-events: none; - margin: 0 !important; - padding: 0 !important; - transition: opacity 0.12s ease, max-height 0.2s ease; -} -.admin-card:has(.doclib-card-expanded) > .memory-bulk-bar { - display: none; -} -.admin-card:has(.doclib-card-expanded) > .doclib-grid, -.admin-card:has(.doclib-card-expanded) > .hwfit-cached-list { - max-height: none !important; - min-height: 0 !important; - overflow: visible !important; -} -/* Firefox-mobile (no :has()) fallback: when reading an email, fully remove the - list-mode chrome (accounts row, toolbar, bulk bar, desc) — display:none so no - residual flex-gap leaves an empty strip above the reader — and zero the - admin-card gap so the grid/reader starts flush at the top. */ -#email-lib-modal.email-reading .admin-card > *:not(.doclib-grid):not(.hwfit-cached-list) { - display: none !important; -} -#email-lib-modal .admin-card:has(.doclib-card-expanded), -#email-lib-modal.email-reading .admin-card { - gap: 0 !important; - padding: 0 !important; - margin-bottom: 0 !important; - border: 0 !important; - border-radius: 0 !important; - background: transparent !important; -} -/* Override for chat-rows + research-rows: those expand inline with a - bounded preview (max-height: 60vh), so the grid must stay clipped - and scrollable. Without this scoping the overflow:visible above lets - the expanded chat preview escape the grid and overlap whatever's - underneath/beside the modal (e.g., a docked sibling panel) — what - the user calls "merging with other windows". */ -.admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - overflow: hidden auto !important; -} -/* Email modal needs the grid to STAY a constrained flex container when - expanded — overflow:visible (set above for cookbook) lets content escape - instead of letting the expanded reader fill via flex:1. We keep the same - max-height:none + min-height:0 but flip overflow back so the reader - actually fills the modal. */ -#email-lib-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid, -#email-lib-modal.email-reading .admin-card > .doclib-grid { - overflow: hidden !important; - display: flex !important; - flex: 1 1 auto !important; -} -#email-lib-modal.email-reading #email-lib-grid > .date-section-header { - display: none !important; -} -#email-lib-modal.email-reading .doclib-modal-content { - min-height: var(--email-reading-modal-min-h, auto); -} -#email-lib-modal .doclib-card.doclib-card-expanded { - flex: 1 1 auto !important; - height: 100% !important; - /* Neutral frame on the active email — the accent border + glow felt - overbearing on desktop; the size jump alone is enough signal. */ - border: 1px solid var(--border) !important; - box-shadow: 0 6px 18px rgba(0,0,0,0.12) !important; - animation: none !important; -} -/* Desktop-only, ONLY on the currently-expanded email card. Nudges the title - row down 6px / right 2px, bolds the subject, hides the timestamp from the - meta line (the reader header carries its own date), and pulls the prev/next - nav arrows in 4px. */ -@media (min-width: 769px) { - #email-lib-modal .doclib-card-expanded .email-card-titlerow, - #email-lib-modal .doclib-card-expanded .email-card-titlerow > * { - position: relative; - top: 6px; - left: 2px; - } - /* Smaller subject — the reader header below already carries weight. */ - #email-lib-modal .doclib-card-expanded .email-card-titlerow .memory-item-title { - font-weight: 700; - font-size: 12px; - } - /* A bit more breathing room around the title row so the smaller bold text - doesn't crowd the meta line below. (Timestamp kept — user changed mind.) */ - #email-lib-modal .doclib-card-expanded .email-card-titlerow { - padding: 4px 0 6px; - line-height: 1.35; - } - /* Nudge the date down/left in the meta row, and give the meta line +4px of - extra bottom space so the shifted date isn't clipped. */ - #email-lib-modal .doclib-card-expanded .email-meta-date { - position: relative; - top: 4px; - left: 0; - } - #email-lib-modal .doclib-card-expanded .memory-item-meta { - padding-bottom: 4px; - } - #email-lib-modal .doclib-card-expanded .email-card-nav-arrows { - position: relative; - left: -4px; - } -} -/* Skills modal: same mechanism as email above. The default expanded-grid - rule (overflow:visible, no flex) lets the card expand inline to ~content - height instead of filling — which is why the skill preview stopped at - partial height. Flip the grid back to a constrained flex container and - let the expanded card fill it, exactly like the email reader. */ -#memory-modal .admin-card:has(.doclib-card-expanded) > .doclib-grid { - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - flex: 1 1 auto !important; -} -#memory-modal .doclib-card.doclib-card-expanded { - flex: 1 1 auto !important; - height: 100% !important; -} -#email-lib-modal .modal-content { - transition: width 0.22s ease-out, height 0.22s ease-out; -} -@media (min-width: 769px) { - body:not(.email-doc-split-active) #email-lib-modal:not(.email-lib-fullscreen):not(.modal-left-docked):not(.modal-right-docked) .modal-content:not([style*="height"]) { - min-height: min(560px, 85vh); - } -} - -/* Serve panel specifically: the admin-card inline style is - `overflow:hidden` (so the toolbar/header don't drift), and the list - inside has overflow:visible. On short windows that combination - clipped the cards off the bottom with no scrollbar. Make the list - itself the scroll surface so the rest of the card stays put. */ -.cookbook-group[data-backend-group="Serve"] > .admin-card { - min-height: 0; -} -.cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list, -.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list { - flex: 1 1 0; - min-height: 0; - overflow-y: auto !important; - overscroll-behavior: contain; -} -.cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list .doclib-card.doclib-card-expanded { - flex: 0 0 auto !important; - overflow: visible !important; -} -/* Launch model cards use the same domino entrance as the other library lists - when a cached-model scan finishes. The class is removed after the one-shot - entrance, so filtering and sorting stay instantaneous. */ -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { - animation: section-domino-in 0.36s cubic-bezier(0.22, 1.61, 0.36, 1) backwards; -} -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(1) { animation-delay: 0.04s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(2) { animation-delay: 0.08s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(3) { animation-delay: 0.12s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(4) { animation-delay: 0.16s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(5) { animation-delay: 0.20s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(6) { animation-delay: 0.24s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(7) { animation-delay: 0.28s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(8) { animation-delay: 0.32s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(9) { animation-delay: 0.36s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(10) { animation-delay: 0.40s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(11) { animation-delay: 0.44s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(12) { animation-delay: 0.48s; } -#cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card:nth-child(n+13) { animation-delay: 0.48s; } -@media (prefers-reduced-motion: reduce) { - #cookbook-modal .hwfit-cached-list.cookbook-serve-models-just-loaded > .doclib-card { - animation: none; - } -} -/* Drag-and-drop visual hint for the email compose pane. Subtle accent - outline + tinted overlay so it's obvious files will attach if dropped. */ -.doc-editor-pane.email-dragover { - outline: 2px dashed var(--accent, #2563eb); - outline-offset: -8px; - background: color-mix(in srgb, var(--accent, #2563eb) 6%, transparent); -} -.doc-editor-pane.email-dragover::after { - content: 'Drop to attach'; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: var(--accent, #2563eb); - color: #fff; - padding: 8px 16px; - border-radius: 6px; - font-size: 12px; - pointer-events: none; - z-index: 1000; -} - -/* Email reader window — make the body always fill the available vertical - space regardless of how the window is positioned/resized. The user can - drag the bottom-right corner to resize; the body re-flexes automatically. */ -.email-window-modal .modal-content { - display: flex !important; - flex-direction: column !important; - resize: both; - overflow: hidden; - min-width: 320px; - min-height: 240px; - /* When user resizes/drags so the window covers a side, force the inner - body to fill all the way to the bottom. The default max-height: 85vh - (set inline by _makeDraggable's exitFullscreen) and 80vh from the - opener both cap at the *viewport* — if the browser window then changes - size (e.g. OS aero-snap), the modal stays at the old height and - truncates rows. Drop the cap whenever an explicit width OR height has - been written inline by the resize handle / drag handler. */ -} -.email-window-modal .modal-content[style*="width"], -.email-window-modal .modal-content[style*="height"] { - max-height: calc(100vh - 16px) !important; -} -.email-window-modal .email-window-body { - flex: 1 1 auto !important; - min-height: 0 !important; - /* Explicit basis of 0 makes the body grow to fill remaining space rather - than its content's intrinsic height (which was capping the thread). */ - flex-basis: 0 !important; - max-height: none !important; - overflow: auto !important; - overscroll-behavior: contain; -} - -/* Individual email reader window — fullscreen mode (drag-to-top snap or - double-click header). Same recipe as the library modal: pin to viewport - edges and let the inner body absorb the remaining height. */ -body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .modal-content { - position: fixed !important; - inset: 0 !important; - left: 0 !important; - top: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; - display: flex !important; - flex-direction: column !important; -} -body:not(.email-doc-split-active) .email-window-modal.email-window-fullscreen .email-window-body { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; -} - -/* Email library modal: fullscreen mode + larger mobile sheet (mirrors the - cookbook treatment so the toolbar and bulk actions stay reachable). - Leaves whichever navigation strip is currently visible on the left - (the wide sidebar OR the icon rail — they're mutually exclusive, so - one of the two CSS vars is 0 at any given time, set by init.js). */ -body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { - position: fixed !important; - top: 0 !important; - left: calc(var(--icon-rail-w, 48px) + var(--sidebar-w, 0px)) !important; - right: 0 !important; - bottom: 0 !important; - width: auto !important; - max-width: none !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - transform: none !important; -} -/* Mobile: the /email route adds .email-lib-fullscreen, but the desktop full- - bleed rule above squares off the corners and offsets the email by the icon - rail. On phones the email should be the normal bottom-sheet — full width, - rounded top corners. Same specificity as the rule above + later in source, - so it wins on mobile. */ -@media (max-width: 768px) { - body:not(.email-doc-split-active) #email-lib-modal.email-lib-fullscreen:not(.modal-right-docked) .modal-content { - left: 0 !important; - right: 0 !important; - width: 100vw !important; - max-width: 100vw !important; - height: 100dvh !important; - max-height: 100dvh !important; - border-radius: 14px 14px 0 0 !important; - border-top: 1px solid var(--border) !important; - } -} -/* Make the inner panes actually grow to fill the fullscreen container - instead of staying at their natural size. The body owns the remaining - height below the header; the admin-card + grid then expand into it. */ -#email-lib-modal.email-lib-fullscreen .modal-body { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; -} -#email-lib-modal.email-lib-fullscreen .modal-body > .admin-card { - flex: 1 1 auto !important; - min-height: 0 !important; -} -#email-lib-modal.email-lib-fullscreen .doclib-grid { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow: auto !important; -} -@media (max-width: 768px) { - /* Mobile email modal sizing — keep in sync with the rule earlier in the - file. 75dvh tall always so flex children (the expanded email reader) - have a real height to grow into. */ - #email-lib-modal .modal-content { - max-height: 90dvh !important; - max-height: 90vh !important; - height: 90dvh !important; - height: 90vh !important; - } - /* Inner panes grow to fill the modal-content — without flex:1 on the - body, the expanded email reader sits in a tiny box because there's - nothing pushing it to take the remaining height. overflow:hidden + - min-height:0 lets each layer pass its constraints down. */ - #email-lib-modal .modal-body, - #email-lib-modal .admin-card { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; - max-height: none !important; - } - #email-lib-modal .doclib-grid { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow-y: auto !important; - } - /* modal-content keeps its scroll OFF here — the inner flex children - (doclib-grid for the list view, email-reader-body for the expanded - reader) own the scroll surfaces. Without this, double-scroll layouts - trap touches and the reader can't claim full height. */ - #email-lib-modal .modal-content { - overflow: hidden !important; - -webkit-overflow-scrolling: touch; - overscroll-behavior: contain; - touch-action: pan-y; - } - /* Attachment row on phones: cap to ~2 rows so it never dominates the - reader. If more chips exist, the row scrolls vertically. Smaller chip - padding + max-width keeps each chip compact so two rows usually fit - everything anyway. */ - #email-lib-modal .email-reader-atts { - padding: 4px 10px !important; - gap: 3px !important; - max-height: calc(2 * 22px + 12px); /* 2 rows × chip height + padding */ - overflow-y: auto; - align-content: flex-start; - } -#email-lib-modal .email-attachment-chip { - padding: 1px 6px !important; - font-size: 10px !important; - max-width: min(240px, calc(100vw - 44px)) !important; - line-height: 18px !important; - } -} - - -/* Mobile: only ONE scroll surface inside the cookbook modal. The - modal-content is the scroller. Everything inside (cookbook-body, - cookbook-group, all the inner lists/panels) gets overflow: visible - so touch-pan never gets trapped in a nested scroller. - Without this, three levels of overflow:auto + max-height combinations - produce dead-zone areas where swipes do nothing. */ -@media (max-width: 768px) { - #cookbook-modal .modal-content { - overflow-y: auto !important; - -webkit-overflow-scrolling: touch; - overscroll-behavior: contain; - touch-action: pan-y; - } - #cookbook-modal .cookbook-body, - #cookbook-modal .cookbook-group, - #cookbook-modal .cookbook-section-body, - #cookbook-modal .hwfit-cached-list, - #cookbook-modal .doclib-grid, - #cookbook-modal .hwfit-list, - #cookbook-modal .hwfit-panel-cmd { - overflow: visible !important; - max-height: none !important; - min-height: 0 !important; - } - /* Running tmux output: cap how tall an expanded card gets (a long-lived job - can leave thousands of lines) so it doesn't balloon, but keep its own - scroll so you can read it. Outputs default collapsed on mobile now, so - you're usually looking at one expanded card at a time — no wall of nested - scrollers, and you collapse it to move past. */ - #cookbook-modal .cookbook-output-pre, - #cookbook-modal .cookbook-task .cookbook-output-pre { - max-height: 45vh !important; - min-height: 0 !important; - overflow-y: auto !important; - overflow-x: hidden !important; - overscroll-behavior: auto; /* chain to the modal at the scroll boundary */ - } - /* cookbook-body's flex:1 was needed when it owned scrolling — drop it - so the inner content drives modal-content's scroll height. */ - #cookbook-modal .cookbook-body { - flex: 0 0 auto !important; - height: auto !important; - } - /* Launch tab's cached-list normally has `flex: 1 1 0; min-height: 0` - (so it fills the modal on desktop). On mobile the parent now has - `height: auto`, which collapses `flex: 1 1 0` to ZERO PX — - models render but the list area is invisible because the flex - basis is 0 and there's no free space to grow into. Switch to - content-sized flex so the list grows with its children. */ - #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > .hwfit-cached-list, - #cookbook-modal .cookbook-group[data-backend-group="Serve"] > .admin-card > #hwfit-cached-list { - flex: 0 0 auto !important; - overflow: visible !important; - } -} -#cookbook-modal .hwfit-cached-list { - flex-shrink: 0; -} -.memory-toolbar { - transition: opacity 0.12s ease, max-height 0.2s ease; - max-height: 120px; -} -.doclib-card { - background: color-mix(in srgb, var(--fg) 3%, transparent); - border: 1px solid var(--border); - border-radius: 8px; - cursor: pointer; - transition: all 0.15s, opacity 0.3s, max-height 0.3s ease; - position: relative; -} -.doclib-card.doclib-card-deleting { - opacity: 0; - max-height: 0 !important; - overflow: hidden; - margin: 0; - padding: 0; - border-color: transparent; -} -.doclib-card.selected, -/* Universal selection highlight: any card-shaped row (documents, chats, - archive, research, skills, memories, tasks) gets the same accent outline - when its checkbox is checked. Done via :has() so the highlight follows - selection without per-renderer JS changes. The canonical checkbox class - is .memory-select-cb across every list. */ -.memory-item:has(.memory-select-cb:checked), -.doclib-card:has(.memory-select-cb:checked), -.doclib-chat-row:has(.memory-select-cb:checked) { - border-color: color-mix(in srgb, var(--red) 40%, transparent); - background: color-mix(in srgb, var(--red) 4%, transparent); - /* 2px accent outline overlaid on the 1px border — reads as a thicker - selected border without shifting layout (every card would otherwise - need a 2px transparent border to keep the same width). */ - outline: 2px solid var(--accent-primary, var(--red)); - outline-offset: -1px; -} -.doclib-card-header { - display: flex; - align-items: center; - padding: 7px 8px; - gap: 6px; - min-width: 0; -} -/* Mobile only: push the unexpanded email card's title-row text down 4px so - it sits visually centered with the surrounding card padding. Targets the - actual title-row class (.email-card-titlerow) — the earlier attempt used - .doclib-card-header which the email card builds differently. */ -@media (max-width: 768px) { - #email-lib-modal .doclib-card:not(.doclib-card-expanded) .email-card-titlerow { - margin-top: 4px; - } -} -.doclib-card-title { - font-size: 11px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; - flex: 1; -} -.doclib-card-ver { - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent, var(--red)) 15%, transparent); - color: var(--accent, var(--red)); - font-size: 9px; - flex-shrink: 0; - font-weight: 600; - letter-spacing: 0.3px; - min-width: 24px; - text-align: left; - box-sizing: border-box; -} -.doclib-card-ver-muted { - background: color-mix(in srgb, var(--fg) 6%, transparent); - color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.doclib-card-lang { - padding: 1px 6px; - border-radius: 8px; - background: color-mix(in srgb, var(--accent) 10%, transparent); - color: var(--accent); - font-size: 9px; - text-align: left; - flex-shrink: 0; - min-width: 52px; - box-sizing: border-box; -} -.doclib-card-session { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 60px; - width: 100px; - max-width: 100px; - font-size: 9px; - color: var(--fg-muted); - flex-shrink: 0; - text-align: left; -} -.doclib-card-time { - flex-shrink: 0; - opacity: 0.5; - font-size: 9px; - color: var(--fg-muted); - min-width: 50px; - text-align: left; -} -/* Footer — used by archive cards */ -.doclib-card-footer { - display: contents; -} -/* Preview — hidden by default, shown on expand */ -.doclib-card-preview { - display: none; - position: relative; - padding: 8px 12px; - font-family: 'Berkeley Mono', 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.5; - color: var(--code-fg, var(--hl-fg, var(--fg))); - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin: 0; -} -.doclib-card-preview pre { - margin: 0; - white-space: pre-wrap; - word-break: break-all; -} -.doclib-card-expanded .doclib-card-preview pre { - flex: 1 1 0; - overflow-y: auto; - min-height: 0; -} -.doclib-card-preview code.hljs { - background: none; - padding: 0; -} -/* Expanded-only action bar — inside preview. Buttons shifted up 4px by - trimming the row's top margin so they sit closer to the preview text. */ -.doclib-card-expanded-actions { - display: none; - align-items: flex-start; - gap: 6px; - padding: 8px 0 2px; - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 4px; -} -.doclib-card-expanded-actions > .doclib-card-action-btn, -.doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - position: relative; - top: -4px; -} -.doclib-action-group { - display: flex; - flex-direction: column; - gap: 3px; -} -.doclib-action-btn-row { - display: flex; - gap: 6px; -} -.doclib-action-hint-row { - display: flex; - gap: 6px; -} -/* Match the chat/research footer buttons exactly: flat, bordered, - app font, accent on hover. */ -.doclib-card-action-btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - box-sizing: border-box; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - cursor: pointer; - /* These buttons live inside .doclib-card-preview, which forces a - monospace font. The chat/research footer buttons instead inherit the - modal font, which is the app font (Fira Code) on mobile and Inter on - desktop (.modal-content's Inter rule is inside @media min-width:769px). - Mirror that here so all three footers match in both contexts. */ - font-family: var(--font-family, 'Fira Code', monospace); - transition: border-color 0.15s, color 0.15s; -} -@media (min-width: 769px) { - .doclib-card-action-btn { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - letter-spacing: -0.015em; - } -} -.doclib-card-action-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -.doclib-card-text-btn-danger { - color: var(--color-danger, #e06c75); - border-color: var(--color-danger, #e06c75); -} -.doclib-card-text-btn-danger:hover { - color: #ff4d4d; - border-color: #ff4d4d; -} -.doclib-card-expanded-actions > .doclib-card-action-btn { - font-size: 10px; - padding: 3px 8px; -} -/* Push the Open/Clone group to the right edge of the action bar — Delete - anchors the left, primary actions sit opposite. */ -.doclib-card-expanded-actions > .doclib-action-group { - margin-left: auto; -} -.doclib-card-expanded-actions > .doclib-expanded-delete-btn, -.doclib-card-expanded-actions > .doclib-expanded-archive-btn { - left: 0 !important; -} -.doclib-expanded-mobile-more { display: none; } -.doclib-btn-hint { - font-weight: normal; - opacity: 0.35; - font-size: 8px; - min-width: 58px; - padding: 0 8px; - box-sizing: border-box; -} -@media (max-width: 768px) { - .doclib-btn-hint { display: none !important; } -} - -/* Chat row expand preview — matches the documents-tab expand style. */ -.doclib-card-chevron { - display: inline-flex; - align-items: center; - justify-content: center; - align-self: center; - flex-shrink: 0; - width: 14px; - height: 14px; - line-height: 0; - opacity: 0.3; - transform: rotate(0deg); - transition: transform 0.2s ease, opacity 0.15s; -} -.doclib-card-chevron svg { display: block; } -.doclib-card-expanded .doclib-card-chevron, -.task-card.expanded .doclib-card-chevron { - opacity: 0.8; - transform: rotate(180deg); -} -.doclib-card > .memory-item-actions { order: 1; } -.doclib-card > .doclib-card-chevron { order: 2; } -.doclib-chat-header .memory-item-actions, -.task-card .memory-item-actions { order: 2; } -.doclib-chat-header .doclib-card-chevron, -.task-card .doclib-card-chevron { order: 3; } -.hwfit-cached-list > .doclib-card > .memory-item-actions { order: 2; } -.hwfit-cached-list > .doclib-card > .doclib-card-chevron { order: 3; } -#email-lib-modal .doclib-card > .memory-item-actions { order: 1; align-self: center; } -#email-lib-modal .doclib-card > .doclib-card-chevron { display: none; } -.doclib-chat-row { flex-direction: column; align-items: stretch; } -.doclib-chat-row .doclib-card-chevron { - display: inline-flex; - opacity: 0.55; - transition: transform 0.2s ease, opacity 0.15s; - flex-shrink: 0; -} -.doclib-chat-row:not(.doclib-card-expanded) .doclib-card-chevron { - transform: rotate(0deg); -} -.doclib-chat-row.doclib-card-expanded .doclib-card-chevron { - opacity: 0.8; - transform: rotate(180deg); -} -/* Release .memory-item's base max-height: 200px when a chat row is - expanded. Without this, the row clips at 200px on desktop and the - preview's messages/actions visually spill past the row's box — - "colliding" with the next chat in the column. The mobile takeover - below already handles this via the same override; this rule is the - desktop-or-any-viewport equivalent. */ -.doclib-chat-row.doclib-card-expanded { - max-height: none; - overflow: visible; -} -/* When a chat row is expanded, take over the grid the same way documents - do: hide siblings, hide the admin-card toolbar, and let this row plus - its preview claim the full available height. .memory-item's default - max-height: 200px must be overridden or the preview will be tiny. */ -/* Mobile-only "card takeover" for an expanded chat — hides siblings, - hides the admin-card toolbar, and lets this row + its preview claim - the full available height. On desktop the expanded preview just - renders inline below the row with its messages constrained by - max-height + overflow:auto so the user gets a normal scroll. */ -@media (max-width: 820px) { - .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - flex: 1 1 auto !important; - max-height: none !important; - overflow-y: auto !important; - } - .doclib-chat-row.doclib-card-expanded { - flex: 1 1 auto !important; - max-height: none !important; - min-height: 0 !important; - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - align-items: stretch !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-header { - flex: 0 0 auto !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { - flex: 1 1 auto !important; - min-height: 0 !important; - overflow: hidden !important; - display: flex !important; - flex-direction: column !important; - padding-bottom: 4px !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { - -webkit-overflow-scrolling: touch; - max-height: none !important; - flex: 1 1 auto !important; - min-height: 0 !important; - } -} -.doclib-chat-preview { - font-size: 11px; - padding: 0 4px 6px; -} -.doclib-chat-preview .doclib-chat-open-btn { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - color: var(--fg-muted); - border: 1px solid var(--border); - background: none; - cursor: pointer; - font-family: inherit; - transition: border-color 0.15s, color 0.15s; -} -.doclib-chat-preview .task-completed-report-btn { - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, var(--border)); -} -.doclib-chat-preview .doclib-chat-open-btn:hover { - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -.doclib-chat-preview-messages { - margin-top: 6px; - max-height: 320px; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - padding-right: 4px; -} -/* Desktop expanded state — match the documents-tab behavior: hide - sibling chats, hide the admin-card toolbar/header, and let the - expanded row + its messages preview claim the full modal height. - Chat rows use `memory-item doclib-chat-row` (no `doclib-card` class) - so the document-tab's global rules at line 12284-12304 skip them — - these mirror those rules scoped to chat rows. The mobile media query - above (820px) keeps the same takeover behavior for phones. */ -@media (min-width: 821px) { - .doclib-grid:has(.doclib-chat-row.doclib-card-expanded) > .doclib-chat-row:not(.doclib-card-expanded) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > *:not(.doclib-grid) { - display: none; - } - .admin-card:has(.doclib-chat-row.doclib-card-expanded) > .doclib-grid { - flex: 1 1 auto !important; - max-height: none !important; - overflow: hidden auto !important; - } - .doclib-chat-row.doclib-card-expanded { - flex: 1 1 auto !important; - max-height: none !important; - min-height: 0 !important; - display: flex !important; - flex-direction: column !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview { - flex: 1 1 auto !important; - min-height: 0 !important; - display: flex !important; - flex-direction: column !important; - } - .doclib-chat-row.doclib-card-expanded .doclib-chat-preview-messages { - flex: 1 1 auto !important; - min-height: 0 !important; - max-height: none !important; - overflow-y: auto !important; - } -} -.doclib-chat-preview-actions { - display: flex; - justify-content: flex-end; - align-items: center; - gap: 8px; - padding: 2px 0 2px; - border-top: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 2px; - flex-shrink: 0; - /* Whole action footer nudged down 5px. */ - position: relative; - top: 5px; -} -.doclib-chat-delete-btn, -.doclib-chat-archive-btn, -.doclib-chat-restore-btn, -.doclib-chat-discuss-btn, -.doclib-chat-copy-btn { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - padding: 3px 8px; - border-radius: 4px; - background: none; - cursor: pointer; - font-family: inherit; - transition: border-color 0.15s, color 0.15s; -} -.doclib-chat-archive-btn, -.doclib-chat-restore-btn, -.doclib-chat-discuss-btn, -.doclib-chat-copy-btn { - color: var(--fg-muted); - border: 1px solid var(--border); -} -.doclib-chat-restore-btn:hover { - color: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -.doclib-chat-archive-btn:hover, -.doclib-chat-discuss-btn:hover, -.doclib-chat-copy-btn:hover { - color: var(--accent, var(--red)); - border-color: var(--accent, var(--red)); -} -.doclib-chat-delete-btn { - color: var(--color-danger, #e06c75); - border: 1px solid var(--color-danger, #e06c75); -} -.doclib-chat-delete-btn:hover { - color: #ff4d4d; - border-color: #ff4d4d; -} -/* When a chat is archived there's no Archive button, so Delete becomes - the only left-side action and needs the auto margin to push Open right. */ -.doclib-chat-preview-actions:not(:has(.doclib-chat-archive-btn)) > .doclib-chat-delete-btn { - margin-right: auto; -} -/* Hide the "..." menu while the chat card is expanded — archive + - delete live in the preview footer instead. */ -.doclib-chat-row.doclib-card-expanded ._chat-menu { display: none; } -.doclib-chat-preview-actions.doclib-chat-preview-actions-top { - border-top: none; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); - margin-top: 4px; - margin-bottom: 6px; - padding: 0 0 6px; - justify-content: flex-start; -} -.doclib-chat-preview-messages .doclib-chat-msg { - margin: 4px 0 10px; - padding-left: 8px; - border-left: 2px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.doclib-chat-preview-messages .doclib-chat-msg.user { - border-left-color: color-mix(in srgb, var(--accent, var(--red)) 60%, transparent); -} -.doclib-chat-preview-messages .doclib-chat-msg-role { - font-size: 9px; - font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - opacity: 0.55; - margin-bottom: 2px; -} -.doclib-chat-preview-messages .doclib-chat-msg.user .doclib-chat-msg-role { - color: var(--accent, var(--red)); - opacity: 0.85; -} -.doclib-chat-preview-messages .doclib-chat-msg-body { - white-space: pre-wrap; - word-break: break-word; - opacity: 0.85; - line-height: 1.45; -} -/* Chat-bubble preview — mirrors the real chat layout. User bubbles hug - right with an accent tint; assistant bubbles hug left with a neutral - panel tint and a small model tag at the top. */ -.doclib-chat-bubble-row { - display: flex; - margin: 6px 0; -} -.doclib-chat-bubble-row.user { justify-content: flex-end; } -.doclib-chat-bubble-row.assistant { justify-content: flex-start; } -.doclib-chat-bubble { - max-width: 85%; - padding: 6px 10px 8px; - border-radius: 14px; - font-size: 11px; - line-height: 1.45; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 4%, transparent); -} -.doclib-chat-bubble-row.user .doclib-chat-bubble { - background: color-mix(in srgb, var(--accent, var(--red)) 14%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); - border-bottom-right-radius: 4px; -} -.doclib-chat-bubble-row.assistant .doclib-chat-bubble { - border-bottom-left-radius: 4px; -} -.doclib-chat-msg-model { - display: inline-block; - font-size: 8px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; - opacity: 0.55; - margin-bottom: 3px; - padding: 1px 6px; - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: var(--fg-muted, var(--fg)); -} -.doclib-chat-bubble-body { word-break: break-word; } -.doclib-chat-bubble-body p { margin: 4px 0; } -.doclib-chat-bubble-body p:first-child { margin-top: 0; } -.doclib-chat-bubble-body p:last-child { margin-bottom: 0; } -.doclib-chat-bubble-body pre { - font-size: 10px; - margin: 4px 0; - padding: 6px 8px; - background: color-mix(in srgb, var(--bg) 60%, transparent); - border-radius: 6px; - overflow-x: auto; -} -.doclib-chat-bubble-body code { - font-size: 10px; - padding: 1px 4px; - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.doclib-chat-bubble-body pre code { background: none; padding: 0; } -.doclib-chat-bubble-body ul, -.doclib-chat-bubble-body ol { - margin: 4px 0; - padding-left: 18px; -} -.doclib-card-collapse-chevron { - display: none; - align-items: center; - justify-content: center; - width: 24px; - height: 22px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: transparent; - color: var(--color-muted, var(--fg)); - font: inherit; - opacity: 0.3; - cursor: pointer; - flex-shrink: 0; - margin: 0 0 0 4px; - transition: opacity 0.15s, background 0.15s, border-color 0.15s; -} -.doclib-card-collapse-chevron:hover { - opacity: 1; - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 24%, transparent); -} -.cookbook-serve-title { - display: flex; - align-items: center; - min-width: 0; -} -.cookbook-serve-title .doclib-card-collapse-chevron { - display: inline-flex; - margin-left: auto; - margin-top: 0; -} -.doclib-card-expanded .doclib-card-collapse-chevron { - display: inline-flex; -} -/* Expanded card — fills the whole grid */ -.doclib-card.doclib-card-expanded { - flex: 1; - min-height: 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); - border: 1px solid var(--border); - border-radius: 8px; - display: flex; - flex-direction: column; - overflow: hidden; - animation: doclib-expand 0.2s ease-out; -} -.doclib-card.doclib-card-expanded { - flex-direction: column !important; - align-items: stretch !important; - max-height: none !important; - gap: 4px !important; -} -.doclib-card.doclib-card-expanded > .memory-item-actions { display: none; } -/* Non-preview children of an expanded doc card carry an inline flex:1 from - the row layout. With the card now flex-column those children would steal - half the height and shrink the preview — pin them at auto height. */ -.doclib-card.doclib-card-expanded > div:not(.doclib-card-preview):not(.doclib-card-header):not(.memory-item-actions):not(.email-card-reader) { - flex: 0 0 auto !important; -} -/* The email reader IS the scroll container — keep its flex:1 so its - internal body can claim the rest of the card's height and scroll. */ -.doclib-card.doclib-card-expanded > .email-card-reader { - flex: 1 1 auto !important; - min-height: 0 !important; -} -.doclib-card.doclib-card-expanded .doclib-card-preview { - display: flex; - flex-direction: column; - flex: 1 1 auto !important; - /* The preview matches chat rows: document content scrolls, while the - footer stays in the card rather than becoming the last scroll item. */ - overflow: hidden; - min-height: 0; -} -.doclib-card.doclib-card-expanded .doclib-card-expanded-actions { - display: flex; - flex-shrink: 0; -} -/* Document cards can be expanded in the normal windowed library, where the - grid has no definite height. A zero-basis then collapses to nothing - while the footer remains visible. Keep the document text's intrinsic height - in that layout; bounded/fullscreen layouts can still shrink it and scroll. */ -#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview > pre { - flex: 1 1 auto; - min-height: 1.5em; -} - -/* Collapsible skills section headers (Your skills / Built-in). */ -.skills-section-header { - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - user-select: none; - padding: 6px 4px 4px; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.06em; - opacity: 0.6; - font-weight: 600; -} -.skills-section-header:hover { opacity: 0.9; } -.skills-section-chevron { transition: transform 0.15s ease; flex-shrink: 0; } -.skills-section-header.collapsed .skills-section-chevron { transform: rotate(-90deg); } -.skills-section-count { - margin-left: auto; - opacity: 0.6; - font-variant-numeric: tabular-nums; - font-weight: normal; -} -.skill-card-section-hidden { display: none !important; } -/* Warning banner shown when previewing/editing a built-in capability. */ -.skill-builtin-warn { - font-size: 9.5px; - line-height: 1.45; - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 35%, transparent); - border-radius: 6px; - padding: 6px 9px; - margin: 0 0 8px; - flex-shrink: 0; -} - -/* #skills-list wears both .memory-list and .doclib-grid. doclib-grid's - max-height:400px would cap the list AND clamp an expanded card — keep - the memory-list fill-the-modal behaviour instead. */ -#skills-list.doclib-grid { - max-height: none; - flex: 1; - min-height: 0; -} -#skills-list.doclib-grid:has(.doclib-card-expanded) { - overflow: hidden; /* expanded card owns its own scroll */ -} -/* When a skill is expanded, hide the sibling Add-Skill form card so the - expanded SKILL.md uses the full panel. Otherwise the two admin-cards - split the height ~50/50 and the expanded skill is stuck in its half - while the Add form idles in the other. */ -.memory-tab-panel[data-memory-panel="skills"]:has(.doclib-card-expanded) > .admin-card:nth-of-type(2) { - display: none !important; -} - -/* Skills cards reuse the doclib expand/footer machinery. The SKILL.md - preview + the edit textarea need to fill the expanded card and own - their own scroll, same as a document preview. */ -/* Collapsed skill rows used a smaller (0.9em code) title, so the click row - read slimmer than document/chat/library cards. Give the header a min-height - so the tap target matches the other library items. */ -.skill-card > .skill-card-header { - min-height: 53px; - box-sizing: border-box; - display: flex; - align-items: center; - gap: 6px; -} -/* Skill rows share Tasks' card hover colors. Draft state belongs in the - status badge, not in a second opacity layer across the whole row. */ -/* Name + description column. Name wraps to 2 lines (skill slugs are long — - use the space rather than truncating to "check-model-downl…"). */ -.skill-card-textcol { - flex: 1 1 auto; - min-width: 0; - overflow: hidden; -} -.skill-card-title-row { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.skill-card-name { - font-family: inherit; - font-weight: 500; - font-size: 12px; - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - word-break: normal; - line-height: normal; -} -.skill-card-title-row .skill-card-name { min-width: 0; } -.skill-status-published { - width: 20px; - height: 20px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 15%, transparent); - border-color: color-mix(in srgb, var(--color-success, #4ade80) 32%, transparent); - border-radius: 50%; -} -.skill-stats .skill-status-published { - width: 15px; - height: 15px; - margin-left: 3px; - vertical-align: middle; -} -.skill-stats .skill-status-published svg { - width: 9px; - height: 9px; -} -.skill-card-desc { - font-size: 10px; - opacity: 0.4; - margin-top: -1px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.skills-audit-model-field { - display: flex; - align-items: center; - gap: 8px; - margin-top: 10px; - font-size: 11px; -} -.skills-audit-model-field > span { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 68%, transparent); -} -.skills-audit-model-field select { - min-width: 0; - flex: 1; - height: 26px; - padding: 0 6px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.skill-card-tags { - display: flex; - gap: 4px; - flex-wrap: wrap; - margin-top: 4px; -} -.skill-tag-pill { - padding: 1px 5px; - font-size: 9px; -} -.skill-usage-pill { - margin-left: auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.skill-tag-filter-chip { cursor: pointer; } - -@media (max-width: 768px) { - .skill-card > .skill-card-header { - align-items: flex-start; - } - .skill-card-textcol { - min-width: 0; - } - .skill-card-tags { - max-width: 100%; - overflow: hidden; - } - .skill-card-tags .skill-tag-pill { - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -} -/* Right cluster — pills (right-aligned), stats, then the menu/chevron. */ -.skill-card-right { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; - margin-left: auto; -} -.skill-card-right .memory-cat-badge { flex-shrink: 0; } -.skill-status-bin { - background: color-mix(in srgb, var(--color-danger, #d9534f) 20%, transparent); - color: var(--color-danger, #d9534f); -} -.skill-model-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-model-student { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.skill-audit-pass { - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 16%, transparent); -} -.skill-audit-needs_work { - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 16%, transparent); -} -.skill-audit-fail { - color: var(--color-danger, #e06c75); - background: color-mix(in srgb, var(--color-danger, #e06c75) 16%, transparent); -} -.skill-audit-inconclusive, -.skill-audit-skipped, -.skill-audit-unknown { - color: color-mix(in srgb, var(--fg) 64%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-model-teacher { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 24%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-necessity-duplicate { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 22%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-trivial { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 62%, transparent); - border-color: color-mix(in srgb, var(--fg) 16%, transparent); -} -.skill-necessity-irrelevant { - background: color-mix(in srgb, var(--color-danger, #e06c75) 18%, transparent); - color: var(--color-danger, #e06c75); -} -.skills-summary-strip { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - margin: -3px 0 8px; -} -.skills-summary-strip::-webkit-scrollbar { display: none; } -.skills-summary-strip:empty { - display: none; -} -.skills-summary-chip { - display: inline-flex; - align-items: center; - gap: 5px; - height: 23px; - padding: 0 8px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 10px; - line-height: 1; - cursor: pointer; - flex: 0 0 auto; -} -.skills-summary-chip:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.skills-summary-chip.active { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - color: var(--fg); -} -.skills-summary-chip strong { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10px; - color: var(--fg); -} - -/* Shared filter/tag strip contract. These rows all sit immediately below - their screen's search or primary toolbar and stay one line at every width. */ -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -) { - align-items: center; - flex-wrap: nowrap !important; - gap: 6px; - width: 100%; - max-width: 100%; - min-width: 0; - max-height: none; - overflow-x: auto !important; - overflow-y: hidden !important; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - box-sizing: border-box; - padding: 0; - margin: -3px 0 8px; -} -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -)::-webkit-scrollbar { display: none; } -:is( - .skills-summary-strip > .skills-summary-chip, - #memory-category-filters > .skills-summary-chip, - .memory-category-filters > .memory-cat-chip, - .doclib-lang-chips > :is(.memory-cat-chip, .skills-summary-chip), - .doclib-chips > .doclib-chip, - .notes-labels-bar > .notes-label-chip, - .tasks-activity-filters > .memory-cat-chip, - .gallery-tag-chips > .gallery-chip, - .gallery-album-chips > .gallery-chip, - .gallery-ai-tags > .gallery-ai-chip, - .cal-filters > .cal-filter-item -) { - flex: 0 0 auto; - box-sizing: border-box; - height: 23px !important; - min-height: 23px; - padding: 0 8px !important; - border-radius: 6px !important; - font-size: 10px !important; - line-height: 1; - white-space: nowrap; -} -.doclib-lang-chips { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.doclib-chip-scroll-frame { - position: relative; - display: flex; - align-items: center; - flex: 1 1 100%; - min-width: 0; - max-width: 100%; -} -.doclib-chip-scroll-frame > .chip-scroll-strip { - flex: 1 1 auto; - min-width: 0; - margin-left: 0; - margin-right: 0; -} -.doclib-chip-scroll-arrow { - display: none; - position: absolute; - top: calc(50% - 1px) !important; - transform: translateY(-50%); - margin-top: 0; - z-index: 2; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - box-sizing: border-box; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 18px; - cursor: pointer; - opacity: 0.84; - transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease; -} -.doclib-chip-scroll-arrow-glyph { - display: inline-block; - position: relative; - top: -1px; - line-height: 1; -} -.doclib-chip-scroll-arrow:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); -} -@media (max-width: 768px) { - .doclib-chip-scroll-arrow { display: none !important; } -} -.doclib-chip-scroll-frame.has-overflow > .doclib-chip-scroll-arrow:not([hidden]) { - display: inline-flex; -} -.doclib-chip-scroll-arrow.left { left: 0; } -.doclib-chip-scroll-arrow.right { right: 0; } -.doclib-chip-scroll-frame.has-overflow > .chip-scroll-strip { - padding-left: 0 !important; - padding-right: 22px !important; -} -.doclib-chip-scroll-frame > .chip-scroll-strip.is-pointer-dragging { - cursor: grabbing; - user-select: none; -} -.doclib-chip-scroll-frame > .chip-scroll-strip:not(.is-pointer-dragging) { - cursor: grab; -} -/* Calendar tags are wrapped by chipScroll.js after render. Keep that wrapper - content-sized inside the calendar's column layout; the default flex growth - otherwise turns the one-line tag strip into a large empty panel. */ -#cal-body > .doclib-chip-scroll-frame:has(> .cal-filters) { - flex: 0 0 auto; - min-height: 0; -} -/* The completed-task filters are a compact toolbar, not a flexible panel. - Without this override the shared chip frame grows into the available - height of the card, which is especially visible the first time the mobile - view switches to Completed. */ -.tasks-completed-card > .doclib-chip-scroll-frame:has(> #tasks-completed-status-chips) { - flex: 0 0 auto; - min-height: 0; -} -/* The active Tasks filter row is also a compact toolbar. Keep its wrapper - content-sized so changing a filter cannot make the mobile panel grow a - large empty gap between the chips and the task cards. */ -.admin-card > .doclib-chip-scroll-frame:has(> #tasks-filter-chips) { - flex: 0 0 auto; - min-height: 0; -} -#memory-category-filters { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.cookbook-serve-tag-label { - display: inline-flex; - align-items: center; - min-width: 0; -} -#serve-search, -#hwfit-cache-select, -#serve-tags { - position: relative; - top: -2px; -} -#hwfit-cache-select { - top: -4px; -} -/* Calendar previously framed its tags as a separate card. It now follows the - same unframed placement as the other filter strips. */ -.cal-filters { - border: 0; - background: none; - box-shadow: none; -} -.skill-duplicate-keep { - background: color-mix(in srgb, var(--color-success, #4ade80) 22%, transparent); - color: var(--color-success, #4ade80); -} -.skill-duplicate-group { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - color: color-mix(in srgb, var(--fg) 62%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-duplicate-lower { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); - color: var(--color-danger, #e06c75); -} -.skill-stats { - font-size: 9px; - font-family: monospace; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.skill-turns-saved { - font-size: 9px; - opacity: 0.82; - font-variant-numeric: tabular-nums; - white-space: nowrap; - flex: 0 0 auto; -} -.skill-expanded-savings { - display: flex; - align-items: center; - min-height: 18px; - margin: 0 0 5px; -} -.skill-turns-positive { - color: color-mix(in srgb, var(--color-success, #4ade80) 82%, var(--fg)); -} -.skill-turns-zero, -.skill-turns-unknown { - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.skill-turns-negative { - color: color-mix(in srgb, var(--color-danger, #e06c75) 80%, var(--fg)); -} -.skill-conf { - display: inline-block; - min-width: 4ch; - text-align: right; - font-weight: 700; - color: var(--accent, var(--red)); -} -.skill-verified, -.skill-teachermark, -.skill-needsmark { - display: inline-flex; - align-items: center; - vertical-align: middle; - margin-right: 3px; -} -.skill-verified { color: var(--accent, #4ade80); } -.skill-teachermark { color: var(--color-warning, #f0ad4e); } -.skill-needsmark { color: var(--color-danger, #e06c75); cursor: help; } - -/* The card currently being processed by an "Audit now" run glows + pulses so - it's obvious which one the audit is on. */ -@keyframes skill-audit-pulse { - 0%, 100% { box-shadow: 0 0 6px 0 color-mix(in srgb, var(--accent, #4ade80) 55%, transparent); } - 50% { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent, #4ade80) 85%, transparent); } -} -.skill-card.skill-audit-active { - border-color: var(--accent, #4ade80) !important; - animation: skill-audit-pulse 1.3s ease-in-out infinite; -} - -/* A test is running for this skill — the app's whirlpool spinner is injected - next to the name from JS (see _setCardRunning) so the collapsed/folded card - still makes it obvious work is in progress. */ -/* Collapsed bar shows the kebab menu; expanded shows an up-chevron to - collapse. (Toggled by the .doclib-card-expanded class.) */ -.skill-kebab-btn { - display: inline-flex; - align-items: center; - justify-content: center; - background: none; - border: none; - padding: 4px; - margin: 0; - cursor: pointer; - color: var(--fg); - opacity: 0.5; - border-radius: 5px; - flex-shrink: 0; - position: relative; - top: -2px; -} -.skill-kebab-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-chevron-up { display: none; align-items: center; opacity: 0.5; flex-shrink: 0; } -/* Kebab dropdown */ -.skill-kebab-menu { - position: fixed; - /* z-index is set inline via topPortalZ() at open time (#4720); a static - value here loses once the modal bring-to-front counter climbs past it. */ - min-width: auto; - width: max-content; - padding: 4px; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 6px 20px rgba(0,0,0,0.22); - display: flex; - flex-direction: column; - gap: 1px; -} -.skill-kebab-item { - display: flex; - align-items: center; - gap: 10px; - width: 100%; - padding: 6px 8px; - background: none; - border: none; - border-radius: 8px; - cursor: pointer; - color: var(--fg); - font-size: 11px; - text-align: left; -} -.skill-kebab-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-kebab-item.danger { color: var(--color-error, #e55); } -.skill-kebab-item.danger:hover { background: color-mix(in srgb, var(--color-error, #e55) 15%, transparent); } -/* Section labels separating "Your skills" from the read-only "Built-in - capabilities" list. */ -.skills-section-label { - font-size: 10px; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.04em; - margin: 10px 2px 4px; - flex-shrink: 0; -} -.skills-section-label:first-child { margin-top: 0; } -/* When a learned skill is expanded the grid hides sibling cards; hide the - section labels too so they don't orphan above the collapsed list. */ -#skills-list:has(.doclib-card-expanded) .skills-section-label { display: none; } -/* Built-in cards are read-only — no expand/hover-pointer affordance. */ -.skill-builtin-card { cursor: default; } -.skill-builtin-card:hover { border-color: var(--border); } -.skill-card-preview .skill-md-pre { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - white-space: pre-wrap; - word-break: break-word; -} -.skill-preview-copy { - position: absolute; - top: 6px; - right: 8px; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - padding: 0; - opacity: 0; - color: var(--fg); - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - cursor: pointer; - transition: opacity .15s, color .15s, border-color .15s; -} -.skill-card-preview:hover .skill-preview-copy { opacity: 0.7; } -.skill-preview-copy:hover, -.skill-preview-copy:focus-visible { opacity: 1 !important; } -.skill-preview-copy.copied { - opacity: 1; - color: var(--color-save-green, #4caf50); - border-color: var(--color-save-green, #4caf50); -} -/* :has()-free expand layout. skills.js adds .skills-has-expanded to the - skills admin-card when a skill opens, so this works on engines that don't - support :has() (e.g. older Firefox mobile, where the expand stuck at - ~50%). Hide everything but the list so the grid fills the card; the - absolutely-positioned expanded card (mobile) then fills the grid. */ -.admin-card.skills-has-expanded > *:not(#skills-list) { - display: none !important; -} -.admin-card.skills-has-expanded > #skills-list.doclib-grid { - flex: 1 1 auto !important; - height: 100% !important; - min-height: 0 !important; - overflow: hidden !important; -} -/* Expand animation — a clean opacity fade. The mobile card snaps to a - full-screen overlay (position:absolute), so any translate/scale on it - reads as a jarring "explosion"; a pure fade just appears smoothly. (No - transform also keeps skills.js's height measurement accurate.) */ -@keyframes skill-card-expand { - from { opacity: 0; } - to { opacity: 1; } -} -.skill-card.doclib-card-expanded { - animation: skill-card-expand 0.18s ease-out; -} -/* Switching directly from one expanded card to another: no fade (it would - show the previous card collapsing through the semi-transparent new one). */ -.skill-card.doclib-card-expanded.skill-expand-instant { - animation: none !important; -} -@media (prefers-reduced-motion: reduce) { - .skill-card.doclib-card-expanded, - .skill-card.doclib-card-expanded .doclib-card-preview { - animation: none !important; - } -} -/* The preview WRAPPER inherits flex:1 (grow) from the generic doclib expand - rule — that's what stretched the card and left a void / pushed the footer - out. Size it to content (shrink + scroll only when long) so the footer - sits right under the preview and stays fully visible. */ -.skill-card.doclib-card-expanded .doclib-card-preview { - flex: 0 1 auto !important; - min-height: 0; - /* The footer lives INSIDE this wrapper. Don't let the wrapper scroll, or - the footer scrolls off with the content. Keep it clipped; the - inside is the scroller, so the footer stays pinned at the bottom. */ - overflow: hidden !important; -} -.skill-card.doclib-card-expanded .skill-md-pre { - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them - back down 4px so they're vertically centered in the skill card footer. */ -.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn, -.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - top: 0; -} -/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */ -#skills-bulk-bar .memory-toolbar-btn { - position: relative; - top: -2px; -} -/* ── Audit-all progress panel ── */ -.skills-audit-panel { - border: 1px solid var(--border); - border-radius: 8px; - padding: 8px 10px; - margin: 6px 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } -.skills-audit-title { font-size: 12px; font-weight: 600; } -.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; } -.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; } -.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; } -.skills-audit-log { - max-height: 22vh; overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10.5px; line-height: 1.5; opacity: 0.8; -} -/* ── Skill test monitor + AI eval verdict ── */ -.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; } -.skill-test-log { - flex: 1 1 auto; - min-height: 120px; - max-height: 48vh; - overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.45; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - white-space: pre-wrap; - word-break: break-word; -} -.skill-test-log > div { margin: 1px 0; } -.skill-test-meta { opacity: 0.5; } -.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; } -.skill-test-round { opacity: 0.6; margin-top: 6px !important; } -.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; } -.skill-test-out { opacity: 0.7; padding-left: 10px; } -.skill-test-say { white-space: pre-wrap; } -.skill-test-err { color: var(--color-danger, #e06c75); } -.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } -.skill-eval-badge { - font-size: 10px; font-weight: 700; letter-spacing: 0.03em; - padding: 2px 8px; border-radius: 10px; flex-shrink: 0; -} -.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); } -.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); } -.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); } -.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); } -.skill-eval-summary { font-size: 11px; opacity: 0.85; } -.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; } -.skill-eval-issues li { margin: 2px 0; } -.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } -.skill-eval-approve.suggested { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; -} -/* Already published — the button confirms the state (click to unpublish). */ -.skill-eval-approve.is-approved { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; - background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important; -} -/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title, - When to use, How, Tags) is accent-colored while the rest stays muted — real - placeholders can't be partially colored. The native placeholder is a single - space so :placeholder-shown still toggles the overlay. */ -.skill-ph-wrap { position: relative; } -.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; } -.skill-rich-ph { - position: absolute; - left: 11px; right: 11px; top: 50%; - transform: translateY(-50%); - pointer-events: none; - font-size: 12px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 40%, transparent); - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -/* Textarea: align the hint to the first line instead of vertical-centering. */ -.skill-rich-ph-top { top: 7px; transform: none; } -.skill-rich-ph .k { color: var(--accent, var(--red)); } -/* Keep the memory-input enter hint pinned to the field's right edge instead - of letting it drift after the example text. */ -#new-memory-input ~ .skill-rich-ph > svg { - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - margin-left: 0 !important; -} -/* Hide the overlay once the field has real content. */ -.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; } -.skill-md-editor { - flex: 1 1 auto; - min-height: 0; - width: 100%; - box-sizing: border-box; - resize: none; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); -} -.doclib-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -/* Unified loading row across every Library tab (Chats / Documents / Research / - Archive): one opacity, one size, with the whirlpool spinner next to it. */ -.lib-loading-row { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 28px 16px; - font-size: 12px; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.pdf-loading-state { - min-height: 120px; - margin: 0; - box-sizing: border-box; - flex-direction: column; - gap: 10px; - border: 0; - border-radius: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - box-shadow: none; - font-size: 12px; - font-weight: 600; - letter-spacing: 0.01em; -} -.pdf-loading-state .ai-spinner-whirlpool { - color: var(--accent, var(--red)); - opacity: 0.95; -} -.pdf-loading-state .ai-spinner-whirlpool canvas { - display: block; -} -.doclib-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg-muted); - font-size: 11px; - cursor: pointer; - transition: border-color 0.15s, color 0.15s; - flex-shrink: 0; - position: relative; - top: 10px; -} -/* Soft dark gradient above the button — blends the list content into the - load-more strip instead of a hard cutoff. Spans the modal width and - sits behind/above the button. */ -.doclib-load-more::before { - content: ""; - position: absolute; - left: 50%; - transform: translateX(-50%); - bottom: 100%; - margin-bottom: 4px; - width: min(600px, 92vw); - height: 34px; - pointer-events: none; - background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent); -} -.doclib-load-more:hover { - border-color: var(--red); - color: var(--red); -} -.doclib-inline-load-more { - border: 0; - border-radius: 0; - padding: 0; - background: none; - top: 0; - font-size: 9px !important; - line-height: 1.2 !important; -} - -/* Document library toolbar buttons */ -.doclib-toolbar-btn { - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - font-size: 11px; - padding: 5px 10px; - border-radius: 6px; - cursor: pointer; - transition: all 0.15s; - white-space: nowrap; - font-family: inherit; - display: inline-flex; - align-items: center; - gap: 3px; -} -.doclib-toolbar-btn:hover { - color: var(--fg); - border-color: var(--fg); -} -.doclib-toolbar-btn.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -.doclib-toolbar-btn.danger { - color: var(--color-error, #e55); -} -.doclib-toolbar-btn.danger:hover:not(:disabled) { - border-color: var(--color-error, #e55); -} -.doclib-toolbar-btn:disabled { - opacity: 0.4; - cursor: default; -} - -/* Bulk action bar */ -.doclib-bulk-bar { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--red) 5%, transparent); - font-size: 10px; - margin-bottom: 8px; -} -.doclib-bulk-bar.hidden { - display: none; -} -.doclib-bulk-check-all { - display: flex; - align-items: center; - gap: 3px; - cursor: pointer; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-size: 10px; -} -#doclib-bulk-bar .memory-toolbar-btn { - position: relative; - top: -3px; -} - -/* Custom checkboxes */ -.doclib-select-cb, -.doclib-bulk-check-all input { - -webkit-appearance: none; - appearance: none; - width: 13px; - height: 13px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - cursor: pointer; - flex-shrink: 0; - margin: 0; - position: relative; - transition: all 0.15s; -} -.doclib-select-cb:hover, -.doclib-bulk-check-all input:hover { - border-color: var(--red); -} -.doclib-select-cb:checked, -.doclib-bulk-check-all input:checked { - background: var(--red); - border-color: var(--red); -} -.doclib-select-cb:checked::after, -.doclib-bulk-check-all input:checked::after { - content: ''; - position: absolute; - left: 3px; - top: 0px; - width: 4px; - height: 8px; - border: solid var(--bg); - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); -} - - -/* + button outside scroll area (when doc is on right side) */ - -@media (max-width: 600px) { - .doclib-toolbar { - flex-direction: column; - } - .doclib-card-session, - .doclib-card-time { - display: none; - } - .doclib-card-preview { - max-height: 40vh; - } -} - -/* Collapse card tags before they compete with attachment/favorite controls. */ -.email-card-tags .email-tags-more { - color: var(--email-tags-more-color, var(--fg)); - background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent); -} -.email-tags-more-single, -[data-email-tags-count-mini] { display: none; } -.email-tags-more-single svg { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag, -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra { - display: none; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single, -.email-card-tags.email-tags-expanded .email-tags-more-single { - display: inline-flex; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini], -.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; } - -/* Keep the reader actions in drafting order. */ -.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; } -.email-reader-actions-inline > [data-act="reply"] { order: 2; } -.email-reader-actions-inline > [data-act="reply-all"] { order: 3; } -.email-reader-actions-inline > [data-act="forward"] { order: 4; } -.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed { - display: none !important; -} - -/* ── Archive browser ── */ -.archive-list { - grid-template-columns: 1fr !important; - gap: 4px !important; -} -.archive-row { - flex-direction: row !important; -} -.archive-row .doclib-card-header { - flex: 1; - padding: 8px 10px; -} -.archive-row .doclib-card-title { - flex: 1; - min-width: 0; -} -/* Archive column layout */ -.archive-header { - display: flex; - align-items: center; - padding: 4px 10px; - gap: 6px; - font-size: 9px; - font-weight: 600; - opacity: 0.35; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.archive-col-title { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: -4px; -} -.archive-col-msgs { - width: 40px; - flex-shrink: 0; - text-align: left; - margin-left: -8px; -} -.archive-col-model { - width: 90px; - flex-shrink: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.archive-col-time { - width: 55px; - flex-shrink: 0; - text-align: right; -} -.archive-col-menu { - width: 24px; - flex-shrink: 0; -} -.archive-menu-btn { - background: transparent; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - padding: 4px; - line-height: 0; - border-radius: 4px; - transition: background .15s, opacity .15s; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - margin-left: 4px; - vertical-align: middle; - height: 100%; -} -.archive-menu-btn svg { - display: block; - margin-top: -6px; -} -.archive-menu-btn:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - opacity: 1; -} - -/* ── Gallery (image library) ── */ - -.gallery-modal-content { - width: min(820px, 94vw); - max-height: 92vh; - font-size: 12px; -} -/* Keep the Gallery frame anchored while tabs or bulk-selection controls - change the content height. Centering the frame made every shorter tab move - the whole window vertically. */ -@media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal, - #cookbook-modal { - align-items: flex-start; - padding-top: 4vh; - box-sizing: border-box; - } -} -/* While the Edit tab is active the editor needs a *definite* height so - the right-side tool panel (`.ge-right-panel { overflow-y:auto }`) - scrolls INTERNALLY. Without this the modal only has `max-height`, so - `.gallery-editor { height:100% }` can't resolve — the editor grows to - its full content height, overflows the modal body, and the modal-body - scrollbar that appears can't be wheel-scrolled because the editor's - `overscroll-behavior:contain` blocks the chain. Pinning a real height - here makes the panel bounded and scrollable as designed. Scoped to - the editor view (matched via the container's inline `display:flex`) - so the Photos/Albums views keep sizing to their content. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) { - height: 92vh; -} -/* The editor tab owns the modal body's scroll context. Keep the landing - chooser and the mounted editor inside the available sheet height so a long - saved-project list cannot push the launch controls below the viewport. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body { - display: flex; - min-height: 0; - overflow: hidden; -} -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container { - min-height: 0; - overflow: hidden; -} -/* Photo-detail view sizing (issue #314). - The detail view is rendered as a `position:absolute; inset:0` overlay - *inside* `.gallery-images-container`, painted over the photo grid. Because - it's absolutely positioned it can't contribute to the container's height — - the container (and therefore the overlay's `inset:0` box) collapses to the - height of the grid sitting behind it. When the library only has a few - photos that grid is short, so the detail view is crushed: the image is - clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a - tiny, internally-scrolling strip. (With a large library the grid is tall, - which is why it looked fine in the demo video but cramped for users with - few photos.) - Fix: when the detail view is open, hide the grid-view siblings and drop the - overlay into normal flow. The container — and the window, up to its 92vh - max-height — then sizes to the detail's own content (image + metadata), so - nothing is clipped or squeezed regardless of how many photos exist. Scoped - via the detail element's inline `display:flex` so the grid / albums views - keep sizing to their own content. Works on both desktop and the mobile - full-screen sheet. */ -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) { - display: none !important; -} -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail { - position: static; -} -/* Containing block for the photo-detail overlay — keeps it inside the body - so it sits below the modal header and the tab strip instead of covering them. */ -.gallery-images-container { - position: relative; -} -.gallery-modal-content .modal-header h4 { - font-size: 1rem; -} -.gallery-stats { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-bottom: 4px; -} -.gallery-toolbar { - display: flex; - gap: 6px; - margin-bottom: 4px; - align-items: center; -} -.gallery-toolbar-break { display: none; } -/* Keep the hint anchored while independently seating the input in the row. */ -.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; } -.gallery-search { - position: relative; - top: 4px; - flex: 1; - width: 100%; - padding: 4px 8px; - padding-right: 78px; /* room for the enter hint */ - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - font: inherit; - font-size: 12px; - outline: none; -} -.gallery-search-enter-hint { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - font-weight: 600; - color: var(--accent, var(--red)); - pointer-events: none; - opacity: 0; - transition: opacity 0.12s; - white-space: nowrap; -} -/* Show the hint only once the user has typed something (placeholder gone). */ -.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; } -.gallery-search:focus { - border-color: var(--red); -} -.gallery-model-filter, -.gallery-sort { - padding: 4px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - position: relative; - top: 4px; -} -.gallery-selection-bar { - margin-bottom: 4px; - position: relative; - top: 2px; - padding-right: 18px; -} -.gallery-tag-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 4px; -} -.gallery-chip { - height: 28px; - box-sizing: border-box; - padding: 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - border-radius: 14px; - font-size: 12px; - line-height: 1; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: border-color 0.15s, background 0.15s; -} -.gallery-chip:hover { - border-color: var(--red); -} -.gallery-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Album chips row */ -.gallery-album-chips { - display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px; -} -.gallery-chip-fav { color: var(--red); } -.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); } -.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; } -/* Active-album indicator — appears when the Photos grid is filtered to one album. */ -.gallery-chip-active-album, -.gallery-active-filter-pill { - display: inline-flex; align-items: center; gap: 4px; - background: color-mix(in srgb, var(--red) 14%, transparent); - border-color: color-mix(in srgb, var(--red) 45%, transparent); - position: relative; - top: 6px; -} -.gallery-active-filter-pill { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - color: color-mix(in srgb, var(--fg) 86%, transparent); -} -.gallery-clear-all-filters { - height: 28px; - border-style: dashed; - opacity: 0.72; -} -.gallery-clear-all-filters:hover { - opacity: 1; - color: var(--red); -} -.gallery-chip-clear { - background: none; - border: none; - color: inherit; - opacity: 0.6; - cursor: pointer; - padding: 0; - margin-left: 1px; - width: 12px; - height: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 12px; - line-height: 1; - position: relative; - top: -4px; -} -.gallery-chip-clear:hover { opacity: 1; } - - - -/* Favorite button on card */ -/* Video cards: fills the same slot as , plus a small play badge - so it's clear the thumbnail represents a video. */ -.gallery-card video { - width: 100%; - aspect-ratio: 1 / 1; - object-fit: cover; - background: #000; - display: block; -} -.gallery-card-play { - position: absolute; - left: 50%; top: 50%; - transform: translate(-50%, -50%); - width: 36px; height: 36px; - display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.55); - color: #fff; - border-radius: 50%; - pointer-events: none; - z-index: 1; -} -.gallery-card-play svg { margin-left: 2px; } - -.gallery-fav-btn { - position: absolute; top: 0px; right: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; font-size: 14px; - color: rgba(255,255,255,0.6); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s; - padding: 0; -} -.gallery-fav-btn.gallery-fav-active { opacity: 1; color: var(--red); } - -.gallery-dl-btn { - position: absolute; top: 0px; left: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; - color: rgba(255,255,255,0.75); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s, background 0.15s; - padding: 0; -} -.gallery-dl-btn:hover { background: rgba(0,0,0,0.7); color: #fff; } -/* In select mode the per-thumbnail hover buttons (favorite + download) just - get in the way of picking — hide them so the card is a clean select target. - `body.gallery-selecting` is the authoritative signal (one class for the - whole grid) — the per-card .gallery-card-selectable is kept as a backup - in case the body class is missed. */ -body.gallery-selecting .gallery-fav-btn, -body.gallery-selecting .gallery-dl-btn, -.gallery-card-selectable .gallery-fav-btn, -.gallery-card-selectable .gallery-dl-btn { - display: none !important; -} - -/* AI tag chips in detail view */ -.gallery-ai-tags { display: flex; gap: 4px; flex-wrap: wrap; } -.gallery-ai-chip { - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); - border-radius: 10px; padding: 3px 8px; font-size: 10px; - color: var(--fg); opacity: 0.8; - font: inherit; font-size: 10px; - cursor: pointer; - transition: background 0.12s, opacity 0.12s, border-color 0.12s; -} -.gallery-ai-chip:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); -} -/* AI-generated tags: muted neutral with a tiny sparkle marker so the - user can tell at a glance these came from the model, not them. */ -.gallery-aitag-chip { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-aitag-chip:hover { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.gallery-aitag-mark { - font-size: 8px; - opacity: 0.55; - color: var(--accent, var(--red)); -} -/* User-applied tags keep the accent-colored chip — they're the personal/ - curated tags, so they get the strongest visual weight. */ -.gallery-user-chip { - font-weight: 600; -} -/* × to remove a user tag (appears inside the chip). */ -.gallery-tag-x { - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 5px; - width: 13px; - height: 13px; - border-radius: 50%; - font-size: 12px; - line-height: 1; - opacity: 0.55; - transition: opacity 0.12s, background 0.12s, color 0.12s; -} -.gallery-tag-x:hover { - opacity: 1; - background: color-mix(in srgb, var(--red, #ff5555) 22%, transparent); - color: var(--red, #ff5555); -} - -.gallery-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - align-content: start; - gap: 8px; - max-height: calc(92vh - 165px); - overflow-y: auto; - padding: 2px; - transition: border-color 0.15s, background 0.15s; -} -.gallery-grid.gallery-dragover { - border: 2px dashed var(--red); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.gallery-card { - position: relative; - aspect-ratio: 1; - border-radius: 6px; - overflow: hidden; - border: 1px solid var(--border); - cursor: pointer; - transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s; -} -.gallery-card.gallery-card-focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 36%, transparent); -} -/* Upload-affordance tile pinned to the top-left of the Photos grid. Matches - the Upload album tile in the Albums tab — dashed border, centered icon. */ -.gallery-card-upload { - border-style: dashed; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - opacity: 0.75; - transition: opacity 0.12s, border-color 0.15s, transform 0.15s; -} -/* The "Start AI tag" button turns into a Cancel control during a tag run. */ -.gallery-tag-cancelling { - color: var(--red) !important; - border-color: color-mix(in srgb, var(--red) 45%, var(--border)) !important; -} -/* Skeleton/shimmer placeholder tiles shown while the first page of photos loads. */ -.gallery-card-skeleton { - cursor: default; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.gallery-card-skeleton:hover { transform: none; box-shadow: none; border-color: var(--border); } -/* Chat attachment image: shimmer skeleton + centered whirlpool shown while a - sent photo uploads / its thumbnail loads (see chatRenderer buildAttachCards). */ -.attach-image-skeleton { - position: relative; - width: 160px; height: 120px; - border-radius: 6px; - display: flex; align-items: center; justify-content: center; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.attach-image-skeleton > .spinner-whirlpool { margin: 0 !important; } - -/* Corner "Aa" button on a chat photo thumbnail — opens the vision/OCR editor - so the user can correct what the vision model fed to the LLM. */ -.attach-image-preview { position: relative; } -.attach-vision-model, -.attach-image-name { - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.attach-vision-model { - font-size: 10px; - color: var(--accent, var(--red)); - opacity: 0.85; - margin-top: 3px; -} -.attach-image-name { - font-size: 10px; - opacity: 0.5; - margin-top: 1px; -} -.attach-ocr-btn { - position: absolute; top: 4px; right: 4px; - z-index: 5; /* sit above msg-action overlays so the click lands here */ - height: 22px; - background: rgba(0,0,0,0.55); color: #fff; - border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; - padding: 0 8px 0 6px; cursor: pointer; - display: inline-flex; align-items: center; justify-content: center; gap: 5px; - font-size: 11px; font-weight: 500; line-height: 1; - opacity: 0.75; transition: opacity 0.15s; -} -.attach-ocr-btn:hover { opacity: 1; } -.attach-ocr-btn svg { display: block; } -/* On mobile keep just the (larger) edit icon — the "Caption" label crowds - the corner of the small chat photo. */ -@media (max-width: 768px) { - .attach-ocr-btn .attach-ocr-label { display: none; } - .attach-ocr-btn { - width: 28px; height: 28px; - padding: 0; - } - .attach-ocr-btn svg { width: 16px; height: 16px; } -} - -/* Vision/OCR text editor modal — opened from the "Aa" button above. */ -.vision-editor-overlay { - position: fixed; inset: 0; z-index: 9999; - background: rgba(0,0,0,0.55); - display: flex; align-items: center; justify-content: center; - padding: 16px; -} -.vision-editor-panel { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 14px 16px; - width: min(560px, 92vw); - max-height: 88vh; - display: flex; flex-direction: column; gap: 8px; - box-shadow: 0 14px 40px rgba(0,0,0,0.4); -} -.vision-editor-title { - font-size: 13px; font-weight: 600; color: var(--fg); - display: flex; align-items: center; gap: 6px; -} -.vision-editor-desc { font-size: 11px; opacity: 0.6; line-height: 1.4; } -.vision-editor-text { - width: 100%; min-height: 180px; - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 8px 10px; - font-family: inherit; font-size: 12px; line-height: 1.5; - resize: vertical; outline: none; -} -.vision-editor-text:focus { border-color: var(--accent-primary, var(--red)); } -.vision-editor-hint { font-size: 10.5px; opacity: 0.55; line-height: 1.4; } -.vision-editor-actions { - display: flex; gap: 8px; justify-content: flex-end; - margin-top: 4px; -} -.vision-editor-btn { - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; - transition: border-color 0.15s, background 0.15s; - display: inline-flex; align-items: center; gap: 5px; -} -.vision-editor-btn svg { display: block; } -/* Optical nudge: the button text sits 1px higher than the SVG glyphs - alongside it. Drop the text down 1px so they line up. */ -.vision-editor-btn .vision-btn-label { position: relative; top: 1px; } -.vision-editor-btn:hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); } -.vision-editor-btn-primary { - background: var(--accent-primary, var(--red)); - color: #fff; border-color: transparent; -} -.vision-editor-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } - -/* Quick full-size lightbox shown when the user taps a chat photo thumbnail. */ -.attach-lightbox { - position: fixed; inset: 0; z-index: 9998; - background: rgba(0, 0, 0, 0.85); - display: flex; align-items: center; justify-content: center; - padding: 16px; cursor: zoom-out; - animation: attach-lightbox-fade 0.12s ease-out; -} -.attach-lightbox img { - max-width: 100%; max-height: 100%; - border-radius: 6px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); - object-fit: contain; -} -.attach-lightbox-err { - position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); - background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 10px; font-size: 12px; -} -@keyframes attach-lightbox-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -.gallery-card-upload:hover { - opacity: 1; - border-color: var(--red); - transform: translateY(-1px); -} -.gallery-card-upload-inner { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 6px; - color: var(--fg); -} -.gallery-card-upload-label { - font-size: 12px; - font-weight: 500; - opacity: 0.8; -} -.gallery-card:hover { - border-color: var(--red); - box-shadow: 0 2px 8px color-mix(in srgb, var(--red) 15%, transparent); - transform: translateY(-1px); -} -.gallery-card-overlay { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 8px; - background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 34%, rgba(0,0,0,0.22)); - color: #fff; - opacity: 0; - pointer-events: none; - transition: opacity 0.15s; -} -.gallery-card-badges { - display: flex; - gap: 4px; - flex-wrap: wrap; - align-items: flex-end; - justify-content: flex-end; - margin-top: auto; - margin-bottom: 34px; -} -.gallery-card-badge { - padding: 2px 6px; - border-radius: 999px; - background: rgba(0, 0, 0, 0.55); - border: 1px solid rgba(255, 255, 255, 0.14); - color: rgba(255, 255, 255, 0.88); - font-size: 9px; - font-weight: 600; - line-height: 1.1; -} -.gallery-card-badge-fav { - color: #ff5f6f; -} -.gallery-select-dot { - position: absolute; top: 6px; left: 6px; z-index: 2; - display: block; - width: 10px; height: 10px; border-radius: 50%; cursor: pointer; - background: color-mix(in srgb, var(--fg) 30%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 50%, transparent); - transition: background 0.15s; -} -.gallery-select-dot.selected { background: var(--red); border-color: var(--red); } -.gallery-select-dot:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -.gallery-select-btn { - padding: 9px 10px 11px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; transition: all 0.15s; - margin-top: -4px !important; -} -.gallery-select-btn:hover { opacity: 1; } -/* Match the library Select toggle's red tint (.memory-toolbar-btn.active). - Bumped contrast so the toggled state actually reads on mobile — the prior - 15% red on transparent was nearly invisible against the page background. */ -.gallery-select-btn.active { - background: color-mix(in srgb, var(--red) 28%, transparent); - color: var(--red); - border-color: var(--red); - font-weight: 600; - opacity: 1; -} -/* Toolbar action buttons sit 2px lower than the generic select-btn so - they baseline-align with the toolbar's selects/inputs instead of - floating above them. */ -.gallery-toolbar-action { - margin-top: 0 !important; - display: inline-flex; - align-items: center; - gap: 2px; -} -.gallery-bulk-bar { - display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 12px; - justify-content: flex-end; -} -.gallery-bulk-bar.hidden { display: none; } -.gallery-bulk-all { - display: inline-flex; align-items: center; gap: 4px; - font-size: 11px; opacity: 0.75; cursor: pointer; margin-right: auto; -} -.gallery-bulk-all input { accent-color: var(--accent, var(--red)); cursor: pointer; } -.gallery-bulk-delete { - padding: 3px 10px; background: transparent; color: var(--red); - border: 1px solid var(--red); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; -} -.gallery-bulk-delete:hover { background: var(--red); color: #fff; } -.gallery-bulk-cancel { - padding: 3px 10px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; -} -.gallery-bulk-cancel:hover { opacity: 1; } -.gallery-card img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} -.gallery-card-info { - position: absolute; - bottom: 0; - left: 0; - right: 0; - padding: 6px 8px; - background: linear-gradient(transparent, rgba(0,0,0,0.75)); - color: #fff; -} -.gallery-card-prompt { - font-size: 10px; - line-height: 1.3; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.gallery-card-meta { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - font-size: 9px; - opacity: 0.8; -} -.gallery-card-model { - padding: 1px 5px; - border-radius: 6px; - background: rgba(255,255,255,0.15); -} -.gallery-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -.gallery-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: none; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-load-more:hover { - border-color: var(--red); - color: var(--red); -} - -/* Gallery detail overlay */ -.gallery-detail { - position: absolute; - inset: 0; - background: var(--panel); - z-index: 10; - display: flex; - flex-direction: column; - overflow-y: auto; -} -.gallery-detail-header { - display: flex; - align-items: center; - gap: 6px; - padding: 2px 8px 4px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - /* Pin to top so a tall (portrait) photo can't cover the Back / ⋮ - menu when the detail body grows past the visible area. */ - position: sticky; - top: 0; - background: var(--panel); - /* Above the image's rotate/nav buttons (z-index:3) so they scroll cleanly - behind the header instead of colliding with it. */ - z-index: 5; - min-height: 0; -} -.gallery-detail-title { - max-width: min(360px, 40vw); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 12px; - font-weight: 600; - opacity: 0.82; - position: relative; - top: 1px; -} -.gallery-detail-back { - background: none; - border: none; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 12px; - padding: 4px 8px; - border-radius: 4px; - position: relative; - top: -2px; -} -/* Slightly larger header icons (back/edit/heart/⋮) — override the inline 14px. */ -.gallery-detail-header svg { width: 16px; height: 16px; } -.gallery-detail-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.gallery-detail-action { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - padding: 3px 8px; - border-radius: 4px; - transition: border-color 0.15s; -} -.gallery-detail-action:hover { - border-color: var(--fg); -} -.gallery-detail-action.danger { - color: var(--color-error, #e55); -} -/* Overflow menu — replaces the seven individual action buttons that used - to crowd the right side of the detail header. */ -.gallery-detail-menu-wrap { position: relative; } -.gallery-detail-menu-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 26px; - padding: 0; - position: relative; - top: -2px; -} -.gallery-detail-menu { - position: absolute; - top: calc(100% + 4px); - right: 0; - min-width: 150px; - padding: 3px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 12; - display: flex; - flex-direction: column; - gap: 0; -} -.gallery-detail-menu[hidden] { display: none; } -.gallery-detail-menu .dropdown-item-compact { - width: 100%; - background: none; - border: none; - text-align: left; - font: inherit; - /* Tighter than the global default — the photo-detail menu has 8 - items so every pixel of vertical padding adds up. */ - padding: 4px 8px; - font-size: 11px; - gap: 8px; - line-height: 1.2; -} -.gallery-detail-menu .dropdown-item-compact .dropdown-icon, -.gallery-detail-menu .dropdown-item-compact .dropdown-icon svg { - width: 12px; - height: 12px; -} -.gallery-detail-body { - display: flex; - gap: 16px; - padding: 12px; - flex: 1; - min-height: 0; - overflow: hidden; -} -.gallery-detail-image { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - min-width: 0; - min-height: 0; - height: 100%; - padding-inline: 56px; - box-sizing: border-box; - overflow: hidden; -} -/* Shrink-wraps the actual image so overlay children (heart, face boxes) - anchor to the IMAGE bounds, not the wider letterboxed wrapper. */ -.gallery-detail-img-frame { - position: relative; - display: inline-flex; - max-width: 100%; - max-height: 100%; -} -.gallery-detail-nav { - position: absolute; - top: 50%; - transform: translateY(-50%); - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-nav:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-nav-prev { left: 8px; } -.gallery-detail-nav-next { right: 8px; } - -/* Rotate buttons — same pattern as the prev/next arrows but pinned to the - top corners. Hover-revealed so they don't clutter the image. */ -.gallery-detail-rotate { - position: absolute; - top: 8px; - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-rotate:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-rotate-ccw { left: 8px; } -.gallery-detail-rotate-cw { right: 8px; } -/* Inline heart that lives next to the "Date" label in the sidebar. - Tiny outline icon by default; fills red when favorited. */ -.gallery-detail-date-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; -} -.gallery-detail-fav-inline { - background: none; - border: none; - padding: 0; - /* Nudge up 8px so the heart sits visually above the Date label - baseline rather than centred on it. */ - margin-top: -8px; - border-radius: 4px; - color: var(--fg-muted); - opacity: 0.6; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; - transition: opacity 0.15s, color 0.15s; -} -/* Shrink the SVG so the heart matches the height of the small uppercase - Date label sitting next to it. */ -.gallery-detail-fav-inline svg { - width: 12px; - height: 12px; - display: block; -} -.gallery-detail-fav-inline:hover { - opacity: 1; -} -.gallery-detail-fav-inline.active { - opacity: 1; - color: var(--red); -} -.gallery-detail-sidebar { - width: 290px; - flex-shrink: 0; - /* Vertical scroll for long metadata, but never horizontal — overflow:auto - alone would let a wide child (e.g. long album name in the select) push - the sidebar into horizontal-scroll mode. */ - overflow-x: hidden; - overflow-y: auto; - min-width: 0; -} -.gallery-detail-inspector-head { - padding-bottom: 10px; - margin-bottom: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.gallery-detail-eyebrow { - font-size: 9px; - font-weight: 700; - color: var(--accent, var(--red)); - text-transform: uppercase; - letter-spacing: 0.08em; - margin-bottom: 3px; -} -.gallery-detail-inspector-title { - font-size: 14px; - font-weight: 650; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-meta-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 6px; - margin: 4px 0 12px; -} -.gallery-detail-meta-card { - min-width: 0; - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-radius: 7px; - padding: 7px 8px; -} -.gallery-detail-meta-card span { - display: block; - font-size: 9px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 48%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 3px; -} -.gallery-detail-meta-card strong { - display: block; - min-width: 0; - font-size: 11px; - font-weight: 600; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-inline-link { - appearance: none; - background: none; - border: none; - color: var(--accent, var(--red)); - padding: 0; - font: inherit; - font-weight: inherit; - cursor: pointer; - text-align: left; -} -.gallery-detail-mini-actions { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 7px; -} -.gallery-detail-mini-btn { - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 8px; - font: inherit; - font-size: 10.5px; - cursor: pointer; - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-detail-mini-btn:hover { - border-color: color-mix(in srgb, var(--fg) 42%, var(--border)); -} -/* Constrain any child that could otherwise stretch the sidebar wider than - its declared width (selects with long option text, long URLs, etc.). */ -.gallery-detail-sidebar select, -.gallery-detail-sidebar input, -.gallery-detail-sidebar .gallery-detail-prompt { - max-width: 100%; - box-sizing: border-box; -} -.gallery-date-rel { - opacity: 0.55; - margin-left: 4px; - font-size: 11px; - font-style: italic; -} -.gallery-detail-section label { - display: block; - font-size: 10px; - font-weight: 600; - opacity: 0.6; - margin-bottom: 3px; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.gallery-detail-section div { - font-size: 12px; - word-break: break-word; -} -.gallery-detail-prompt { - white-space: pre-wrap; - line-height: 1.4; -} -.gallery-tag-input, -.gallery-detail-name-input { - width: 100%; - padding: 5px 8px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - font: inherit; - font-size: 11px; - margin-bottom: 4px; -} -.gallery-tag-input:focus, -.gallery-detail-name-input:focus { - border-color: var(--red); - outline: none; -} -/* ↵ enter hint inside the Add-a-tag field (accent), replacing the "press Enter" - placeholder text. */ -.gallery-tag-input-wrap { position: relative; } -.gallery-tag-enter-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; /* the input has 4px bottom margin; center on the box */ - color: var(--accent, var(--red)); - font-size: 13px; - line-height: 1; - pointer-events: none; - opacity: 0.8; -} -.gallery-detail-name-input { font-size: 13px; font-weight: 500; } -/* ↵ enter hint on the image name field — accent, shown while editing to signal - that Enter saves. */ -.gallery-name-wrap { position: relative; } -.gallery-name-enter { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; - color: var(--accent, var(--red)); - opacity: 0.85; - pointer-events: none; - display: none; -} -.gallery-tag-save { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 10px; - padding: 3px 10px; - border-radius: 4px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-tag-save:hover { - border-color: var(--red); - color: var(--red); -} - -@media (max-width: 600px) { - .gallery-grid { - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - } - /* Single-row toolbar on mobile: search + Sources + Recent (sort) all share - one row. The search bar shrinks so the dropdowns fit beside it. */ - .gallery-toolbar { - flex-wrap: nowrap; - gap: 4px; - } - .gallery-search-wrap { - flex: 1 1 0; - min-width: 0; - } - .gallery-search { - /* Drop the desktop right-padding reserved for the "enter to tag" hint — - hide the hint on mobile (below) so the input can use its full width. */ - padding-right: 8px; - } - .gallery-search-enter-hint { display: none; } - /* Disable the row break so the filters stay inline with search. */ - .gallery-toolbar-break { display: none; } - .gallery-model-filter, - .gallery-sort { - flex: 0 0 auto; - /* Cap each dropdown so search keeps a usable amount of width. */ - max-width: 100px; - font-size: 11px; - padding: 4px 6px; - /* Keep the same vertical alignment as the desktop toolbar. */ - position: relative; - top: 4px; - } - .gallery-select-btn { - padding: 6px 8px 8px; - font-size: 9px; - flex-shrink: 0; - /* Push Select to the far right of the single-row toolbar, regardless - of its DOM position (sits between search and the dropdowns). */ - order: 99; - margin-left: auto; - } - /* Tabs scroll horizontally rather than wrapping when narrow. */ - .gallery-tabs { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .gallery-tabs::-webkit-scrollbar { display: none; } - .gallery-tab { flex-shrink: 0; } - /* Album chips also scroll horizontally so they don't stack. */ - .gallery-album-chips { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .gallery-album-chips::-webkit-scrollbar { display: none; } - .gallery-chip { flex-shrink: 0; } - /* Tasks filter chips — same horizontal-swipe behaviour as the gallery - albums / library chips on mobile: one row, slide-to-see-more. */ - .tasks-activity-filters { - overflow-x: auto; - flex-wrap: nowrap !important; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .tasks-activity-filters::-webkit-scrollbar { display: none; } - .tasks-activity-filters > * { flex-shrink: 0; } - /* Detail view */ - .gallery-detail-body { - flex-direction: column; - overflow: visible; - } - .gallery-detail-image { - flex: 0 0 auto; - width: 100%; - height: clamp(280px, 58vh, 520px); - padding-inline: 48px; - } - .gallery-detail-sidebar { - width: 100%; - } - .gallery-detail-title { - display: none; - } - .gallery-detail-meta-grid { - grid-template-columns: 1fr; - } - /* Album grid: smaller tile minimum on mobile. */ - .gallery-albums-grid { - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 8px; - } -} - - -/* ── Scoreboard table ── */ -.scoreboard-table { - width: 100%; - border-collapse: collapse; - font-size: 0.88em; -} -.scoreboard-table th { - text-align: left; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 55%, transparent); - text-transform: uppercase; - letter-spacing: 0.5px; -} -.scoreboard-table th:not(:first-child) { - text-align: center; -} -.scoreboard-table td { - padding: 5px 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; -} -.scoreboard-table td.scoreboard-model { - text-align: left; - font-weight: 500; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.scoreboard-table td.scoreboard-pct { - font-weight: 600; - color: var(--red); -} -.scoreboard-table tbody tr:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} - -#scoreboard-overlay svg { - color: var(--accent, var(--red)); -} - -/* ── Compare search results ── */ -.compare-search-results { - display: flex; - flex-direction: column; - gap: 2px; -} -.compare-search-result { - padding: 6px 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); -} -.compare-search-result:last-child { - border-bottom: none; -} -.search-result-title { - color: var(--color-accent); - text-decoration: none; - font-weight: 500; - font-size: 0.9em; - display: block; - line-height: 1.3; -} -.search-result-title:hover { - color: var(--color-link-hover); - text-decoration: underline; -} -.search-result-snippet { - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.82em; - line-height: 1.4; - margin-top: 2px; -} -.search-result-url { - color: color-mix(in srgb, var(--fg) 35%, transparent); - font-size: 0.75em; - margin-top: 3px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - - -/* ── Cookbook Tool ── */ - -/* ── Cookbook ── */ -.cookbook-serve-preset { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - margin-bottom: 3px; - cursor: pointer; - transition: background 0.15s; -} -.cookbook-serve-preset:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cookbook-serve-preset-name { - font-size: 11px; - font-weight: 500; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-serve-preset-meta { - font-size: 9px; - font-family: 'Fira Code', monospace; - color: var(--fg-muted); - opacity: 0.5; -} -.cookbook-serve-preset-launch { - background: none; - border: none; - color: #50fa7b; - cursor: pointer; - font-size: 10px; - opacity: 0.6; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-launch:hover { opacity: 1; } -.cookbook-serve-preset-rm { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 10px; - opacity: 0.3; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-rm:hover { opacity: 0.8; color: var(--red); } -.cookbook-body { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - flex: 1; - min-height: 0; - scrollbar-width: thin; -} -#cookbook-modal .modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -#cookbook-modal .modal-header { - flex: 0 0 auto; -} -#cookbook-modal .cookbook-body { - min-height: 0; - overflow-y: auto; - overflow-x: hidden; -} -#cookbook-modal .cookbook-group { - min-height: 0; -} -#cookbook-modal .cookbook-group > .admin-card { - min-height: 0; - /* Let .cookbook-body be the SINGLE scroll surface. Nesting another - overflow:auto here trapped the wheel inside the cached-list when a - serve panel expanded — the page couldn't scroll past the panel's - bottom (Launch button got hidden). */ - overflow: visible !important; -} -#cookbook-modal .cookbook-section-body { - min-height: 0; -} -.cookbook-group { - display: flex; - flex-direction: column; - gap: 8px; - flex: 1; - min-height: 0; -} - -/* Cards — match admin-card */ -.cookbook-card { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-card-header { - display: flex; - align-items: center; - justify-content: space-between; -} -.cookbook-card-header-actions { display: flex; gap: 4px; } -.cookbook-card-title { font-size: 13px; font-weight: 600; } -.cookbook-card-desc { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} - -/* Fields grid */ -.cookbook-fields { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 6px; - margin-top: 4px; -} -.cookbook-field-label { - display: flex; - flex-direction: column; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - gap: 3px; -} - -/* Inputs — match admin-add-form input */ -.cookbook-field-input { - padding: 5px 8px; - font-size: 12px; - font-family: inherit; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - outline: none; -} -.cookbook-field-input:focus { border-color: var(--red); } -#hwfit-dl-server, #hwfit-usecase, #hwfit-server-select, #hwfit-cache-server, #serve-sort { height: 28px; width: 88px; } -/* Serve tab — match Documents sizing, but leave room for the active "Cancel" - label and center it vertically so the descenders don't clip. */ -#hwfit-cache-select { - min-width: 58px; - height: 28px; - position: relative; - top: -3px; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; -} - -/* Command preview */ -.cookbook-cmd-preview { - margin: 4px 0 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - color: var(--fg); -} - -/* Buttons — match admin-btn-sm */ -.cookbook-actions { display: flex; gap: 6px; margin-top: 2px; } -.cookbook-btn { - padding: 4px 10px; - min-width: 54px; - text-align: center; - font-size: 11px; - font-family: inherit; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - cursor: pointer; - transition: all 0.15s; -} -.cookbook-btn:hover { - background: var(--border); - border-color: var(--accent); -} -.cookbook-btn:disabled { opacity: 0.5; cursor: not-allowed; } -.cookbook-run-btn { - background: var(--accent); - color: var(--panel); - border-color: var(--accent); - font-weight: 600; -} -.cookbook-run-btn:hover { opacity: 0.85; } -.cookbook-stop-btn { - background: var(--color-error); - border-color: var(--color-error); -} - -/* Output */ -.cookbook-output-wrap { - position: relative; - margin: 0; -} -.cookbook-output-kill { - position: absolute; - top: 4px; - right: 34px; - width: 22px; - height: 22px; - padding: 0; - background: none; - border: 1px solid transparent; - border-radius: 4px; - color: var(--fg-muted); - font-size: 16px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity .15s, color .15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-output-kill:hover { color: var(--color-error, var(--warn)); border-color: var(--border); } -.cookbook-output-wrap .copy-code { - position: absolute; - top: 6px; - right: 6px; -} -.cookbook-output-wrap:hover .copy-code { opacity: 0.7; } -.cookbook-output-pre { - margin: 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - max-height: 180px; - overflow-y: auto; -} -.cookbook-output-error { color: var(--color-error); } - -/* Downloaded/cached models */ -.hwfit-cached-item { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 6px; - padding: 6px 8px; - margin: 2px 0; - border-radius: 6px; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 3%, transparent); - cursor: default; - font-size: 12px; -} -.hwfit-cached-item .hwfit-serve-panel { - flex-basis: 100%; - margin-top: 4px; -} -.hwfit-cached-item:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); -} -.hwfit-cached-header { - font-size: 10px; - opacity: 0.4; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - border: none !important; - background: none !important; - padding: 2px 8px; - cursor: default !important; -} -.hwfit-cached-header:hover { background: none !important; } -.hwfit-cached-name { - font-weight: 600; - flex-shrink: 0; -} -.hwfit-cached-repo { - flex: 1; - color: var(--fg-muted); - font-size: 11px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-cached-size, -.hwfit-cached-files { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; -} -.hwfit-cached-serve, -.hwfit-cached-delete { - flex-shrink: 0; -} -.hwfit-cached-delete { - opacity: 0.4; - font-size: 10px; -} -.hwfit-cached-delete:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} -.hwfit-cached-badge { - font-size: 9px; - padding: 1px 6px; - border-radius: 3px; - font-weight: 600; - white-space: nowrap; -} -.hwfit-cached-ready { - background: color-mix(in srgb, var(--color-success, #4caf50) 20%, transparent); - color: var(--color-success, #4caf50); -} -.hwfit-cached-dl { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 20%, transparent); - color: var(--color-warning, #f0ad4e); -} - -/* Cookbook notification dot */ -#tool-cookbook-btn { - position: relative; -} -#rail-cookbook { - position: relative; -} -.cookbook-open-loading { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - flex-shrink: 0; - margin-left: 2px; - color: var(--accent, var(--red)); - opacity: 0.9; -} -.cookbook-open-loading .ai-spinner, -.cookbook-open-loading .ai-spinner-whirlpool { - display: inline-flex !important; - width: 14px; - height: 14px; - margin: 0 !important; - gap: 0 !important; -} -.cookbook-open-loading canvas { - display: block; -} -.cookbook-section-loading-wp, -.cookbook-section-loading-wp canvas { - width: 22px !important; - height: 22px !important; - display: block !important; -} -#rail-cookbook .cookbook-open-loading { - position: absolute; - right: 2px; - top: -2px; - width: 12px; - height: 12px; - margin-left: 0; -} -/* The serve-list 'downloading' / 'download stalled' status text sits a hair - low against the rest of the meta line on mobile — nudge it up 2px. */ -@media (max-width: 768px) { - .cookbook-dl-status { - position: relative; - top: -2px; - } -} -.cookbook-notif-dot { - width: 6px; - height: 6px; - flex-shrink: 0; - border-radius: 50%; - background: var(--color-success, #4caf50); - /* Drives the breathing glow in the keyframes (matches email's dot). */ - --notif-glow: var(--color-success, #4caf50); - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -.cookbook-notif-dot.cookbook-notif-error { - background: var(--color-error, #f44); - --notif-glow: var(--color-error, #f44); - position: relative; - left: -2px; - top: -1px; -} -.rail-notify-error { color: var(--color-error, #f44) !important; } -.cookbook-tab-error-dot { - display: inline-block; width: 5px; height: 5px; border-radius: 50%; - background: var(--color-error, #f44); margin-left: 4px; vertical-align: middle; - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -@keyframes cookbook-notif-pulse { - 0%, 100% { - opacity: 1; - box-shadow: 0 0 0 0 color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 60%, transparent); - } - 50% { - opacity: 0.85; - box-shadow: 0 0 6px 2px color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 55%, transparent); - } -} -.cookbook-clear-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-clear-btn:hover { - color: var(--red) !important; - border-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 8%, transparent) !important; -} -.cookbook-bulk-bar { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - margin-bottom: 4px; - background: color-mix(in srgb, var(--red) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 6px; - font-size: 11px; -} -.cookbook-bulk-bar.hidden { display: none; } -.serve-select-cb { - width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; cursor: pointer; - background: var(--border); transition: background 0.15s; - align-self: center; - position: relative; - top: -2px; -} -#serve-bulk-bar { - position: relative; - top: -6px; -} -#serve-bulk-bar #serve-bulk-cancel { - top: -2px !important; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 28px; - height: 24px; - line-height: 1; -} -#serve-bulk-bar #serve-bulk-cancel svg { - top: 0; -} -.serve-select-cb.selected { background: var(--red); } -.serve-select-cb:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -#hwfit-cache-select.active { - background: var(--red); - color: #fff; - border-color: var(--red); -} -.cookbook-serve-dirs { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.cookbook-serve-dir-pill { - font-size: 10px; - font-family: 'Fira Code', monospace; - padding: 2px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); - color: var(--fg-muted); - letter-spacing: 0.2px; -} -/* Status pills shown inline in a Serve-tab card title (next to the model - name) when the model has a live serve / download task. Shared base - class so "running" and "downloading" sit on the same row with the - same chrome; only color varies. */ -.cookbook-serve-running-pill, -.cookbook-serve-downloading-pill { - display: inline-block; - margin-left: 6px; - padding: 1px 7px; - border-radius: 10px; - font-size: 9px; - font-weight: 500; - text-transform: lowercase; - letter-spacing: 0.3px; - vertical-align: 2px; - position: relative; - top: 1px; /* nudged down 2px from the old -1px so it sits - flush with the cap-height of the title text */ -} -.cookbook-serve-running-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); -} -.cookbook-serve-downloading-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); - opacity: 0.85; -} -.cookbook-serve-downloading-pill.is-stalled { - /* Stalled downloads stay visible but read as warning, not progress. */ - color: var(--fg-muted, #888); - background: color-mix(in srgb, var(--fg-muted, #888) 10%, transparent); - border-color: color-mix(in srgb, var(--fg-muted, #888) 30%, transparent); - opacity: 1; -} -.cookbook-serve-running-pill.is-clickable { - cursor: pointer; - transition: background 0.12s, border-color 0.12s; -} -.cookbook-serve-running-pill.is-clickable:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -.cookbook-serve-dir-edit { - font-size: 9px; - color: var(--fg-muted); - opacity: 0.4; - cursor: pointer; - text-decoration: underline; - text-underline-offset: 2px; -} -.cookbook-serve-dir-edit:hover { - opacity: 0.7; - color: var(--accent, var(--red)); -} -.cookbook-gpu-group { - display: flex; - gap: 3px; - margin-top: -4px; -} -.cookbook-gpu-btn { - width: 30px; height: 30px; - font-size: 12px; - font-family: 'Fira Code', monospace; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - color: var(--fg-muted); - cursor: pointer; - opacity: 0.4; - transition: all 0.15s; - padding: 0; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-gpu-btn:hover { - opacity: 0.7; - border-color: var(--fg-muted); -} -.cookbook-gpu-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 55%, transparent); - color: var(--fg); - font-weight: 600; -} -/* Probe annotation states (set by "Free?" button) */ -.cookbook-gpu-btn.gpu-free { - opacity: 1; - border-color: #4ade80; - color: #4ade80; -} -.cookbook-gpu-btn.gpu-busy { - opacity: 0.85; - border-color: color-mix(in srgb, var(--red) 70%, transparent); - color: var(--red); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-btn.gpu-missing { - opacity: 0.2; - text-decoration: line-through; - cursor: not-allowed; -} -/* "Clear Server" lives in the actions row next to "Probe GPUs". - Inherits .cookbook-btn sizing; this rule just adds the destructive - red tint on hover so the "this kills processes" cue stays. */ -.cookbook-gpu-clear:hover:not(:disabled) { - color: var(--red); - border-color: color-mix(in srgb, var(--red) 60%, var(--border)); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -/* GPU probe popup — per-GPU process list with kill buttons */ -.cookbook-gpu-popup { - /* Fixed positioning (relative to viewport) so we never get pulled into - a scrolling/transform stacking context from an ancestor. Z-index has - to clear the cookbook modal (260) and the rest of the high-z UI - layers (themed-confirm and various overlays sit around 9000-10000). */ - position: fixed; - z-index: 10010; - min-width: 280px; - max-width: 420px; - background: var(--panel, #1a1a1a); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 8px 24px rgba(0,0,0,0.4); - font-family: 'Fira Code', monospace; - font-size: 11px; - color: var(--fg); -} -.cookbook-gpu-popup-head { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; -} -.cookbook-gpu-popup-stats { - flex: 1; - font-weight: 400; - color: var(--fg-muted); - font-size: 10px; -} -.cookbook-gpu-popup-close { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 14px; - padding: 0 4px; -} -.cookbook-gpu-popup-close:hover { color: var(--fg); } -.cookbook-gpu-popup-body { - padding: 4px 0; - max-height: 320px; - overflow-y: auto; -} -.cookbook-gpu-popup-empty { - padding: 10px; - color: var(--fg-muted); - font-style: italic; -} -.cookbook-gpu-proc { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; -} -.cookbook-gpu-proc:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); } -.cookbook-gpu-proc-info { - flex: 1; - display: flex; - gap: 8px; - align-items: center; - min-width: 0; -} -.cookbook-gpu-proc-pid { - color: var(--fg-muted); - width: 56px; - flex-shrink: 0; -} -.cookbook-gpu-proc-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.cookbook-gpu-proc-mem { - color: var(--fg-muted); - flex-shrink: 0; -} -.cookbook-gpu-proc-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} -.cookbook-gpu-kill { - border: 1px solid color-mix(in srgb, var(--red) 60%, transparent); - background: none; - color: var(--red); - border-radius: 3px; - padding: 2px 8px; - cursor: pointer; - font-family: inherit; - font-size: 10px; -} -.cookbook-gpu-kill[data-sig="KILL"] { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.cookbook-gpu-kill:hover:not(:disabled) { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.cookbook-gpu-kill:disabled { opacity: 0.4; cursor: wait; } -.cookbook-serve-title { - display: flex; - align-items: center; - gap: 4px; - min-width: 0; -} -.cookbook-serve-title-name { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-hf-link { - font-size: 9px; - text-decoration: none; - color: var(--fg-muted); - opacity: 0.5; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - vertical-align: 1px; - letter-spacing: 0.3px; - font-weight: 600; - flex-shrink: 0; -} -.cookbook-hf-link:hover { - opacity: 0.8; - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -/* In the Trending models list the HF pill is the row's main affordance — - tint it accent so it stands out next to the model name. */ -#cookbook-hf-latest-list .cookbook-hf-link { - color: var(--accent, var(--red)); - opacity: 0.85; - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); -} - -/* Running tab sections */ -.cookbook-saved-section, -.cookbook-serve-section, -.cookbook-dl-section { - border: 1px solid var(--border); - border-radius: 6px; - margin-bottom: 6px; - overflow: hidden; -} -/* Divider between server-group sections in the Active tab so the - transition between e.g. local + remote-host server blocks reads - visually instead of running into each other. */ -.cookbook-group[data-backend-group="Running"] > [class*="cookbook-server-section-"] + [class*="cookbook-server-section-"] { - margin-top: 14px; - padding-top: 14px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.cookbook-section-header { - display: flex; - align-items: center; - gap: 6px; - padding: 7px 10px; - cursor: pointer; - user-select: none; - /* Persistent surface + border so it reads as a clickable bar instead of - blending into the panel background. */ - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - transition: background 0.15s, border-color 0.15s; -} -.cookbook-section-header:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: color-mix(in srgb, var(--fg) 22%, transparent); -} -.cookbook-section-header.has-server-color { - color: var(--fg); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 45%, var(--border)); - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 4%, var(--bg)); - box-shadow: inset 6px 0 0 var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color:hover { - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 7%, var(--bg)); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 60%, var(--border)); -} -.cookbook-section-header.has-server-color .cookbook-section-title, -.cookbook-section-header.has-server-color .cookbook-section-chevron { - color: var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color .cookbook-srv-status.ok { - background: var(--cookbook-server-accent, var(--cookbook-server-color)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 55%, transparent); -} -.cookbook-section-header .cookbook-clear-btn { - margin-left: 0; - position: relative; - top: -3px; -} -@media (max-width: 768px) { - .cookbook-section-header .cookbook-clear-btn { - top: -3px; - } - .cookbook-task-check { - top: 4px; - } -} -/* "Stop all" sits just left of "Clear finished"; it carries the auto margin so - the pair is pushed together to the right of the section title. */ -.cookbook-section-header .cookbook-stop-all-btn { - margin-left: auto; - margin-right: 6px; - position: relative; - top: -3px; -} -.cookbook-stop-all-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-stop-all-btn:hover { - color: var(--warn, #f0ad4e) !important; - border-color: var(--warn, #f0ad4e) !important; - background: color-mix(in srgb, var(--warn, #f0ad4e) 8%, transparent) !important; -} -.cookbook-section-chevron { - flex-shrink: 0; - opacity: 0.6; - transition: transform 0.15s ease, opacity 0.15s ease; -} -.cookbook-section-body { - padding: 0; -} -.cookbook-saved-toggle-header { - font-size: 10px; - font-weight: 600; - color: var(--fg-muted); - text-transform: uppercase; - letter-spacing: 0.3px; - margin-bottom: 4px; -} -.cookbook-saved-toggle:hover { color: var(--fg); } -.cookbook-saved-item { - margin: 2px 0; - border-radius: 4px; - font-size: 11px; -} -.cookbook-saved-header { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 6px; - border-radius: 4px; - transition: background 0.08s; -} -.cookbook-saved-header:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.cookbook-saved-item .hwfit-serve-panel { - margin: 4px 0 4px 6px; -} -.cookbook-saved-host { - font-size: 10px; - color: var(--fg-muted); - font-family: 'Fira Code', monospace; -} -.cookbook-saved-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-saved-launch { - font-size: 10px; - padding: 2px 8px; -} -.cookbook-saved-del { - background: none; - border: none; - color: var(--fg-muted); - opacity: 0.3; - cursor: pointer; - font-size: 11px; - padding: 0 2px; -} -.cookbook-saved-del:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} - -/* Serve config panel */ -.hwfit-serve-panel { - margin-top: 6px; - padding: 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex: 1; - display: flex; - flex-direction: column; - position: relative; -} -.cookbook-serve-slots { - display: flex; - gap: 3px; - justify-content: flex-end; - margin-bottom: 4px; -} -/* When the Save split sits inside Row 1 (next to GPUs), align it with the - input baseline (the row's grid cells stretch top-down; without this the - Save buttons sit above the GPU button group). */ -.hwfit-serve-row .cookbook-serve-slots { - align-self: end; - margin-bottom: 4px; -} -.cookbook-slot-btn { - min-width: 22px; height: 28px; - padding: 0 6px; - font-size: 10px; font-weight: 600; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - white-space: nowrap; - max-width: 80px; - overflow: hidden; - text-overflow: ellipsis; - color: var(--fg-muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.5; - transition: all 0.15s; -} -.cookbook-slot-btn:hover { opacity: 0.9; border-color: var(--fg-muted); } -/* Saved-configs split button: "Save" + dropdown-arrow joined into one control, - but each segment keeps its own style — Save uses the filled accent - "Done"-button look; the arrow stays a subtle outlined menu trigger. */ -.cookbook-saved-split { gap: 0; } -.cookbook-saved-save, -.cookbook-saved-arrow { max-width: none; } -.cookbook-saved-save { - padding: 0 10px; - gap: 4px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - font-weight: 600; - opacity: 1; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.cookbook-saved-save:hover { - background: var(--border); - border-color: var(--accent); - opacity: 1; -} -.cookbook-saved-arrow { - padding: 0 6px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - opacity: 1; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: none; -} -.cookbook-saved-menu .cookbook-saved-favorite { - border-left: 3px solid var(--red); - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-saved-fav-btn { - width: 20px; - height: 20px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: none; - color: var(--fg-muted); - opacity: 0.55; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -.cookbook-saved-fav-btn:hover, -.cookbook-saved-fav-btn.active { - color: var(--red); - opacity: 1; - background: color-mix(in srgb, var(--red) 10%, transparent); -} -.cookbook-saved-fav-badge { - flex-shrink: 0; - background: transparent; - padding: 0; -} -.cookbook-serve-favorite-model { - border-left-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-serve-fav-badge { - display: inline-flex; - align-items: center; - vertical-align: 1px; - margin-left: 5px; - margin-right: 2px; - background: transparent; - padding: 0; -} -.cookbook-slot-saved { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); } -.cookbook-slot-saved:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); } -.cookbook-slot-btn.active { opacity: 1; background: var(--accent); color: #fff; border-color: var(--accent); -} -.cookbook-slot-wrap { - position: relative; - display: inline-flex; -} -.cookbook-slot-del { - position: absolute; - top: -5px; - right: -5px; - width: 14px; - height: 14px; - padding: 0; - border: 1px solid var(--border); - border-radius: 50%; - background: var(--bg, #1a1a1a); - color: var(--fg-muted); - font-size: 12px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, color 0.15s, border-color 0.15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-slot-del:hover { - color: #f44; - border-color: #f44; -} -.cookbook-card-backend { display: none; } -/* Dependencies row — aligned tags */ -.cookbook-dep-row { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel); -} -/* Let the deps list fill the available window height instead of being - capped at the default .doclib-grid 400-px box. */ -[data-backend-group="Dependencies"] #cookbook-deps-list { - flex: 1; - max-height: none; -} - -/* Settings tab: stack the HF token + Servers cards as distinct blocks - (matches the Download tab's block-per-section layout). */ -.cookbook-settings-stack { - display: flex; - flex-direction: column; - gap: 10px; - flex: 1; - /* Scroll the whole settings panel so the Servers card can grow to hold every - server (it used to be a cramped internal scroll box that clipped them). */ - overflow-y: auto; - min-height: 0; -} -.cookbook-settings-stack.hidden { display: none; } -.cookbook-dep-row.cookbook-dep-blocked { opacity: 0.4; } -.cookbook-dep-info { flex: 1; min-width: 0; } -.cookbook-dep-section { - display: flex; - align-items: baseline; - gap: 8px; - margin: 12px 2px 4px; -} -.cookbook-dep-section-title { - font-size: 11px; - font-weight: 600; - letter-spacing: 0.03em; -} -.cookbook-dep-section-note { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-subgroup { - margin: 6px 0 8px 12px; - padding: 7px 8px 8px; - border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - border-radius: 0 6px 6px 0; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-dep-subgroup-title { - display: flex; - align-items: baseline; - gap: 7px; - margin: 0 1px 1px; - font-size: 10.5px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 78%, transparent); -} -.cookbook-dep-subgroup-title em { - font-size: 9.5px; - font-style: normal; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.cookbook-dep-tag { - font-size: 9px; - padding: 0 8px; - border-radius: 4px; - white-space: nowrap; - text-align: center; - min-width: 62px; - box-sizing: border-box; - line-height: 1; - /* Explicit height so the tags (Install / Installed) are the EXACT - same height as the sibling tags — Firefox gives buttons a taller - native box otherwise. Mirrors the server-row tag rule (height:24px). */ - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.cookbook-dep-target { - border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-cat { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -/* Rebuild tag — same look as the LLM category tag, sits to its left. */ -.cookbook-dep-rebuild { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 75%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent); - cursor: pointer; - font-family: inherit; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - position: relative; - top: -2px; -} -.cookbook-dep-reinstall { - top: -3px; -} -.cookbook-dep-rebuild:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); -} -.cookbook-dep-installed { - background: color-mix(in srgb, var(--green, #50fa7b) 18%, transparent); - color: var(--green, #50fa7b); - border: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); - /* Match Install + Installed ▾ so all three variants align in mixed rows. */ - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-na { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); - cursor: help; - /* Match other dep tag widths so N/A rows line up with Install / Installed. */ - min-width: 75.85px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-install { - background: var(--accent, var(--red)); - color: #fff; - border: none; - cursor: pointer; - font-family: inherit; - font-weight: 500; - position: relative; - top: -3px; - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - /* Strip the native button box so it's the same height as the sibling tags - (Firefox renders taller otherwise); height comes from .cookbook-dep-tag. */ - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-build-deps { - width: auto; - min-width: 87.7px; - max-width: 100%; - white-space: normal; - overflow-wrap: anywhere; - line-height: 1.15; - text-align: center; - height: auto; - min-height: 24px; -} -/* The install state is the terminal control in a dependency row. Keep it on - the far edge even when optional rebuild/category controls are present. */ -.cookbook-dep-row > .cookbook-dep-installed, -.cookbook-dep-row > .cookbook-dep-na, -.cookbook-dep-row > .cookbook-dep-install:not(.cookbook-dep-build-deps) { - margin-left: auto; -} -/* Center the "?" glyph inside the help chip. Without text-align it sits 0.5px - left of true center because of the character's natural baseline offset. */ -.hwfit-help-chip { - text-align: center; - padding-left: 0.5px; -} -.cookbook-dep-install:hover { opacity: 0.85; } -/* Installed split button: "Installed" label + separator + ▾ caret; clicking it - opens the actions menu (Update). Replaces the old ⋮ button. */ -.cookbook-dep-installed-btn { - padding: 0; - width: 87.7px; - min-width: 87.7px; - cursor: pointer; - font-family: inherit; - overflow: hidden; - position: relative; - top: -3px; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-installed-btn .cookbook-dep-caret { - display: inline-flex; - align-items: center; - align-self: stretch; - padding: 0 5px; - font-size: 14px; - line-height: 1; - position: relative; - top: 1px; - left: 4px; - opacity: 0.85; - border-left: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); -} -.hwfit-serve-row { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - margin-bottom: 6px; -} -/* Tighter min on the core-knobs row only so Dtype / TP / GPU Mem / Max - Seqs fit on one line without crushing Row 1's GPU button strip or - the Backend picker. */ -.hwfit-serve-row-core { - grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); -} -/* Save / saved-config split aligned to the right end of Row 1 and - nudged up 4px so it sits flush with the rest of the row. */ -.hwfit-serve-row .cookbook-serve-slots { - justify-self: end; - position: relative; - top: -4px; -} -/* The Settings label wraps the Save split — right-align its title text - so the word "Settings" sits above the right-aligned Save button - instead of floating off to the left of its grid cell. 8px right - padding now (16px → 8px) per follow-up tweak, brings the title back - over the actual Save button instead of overshooting it. */ -.hwfit-serve-row label:has(> .cookbook-serve-slots) { - grid-column: -2 / -1; - justify-self: end; - text-align: right; - padding-right: 0; -} -.hwfit-serve-row label:has(> .cookbook-serve-slots) > span { - display: inline-block; - position: relative; - left: -33px; -} -.hwfit-serve-topline { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - margin: 0 0 6px; -} -.hwfit-serve-topline .hwfit-serve-runtime-text { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-serve-preset-row { - display: flex; - justify-content: flex-end; - margin: 0; - flex: 0 0 auto; - position: relative; - top: -6px; -} -.hwfit-serve-row label { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; - letter-spacing: 0.3px; -} -.hwfit-serve-row-core .hwfit-context-label { - grid-column: span 2; - width: auto; - min-width: 0; - max-width: none; - justify-self: start; -} -.hwfit-context-control { - display: block; - position: relative; - align-items: center; - margin-top: 2px; - width: 100px; -} -.hwfit-context-control .hwfit-sf[data-field="ctx"] { - min-width: 0; - width: 100%; - padding-right: 40px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]) { - position: relative; - left: -66px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - position: relative; - left: -64px; -} -.hwfit-serve-row-core .hwfit-gpus-label { - position: relative; - left: -70px; -} -.hwfit-context-calc-btn { - position: absolute; - right: 3px; - top: -4px; - width: 34px; - height: 28px; - min-width: 34px; - padding: 0; - font-size: 10px; - line-height: 1; - display: inline-flex; - align-items: center; - justify-content: center; - text-align: center; - overflow: hidden; - white-space: nowrap; -} -.hwfit-context-calc-btn .spinner-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 auto !important; -} -.hwfit-context-calc-btn .ai-spinner-whirlpool { - width: 12px !important; - height: 12px !important; -} -@media (min-width: 769px) { - [data-llama-mode-toggle].mode-toggle.mode-toggle-three .mode-toggle-btn > span { - top: -6px !important; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { - font-size: 0; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { - content: 'Unif'; - font-size: 11px; - } -} -@media (max-width: 768px) { - .hwfit-serve-row-core .hwfit-context-label { - display: flex; - flex-direction: column; - align-items: flex-start; - } - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]), - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - left: 0; - } - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_env_preset"]), - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="extra_env"]) { - grid-column: 1 / -1 !important; - } - .hwfit-context-control { - display: inline-flex; - align-items: center; - gap: 4px; - width: auto; - } - .hwfit-context-control .hwfit-sf[data-field="ctx"] { - width: 100px; - padding-right: 6px; - } - .hwfit-context-calc-btn { - position: relative; - top: -2px; - left: 2px; - right: auto; - flex: 0 0 34px; - width: 34px; - min-width: 34px; - } -} -.hwfit-auto-ctx-note { - display: block; - margin-top: 3px; - font-size: 9px; - opacity: 0.6; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Ctx slider — ported from origin/main. Sits in the Scan/Download toolbar - next to the quant dropdown. Drives _ctxValue() in cookbook-hwfit.js. */ -.hwfit-ctx-control { - height: 28px; - min-width: 134px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - gap: 5px; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - font-size: 11px; /* slightly smaller than .cookbook-field-input so Context reads quieter next to Engine/Quant */ -} -.hwfit-ctx-control span { - /* Match Quant/Engine select label style: no uppercase, no letter-spacing. */ - text-transform: none; - letter-spacing: 0; - opacity: 0.9; -} -.hwfit-ctx-control > span:first-child { - font-size: 9.5px; -} -/* Editor-style slider (same look as the gallery editor sliders): thin pill - rail that fattens on interaction, circular red thumb that grows on hover. */ -.hwfit-ctx-control input[type="range"] { - width: 64px; - min-width: 64px; - height: 4px; - padding: 0; - border: 0; - -webkit-appearance: none; - appearance: none; - /* Hard-coded grey so the rail is GUARANTEED visible regardless of theme — - every theme-derived color we tried (--fg-muted, --border, accent-bg mix) - kept blending into the panel background on at least one theme. */ - background: rgba(150, 150, 150, 0.65); - border-radius: 999px; - accent-color: var(--red); - cursor: pointer; - transition: height 0.15s ease, background 0.15s ease; -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - background: var(--fg); -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - height: 10px; -} -.hwfit-ctx-control input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]::-moz-range-thumb { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]:hover::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:focus::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:active::-webkit-slider-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control input[type="range"]:hover::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:focus::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:active::-moz-range-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control output { - min-width: 28px; - text-align: right; - color: var(--fg); - font-weight: 600; -} -.hwfit-sf { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - font-family: inherit; - font-size: 12px; - padding: 0 6px; - height: 28px; - box-sizing: border-box; -} -.hwfit-sf[data-field="backend"], -.hwfit-sf[data-field="dtype"], -.hwfit-sf[data-field="tp"] { - height: 32px; - box-sizing: border-box; - width: 100%; -} -.hwfit-sf:focus { - border-color: var(--accent, var(--red)); - outline: none; -} -.hwfit-sf[type="text"] { width: 100%; } -.hwfit-sf.hwfit-sf-wide { width: 100%; } -.hwfit-serve-checks { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 6px; -} -.hwfit-sf-cb { - display: flex; - flex-direction: row; - align-items: center; - gap: 6px; - font-size: 11px; - color: var(--fg-muted); - cursor: pointer; - padding: 3px 0; -} diff --git a/static/index.html b/static/index.html index cba90c7c9..ad04daf89 100644 --- a/static/index.html +++ b/static/index.html @@ -246,15 +246,24 @@ real request is discarded and the font fetched a second time. --> - - - - - - - - - + + + + + + + + + + + + + + + + + +
then collapses to nothing - while the footer remains visible. Keep the document text's intrinsic height - in that layout; bounded/fullscreen layouts can still shrink it and scroll. */ -#doclib-modal .doclib-card.doclib-card-expanded .doclib-card-preview > pre { - flex: 1 1 auto; - min-height: 1.5em; -} - -/* Collapsible skills section headers (Your skills / Built-in). */ -.skills-section-header { - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - user-select: none; - padding: 6px 4px 4px; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.06em; - opacity: 0.6; - font-weight: 600; -} -.skills-section-header:hover { opacity: 0.9; } -.skills-section-chevron { transition: transform 0.15s ease; flex-shrink: 0; } -.skills-section-header.collapsed .skills-section-chevron { transform: rotate(-90deg); } -.skills-section-count { - margin-left: auto; - opacity: 0.6; - font-variant-numeric: tabular-nums; - font-weight: normal; -} -.skill-card-section-hidden { display: none !important; } -/* Warning banner shown when previewing/editing a built-in capability. */ -.skill-builtin-warn { - font-size: 9.5px; - line-height: 1.45; - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 35%, transparent); - border-radius: 6px; - padding: 6px 9px; - margin: 0 0 8px; - flex-shrink: 0; -} - -/* #skills-list wears both .memory-list and .doclib-grid. doclib-grid's - max-height:400px would cap the list AND clamp an expanded card — keep - the memory-list fill-the-modal behaviour instead. */ -#skills-list.doclib-grid { - max-height: none; - flex: 1; - min-height: 0; -} -#skills-list.doclib-grid:has(.doclib-card-expanded) { - overflow: hidden; /* expanded card owns its own scroll */ -} -/* When a skill is expanded, hide the sibling Add-Skill form card so the - expanded SKILL.md uses the full panel. Otherwise the two admin-cards - split the height ~50/50 and the expanded skill is stuck in its half - while the Add form idles in the other. */ -.memory-tab-panel[data-memory-panel="skills"]:has(.doclib-card-expanded) > .admin-card:nth-of-type(2) { - display: none !important; -} - -/* Skills cards reuse the doclib expand/footer machinery. The SKILL.md - preview + the edit textarea need to fill the expanded card and own - their own scroll, same as a document preview. */ -/* Collapsed skill rows used a smaller (0.9em code) title, so the click row - read slimmer than document/chat/library cards. Give the header a min-height - so the tap target matches the other library items. */ -.skill-card > .skill-card-header { - min-height: 53px; - box-sizing: border-box; - display: flex; - align-items: center; - gap: 6px; -} -/* Skill rows share Tasks' card hover colors. Draft state belongs in the - status badge, not in a second opacity layer across the whole row. */ -/* Name + description column. Name wraps to 2 lines (skill slugs are long — - use the space rather than truncating to "check-model-downl…"). */ -.skill-card-textcol { - flex: 1 1 auto; - min-width: 0; - overflow: hidden; -} -.skill-card-title-row { - display: flex; - align-items: center; - gap: 6px; - min-width: 0; -} -.skill-card-name { - font-family: inherit; - font-weight: 500; - font-size: 12px; - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - word-break: normal; - line-height: normal; -} -.skill-card-title-row .skill-card-name { min-width: 0; } -.skill-status-published { - width: 20px; - height: 20px; - padding: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 15%, transparent); - border-color: color-mix(in srgb, var(--color-success, #4ade80) 32%, transparent); - border-radius: 50%; -} -.skill-stats .skill-status-published { - width: 15px; - height: 15px; - margin-left: 3px; - vertical-align: middle; -} -.skill-stats .skill-status-published svg { - width: 9px; - height: 9px; -} -.skill-card-desc { - font-size: 10px; - opacity: 0.4; - margin-top: -1px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.skills-audit-model-field { - display: flex; - align-items: center; - gap: 8px; - margin-top: 10px; - font-size: 11px; -} -.skills-audit-model-field > span { - flex: 0 0 auto; - color: color-mix(in srgb, var(--fg) 68%, transparent); -} -.skills-audit-model-field select { - min-width: 0; - flex: 1; - height: 26px; - padding: 0 6px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - color: var(--fg); - font: inherit; -} -.skill-card-tags { - display: flex; - gap: 4px; - flex-wrap: wrap; - margin-top: 4px; -} -.skill-tag-pill { - padding: 1px 5px; - font-size: 9px; -} -.skill-usage-pill { - margin-left: auto; - color: color-mix(in srgb, var(--fg) 62%, transparent); -} -.skill-tag-filter-chip { cursor: pointer; } - -@media (max-width: 768px) { - .skill-card > .skill-card-header { - align-items: flex-start; - } - .skill-card-textcol { - min-width: 0; - } - .skill-card-tags { - max-width: 100%; - overflow: hidden; - } - .skill-card-tags .skill-tag-pill { - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -} -/* Right cluster — pills (right-aligned), stats, then the menu/chevron. */ -.skill-card-right { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; - margin-left: auto; -} -.skill-card-right .memory-cat-badge { flex-shrink: 0; } -.skill-status-bin { - background: color-mix(in srgb, var(--color-danger, #d9534f) 20%, transparent); - color: var(--color-danger, #d9534f); -} -.skill-model-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-model-student { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.skill-audit-pass { - color: var(--color-success, #4ade80); - background: color-mix(in srgb, var(--color-success, #4ade80) 16%, transparent); -} -.skill-audit-needs_work { - color: var(--color-warning, #f0ad4e); - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 16%, transparent); -} -.skill-audit-fail { - color: var(--color-danger, #e06c75); - background: color-mix(in srgb, var(--color-danger, #e06c75) 16%, transparent); -} -.skill-audit-inconclusive, -.skill-audit-skipped, -.skill-audit-unknown { - color: color-mix(in srgb, var(--fg) 64%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-model-teacher { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 24%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-pill { - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.skill-necessity-duplicate { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 22%, transparent); - color: var(--color-warning, #f0ad4e); -} -.skill-necessity-trivial { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 62%, transparent); - border-color: color-mix(in srgb, var(--fg) 16%, transparent); -} -.skill-necessity-irrelevant { - background: color-mix(in srgb, var(--color-danger, #e06c75) 18%, transparent); - color: var(--color-danger, #e06c75); -} -.skills-summary-strip { - display: flex; - align-items: center; - gap: 6px; - flex-wrap: nowrap; - overflow-x: auto; - overflow-y: hidden; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - margin: -3px 0 8px; -} -.skills-summary-strip::-webkit-scrollbar { display: none; } -.skills-summary-strip:empty { - display: none; -} -.skills-summary-chip { - display: inline-flex; - align-items: center; - gap: 5px; - height: 23px; - padding: 0 8px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 10px; - line-height: 1; - cursor: pointer; - flex: 0 0 auto; -} -.skills-summary-chip:hover { - background: color-mix(in srgb, var(--fg) 8%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); -} -.skills-summary-chip.active { - background: color-mix(in srgb, var(--accent, var(--red)) 13%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent); - color: var(--fg); -} -.skills-summary-chip strong { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10px; - color: var(--fg); -} - -/* Shared filter/tag strip contract. These rows all sit immediately below - their screen's search or primary toolbar and stay one line at every width. */ -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -) { - align-items: center; - flex-wrap: nowrap !important; - gap: 6px; - width: 100%; - max-width: 100%; - min-width: 0; - max-height: none; - overflow-x: auto !important; - overflow-y: hidden !important; - overscroll-behavior-x: contain; - touch-action: pan-x; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - box-sizing: border-box; - padding: 0; - margin: -3px 0 8px; -} -:is( - .skills-summary-strip, - #memory-category-filters, - .memory-category-filters:has(> .memory-cat-chip), - .doclib-lang-chips, - .doclib-chips, - .notes-labels-bar, - .tasks-activity-filters, - .gallery-tag-chips, - .gallery-album-chips, - .gallery-ai-tags, - .cal-filters -)::-webkit-scrollbar { display: none; } -:is( - .skills-summary-strip > .skills-summary-chip, - #memory-category-filters > .skills-summary-chip, - .memory-category-filters > .memory-cat-chip, - .doclib-lang-chips > :is(.memory-cat-chip, .skills-summary-chip), - .doclib-chips > .doclib-chip, - .notes-labels-bar > .notes-label-chip, - .tasks-activity-filters > .memory-cat-chip, - .gallery-tag-chips > .gallery-chip, - .gallery-album-chips > .gallery-chip, - .gallery-ai-tags > .gallery-ai-chip, - .cal-filters > .cal-filter-item -) { - flex: 0 0 auto; - box-sizing: border-box; - height: 23px !important; - min-height: 23px; - padding: 0 8px !important; - border-radius: 6px !important; - font-size: 10px !important; - line-height: 1; - white-space: nowrap; -} -.doclib-lang-chips { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.doclib-chip-scroll-frame { - position: relative; - display: flex; - align-items: center; - flex: 1 1 100%; - min-width: 0; - max-width: 100%; -} -.doclib-chip-scroll-frame > .chip-scroll-strip { - flex: 1 1 auto; - min-width: 0; - margin-left: 0; - margin-right: 0; -} -.doclib-chip-scroll-arrow { - display: none; - position: absolute; - top: calc(50% - 1px) !important; - transform: translateY(-50%); - margin-top: 0; - z-index: 2; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - padding: 0; - border: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); - border-radius: 50%; - box-sizing: border-box; - background: color-mix(in srgb, var(--bg) 86%, var(--fg) 14%); - color: var(--accent, var(--red)); - box-shadow: 0 6px 18px rgba(0,0,0,0.16); - font-size: 18px; - line-height: 18px; - cursor: pointer; - opacity: 0.84; - transition: opacity 0.12s ease, background 0.12s ease, color 0.12s ease; -} -.doclib-chip-scroll-arrow-glyph { - display: inline-block; - position: relative; - top: -1px; - line-height: 1; -} -.doclib-chip-scroll-arrow:hover { - opacity: 1; - background: color-mix(in srgb, var(--bg) 76%, var(--fg) 24%); -} -@media (max-width: 768px) { - .doclib-chip-scroll-arrow { display: none !important; } -} -.doclib-chip-scroll-frame.has-overflow > .doclib-chip-scroll-arrow:not([hidden]) { - display: inline-flex; -} -.doclib-chip-scroll-arrow.left { left: 0; } -.doclib-chip-scroll-arrow.right { right: 0; } -.doclib-chip-scroll-frame.has-overflow > .chip-scroll-strip { - padding-left: 0 !important; - padding-right: 22px !important; -} -.doclib-chip-scroll-frame > .chip-scroll-strip.is-pointer-dragging { - cursor: grabbing; - user-select: none; -} -.doclib-chip-scroll-frame > .chip-scroll-strip:not(.is-pointer-dragging) { - cursor: grab; -} -/* Calendar tags are wrapped by chipScroll.js after render. Keep that wrapper - content-sized inside the calendar's column layout; the default flex growth - otherwise turns the one-line tag strip into a large empty panel. */ -#cal-body > .doclib-chip-scroll-frame:has(> .cal-filters) { - flex: 0 0 auto; - min-height: 0; -} -/* The completed-task filters are a compact toolbar, not a flexible panel. - Without this override the shared chip frame grows into the available - height of the card, which is especially visible the first time the mobile - view switches to Completed. */ -.tasks-completed-card > .doclib-chip-scroll-frame:has(> #tasks-completed-status-chips) { - flex: 0 0 auto; - min-height: 0; -} -/* The active Tasks filter row is also a compact toolbar. Keep its wrapper - content-sized so changing a filter cannot make the mobile panel grow a - large empty gap between the chips and the task cards. */ -.admin-card > .doclib-chip-scroll-frame:has(> #tasks-filter-chips) { - flex: 0 0 auto; - min-height: 0; -} -#memory-category-filters { - flex: 0 0 auto; - min-height: 25px; - padding: 1px 0 !important; - scroll-padding-inline: 1px; -} -.cookbook-serve-tag-label { - display: inline-flex; - align-items: center; - min-width: 0; -} -#serve-search, -#hwfit-cache-select, -#serve-tags { - position: relative; - top: -2px; -} -#hwfit-cache-select { - top: -4px; -} -/* Calendar previously framed its tags as a separate card. It now follows the - same unframed placement as the other filter strips. */ -.cal-filters { - border: 0; - background: none; - box-shadow: none; -} -.skill-duplicate-keep { - background: color-mix(in srgb, var(--color-success, #4ade80) 22%, transparent); - color: var(--color-success, #4ade80); -} -.skill-duplicate-group { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - color: color-mix(in srgb, var(--fg) 62%, transparent); - background: color-mix(in srgb, var(--fg) 8%, transparent); -} -.skill-duplicate-lower { - background: color-mix(in srgb, var(--color-danger, #e06c75) 14%, transparent); - color: var(--color-danger, #e06c75); -} -.skill-stats { - font-size: 9px; - font-family: monospace; - white-space: nowrap; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.skill-turns-saved { - font-size: 9px; - opacity: 0.82; - font-variant-numeric: tabular-nums; - white-space: nowrap; - flex: 0 0 auto; -} -.skill-expanded-savings { - display: flex; - align-items: center; - min-height: 18px; - margin: 0 0 5px; -} -.skill-turns-positive { - color: color-mix(in srgb, var(--color-success, #4ade80) 82%, var(--fg)); -} -.skill-turns-zero, -.skill-turns-unknown { - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.skill-turns-negative { - color: color-mix(in srgb, var(--color-danger, #e06c75) 80%, var(--fg)); -} -.skill-conf { - display: inline-block; - min-width: 4ch; - text-align: right; - font-weight: 700; - color: var(--accent, var(--red)); -} -.skill-verified, -.skill-teachermark, -.skill-needsmark { - display: inline-flex; - align-items: center; - vertical-align: middle; - margin-right: 3px; -} -.skill-verified { color: var(--accent, #4ade80); } -.skill-teachermark { color: var(--color-warning, #f0ad4e); } -.skill-needsmark { color: var(--color-danger, #e06c75); cursor: help; } - -/* The card currently being processed by an "Audit now" run glows + pulses so - it's obvious which one the audit is on. */ -@keyframes skill-audit-pulse { - 0%, 100% { box-shadow: 0 0 6px 0 color-mix(in srgb, var(--accent, #4ade80) 55%, transparent); } - 50% { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent, #4ade80) 85%, transparent); } -} -.skill-card.skill-audit-active { - border-color: var(--accent, #4ade80) !important; - animation: skill-audit-pulse 1.3s ease-in-out infinite; -} - -/* A test is running for this skill — the app's whirlpool spinner is injected - next to the name from JS (see _setCardRunning) so the collapsed/folded card - still makes it obvious work is in progress. */ -/* Collapsed bar shows the kebab menu; expanded shows an up-chevron to - collapse. (Toggled by the .doclib-card-expanded class.) */ -.skill-kebab-btn { - display: inline-flex; - align-items: center; - justify-content: center; - background: none; - border: none; - padding: 4px; - margin: 0; - cursor: pointer; - color: var(--fg); - opacity: 0.5; - border-radius: 5px; - flex-shrink: 0; - position: relative; - top: -2px; -} -.skill-kebab-btn:hover { opacity: 1; background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-chevron-up { display: none; align-items: center; opacity: 0.5; flex-shrink: 0; } -/* Kebab dropdown */ -.skill-kebab-menu { - position: fixed; - /* z-index is set inline via topPortalZ() at open time (#4720); a static - value here loses once the modal bring-to-front counter climbs past it. */ - min-width: auto; - width: max-content; - padding: 4px; - background: var(--panel, var(--bg)); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 6px 20px rgba(0,0,0,0.22); - display: flex; - flex-direction: column; - gap: 1px; -} -.skill-kebab-item { - display: flex; - align-items: center; - gap: 10px; - width: 100%; - padding: 6px 8px; - background: none; - border: none; - border-radius: 8px; - cursor: pointer; - color: var(--fg); - font-size: 11px; - text-align: left; -} -.skill-kebab-item:hover { background: color-mix(in srgb, var(--fg) 10%, transparent); } -.skill-kebab-item.danger { color: var(--color-error, #e55); } -.skill-kebab-item.danger:hover { background: color-mix(in srgb, var(--color-error, #e55) 15%, transparent); } -/* Section labels separating "Your skills" from the read-only "Built-in - capabilities" list. */ -.skills-section-label { - font-size: 10px; - opacity: 0.5; - text-transform: uppercase; - letter-spacing: 0.04em; - margin: 10px 2px 4px; - flex-shrink: 0; -} -.skills-section-label:first-child { margin-top: 0; } -/* When a learned skill is expanded the grid hides sibling cards; hide the - section labels too so they don't orphan above the collapsed list. */ -#skills-list:has(.doclib-card-expanded) .skills-section-label { display: none; } -/* Built-in cards are read-only — no expand/hover-pointer affordance. */ -.skill-builtin-card { cursor: default; } -.skill-builtin-card:hover { border-color: var(--border); } -.skill-card-preview .skill-md-pre { - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - white-space: pre-wrap; - word-break: break-word; -} -.skill-preview-copy { - position: absolute; - top: 6px; - right: 8px; - z-index: 1; - display: flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - padding: 0; - opacity: 0; - color: var(--fg); - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - cursor: pointer; - transition: opacity .15s, color .15s, border-color .15s; -} -.skill-card-preview:hover .skill-preview-copy { opacity: 0.7; } -.skill-preview-copy:hover, -.skill-preview-copy:focus-visible { opacity: 1 !important; } -.skill-preview-copy.copied { - opacity: 1; - color: var(--color-save-green, #4caf50); - border-color: var(--color-save-green, #4caf50); -} -/* :has()-free expand layout. skills.js adds .skills-has-expanded to the - skills admin-card when a skill opens, so this works on engines that don't - support :has() (e.g. older Firefox mobile, where the expand stuck at - ~50%). Hide everything but the list so the grid fills the card; the - absolutely-positioned expanded card (mobile) then fills the grid. */ -.admin-card.skills-has-expanded > *:not(#skills-list) { - display: none !important; -} -.admin-card.skills-has-expanded > #skills-list.doclib-grid { - flex: 1 1 auto !important; - height: 100% !important; - min-height: 0 !important; - overflow: hidden !important; -} -/* Expand animation — a clean opacity fade. The mobile card snaps to a - full-screen overlay (position:absolute), so any translate/scale on it - reads as a jarring "explosion"; a pure fade just appears smoothly. (No - transform also keeps skills.js's height measurement accurate.) */ -@keyframes skill-card-expand { - from { opacity: 0; } - to { opacity: 1; } -} -.skill-card.doclib-card-expanded { - animation: skill-card-expand 0.18s ease-out; -} -/* Switching directly from one expanded card to another: no fade (it would - show the previous card collapsing through the semi-transparent new one). */ -.skill-card.doclib-card-expanded.skill-expand-instant { - animation: none !important; -} -@media (prefers-reduced-motion: reduce) { - .skill-card.doclib-card-expanded, - .skill-card.doclib-card-expanded .doclib-card-preview { - animation: none !important; - } -} -/* The preview WRAPPER inherits flex:1 (grow) from the generic doclib expand - rule — that's what stretched the card and left a void / pushed the footer - out. Size it to content (shrink + scroll only when long) so the footer - sits right under the preview and stays fully visible. */ -.skill-card.doclib-card-expanded .doclib-card-preview { - flex: 0 1 auto !important; - min-height: 0; - /* The footer lives INSIDE this wrapper. Don't let the wrapper scroll, or - the footer scrolls off with the content. Keep it clipped; the - inside is the scroller, so the footer stays pinned at the bottom. */ - overflow: hidden !important; -} -.skill-card.doclib-card-expanded .skill-md-pre { - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them - back down 4px so they're vertically centered in the skill card footer. */ -.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn, -.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - top: 0; -} -/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */ -#skills-bulk-bar .memory-toolbar-btn { - position: relative; - top: -2px; -} -/* ── Audit-all progress panel ── */ -.skills-audit-panel { - border: 1px solid var(--border); - border-radius: 8px; - padding: 8px 10px; - margin: 6px 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } -.skills-audit-title { font-size: 12px; font-weight: 600; } -.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; } -.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; } -.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; } -.skills-audit-log { - max-height: 22vh; overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10.5px; line-height: 1.5; opacity: 0.8; -} -/* ── Skill test monitor + AI eval verdict ── */ -.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; } -.skill-test-log { - flex: 1 1 auto; - min-height: 120px; - max-height: 48vh; - overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.45; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - white-space: pre-wrap; - word-break: break-word; -} -.skill-test-log > div { margin: 1px 0; } -.skill-test-meta { opacity: 0.5; } -.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; } -.skill-test-round { opacity: 0.6; margin-top: 6px !important; } -.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; } -.skill-test-out { opacity: 0.7; padding-left: 10px; } -.skill-test-say { white-space: pre-wrap; } -.skill-test-err { color: var(--color-danger, #e06c75); } -.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } -.skill-eval-badge { - font-size: 10px; font-weight: 700; letter-spacing: 0.03em; - padding: 2px 8px; border-radius: 10px; flex-shrink: 0; -} -.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); } -.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); } -.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); } -.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); } -.skill-eval-summary { font-size: 11px; opacity: 0.85; } -.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; } -.skill-eval-issues li { margin: 2px 0; } -.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } -.skill-eval-approve.suggested { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; -} -/* Already published — the button confirms the state (click to unpublish). */ -.skill-eval-approve.is-approved { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; - background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important; -} -/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title, - When to use, How, Tags) is accent-colored while the rest stays muted — real - placeholders can't be partially colored. The native placeholder is a single - space so :placeholder-shown still toggles the overlay. */ -.skill-ph-wrap { position: relative; } -.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; } -.skill-rich-ph { - position: absolute; - left: 11px; right: 11px; top: 50%; - transform: translateY(-50%); - pointer-events: none; - font-size: 12px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 40%, transparent); - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -/* Textarea: align the hint to the first line instead of vertical-centering. */ -.skill-rich-ph-top { top: 7px; transform: none; } -.skill-rich-ph .k { color: var(--accent, var(--red)); } -/* Keep the memory-input enter hint pinned to the field's right edge instead - of letting it drift after the example text. */ -#new-memory-input ~ .skill-rich-ph > svg { - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - margin-left: 0 !important; -} -/* Hide the overlay once the field has real content. */ -.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; } -.skill-md-editor { - flex: 1 1 auto; - min-height: 0; - width: 100%; - box-sizing: border-box; - resize: none; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); -} -.doclib-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -/* Unified loading row across every Library tab (Chats / Documents / Research / - Archive): one opacity, one size, with the whirlpool spinner next to it. */ -.lib-loading-row { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 28px 16px; - font-size: 12px; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.pdf-loading-state { - min-height: 120px; - margin: 0; - box-sizing: border-box; - flex-direction: column; - gap: 10px; - border: 0; - border-radius: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - box-shadow: none; - font-size: 12px; - font-weight: 600; - letter-spacing: 0.01em; -} -.pdf-loading-state .ai-spinner-whirlpool { - color: var(--accent, var(--red)); - opacity: 0.95; -} -.pdf-loading-state .ai-spinner-whirlpool canvas { - display: block; -} -.doclib-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg-muted); - font-size: 11px; - cursor: pointer; - transition: border-color 0.15s, color 0.15s; - flex-shrink: 0; - position: relative; - top: 10px; -} -/* Soft dark gradient above the button — blends the list content into the - load-more strip instead of a hard cutoff. Spans the modal width and - sits behind/above the button. */ -.doclib-load-more::before { - content: ""; - position: absolute; - left: 50%; - transform: translateX(-50%); - bottom: 100%; - margin-bottom: 4px; - width: min(600px, 92vw); - height: 34px; - pointer-events: none; - background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent); -} -.doclib-load-more:hover { - border-color: var(--red); - color: var(--red); -} -.doclib-inline-load-more { - border: 0; - border-radius: 0; - padding: 0; - background: none; - top: 0; - font-size: 9px !important; - line-height: 1.2 !important; -} - -/* Document library toolbar buttons */ -.doclib-toolbar-btn { - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - font-size: 11px; - padding: 5px 10px; - border-radius: 6px; - cursor: pointer; - transition: all 0.15s; - white-space: nowrap; - font-family: inherit; - display: inline-flex; - align-items: center; - gap: 3px; -} -.doclib-toolbar-btn:hover { - color: var(--fg); - border-color: var(--fg); -} -.doclib-toolbar-btn.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -.doclib-toolbar-btn.danger { - color: var(--color-error, #e55); -} -.doclib-toolbar-btn.danger:hover:not(:disabled) { - border-color: var(--color-error, #e55); -} -.doclib-toolbar-btn:disabled { - opacity: 0.4; - cursor: default; -} - -/* Bulk action bar */ -.doclib-bulk-bar { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--red) 5%, transparent); - font-size: 10px; - margin-bottom: 8px; -} -.doclib-bulk-bar.hidden { - display: none; -} -.doclib-bulk-check-all { - display: flex; - align-items: center; - gap: 3px; - cursor: pointer; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-size: 10px; -} -#doclib-bulk-bar .memory-toolbar-btn { - position: relative; - top: -3px; -} - -/* Custom checkboxes */ -.doclib-select-cb, -.doclib-bulk-check-all input { - -webkit-appearance: none; - appearance: none; - width: 13px; - height: 13px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - cursor: pointer; - flex-shrink: 0; - margin: 0; - position: relative; - transition: all 0.15s; -} -.doclib-select-cb:hover, -.doclib-bulk-check-all input:hover { - border-color: var(--red); -} -.doclib-select-cb:checked, -.doclib-bulk-check-all input:checked { - background: var(--red); - border-color: var(--red); -} -.doclib-select-cb:checked::after, -.doclib-bulk-check-all input:checked::after { - content: ''; - position: absolute; - left: 3px; - top: 0px; - width: 4px; - height: 8px; - border: solid var(--bg); - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); -} - - -/* + button outside scroll area (when doc is on right side) */ - -@media (max-width: 600px) { - .doclib-toolbar { - flex-direction: column; - } - .doclib-card-session, - .doclib-card-time { - display: none; - } - .doclib-card-preview { - max-height: 40vh; - } -} - -/* Collapse card tags before they compete with attachment/favorite controls. */ -.email-card-tags .email-tags-more { - color: var(--email-tags-more-color, var(--fg)); - background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent); -} -.email-tags-more-single, -[data-email-tags-count-mini] { display: none; } -.email-tags-more-single svg { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag, -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra { - display: none; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single, -.email-card-tags.email-tags-expanded .email-tags-more-single { - display: inline-flex; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini], -.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; } - -/* Keep the reader actions in drafting order. */ -.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; } -.email-reader-actions-inline > [data-act="reply"] { order: 2; } -.email-reader-actions-inline > [data-act="reply-all"] { order: 3; } -.email-reader-actions-inline > [data-act="forward"] { order: 4; } -.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed { - display: none !important; -} - -/* ── Archive browser ── */ -.archive-list { - grid-template-columns: 1fr !important; - gap: 4px !important; -} -.archive-row { - flex-direction: row !important; -} -.archive-row .doclib-card-header { - flex: 1; - padding: 8px 10px; -} -.archive-row .doclib-card-title { - flex: 1; - min-width: 0; -} -/* Archive column layout */ -.archive-header { - display: flex; - align-items: center; - padding: 4px 10px; - gap: 6px; - font-size: 9px; - font-weight: 600; - opacity: 0.35; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.archive-col-title { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: -4px; -} -.archive-col-msgs { - width: 40px; - flex-shrink: 0; - text-align: left; - margin-left: -8px; -} -.archive-col-model { - width: 90px; - flex-shrink: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.archive-col-time { - width: 55px; - flex-shrink: 0; - text-align: right; -} -.archive-col-menu { - width: 24px; - flex-shrink: 0; -} -.archive-menu-btn { - background: transparent; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - padding: 4px; - line-height: 0; - border-radius: 4px; - transition: background .15s, opacity .15s; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - margin-left: 4px; - vertical-align: middle; - height: 100%; -} -.archive-menu-btn svg { - display: block; - margin-top: -6px; -} -.archive-menu-btn:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - opacity: 1; -} - -/* ── Gallery (image library) ── */ - -.gallery-modal-content { - width: min(820px, 94vw); - max-height: 92vh; - font-size: 12px; -} -/* Keep the Gallery frame anchored while tabs or bulk-selection controls - change the content height. Centering the frame made every shorter tab move - the whole window vertically. */ -@media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal, - #cookbook-modal { - align-items: flex-start; - padding-top: 4vh; - box-sizing: border-box; - } -} -/* While the Edit tab is active the editor needs a *definite* height so - the right-side tool panel (`.ge-right-panel { overflow-y:auto }`) - scrolls INTERNALLY. Without this the modal only has `max-height`, so - `.gallery-editor { height:100% }` can't resolve — the editor grows to - its full content height, overflows the modal body, and the modal-body - scrollbar that appears can't be wheel-scrolled because the editor's - `overscroll-behavior:contain` blocks the chain. Pinning a real height - here makes the panel bounded and scrollable as designed. Scoped to - the editor view (matched via the container's inline `display:flex`) - so the Photos/Albums views keep sizing to their content. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) { - height: 92vh; -} -/* The editor tab owns the modal body's scroll context. Keep the landing - chooser and the mounted editor inside the available sheet height so a long - saved-project list cannot push the launch controls below the viewport. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body { - display: flex; - min-height: 0; - overflow: hidden; -} -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container { - min-height: 0; - overflow: hidden; -} -/* Photo-detail view sizing (issue #314). - The detail view is rendered as a `position:absolute; inset:0` overlay - *inside* `.gallery-images-container`, painted over the photo grid. Because - it's absolutely positioned it can't contribute to the container's height — - the container (and therefore the overlay's `inset:0` box) collapses to the - height of the grid sitting behind it. When the library only has a few - photos that grid is short, so the detail view is crushed: the image is - clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a - tiny, internally-scrolling strip. (With a large library the grid is tall, - which is why it looked fine in the demo video but cramped for users with - few photos.) - Fix: when the detail view is open, hide the grid-view siblings and drop the - overlay into normal flow. The container — and the window, up to its 92vh - max-height — then sizes to the detail's own content (image + metadata), so - nothing is clipped or squeezed regardless of how many photos exist. Scoped - via the detail element's inline `display:flex` so the grid / albums views - keep sizing to their own content. Works on both desktop and the mobile - full-screen sheet. */ -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) { - display: none !important; -} -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail { - position: static; -} -/* Containing block for the photo-detail overlay — keeps it inside the body - so it sits below the modal header and the tab strip instead of covering them. */ -.gallery-images-container { - position: relative; -} -.gallery-modal-content .modal-header h4 { - font-size: 1rem; -} -.gallery-stats { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-bottom: 4px; -} -.gallery-toolbar { - display: flex; - gap: 6px; - margin-bottom: 4px; - align-items: center; -} -.gallery-toolbar-break { display: none; } -/* Keep the hint anchored while independently seating the input in the row. */ -.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; } -.gallery-search { - position: relative; - top: 4px; - flex: 1; - width: 100%; - padding: 4px 8px; - padding-right: 78px; /* room for the enter hint */ - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - font: inherit; - font-size: 12px; - outline: none; -} -.gallery-search-enter-hint { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - font-weight: 600; - color: var(--accent, var(--red)); - pointer-events: none; - opacity: 0; - transition: opacity 0.12s; - white-space: nowrap; -} -/* Show the hint only once the user has typed something (placeholder gone). */ -.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; } -.gallery-search:focus { - border-color: var(--red); -} -.gallery-model-filter, -.gallery-sort { - padding: 4px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - position: relative; - top: 4px; -} -.gallery-selection-bar { - margin-bottom: 4px; - position: relative; - top: 2px; - padding-right: 18px; -} -.gallery-tag-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 4px; -} -.gallery-chip { - height: 28px; - box-sizing: border-box; - padding: 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - border-radius: 14px; - font-size: 12px; - line-height: 1; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: border-color 0.15s, background 0.15s; -} -.gallery-chip:hover { - border-color: var(--red); -} -.gallery-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Album chips row */ -.gallery-album-chips { - display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px; -} -.gallery-chip-fav { color: var(--red); } -.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); } -.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; } -/* Active-album indicator — appears when the Photos grid is filtered to one album. */ -.gallery-chip-active-album, -.gallery-active-filter-pill { - display: inline-flex; align-items: center; gap: 4px; - background: color-mix(in srgb, var(--red) 14%, transparent); - border-color: color-mix(in srgb, var(--red) 45%, transparent); - position: relative; - top: 6px; -} -.gallery-active-filter-pill { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - color: color-mix(in srgb, var(--fg) 86%, transparent); -} -.gallery-clear-all-filters { - height: 28px; - border-style: dashed; - opacity: 0.72; -} -.gallery-clear-all-filters:hover { - opacity: 1; - color: var(--red); -} -.gallery-chip-clear { - background: none; - border: none; - color: inherit; - opacity: 0.6; - cursor: pointer; - padding: 0; - margin-left: 1px; - width: 12px; - height: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 12px; - line-height: 1; - position: relative; - top: -4px; -} -.gallery-chip-clear:hover { opacity: 1; } - - - -/* Favorite button on card */ -/* Video cards: fills the same slot as , plus a small play badge - so it's clear the thumbnail represents a video. */ -.gallery-card video { - width: 100%; - aspect-ratio: 1 / 1; - object-fit: cover; - background: #000; - display: block; -} -.gallery-card-play { - position: absolute; - left: 50%; top: 50%; - transform: translate(-50%, -50%); - width: 36px; height: 36px; - display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.55); - color: #fff; - border-radius: 50%; - pointer-events: none; - z-index: 1; -} -.gallery-card-play svg { margin-left: 2px; } - -.gallery-fav-btn { - position: absolute; top: 0px; right: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; font-size: 14px; - color: rgba(255,255,255,0.6); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s; - padding: 0; -} -.gallery-fav-btn.gallery-fav-active { opacity: 1; color: var(--red); } - -.gallery-dl-btn { - position: absolute; top: 0px; left: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; - color: rgba(255,255,255,0.75); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s, background 0.15s; - padding: 0; -} -.gallery-dl-btn:hover { background: rgba(0,0,0,0.7); color: #fff; } -/* In select mode the per-thumbnail hover buttons (favorite + download) just - get in the way of picking — hide them so the card is a clean select target. - `body.gallery-selecting` is the authoritative signal (one class for the - whole grid) — the per-card .gallery-card-selectable is kept as a backup - in case the body class is missed. */ -body.gallery-selecting .gallery-fav-btn, -body.gallery-selecting .gallery-dl-btn, -.gallery-card-selectable .gallery-fav-btn, -.gallery-card-selectable .gallery-dl-btn { - display: none !important; -} - -/* AI tag chips in detail view */ -.gallery-ai-tags { display: flex; gap: 4px; flex-wrap: wrap; } -.gallery-ai-chip { - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); - border-radius: 10px; padding: 3px 8px; font-size: 10px; - color: var(--fg); opacity: 0.8; - font: inherit; font-size: 10px; - cursor: pointer; - transition: background 0.12s, opacity 0.12s, border-color 0.12s; -} -.gallery-ai-chip:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); -} -/* AI-generated tags: muted neutral with a tiny sparkle marker so the - user can tell at a glance these came from the model, not them. */ -.gallery-aitag-chip { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-aitag-chip:hover { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.gallery-aitag-mark { - font-size: 8px; - opacity: 0.55; - color: var(--accent, var(--red)); -} -/* User-applied tags keep the accent-colored chip — they're the personal/ - curated tags, so they get the strongest visual weight. */ -.gallery-user-chip { - font-weight: 600; -} -/* × to remove a user tag (appears inside the chip). */ -.gallery-tag-x { - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 5px; - width: 13px; - height: 13px; - border-radius: 50%; - font-size: 12px; - line-height: 1; - opacity: 0.55; - transition: opacity 0.12s, background 0.12s, color 0.12s; -} -.gallery-tag-x:hover { - opacity: 1; - background: color-mix(in srgb, var(--red, #ff5555) 22%, transparent); - color: var(--red, #ff5555); -} - -.gallery-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - align-content: start; - gap: 8px; - max-height: calc(92vh - 165px); - overflow-y: auto; - padding: 2px; - transition: border-color 0.15s, background 0.15s; -} -.gallery-grid.gallery-dragover { - border: 2px dashed var(--red); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.gallery-card { - position: relative; - aspect-ratio: 1; - border-radius: 6px; - overflow: hidden; - border: 1px solid var(--border); - cursor: pointer; - transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s; -} -.gallery-card.gallery-card-focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 36%, transparent); -} -/* Upload-affordance tile pinned to the top-left of the Photos grid. Matches - the Upload album tile in the Albums tab — dashed border, centered icon. */ -.gallery-card-upload { - border-style: dashed; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - opacity: 0.75; - transition: opacity 0.12s, border-color 0.15s, transform 0.15s; -} -/* The "Start AI tag" button turns into a Cancel control during a tag run. */ -.gallery-tag-cancelling { - color: var(--red) !important; - border-color: color-mix(in srgb, var(--red) 45%, var(--border)) !important; -} -/* Skeleton/shimmer placeholder tiles shown while the first page of photos loads. */ -.gallery-card-skeleton { - cursor: default; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.gallery-card-skeleton:hover { transform: none; box-shadow: none; border-color: var(--border); } -/* Chat attachment image: shimmer skeleton + centered whirlpool shown while a - sent photo uploads / its thumbnail loads (see chatRenderer buildAttachCards). */ -.attach-image-skeleton { - position: relative; - width: 160px; height: 120px; - border-radius: 6px; - display: flex; align-items: center; justify-content: center; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.attach-image-skeleton > .spinner-whirlpool { margin: 0 !important; } - -/* Corner "Aa" button on a chat photo thumbnail — opens the vision/OCR editor - so the user can correct what the vision model fed to the LLM. */ -.attach-image-preview { position: relative; } -.attach-vision-model, -.attach-image-name { - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.attach-vision-model { - font-size: 10px; - color: var(--accent, var(--red)); - opacity: 0.85; - margin-top: 3px; -} -.attach-image-name { - font-size: 10px; - opacity: 0.5; - margin-top: 1px; -} -.attach-ocr-btn { - position: absolute; top: 4px; right: 4px; - z-index: 5; /* sit above msg-action overlays so the click lands here */ - height: 22px; - background: rgba(0,0,0,0.55); color: #fff; - border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; - padding: 0 8px 0 6px; cursor: pointer; - display: inline-flex; align-items: center; justify-content: center; gap: 5px; - font-size: 11px; font-weight: 500; line-height: 1; - opacity: 0.75; transition: opacity 0.15s; -} -.attach-ocr-btn:hover { opacity: 1; } -.attach-ocr-btn svg { display: block; } -/* On mobile keep just the (larger) edit icon — the "Caption" label crowds - the corner of the small chat photo. */ -@media (max-width: 768px) { - .attach-ocr-btn .attach-ocr-label { display: none; } - .attach-ocr-btn { - width: 28px; height: 28px; - padding: 0; - } - .attach-ocr-btn svg { width: 16px; height: 16px; } -} - -/* Vision/OCR text editor modal — opened from the "Aa" button above. */ -.vision-editor-overlay { - position: fixed; inset: 0; z-index: 9999; - background: rgba(0,0,0,0.55); - display: flex; align-items: center; justify-content: center; - padding: 16px; -} -.vision-editor-panel { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 14px 16px; - width: min(560px, 92vw); - max-height: 88vh; - display: flex; flex-direction: column; gap: 8px; - box-shadow: 0 14px 40px rgba(0,0,0,0.4); -} -.vision-editor-title { - font-size: 13px; font-weight: 600; color: var(--fg); - display: flex; align-items: center; gap: 6px; -} -.vision-editor-desc { font-size: 11px; opacity: 0.6; line-height: 1.4; } -.vision-editor-text { - width: 100%; min-height: 180px; - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 8px 10px; - font-family: inherit; font-size: 12px; line-height: 1.5; - resize: vertical; outline: none; -} -.vision-editor-text:focus { border-color: var(--accent-primary, var(--red)); } -.vision-editor-hint { font-size: 10.5px; opacity: 0.55; line-height: 1.4; } -.vision-editor-actions { - display: flex; gap: 8px; justify-content: flex-end; - margin-top: 4px; -} -.vision-editor-btn { - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; - transition: border-color 0.15s, background 0.15s; - display: inline-flex; align-items: center; gap: 5px; -} -.vision-editor-btn svg { display: block; } -/* Optical nudge: the button text sits 1px higher than the SVG glyphs - alongside it. Drop the text down 1px so they line up. */ -.vision-editor-btn .vision-btn-label { position: relative; top: 1px; } -.vision-editor-btn:hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); } -.vision-editor-btn-primary { - background: var(--accent-primary, var(--red)); - color: #fff; border-color: transparent; -} -.vision-editor-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } - -/* Quick full-size lightbox shown when the user taps a chat photo thumbnail. */ -.attach-lightbox { - position: fixed; inset: 0; z-index: 9998; - background: rgba(0, 0, 0, 0.85); - display: flex; align-items: center; justify-content: center; - padding: 16px; cursor: zoom-out; - animation: attach-lightbox-fade 0.12s ease-out; -} -.attach-lightbox img { - max-width: 100%; max-height: 100%; - border-radius: 6px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); - object-fit: contain; -} -.attach-lightbox-err { - position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); - background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 10px; font-size: 12px; -} -@keyframes attach-lightbox-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -.gallery-card-upload:hover { - opacity: 1; - border-color: var(--red); - transform: translateY(-1px); -} -.gallery-card-upload-inner { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 6px; - color: var(--fg); -} -.gallery-card-upload-label { - font-size: 12px; - font-weight: 500; - opacity: 0.8; -} -.gallery-card:hover { - border-color: var(--red); - box-shadow: 0 2px 8px color-mix(in srgb, var(--red) 15%, transparent); - transform: translateY(-1px); -} -.gallery-card-overlay { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 8px; - background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 34%, rgba(0,0,0,0.22)); - color: #fff; - opacity: 0; - pointer-events: none; - transition: opacity 0.15s; -} -.gallery-card-badges { - display: flex; - gap: 4px; - flex-wrap: wrap; - align-items: flex-end; - justify-content: flex-end; - margin-top: auto; - margin-bottom: 34px; -} -.gallery-card-badge { - padding: 2px 6px; - border-radius: 999px; - background: rgba(0, 0, 0, 0.55); - border: 1px solid rgba(255, 255, 255, 0.14); - color: rgba(255, 255, 255, 0.88); - font-size: 9px; - font-weight: 600; - line-height: 1.1; -} -.gallery-card-badge-fav { - color: #ff5f6f; -} -.gallery-select-dot { - position: absolute; top: 6px; left: 6px; z-index: 2; - display: block; - width: 10px; height: 10px; border-radius: 50%; cursor: pointer; - background: color-mix(in srgb, var(--fg) 30%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 50%, transparent); - transition: background 0.15s; -} -.gallery-select-dot.selected { background: var(--red); border-color: var(--red); } -.gallery-select-dot:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -.gallery-select-btn { - padding: 9px 10px 11px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; transition: all 0.15s; - margin-top: -4px !important; -} -.gallery-select-btn:hover { opacity: 1; } -/* Match the library Select toggle's red tint (.memory-toolbar-btn.active). - Bumped contrast so the toggled state actually reads on mobile — the prior - 15% red on transparent was nearly invisible against the page background. */ -.gallery-select-btn.active { - background: color-mix(in srgb, var(--red) 28%, transparent); - color: var(--red); - border-color: var(--red); - font-weight: 600; - opacity: 1; -} -/* Toolbar action buttons sit 2px lower than the generic select-btn so - they baseline-align with the toolbar's selects/inputs instead of - floating above them. */ -.gallery-toolbar-action { - margin-top: 0 !important; - display: inline-flex; - align-items: center; - gap: 2px; -} -.gallery-bulk-bar { - display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 12px; - justify-content: flex-end; -} -.gallery-bulk-bar.hidden { display: none; } -.gallery-bulk-all { - display: inline-flex; align-items: center; gap: 4px; - font-size: 11px; opacity: 0.75; cursor: pointer; margin-right: auto; -} -.gallery-bulk-all input { accent-color: var(--accent, var(--red)); cursor: pointer; } -.gallery-bulk-delete { - padding: 3px 10px; background: transparent; color: var(--red); - border: 1px solid var(--red); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; -} -.gallery-bulk-delete:hover { background: var(--red); color: #fff; } -.gallery-bulk-cancel { - padding: 3px 10px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; -} -.gallery-bulk-cancel:hover { opacity: 1; } -.gallery-card img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} -.gallery-card-info { - position: absolute; - bottom: 0; - left: 0; - right: 0; - padding: 6px 8px; - background: linear-gradient(transparent, rgba(0,0,0,0.75)); - color: #fff; -} -.gallery-card-prompt { - font-size: 10px; - line-height: 1.3; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.gallery-card-meta { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - font-size: 9px; - opacity: 0.8; -} -.gallery-card-model { - padding: 1px 5px; - border-radius: 6px; - background: rgba(255,255,255,0.15); -} -.gallery-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -.gallery-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: none; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-load-more:hover { - border-color: var(--red); - color: var(--red); -} - -/* Gallery detail overlay */ -.gallery-detail { - position: absolute; - inset: 0; - background: var(--panel); - z-index: 10; - display: flex; - flex-direction: column; - overflow-y: auto; -} -.gallery-detail-header { - display: flex; - align-items: center; - gap: 6px; - padding: 2px 8px 4px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - /* Pin to top so a tall (portrait) photo can't cover the Back / ⋮ - menu when the detail body grows past the visible area. */ - position: sticky; - top: 0; - background: var(--panel); - /* Above the image's rotate/nav buttons (z-index:3) so they scroll cleanly - behind the header instead of colliding with it. */ - z-index: 5; - min-height: 0; -} -.gallery-detail-title { - max-width: min(360px, 40vw); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 12px; - font-weight: 600; - opacity: 0.82; - position: relative; - top: 1px; -} -.gallery-detail-back { - background: none; - border: none; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 12px; - padding: 4px 8px; - border-radius: 4px; - position: relative; - top: -2px; -} -/* Slightly larger header icons (back/edit/heart/⋮) — override the inline 14px. */ -.gallery-detail-header svg { width: 16px; height: 16px; } -.gallery-detail-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.gallery-detail-action { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - padding: 3px 8px; - border-radius: 4px; - transition: border-color 0.15s; -} -.gallery-detail-action:hover { - border-color: var(--fg); -} -.gallery-detail-action.danger { - color: var(--color-error, #e55); -} -/* Overflow menu — replaces the seven individual action buttons that used - to crowd the right side of the detail header. */ -.gallery-detail-menu-wrap { position: relative; } -.gallery-detail-menu-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 26px; - padding: 0; - position: relative; - top: -2px; -} -.gallery-detail-menu { - position: absolute; - top: calc(100% + 4px); - right: 0; - min-width: 150px; - padding: 3px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 12; - display: flex; - flex-direction: column; - gap: 0; -} -.gallery-detail-menu[hidden] { display: none; } -.gallery-detail-menu .dropdown-item-compact { - width: 100%; - background: none; - border: none; - text-align: left; - font: inherit; - /* Tighter than the global default — the photo-detail menu has 8 - items so every pixel of vertical padding adds up. */ - padding: 4px 8px; - font-size: 11px; - gap: 8px; - line-height: 1.2; -} -.gallery-detail-menu .dropdown-item-compact .dropdown-icon, -.gallery-detail-menu .dropdown-item-compact .dropdown-icon svg { - width: 12px; - height: 12px; -} -.gallery-detail-body { - display: flex; - gap: 16px; - padding: 12px; - flex: 1; - min-height: 0; - overflow: hidden; -} -.gallery-detail-image { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - min-width: 0; - min-height: 0; - height: 100%; - padding-inline: 56px; - box-sizing: border-box; - overflow: hidden; -} -/* Shrink-wraps the actual image so overlay children (heart, face boxes) - anchor to the IMAGE bounds, not the wider letterboxed wrapper. */ -.gallery-detail-img-frame { - position: relative; - display: inline-flex; - max-width: 100%; - max-height: 100%; -} -.gallery-detail-nav { - position: absolute; - top: 50%; - transform: translateY(-50%); - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-nav:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-nav-prev { left: 8px; } -.gallery-detail-nav-next { right: 8px; } - -/* Rotate buttons — same pattern as the prev/next arrows but pinned to the - top corners. Hover-revealed so they don't clutter the image. */ -.gallery-detail-rotate { - position: absolute; - top: 8px; - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-rotate:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-rotate-ccw { left: 8px; } -.gallery-detail-rotate-cw { right: 8px; } -/* Inline heart that lives next to the "Date" label in the sidebar. - Tiny outline icon by default; fills red when favorited. */ -.gallery-detail-date-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; -} -.gallery-detail-fav-inline { - background: none; - border: none; - padding: 0; - /* Nudge up 8px so the heart sits visually above the Date label - baseline rather than centred on it. */ - margin-top: -8px; - border-radius: 4px; - color: var(--fg-muted); - opacity: 0.6; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; - transition: opacity 0.15s, color 0.15s; -} -/* Shrink the SVG so the heart matches the height of the small uppercase - Date label sitting next to it. */ -.gallery-detail-fav-inline svg { - width: 12px; - height: 12px; - display: block; -} -.gallery-detail-fav-inline:hover { - opacity: 1; -} -.gallery-detail-fav-inline.active { - opacity: 1; - color: var(--red); -} -.gallery-detail-sidebar { - width: 290px; - flex-shrink: 0; - /* Vertical scroll for long metadata, but never horizontal — overflow:auto - alone would let a wide child (e.g. long album name in the select) push - the sidebar into horizontal-scroll mode. */ - overflow-x: hidden; - overflow-y: auto; - min-width: 0; -} -.gallery-detail-inspector-head { - padding-bottom: 10px; - margin-bottom: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.gallery-detail-eyebrow { - font-size: 9px; - font-weight: 700; - color: var(--accent, var(--red)); - text-transform: uppercase; - letter-spacing: 0.08em; - margin-bottom: 3px; -} -.gallery-detail-inspector-title { - font-size: 14px; - font-weight: 650; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-meta-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 6px; - margin: 4px 0 12px; -} -.gallery-detail-meta-card { - min-width: 0; - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-radius: 7px; - padding: 7px 8px; -} -.gallery-detail-meta-card span { - display: block; - font-size: 9px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 48%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 3px; -} -.gallery-detail-meta-card strong { - display: block; - min-width: 0; - font-size: 11px; - font-weight: 600; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-inline-link { - appearance: none; - background: none; - border: none; - color: var(--accent, var(--red)); - padding: 0; - font: inherit; - font-weight: inherit; - cursor: pointer; - text-align: left; -} -.gallery-detail-mini-actions { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 7px; -} -.gallery-detail-mini-btn { - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 8px; - font: inherit; - font-size: 10.5px; - cursor: pointer; - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-detail-mini-btn:hover { - border-color: color-mix(in srgb, var(--fg) 42%, var(--border)); -} -/* Constrain any child that could otherwise stretch the sidebar wider than - its declared width (selects with long option text, long URLs, etc.). */ -.gallery-detail-sidebar select, -.gallery-detail-sidebar input, -.gallery-detail-sidebar .gallery-detail-prompt { - max-width: 100%; - box-sizing: border-box; -} -.gallery-date-rel { - opacity: 0.55; - margin-left: 4px; - font-size: 11px; - font-style: italic; -} -.gallery-detail-section label { - display: block; - font-size: 10px; - font-weight: 600; - opacity: 0.6; - margin-bottom: 3px; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.gallery-detail-section div { - font-size: 12px; - word-break: break-word; -} -.gallery-detail-prompt { - white-space: pre-wrap; - line-height: 1.4; -} -.gallery-tag-input, -.gallery-detail-name-input { - width: 100%; - padding: 5px 8px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - font: inherit; - font-size: 11px; - margin-bottom: 4px; -} -.gallery-tag-input:focus, -.gallery-detail-name-input:focus { - border-color: var(--red); - outline: none; -} -/* ↵ enter hint inside the Add-a-tag field (accent), replacing the "press Enter" - placeholder text. */ -.gallery-tag-input-wrap { position: relative; } -.gallery-tag-enter-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; /* the input has 4px bottom margin; center on the box */ - color: var(--accent, var(--red)); - font-size: 13px; - line-height: 1; - pointer-events: none; - opacity: 0.8; -} -.gallery-detail-name-input { font-size: 13px; font-weight: 500; } -/* ↵ enter hint on the image name field — accent, shown while editing to signal - that Enter saves. */ -.gallery-name-wrap { position: relative; } -.gallery-name-enter { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; - color: var(--accent, var(--red)); - opacity: 0.85; - pointer-events: none; - display: none; -} -.gallery-tag-save { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 10px; - padding: 3px 10px; - border-radius: 4px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-tag-save:hover { - border-color: var(--red); - color: var(--red); -} - -@media (max-width: 600px) { - .gallery-grid { - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - } - /* Single-row toolbar on mobile: search + Sources + Recent (sort) all share - one row. The search bar shrinks so the dropdowns fit beside it. */ - .gallery-toolbar { - flex-wrap: nowrap; - gap: 4px; - } - .gallery-search-wrap { - flex: 1 1 0; - min-width: 0; - } - .gallery-search { - /* Drop the desktop right-padding reserved for the "enter to tag" hint — - hide the hint on mobile (below) so the input can use its full width. */ - padding-right: 8px; - } - .gallery-search-enter-hint { display: none; } - /* Disable the row break so the filters stay inline with search. */ - .gallery-toolbar-break { display: none; } - .gallery-model-filter, - .gallery-sort { - flex: 0 0 auto; - /* Cap each dropdown so search keeps a usable amount of width. */ - max-width: 100px; - font-size: 11px; - padding: 4px 6px; - /* Keep the same vertical alignment as the desktop toolbar. */ - position: relative; - top: 4px; - } - .gallery-select-btn { - padding: 6px 8px 8px; - font-size: 9px; - flex-shrink: 0; - /* Push Select to the far right of the single-row toolbar, regardless - of its DOM position (sits between search and the dropdowns). */ - order: 99; - margin-left: auto; - } - /* Tabs scroll horizontally rather than wrapping when narrow. */ - .gallery-tabs { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .gallery-tabs::-webkit-scrollbar { display: none; } - .gallery-tab { flex-shrink: 0; } - /* Album chips also scroll horizontally so they don't stack. */ - .gallery-album-chips { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .gallery-album-chips::-webkit-scrollbar { display: none; } - .gallery-chip { flex-shrink: 0; } - /* Tasks filter chips — same horizontal-swipe behaviour as the gallery - albums / library chips on mobile: one row, slide-to-see-more. */ - .tasks-activity-filters { - overflow-x: auto; - flex-wrap: nowrap !important; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .tasks-activity-filters::-webkit-scrollbar { display: none; } - .tasks-activity-filters > * { flex-shrink: 0; } - /* Detail view */ - .gallery-detail-body { - flex-direction: column; - overflow: visible; - } - .gallery-detail-image { - flex: 0 0 auto; - width: 100%; - height: clamp(280px, 58vh, 520px); - padding-inline: 48px; - } - .gallery-detail-sidebar { - width: 100%; - } - .gallery-detail-title { - display: none; - } - .gallery-detail-meta-grid { - grid-template-columns: 1fr; - } - /* Album grid: smaller tile minimum on mobile. */ - .gallery-albums-grid { - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 8px; - } -} - - -/* ── Scoreboard table ── */ -.scoreboard-table { - width: 100%; - border-collapse: collapse; - font-size: 0.88em; -} -.scoreboard-table th { - text-align: left; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 55%, transparent); - text-transform: uppercase; - letter-spacing: 0.5px; -} -.scoreboard-table th:not(:first-child) { - text-align: center; -} -.scoreboard-table td { - padding: 5px 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; -} -.scoreboard-table td.scoreboard-model { - text-align: left; - font-weight: 500; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.scoreboard-table td.scoreboard-pct { - font-weight: 600; - color: var(--red); -} -.scoreboard-table tbody tr:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} - -#scoreboard-overlay svg { - color: var(--accent, var(--red)); -} - -/* ── Compare search results ── */ -.compare-search-results { - display: flex; - flex-direction: column; - gap: 2px; -} -.compare-search-result { - padding: 6px 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); -} -.compare-search-result:last-child { - border-bottom: none; -} -.search-result-title { - color: var(--color-accent); - text-decoration: none; - font-weight: 500; - font-size: 0.9em; - display: block; - line-height: 1.3; -} -.search-result-title:hover { - color: var(--color-link-hover); - text-decoration: underline; -} -.search-result-snippet { - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.82em; - line-height: 1.4; - margin-top: 2px; -} -.search-result-url { - color: color-mix(in srgb, var(--fg) 35%, transparent); - font-size: 0.75em; - margin-top: 3px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - - -/* ── Cookbook Tool ── */ - -/* ── Cookbook ── */ -.cookbook-serve-preset { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - margin-bottom: 3px; - cursor: pointer; - transition: background 0.15s; -} -.cookbook-serve-preset:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cookbook-serve-preset-name { - font-size: 11px; - font-weight: 500; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-serve-preset-meta { - font-size: 9px; - font-family: 'Fira Code', monospace; - color: var(--fg-muted); - opacity: 0.5; -} -.cookbook-serve-preset-launch { - background: none; - border: none; - color: #50fa7b; - cursor: pointer; - font-size: 10px; - opacity: 0.6; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-launch:hover { opacity: 1; } -.cookbook-serve-preset-rm { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 10px; - opacity: 0.3; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-rm:hover { opacity: 0.8; color: var(--red); } -.cookbook-body { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - flex: 1; - min-height: 0; - scrollbar-width: thin; -} -#cookbook-modal .modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -#cookbook-modal .modal-header { - flex: 0 0 auto; -} -#cookbook-modal .cookbook-body { - min-height: 0; - overflow-y: auto; - overflow-x: hidden; -} -#cookbook-modal .cookbook-group { - min-height: 0; -} -#cookbook-modal .cookbook-group > .admin-card { - min-height: 0; - /* Let .cookbook-body be the SINGLE scroll surface. Nesting another - overflow:auto here trapped the wheel inside the cached-list when a - serve panel expanded — the page couldn't scroll past the panel's - bottom (Launch button got hidden). */ - overflow: visible !important; -} -#cookbook-modal .cookbook-section-body { - min-height: 0; -} -.cookbook-group { - display: flex; - flex-direction: column; - gap: 8px; - flex: 1; - min-height: 0; -} - -/* Cards — match admin-card */ -.cookbook-card { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-card-header { - display: flex; - align-items: center; - justify-content: space-between; -} -.cookbook-card-header-actions { display: flex; gap: 4px; } -.cookbook-card-title { font-size: 13px; font-weight: 600; } -.cookbook-card-desc { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} - -/* Fields grid */ -.cookbook-fields { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 6px; - margin-top: 4px; -} -.cookbook-field-label { - display: flex; - flex-direction: column; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - gap: 3px; -} - -/* Inputs — match admin-add-form input */ -.cookbook-field-input { - padding: 5px 8px; - font-size: 12px; - font-family: inherit; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - outline: none; -} -.cookbook-field-input:focus { border-color: var(--red); } -#hwfit-dl-server, #hwfit-usecase, #hwfit-server-select, #hwfit-cache-server, #serve-sort { height: 28px; width: 88px; } -/* Serve tab — match Documents sizing, but leave room for the active "Cancel" - label and center it vertically so the descenders don't clip. */ -#hwfit-cache-select { - min-width: 58px; - height: 28px; - position: relative; - top: -3px; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; -} - -/* Command preview */ -.cookbook-cmd-preview { - margin: 4px 0 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - color: var(--fg); -} - -/* Buttons — match admin-btn-sm */ -.cookbook-actions { display: flex; gap: 6px; margin-top: 2px; } -.cookbook-btn { - padding: 4px 10px; - min-width: 54px; - text-align: center; - font-size: 11px; - font-family: inherit; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - cursor: pointer; - transition: all 0.15s; -} -.cookbook-btn:hover { - background: var(--border); - border-color: var(--accent); -} -.cookbook-btn:disabled { opacity: 0.5; cursor: not-allowed; } -.cookbook-run-btn { - background: var(--accent); - color: var(--panel); - border-color: var(--accent); - font-weight: 600; -} -.cookbook-run-btn:hover { opacity: 0.85; } -.cookbook-stop-btn { - background: var(--color-error); - border-color: var(--color-error); -} - -/* Output */ -.cookbook-output-wrap { - position: relative; - margin: 0; -} -.cookbook-output-kill { - position: absolute; - top: 4px; - right: 34px; - width: 22px; - height: 22px; - padding: 0; - background: none; - border: 1px solid transparent; - border-radius: 4px; - color: var(--fg-muted); - font-size: 16px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity .15s, color .15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-output-kill:hover { color: var(--color-error, var(--warn)); border-color: var(--border); } -.cookbook-output-wrap .copy-code { - position: absolute; - top: 6px; - right: 6px; -} -.cookbook-output-wrap:hover .copy-code { opacity: 0.7; } -.cookbook-output-pre { - margin: 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - max-height: 180px; - overflow-y: auto; -} -.cookbook-output-error { color: var(--color-error); } - -/* Downloaded/cached models */ -.hwfit-cached-item { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 6px; - padding: 6px 8px; - margin: 2px 0; - border-radius: 6px; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 3%, transparent); - cursor: default; - font-size: 12px; -} -.hwfit-cached-item .hwfit-serve-panel { - flex-basis: 100%; - margin-top: 4px; -} -.hwfit-cached-item:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); -} -.hwfit-cached-header { - font-size: 10px; - opacity: 0.4; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - border: none !important; - background: none !important; - padding: 2px 8px; - cursor: default !important; -} -.hwfit-cached-header:hover { background: none !important; } -.hwfit-cached-name { - font-weight: 600; - flex-shrink: 0; -} -.hwfit-cached-repo { - flex: 1; - color: var(--fg-muted); - font-size: 11px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-cached-size, -.hwfit-cached-files { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; -} -.hwfit-cached-serve, -.hwfit-cached-delete { - flex-shrink: 0; -} -.hwfit-cached-delete { - opacity: 0.4; - font-size: 10px; -} -.hwfit-cached-delete:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} -.hwfit-cached-badge { - font-size: 9px; - padding: 1px 6px; - border-radius: 3px; - font-weight: 600; - white-space: nowrap; -} -.hwfit-cached-ready { - background: color-mix(in srgb, var(--color-success, #4caf50) 20%, transparent); - color: var(--color-success, #4caf50); -} -.hwfit-cached-dl { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 20%, transparent); - color: var(--color-warning, #f0ad4e); -} - -/* Cookbook notification dot */ -#tool-cookbook-btn { - position: relative; -} -#rail-cookbook { - position: relative; -} -.cookbook-open-loading { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - flex-shrink: 0; - margin-left: 2px; - color: var(--accent, var(--red)); - opacity: 0.9; -} -.cookbook-open-loading .ai-spinner, -.cookbook-open-loading .ai-spinner-whirlpool { - display: inline-flex !important; - width: 14px; - height: 14px; - margin: 0 !important; - gap: 0 !important; -} -.cookbook-open-loading canvas { - display: block; -} -.cookbook-section-loading-wp, -.cookbook-section-loading-wp canvas { - width: 22px !important; - height: 22px !important; - display: block !important; -} -#rail-cookbook .cookbook-open-loading { - position: absolute; - right: 2px; - top: -2px; - width: 12px; - height: 12px; - margin-left: 0; -} -/* The serve-list 'downloading' / 'download stalled' status text sits a hair - low against the rest of the meta line on mobile — nudge it up 2px. */ -@media (max-width: 768px) { - .cookbook-dl-status { - position: relative; - top: -2px; - } -} -.cookbook-notif-dot { - width: 6px; - height: 6px; - flex-shrink: 0; - border-radius: 50%; - background: var(--color-success, #4caf50); - /* Drives the breathing glow in the keyframes (matches email's dot). */ - --notif-glow: var(--color-success, #4caf50); - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -.cookbook-notif-dot.cookbook-notif-error { - background: var(--color-error, #f44); - --notif-glow: var(--color-error, #f44); - position: relative; - left: -2px; - top: -1px; -} -.rail-notify-error { color: var(--color-error, #f44) !important; } -.cookbook-tab-error-dot { - display: inline-block; width: 5px; height: 5px; border-radius: 50%; - background: var(--color-error, #f44); margin-left: 4px; vertical-align: middle; - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -@keyframes cookbook-notif-pulse { - 0%, 100% { - opacity: 1; - box-shadow: 0 0 0 0 color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 60%, transparent); - } - 50% { - opacity: 0.85; - box-shadow: 0 0 6px 2px color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 55%, transparent); - } -} -.cookbook-clear-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-clear-btn:hover { - color: var(--red) !important; - border-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 8%, transparent) !important; -} -.cookbook-bulk-bar { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - margin-bottom: 4px; - background: color-mix(in srgb, var(--red) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 6px; - font-size: 11px; -} -.cookbook-bulk-bar.hidden { display: none; } -.serve-select-cb { - width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; cursor: pointer; - background: var(--border); transition: background 0.15s; - align-self: center; - position: relative; - top: -2px; -} -#serve-bulk-bar { - position: relative; - top: -6px; -} -#serve-bulk-bar #serve-bulk-cancel { - top: -2px !important; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 28px; - height: 24px; - line-height: 1; -} -#serve-bulk-bar #serve-bulk-cancel svg { - top: 0; -} -.serve-select-cb.selected { background: var(--red); } -.serve-select-cb:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -#hwfit-cache-select.active { - background: var(--red); - color: #fff; - border-color: var(--red); -} -.cookbook-serve-dirs { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.cookbook-serve-dir-pill { - font-size: 10px; - font-family: 'Fira Code', monospace; - padding: 2px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); - color: var(--fg-muted); - letter-spacing: 0.2px; -} -/* Status pills shown inline in a Serve-tab card title (next to the model - name) when the model has a live serve / download task. Shared base - class so "running" and "downloading" sit on the same row with the - same chrome; only color varies. */ -.cookbook-serve-running-pill, -.cookbook-serve-downloading-pill { - display: inline-block; - margin-left: 6px; - padding: 1px 7px; - border-radius: 10px; - font-size: 9px; - font-weight: 500; - text-transform: lowercase; - letter-spacing: 0.3px; - vertical-align: 2px; - position: relative; - top: 1px; /* nudged down 2px from the old -1px so it sits - flush with the cap-height of the title text */ -} -.cookbook-serve-running-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); -} -.cookbook-serve-downloading-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); - opacity: 0.85; -} -.cookbook-serve-downloading-pill.is-stalled { - /* Stalled downloads stay visible but read as warning, not progress. */ - color: var(--fg-muted, #888); - background: color-mix(in srgb, var(--fg-muted, #888) 10%, transparent); - border-color: color-mix(in srgb, var(--fg-muted, #888) 30%, transparent); - opacity: 1; -} -.cookbook-serve-running-pill.is-clickable { - cursor: pointer; - transition: background 0.12s, border-color 0.12s; -} -.cookbook-serve-running-pill.is-clickable:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -.cookbook-serve-dir-edit { - font-size: 9px; - color: var(--fg-muted); - opacity: 0.4; - cursor: pointer; - text-decoration: underline; - text-underline-offset: 2px; -} -.cookbook-serve-dir-edit:hover { - opacity: 0.7; - color: var(--accent, var(--red)); -} -.cookbook-gpu-group { - display: flex; - gap: 3px; - margin-top: -4px; -} -.cookbook-gpu-btn { - width: 30px; height: 30px; - font-size: 12px; - font-family: 'Fira Code', monospace; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - color: var(--fg-muted); - cursor: pointer; - opacity: 0.4; - transition: all 0.15s; - padding: 0; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-gpu-btn:hover { - opacity: 0.7; - border-color: var(--fg-muted); -} -.cookbook-gpu-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 55%, transparent); - color: var(--fg); - font-weight: 600; -} -/* Probe annotation states (set by "Free?" button) */ -.cookbook-gpu-btn.gpu-free { - opacity: 1; - border-color: #4ade80; - color: #4ade80; -} -.cookbook-gpu-btn.gpu-busy { - opacity: 0.85; - border-color: color-mix(in srgb, var(--red) 70%, transparent); - color: var(--red); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-btn.gpu-missing { - opacity: 0.2; - text-decoration: line-through; - cursor: not-allowed; -} -/* "Clear Server" lives in the actions row next to "Probe GPUs". - Inherits .cookbook-btn sizing; this rule just adds the destructive - red tint on hover so the "this kills processes" cue stays. */ -.cookbook-gpu-clear:hover:not(:disabled) { - color: var(--red); - border-color: color-mix(in srgb, var(--red) 60%, var(--border)); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -/* GPU probe popup — per-GPU process list with kill buttons */ -.cookbook-gpu-popup { - /* Fixed positioning (relative to viewport) so we never get pulled into - a scrolling/transform stacking context from an ancestor. Z-index has - to clear the cookbook modal (260) and the rest of the high-z UI - layers (themed-confirm and various overlays sit around 9000-10000). */ - position: fixed; - z-index: 10010; - min-width: 280px; - max-width: 420px; - background: var(--panel, #1a1a1a); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 8px 24px rgba(0,0,0,0.4); - font-family: 'Fira Code', monospace; - font-size: 11px; - color: var(--fg); -} -.cookbook-gpu-popup-head { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; -} -.cookbook-gpu-popup-stats { - flex: 1; - font-weight: 400; - color: var(--fg-muted); - font-size: 10px; -} -.cookbook-gpu-popup-close { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 14px; - padding: 0 4px; -} -.cookbook-gpu-popup-close:hover { color: var(--fg); } -.cookbook-gpu-popup-body { - padding: 4px 0; - max-height: 320px; - overflow-y: auto; -} -.cookbook-gpu-popup-empty { - padding: 10px; - color: var(--fg-muted); - font-style: italic; -} -.cookbook-gpu-proc { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; -} -.cookbook-gpu-proc:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); } -.cookbook-gpu-proc-info { - flex: 1; - display: flex; - gap: 8px; - align-items: center; - min-width: 0; -} -.cookbook-gpu-proc-pid { - color: var(--fg-muted); - width: 56px; - flex-shrink: 0; -} -.cookbook-gpu-proc-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.cookbook-gpu-proc-mem { - color: var(--fg-muted); - flex-shrink: 0; -} -.cookbook-gpu-proc-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} -.cookbook-gpu-kill { - border: 1px solid color-mix(in srgb, var(--red) 60%, transparent); - background: none; - color: var(--red); - border-radius: 3px; - padding: 2px 8px; - cursor: pointer; - font-family: inherit; - font-size: 10px; -} -.cookbook-gpu-kill[data-sig="KILL"] { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.cookbook-gpu-kill:hover:not(:disabled) { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.cookbook-gpu-kill:disabled { opacity: 0.4; cursor: wait; } -.cookbook-serve-title { - display: flex; - align-items: center; - gap: 4px; - min-width: 0; -} -.cookbook-serve-title-name { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-hf-link { - font-size: 9px; - text-decoration: none; - color: var(--fg-muted); - opacity: 0.5; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - vertical-align: 1px; - letter-spacing: 0.3px; - font-weight: 600; - flex-shrink: 0; -} -.cookbook-hf-link:hover { - opacity: 0.8; - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -/* In the Trending models list the HF pill is the row's main affordance — - tint it accent so it stands out next to the model name. */ -#cookbook-hf-latest-list .cookbook-hf-link { - color: var(--accent, var(--red)); - opacity: 0.85; - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); -} - -/* Running tab sections */ -.cookbook-saved-section, -.cookbook-serve-section, -.cookbook-dl-section { - border: 1px solid var(--border); - border-radius: 6px; - margin-bottom: 6px; - overflow: hidden; -} -/* Divider between server-group sections in the Active tab so the - transition between e.g. local + remote-host server blocks reads - visually instead of running into each other. */ -.cookbook-group[data-backend-group="Running"] > [class*="cookbook-server-section-"] + [class*="cookbook-server-section-"] { - margin-top: 14px; - padding-top: 14px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.cookbook-section-header { - display: flex; - align-items: center; - gap: 6px; - padding: 7px 10px; - cursor: pointer; - user-select: none; - /* Persistent surface + border so it reads as a clickable bar instead of - blending into the panel background. */ - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - transition: background 0.15s, border-color 0.15s; -} -.cookbook-section-header:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: color-mix(in srgb, var(--fg) 22%, transparent); -} -.cookbook-section-header.has-server-color { - color: var(--fg); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 45%, var(--border)); - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 4%, var(--bg)); - box-shadow: inset 6px 0 0 var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color:hover { - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 7%, var(--bg)); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 60%, var(--border)); -} -.cookbook-section-header.has-server-color .cookbook-section-title, -.cookbook-section-header.has-server-color .cookbook-section-chevron { - color: var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color .cookbook-srv-status.ok { - background: var(--cookbook-server-accent, var(--cookbook-server-color)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 55%, transparent); -} -.cookbook-section-header .cookbook-clear-btn { - margin-left: 0; - position: relative; - top: -3px; -} -@media (max-width: 768px) { - .cookbook-section-header .cookbook-clear-btn { - top: -3px; - } - .cookbook-task-check { - top: 4px; - } -} -/* "Stop all" sits just left of "Clear finished"; it carries the auto margin so - the pair is pushed together to the right of the section title. */ -.cookbook-section-header .cookbook-stop-all-btn { - margin-left: auto; - margin-right: 6px; - position: relative; - top: -3px; -} -.cookbook-stop-all-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-stop-all-btn:hover { - color: var(--warn, #f0ad4e) !important; - border-color: var(--warn, #f0ad4e) !important; - background: color-mix(in srgb, var(--warn, #f0ad4e) 8%, transparent) !important; -} -.cookbook-section-chevron { - flex-shrink: 0; - opacity: 0.6; - transition: transform 0.15s ease, opacity 0.15s ease; -} -.cookbook-section-body { - padding: 0; -} -.cookbook-saved-toggle-header { - font-size: 10px; - font-weight: 600; - color: var(--fg-muted); - text-transform: uppercase; - letter-spacing: 0.3px; - margin-bottom: 4px; -} -.cookbook-saved-toggle:hover { color: var(--fg); } -.cookbook-saved-item { - margin: 2px 0; - border-radius: 4px; - font-size: 11px; -} -.cookbook-saved-header { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 6px; - border-radius: 4px; - transition: background 0.08s; -} -.cookbook-saved-header:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.cookbook-saved-item .hwfit-serve-panel { - margin: 4px 0 4px 6px; -} -.cookbook-saved-host { - font-size: 10px; - color: var(--fg-muted); - font-family: 'Fira Code', monospace; -} -.cookbook-saved-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-saved-launch { - font-size: 10px; - padding: 2px 8px; -} -.cookbook-saved-del { - background: none; - border: none; - color: var(--fg-muted); - opacity: 0.3; - cursor: pointer; - font-size: 11px; - padding: 0 2px; -} -.cookbook-saved-del:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} - -/* Serve config panel */ -.hwfit-serve-panel { - margin-top: 6px; - padding: 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex: 1; - display: flex; - flex-direction: column; - position: relative; -} -.cookbook-serve-slots { - display: flex; - gap: 3px; - justify-content: flex-end; - margin-bottom: 4px; -} -/* When the Save split sits inside Row 1 (next to GPUs), align it with the - input baseline (the row's grid cells stretch top-down; without this the - Save buttons sit above the GPU button group). */ -.hwfit-serve-row .cookbook-serve-slots { - align-self: end; - margin-bottom: 4px; -} -.cookbook-slot-btn { - min-width: 22px; height: 28px; - padding: 0 6px; - font-size: 10px; font-weight: 600; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - white-space: nowrap; - max-width: 80px; - overflow: hidden; - text-overflow: ellipsis; - color: var(--fg-muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.5; - transition: all 0.15s; -} -.cookbook-slot-btn:hover { opacity: 0.9; border-color: var(--fg-muted); } -/* Saved-configs split button: "Save" + dropdown-arrow joined into one control, - but each segment keeps its own style — Save uses the filled accent - "Done"-button look; the arrow stays a subtle outlined menu trigger. */ -.cookbook-saved-split { gap: 0; } -.cookbook-saved-save, -.cookbook-saved-arrow { max-width: none; } -.cookbook-saved-save { - padding: 0 10px; - gap: 4px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - font-weight: 600; - opacity: 1; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.cookbook-saved-save:hover { - background: var(--border); - border-color: var(--accent); - opacity: 1; -} -.cookbook-saved-arrow { - padding: 0 6px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - opacity: 1; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: none; -} -.cookbook-saved-menu .cookbook-saved-favorite { - border-left: 3px solid var(--red); - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-saved-fav-btn { - width: 20px; - height: 20px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: none; - color: var(--fg-muted); - opacity: 0.55; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -.cookbook-saved-fav-btn:hover, -.cookbook-saved-fav-btn.active { - color: var(--red); - opacity: 1; - background: color-mix(in srgb, var(--red) 10%, transparent); -} -.cookbook-saved-fav-badge { - flex-shrink: 0; - background: transparent; - padding: 0; -} -.cookbook-serve-favorite-model { - border-left-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-serve-fav-badge { - display: inline-flex; - align-items: center; - vertical-align: 1px; - margin-left: 5px; - margin-right: 2px; - background: transparent; - padding: 0; -} -.cookbook-slot-saved { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); } -.cookbook-slot-saved:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); } -.cookbook-slot-btn.active { opacity: 1; background: var(--accent); color: #fff; border-color: var(--accent); -} -.cookbook-slot-wrap { - position: relative; - display: inline-flex; -} -.cookbook-slot-del { - position: absolute; - top: -5px; - right: -5px; - width: 14px; - height: 14px; - padding: 0; - border: 1px solid var(--border); - border-radius: 50%; - background: var(--bg, #1a1a1a); - color: var(--fg-muted); - font-size: 12px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, color 0.15s, border-color 0.15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-slot-del:hover { - color: #f44; - border-color: #f44; -} -.cookbook-card-backend { display: none; } -/* Dependencies row — aligned tags */ -.cookbook-dep-row { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel); -} -/* Let the deps list fill the available window height instead of being - capped at the default .doclib-grid 400-px box. */ -[data-backend-group="Dependencies"] #cookbook-deps-list { - flex: 1; - max-height: none; -} - -/* Settings tab: stack the HF token + Servers cards as distinct blocks - (matches the Download tab's block-per-section layout). */ -.cookbook-settings-stack { - display: flex; - flex-direction: column; - gap: 10px; - flex: 1; - /* Scroll the whole settings panel so the Servers card can grow to hold every - server (it used to be a cramped internal scroll box that clipped them). */ - overflow-y: auto; - min-height: 0; -} -.cookbook-settings-stack.hidden { display: none; } -.cookbook-dep-row.cookbook-dep-blocked { opacity: 0.4; } -.cookbook-dep-info { flex: 1; min-width: 0; } -.cookbook-dep-section { - display: flex; - align-items: baseline; - gap: 8px; - margin: 12px 2px 4px; -} -.cookbook-dep-section-title { - font-size: 11px; - font-weight: 600; - letter-spacing: 0.03em; -} -.cookbook-dep-section-note { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-subgroup { - margin: 6px 0 8px 12px; - padding: 7px 8px 8px; - border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - border-radius: 0 6px 6px 0; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-dep-subgroup-title { - display: flex; - align-items: baseline; - gap: 7px; - margin: 0 1px 1px; - font-size: 10.5px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 78%, transparent); -} -.cookbook-dep-subgroup-title em { - font-size: 9.5px; - font-style: normal; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.cookbook-dep-tag { - font-size: 9px; - padding: 0 8px; - border-radius: 4px; - white-space: nowrap; - text-align: center; - min-width: 62px; - box-sizing: border-box; - line-height: 1; - /* Explicit height so the tags (Install / Installed) are the EXACT - same height as the sibling tags — Firefox gives buttons a taller - native box otherwise. Mirrors the server-row tag rule (height:24px). */ - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.cookbook-dep-target { - border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-cat { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -/* Rebuild tag — same look as the LLM category tag, sits to its left. */ -.cookbook-dep-rebuild { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 75%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent); - cursor: pointer; - font-family: inherit; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - position: relative; - top: -2px; -} -.cookbook-dep-reinstall { - top: -3px; -} -.cookbook-dep-rebuild:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); -} -.cookbook-dep-installed { - background: color-mix(in srgb, var(--green, #50fa7b) 18%, transparent); - color: var(--green, #50fa7b); - border: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); - /* Match Install + Installed ▾ so all three variants align in mixed rows. */ - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-na { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); - cursor: help; - /* Match other dep tag widths so N/A rows line up with Install / Installed. */ - min-width: 75.85px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-install { - background: var(--accent, var(--red)); - color: #fff; - border: none; - cursor: pointer; - font-family: inherit; - font-weight: 500; - position: relative; - top: -3px; - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - /* Strip the native button box so it's the same height as the sibling tags - (Firefox renders taller otherwise); height comes from .cookbook-dep-tag. */ - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-build-deps { - width: auto; - min-width: 87.7px; - max-width: 100%; - white-space: normal; - overflow-wrap: anywhere; - line-height: 1.15; - text-align: center; - height: auto; - min-height: 24px; -} -/* The install state is the terminal control in a dependency row. Keep it on - the far edge even when optional rebuild/category controls are present. */ -.cookbook-dep-row > .cookbook-dep-installed, -.cookbook-dep-row > .cookbook-dep-na, -.cookbook-dep-row > .cookbook-dep-install:not(.cookbook-dep-build-deps) { - margin-left: auto; -} -/* Center the "?" glyph inside the help chip. Without text-align it sits 0.5px - left of true center because of the character's natural baseline offset. */ -.hwfit-help-chip { - text-align: center; - padding-left: 0.5px; -} -.cookbook-dep-install:hover { opacity: 0.85; } -/* Installed split button: "Installed" label + separator + ▾ caret; clicking it - opens the actions menu (Update). Replaces the old ⋮ button. */ -.cookbook-dep-installed-btn { - padding: 0; - width: 87.7px; - min-width: 87.7px; - cursor: pointer; - font-family: inherit; - overflow: hidden; - position: relative; - top: -3px; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-installed-btn .cookbook-dep-caret { - display: inline-flex; - align-items: center; - align-self: stretch; - padding: 0 5px; - font-size: 14px; - line-height: 1; - position: relative; - top: 1px; - left: 4px; - opacity: 0.85; - border-left: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); -} -.hwfit-serve-row { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - margin-bottom: 6px; -} -/* Tighter min on the core-knobs row only so Dtype / TP / GPU Mem / Max - Seqs fit on one line without crushing Row 1's GPU button strip or - the Backend picker. */ -.hwfit-serve-row-core { - grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); -} -/* Save / saved-config split aligned to the right end of Row 1 and - nudged up 4px so it sits flush with the rest of the row. */ -.hwfit-serve-row .cookbook-serve-slots { - justify-self: end; - position: relative; - top: -4px; -} -/* The Settings label wraps the Save split — right-align its title text - so the word "Settings" sits above the right-aligned Save button - instead of floating off to the left of its grid cell. 8px right - padding now (16px → 8px) per follow-up tweak, brings the title back - over the actual Save button instead of overshooting it. */ -.hwfit-serve-row label:has(> .cookbook-serve-slots) { - grid-column: -2 / -1; - justify-self: end; - text-align: right; - padding-right: 0; -} -.hwfit-serve-row label:has(> .cookbook-serve-slots) > span { - display: inline-block; - position: relative; - left: -33px; -} -.hwfit-serve-topline { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - margin: 0 0 6px; -} -.hwfit-serve-topline .hwfit-serve-runtime-text { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-serve-preset-row { - display: flex; - justify-content: flex-end; - margin: 0; - flex: 0 0 auto; - position: relative; - top: -6px; -} -.hwfit-serve-row label { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; - letter-spacing: 0.3px; -} -.hwfit-serve-row-core .hwfit-context-label { - grid-column: span 2; - width: auto; - min-width: 0; - max-width: none; - justify-self: start; -} -.hwfit-context-control { - display: block; - position: relative; - align-items: center; - margin-top: 2px; - width: 100px; -} -.hwfit-context-control .hwfit-sf[data-field="ctx"] { - min-width: 0; - width: 100%; - padding-right: 40px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]) { - position: relative; - left: -66px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - position: relative; - left: -64px; -} -.hwfit-serve-row-core .hwfit-gpus-label { - position: relative; - left: -70px; -} -.hwfit-context-calc-btn { - position: absolute; - right: 3px; - top: -4px; - width: 34px; - height: 28px; - min-width: 34px; - padding: 0; - font-size: 10px; - line-height: 1; - display: inline-flex; - align-items: center; - justify-content: center; - text-align: center; - overflow: hidden; - white-space: nowrap; -} -.hwfit-context-calc-btn .spinner-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 auto !important; -} -.hwfit-context-calc-btn .ai-spinner-whirlpool { - width: 12px !important; - height: 12px !important; -} -@media (min-width: 769px) { - [data-llama-mode-toggle].mode-toggle.mode-toggle-three .mode-toggle-btn > span { - top: -6px !important; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { - font-size: 0; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { - content: 'Unif'; - font-size: 11px; - } -} -@media (max-width: 768px) { - .hwfit-serve-row-core .hwfit-context-label { - display: flex; - flex-direction: column; - align-items: flex-start; - } - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]), - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - left: 0; - } - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_env_preset"]), - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="extra_env"]) { - grid-column: 1 / -1 !important; - } - .hwfit-context-control { - display: inline-flex; - align-items: center; - gap: 4px; - width: auto; - } - .hwfit-context-control .hwfit-sf[data-field="ctx"] { - width: 100px; - padding-right: 6px; - } - .hwfit-context-calc-btn { - position: relative; - top: -2px; - left: 2px; - right: auto; - flex: 0 0 34px; - width: 34px; - min-width: 34px; - } -} -.hwfit-auto-ctx-note { - display: block; - margin-top: 3px; - font-size: 9px; - opacity: 0.6; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Ctx slider — ported from origin/main. Sits in the Scan/Download toolbar - next to the quant dropdown. Drives _ctxValue() in cookbook-hwfit.js. */ -.hwfit-ctx-control { - height: 28px; - min-width: 134px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - gap: 5px; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - font-size: 11px; /* slightly smaller than .cookbook-field-input so Context reads quieter next to Engine/Quant */ -} -.hwfit-ctx-control span { - /* Match Quant/Engine select label style: no uppercase, no letter-spacing. */ - text-transform: none; - letter-spacing: 0; - opacity: 0.9; -} -.hwfit-ctx-control > span:first-child { - font-size: 9.5px; -} -/* Editor-style slider (same look as the gallery editor sliders): thin pill - rail that fattens on interaction, circular red thumb that grows on hover. */ -.hwfit-ctx-control input[type="range"] { - width: 64px; - min-width: 64px; - height: 4px; - padding: 0; - border: 0; - -webkit-appearance: none; - appearance: none; - /* Hard-coded grey so the rail is GUARANTEED visible regardless of theme — - every theme-derived color we tried (--fg-muted, --border, accent-bg mix) - kept blending into the panel background on at least one theme. */ - background: rgba(150, 150, 150, 0.65); - border-radius: 999px; - accent-color: var(--red); - cursor: pointer; - transition: height 0.15s ease, background 0.15s ease; -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - background: var(--fg); -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - height: 10px; -} -.hwfit-ctx-control input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]::-moz-range-thumb { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]:hover::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:focus::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:active::-webkit-slider-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control input[type="range"]:hover::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:focus::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:active::-moz-range-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control output { - min-width: 28px; - text-align: right; - color: var(--fg); - font-weight: 600; -} -.hwfit-sf { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - font-family: inherit; - font-size: 12px; - padding: 0 6px; - height: 28px; - box-sizing: border-box; -} -.hwfit-sf[data-field="backend"], -.hwfit-sf[data-field="dtype"], -.hwfit-sf[data-field="tp"] { - height: 32px; - box-sizing: border-box; - width: 100%; -} -.hwfit-sf:focus { - border-color: var(--accent, var(--red)); - outline: none; -} -.hwfit-sf[type="text"] { width: 100%; } -.hwfit-sf.hwfit-sf-wide { width: 100%; } -.hwfit-serve-checks { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 6px; -} -.hwfit-sf-cb { - display: flex; - flex-direction: row; - align-items: center; - gap: 6px; - font-size: 11px; - color: var(--fg-muted); - cursor: pointer; - padding: 3px 0; -} diff --git a/static/index.html b/static/index.html index cba90c7c9..ad04daf89 100644 --- a/static/index.html +++ b/static/index.html @@ -246,15 +246,24 @@ real request is discarded and the font fetched a second time. --> - - - - - - - - - + + + + + + + + + + + + + + + + + +
- inside is the scroller, so the footer stays pinned at the bottom. */ - overflow: hidden !important; -} -.skill-card.doclib-card-expanded .skill-md-pre { - flex: 1 1 auto; - min-height: 0; - overflow-y: auto; -} -/* Skill footer buttons sit 4px high (shared rule sets top:-4px) — drop them - back down 4px so they're vertically centered in the skill card footer. */ -.skill-card .doclib-card-expanded-actions > .doclib-card-action-btn, -.skill-card .doclib-card-expanded-actions .doclib-action-btn-row > .doclib-card-action-btn { - top: 0; -} -/* Skills select-mode bulk bar — nudge Cancel / Approve / Delete up 2px. */ -#skills-bulk-bar .memory-toolbar-btn { - position: relative; - top: -2px; -} -/* ── Audit-all progress panel ── */ -.skills-audit-panel { - border: 1px solid var(--border); - border-radius: 8px; - padding: 8px 10px; - margin: 6px 0; - background: color-mix(in srgb, var(--fg) 3%, transparent); -} -.skills-audit-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; } -.skills-audit-title { font-size: 12px; font-weight: 600; } -.skills-audit-bar { height: 4px; border-radius: 2px; background: color-mix(in srgb, var(--fg) 12%, transparent); margin: 6px 0; overflow: hidden; } -.skills-audit-fill { height: 100%; background: var(--accent, var(--red)); transition: width 0.3s ease; } -.skills-audit-summary { font-size: 11px; opacity: 0.7; margin-bottom: 4px; } -.skills-audit-log { - max-height: 22vh; overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 10.5px; line-height: 1.5; opacity: 0.8; -} -/* ── Skill test monitor + AI eval verdict ── */ -.skill-test { display: flex; flex-direction: column; gap: 8px; min-height: 0; } -.skill-test-log { - flex: 1 1 auto; - min-height: 120px; - max-height: 48vh; - overflow-y: auto; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 9.5px; - line-height: 1.45; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - white-space: pre-wrap; - word-break: break-word; -} -.skill-test-log > div { margin: 1px 0; } -.skill-test-meta { opacity: 0.5; } -.skill-test-task { color: var(--accent, var(--red)); font-weight: 500; font-size: 9px; opacity: 0.85; } -.skill-test-round { opacity: 0.6; margin-top: 6px !important; } -.skill-test-tool { color: var(--accent, var(--red)); opacity: 0.85; } -.skill-test-out { opacity: 0.7; padding-left: 10px; } -.skill-test-say { white-space: pre-wrap; } -.skill-test-err { color: var(--color-danger, #e06c75); } -.skill-eval-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } -.skill-eval-badge { - font-size: 10px; font-weight: 700; letter-spacing: 0.03em; - padding: 2px 8px; border-radius: 10px; flex-shrink: 0; -} -.skill-eval-ok { background: color-mix(in srgb, var(--color-success, #4ade80) 26%, transparent); color: var(--color-success, #4ade80); } -.skill-eval-warn { background: color-mix(in srgb, var(--color-warning, #f0ad4e) 26%, transparent); color: var(--color-warning, #f0ad4e); } -.skill-eval-bad { background: color-mix(in srgb, var(--color-danger, #e06c75) 26%, transparent); color: var(--color-danger, #e06c75); } -.skill-eval-unknown { background: color-mix(in srgb, var(--fg) 14%, transparent); } -.skill-eval-summary { font-size: 11px; opacity: 0.85; } -.skill-eval-issues { margin: 6px 0 0; padding-left: 18px; font-size: 11px; opacity: 0.8; } -.skill-eval-issues li { margin: 2px 0; } -.skill-eval-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 8px; } -.skill-eval-approve.suggested { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; -} -/* Already published — the button confirms the state (click to unpublish). */ -.skill-eval-approve.is-approved { - border-color: var(--color-success, #4ade80) !important; - color: var(--color-success, #4ade80) !important; - background: color-mix(in srgb, var(--color-success, #4ade80) 14%, transparent) !important; -} -/* Add-Skill form: a "rich placeholder" overlay so only the FIRST word (Title, - When to use, How, Tags) is accent-colored while the rest stays muted — real - placeholders can't be partially colored. The native placeholder is a single - space so :placeholder-shown still toggles the overlay. */ -.skill-ph-wrap { position: relative; } -.skill-ph-wrap .skill-hint-input { width: 100%; box-sizing: border-box; } -.skill-rich-ph { - position: absolute; - left: 11px; right: 11px; top: 50%; - transform: translateY(-50%); - pointer-events: none; - font-size: 12px; - line-height: 1.2; - color: color-mix(in srgb, var(--fg) 40%, transparent); - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; -} -/* Textarea: align the hint to the first line instead of vertical-centering. */ -.skill-rich-ph-top { top: 7px; transform: none; } -.skill-rich-ph .k { color: var(--accent, var(--red)); } -/* Keep the memory-input enter hint pinned to the field's right edge instead - of letting it drift after the example text. */ -#new-memory-input ~ .skill-rich-ph > svg { - position: absolute; - right: 0; - top: 50%; - transform: translateY(-50%); - margin-left: 0 !important; -} -/* Hide the overlay once the field has real content. */ -.skill-hint-input:not(:placeholder-shown) ~ .skill-rich-ph { display: none; } -.skill-md-editor { - flex: 1 1 auto; - min-height: 0; - width: 100%; - box-sizing: border-box; - resize: none; - font-family: ui-monospace, 'SF Mono', 'Fira Code', monospace; - font-size: 11.5px; - line-height: 1.5; - padding: 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); -} -.doclib-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -/* Unified loading row across every Library tab (Chats / Documents / Research / - Archive): one opacity, one size, with the whirlpool spinner next to it. */ -.lib-loading-row { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - padding: 28px 16px; - font-size: 12px; - color: color-mix(in srgb, var(--fg) 45%, transparent); -} -.pdf-loading-state { - min-height: 120px; - margin: 0; - box-sizing: border-box; - flex-direction: column; - gap: 10px; - border: 0; - border-radius: 0; - background: transparent; - color: color-mix(in srgb, var(--fg) 68%, transparent); - box-shadow: none; - font-size: 12px; - font-weight: 600; - letter-spacing: 0.01em; -} -.pdf-loading-state .ai-spinner-whirlpool { - color: var(--accent, var(--red)); - opacity: 0.95; -} -.pdf-loading-state .ai-spinner-whirlpool canvas { - display: block; -} -.doclib-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: transparent; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg-muted); - font-size: 11px; - cursor: pointer; - transition: border-color 0.15s, color 0.15s; - flex-shrink: 0; - position: relative; - top: 10px; -} -/* Soft dark gradient above the button — blends the list content into the - load-more strip instead of a hard cutoff. Spans the modal width and - sits behind/above the button. */ -.doclib-load-more::before { - content: ""; - position: absolute; - left: 50%; - transform: translateX(-50%); - bottom: 100%; - margin-bottom: 4px; - width: min(600px, 92vw); - height: 34px; - pointer-events: none; - background: linear-gradient(to top, color-mix(in srgb, #000 24%, transparent), transparent); -} -.doclib-load-more:hover { - border-color: var(--red); - color: var(--red); -} -.doclib-inline-load-more { - border: 0; - border-radius: 0; - padding: 0; - background: none; - top: 0; - font-size: 9px !important; - line-height: 1.2 !important; -} - -/* Document library toolbar buttons */ -.doclib-toolbar-btn { - background: none; - border: 1px solid var(--border); - color: var(--fg-muted); - font-size: 11px; - padding: 5px 10px; - border-radius: 6px; - cursor: pointer; - transition: all 0.15s; - white-space: nowrap; - font-family: inherit; - display: inline-flex; - align-items: center; - gap: 3px; -} -.doclib-toolbar-btn:hover { - color: var(--fg); - border-color: var(--fg); -} -.doclib-toolbar-btn.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -.doclib-toolbar-btn.danger { - color: var(--color-error, #e55); -} -.doclib-toolbar-btn.danger:hover:not(:disabled) { - border-color: var(--color-error, #e55); -} -.doclib-toolbar-btn:disabled { - opacity: 0.4; - cursor: default; -} - -/* Bulk action bar */ -.doclib-bulk-bar { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 8px; - border: 1px solid color-mix(in srgb, var(--red) 30%, transparent); - border-radius: 6px; - background: color-mix(in srgb, var(--red) 5%, transparent); - font-size: 10px; - margin-bottom: 8px; -} -.doclib-bulk-bar.hidden { - display: none; -} -.doclib-bulk-check-all { - display: flex; - align-items: center; - gap: 3px; - cursor: pointer; - color: color-mix(in srgb, var(--fg) 60%, transparent); - font-size: 10px; -} -#doclib-bulk-bar .memory-toolbar-btn { - position: relative; - top: -3px; -} - -/* Custom checkboxes */ -.doclib-select-cb, -.doclib-bulk-check-all input { - -webkit-appearance: none; - appearance: none; - width: 13px; - height: 13px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - cursor: pointer; - flex-shrink: 0; - margin: 0; - position: relative; - transition: all 0.15s; -} -.doclib-select-cb:hover, -.doclib-bulk-check-all input:hover { - border-color: var(--red); -} -.doclib-select-cb:checked, -.doclib-bulk-check-all input:checked { - background: var(--red); - border-color: var(--red); -} -.doclib-select-cb:checked::after, -.doclib-bulk-check-all input:checked::after { - content: ''; - position: absolute; - left: 3px; - top: 0px; - width: 4px; - height: 8px; - border: solid var(--bg); - border-width: 0 1.5px 1.5px 0; - transform: rotate(45deg); -} - - -/* + button outside scroll area (when doc is on right side) */ - -@media (max-width: 600px) { - .doclib-toolbar { - flex-direction: column; - } - .doclib-card-session, - .doclib-card-time { - display: none; - } - .doclib-card-preview { - max-height: 40vh; - } -} - -/* Collapse card tags before they compete with attachment/favorite controls. */ -.email-card-tags .email-tags-more { - color: var(--email-tags-more-color, var(--fg)); - background: color-mix(in srgb, var(--email-tags-more-color, var(--fg)) 18%, transparent); -} -.email-tags-more-single, -[data-email-tags-count-mini] { display: none; } -.email-tags-more-single svg { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag, -.email-card-tags.email-tags-mini:not(.email-tags-expanded) > .email-tag-extra { - display: none; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) .email-tags-more-single, -.email-card-tags.email-tags-expanded .email-tags-more-single { - display: inline-flex; -} -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-normal] { display: none; } -.email-card-tags.email-tags-mini:not(.email-tags-expanded) [data-email-tags-count-mini], -.email-card-tags.email-tags-expanded .email-tags-more-single [data-email-tags-count-mini] { display: inline; } - -/* Keep the reader actions in drafting order. */ -.email-reader-actions-inline > [data-act="ai-reply"] { order: 1; } -.email-reader-actions-inline > [data-act="reply"] { order: 2; } -.email-reader-actions-inline > [data-act="reply-all"] { order: 3; } -.email-reader-actions-inline > [data-act="forward"] { order: 4; } -.email-reader-actions-inline.email-reader-actions-compact > .reader-action-overflowed { - display: none !important; -} - -/* ── Archive browser ── */ -.archive-list { - grid-template-columns: 1fr !important; - gap: 4px !important; -} -.archive-row { - flex-direction: row !important; -} -.archive-row .doclib-card-header { - flex: 1; - padding: 8px 10px; -} -.archive-row .doclib-card-title { - flex: 1; - min-width: 0; -} -/* Archive column layout */ -.archive-header { - display: flex; - align-items: center; - padding: 4px 10px; - gap: 6px; - font-size: 9px; - font-weight: 600; - opacity: 0.35; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.archive-col-title { - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: -4px; -} -.archive-col-msgs { - width: 40px; - flex-shrink: 0; - text-align: left; - margin-left: -8px; -} -.archive-col-model { - width: 90px; - flex-shrink: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.archive-col-time { - width: 55px; - flex-shrink: 0; - text-align: right; -} -.archive-col-menu { - width: 24px; - flex-shrink: 0; -} -.archive-menu-btn { - background: transparent; - border: none; - color: var(--fg); - opacity: 0.3; - cursor: pointer; - padding: 4px; - line-height: 0; - border-radius: 4px; - transition: background .15s, opacity .15s; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - margin-left: 4px; - vertical-align: middle; - height: 100%; -} -.archive-menu-btn svg { - display: block; - margin-top: -6px; -} -.archive-menu-btn:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); - opacity: 1; -} - -/* ── Gallery (image library) ── */ - -.gallery-modal-content { - width: min(820px, 94vw); - max-height: 92vh; - font-size: 12px; -} -/* Keep the Gallery frame anchored while tabs or bulk-selection controls - change the content height. Centering the frame made every shorter tab move - the whole window vertically. */ -@media (min-width: 769px) { - #calendar-modal, - #gallery-modal, - #tasks-modal, - #memory-modal, - #doclib-modal, - #compare-model-overlay, - #research-overlay, - #theme-modal, - #settings-modal, - #email-lib-modal, - #cookbook-modal { - align-items: flex-start; - padding-top: 4vh; - box-sizing: border-box; - } -} -/* While the Edit tab is active the editor needs a *definite* height so - the right-side tool panel (`.ge-right-panel { overflow-y:auto }`) - scrolls INTERNALLY. Without this the modal only has `max-height`, so - `.gallery-editor { height:100% }` can't resolve — the editor grows to - its full content height, overflows the modal body, and the modal-body - scrollbar that appears can't be wheel-scrolled because the editor's - `overscroll-behavior:contain` blocks the chain. Pinning a real height - here makes the panel bounded and scrollable as designed. Scoped to - the editor view (matched via the container's inline `display:flex`) - so the Photos/Albums views keep sizing to their content. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) { - height: 92vh; -} -/* The editor tab owns the modal body's scroll context. Keep the landing - chooser and the mounted editor inside the available sheet height so a long - saved-project list cannot push the launch controls below the viewport. */ -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) > .modal-body { - display: flex; - min-height: 0; - overflow: hidden; -} -.gallery-modal-content:has(#gallery-editor-container[style*="flex"]) #gallery-editor-container { - min-height: 0; - overflow: hidden; -} -/* Photo-detail view sizing (issue #314). - The detail view is rendered as a `position:absolute; inset:0` overlay - *inside* `.gallery-images-container`, painted over the photo grid. Because - it's absolutely positioned it can't contribute to the container's height — - the container (and therefore the overlay's `inset:0` box) collapses to the - height of the grid sitting behind it. When the library only has a few - photos that grid is short, so the detail view is crushed: the image is - clipped and the metadata sidebar (`overflow-y:auto`) is squeezed into a - tiny, internally-scrolling strip. (With a large library the grid is tall, - which is why it looked fine in the demo video but cramped for users with - few photos.) - Fix: when the detail view is open, hide the grid-view siblings and drop the - overlay into normal flow. The container — and the window, up to its 92vh - max-height — then sizes to the detail's own content (image + metadata), so - nothing is clipped or squeezed regardless of how many photos exist. Scoped - via the detail element's inline `display:flex` so the grid / albums views - keep sizing to their own content. Works on both desktop and the mobile - full-screen sheet. */ -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > *:not(#gallery-detail) { - display: none !important; -} -#gallery-images-container:has(> #gallery-detail[style*="flex"]) > #gallery-detail { - position: static; -} -/* Containing block for the photo-detail overlay — keeps it inside the body - so it sits below the modal header and the tab strip instead of covering them. */ -.gallery-images-container { - position: relative; -} -.gallery-modal-content .modal-header h4 { - font-size: 1rem; -} -.gallery-stats { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); - margin-bottom: 4px; -} -.gallery-toolbar { - display: flex; - gap: 6px; - margin-bottom: 4px; - align-items: center; -} -.gallery-toolbar-break { display: none; } -/* Keep the hint anchored while independently seating the input in the row. */ -.gallery-search-wrap { position: relative; flex: 1; min-width: 0; display: flex; top: 0; } -.gallery-search { - position: relative; - top: 4px; - flex: 1; - width: 100%; - padding: 4px 8px; - padding-right: 78px; /* room for the enter hint */ - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - font: inherit; - font-size: 12px; - outline: none; -} -.gallery-search-enter-hint { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 10px; - font-weight: 600; - color: var(--accent, var(--red)); - pointer-events: none; - opacity: 0; - transition: opacity 0.12s; - white-space: nowrap; -} -/* Show the hint only once the user has typed something (placeholder gone). */ -.gallery-search:not(:placeholder-shown) ~ .gallery-search-enter-hint { opacity: 0.95; } -.gallery-search:focus { - border-color: var(--red); -} -.gallery-model-filter, -.gallery-sort { - padding: 4px 8px; - background: var(--bg); - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - font: inherit; - font-size: 11px; - cursor: pointer; - position: relative; - top: 4px; -} -.gallery-selection-bar { - margin-bottom: 4px; - position: relative; - top: 2px; - padding-right: 18px; -} -.gallery-tag-chips { - display: flex; - flex-wrap: wrap; - gap: 4px; - margin-bottom: 4px; -} -.gallery-chip { - height: 28px; - box-sizing: border-box; - padding: 0 13px; - display: inline-flex; - align-items: center; - justify-content: center; - gap: 4px; - border-radius: 14px; - font-size: 12px; - line-height: 1; - border: 1px solid var(--border); - background: none; - color: var(--fg); - cursor: pointer; - transition: border-color 0.15s, background 0.15s; -} -.gallery-chip:hover { - border-color: var(--red); -} -.gallery-chip.active { - background: color-mix(in srgb, var(--red) 15%, transparent); - border-color: color-mix(in srgb, var(--red) 40%, transparent); - color: var(--red); -} -/* Album chips row */ -.gallery-album-chips { - display: flex; gap: 4px; flex-wrap: wrap; padding: 0 0 3px; -} -.gallery-chip-fav { color: var(--red); } -.gallery-chip-fav.active { background: color-mix(in srgb, var(--red) 15%, transparent); border-color: var(--red); } -.gallery-chip-add { opacity: 0.5; font-size: 14px; padding: 2px 10px; } -/* Active-album indicator — appears when the Photos grid is filtered to one album. */ -.gallery-chip-active-album, -.gallery-active-filter-pill { - display: inline-flex; align-items: center; gap: 4px; - background: color-mix(in srgb, var(--red) 14%, transparent); - border-color: color-mix(in srgb, var(--red) 45%, transparent); - position: relative; - top: 6px; -} -.gallery-active-filter-pill { - background: color-mix(in srgb, var(--fg) 7%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - color: color-mix(in srgb, var(--fg) 86%, transparent); -} -.gallery-clear-all-filters { - height: 28px; - border-style: dashed; - opacity: 0.72; -} -.gallery-clear-all-filters:hover { - opacity: 1; - color: var(--red); -} -.gallery-chip-clear { - background: none; - border: none; - color: inherit; - opacity: 0.6; - cursor: pointer; - padding: 0; - margin-left: 1px; - width: 12px; - height: 12px; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 12px; - line-height: 1; - position: relative; - top: -4px; -} -.gallery-chip-clear:hover { opacity: 1; } - - - -/* Favorite button on card */ -/* Video cards: fills the same slot as , plus a small play badge - so it's clear the thumbnail represents a video. */ -.gallery-card video { - width: 100%; - aspect-ratio: 1 / 1; - object-fit: cover; - background: #000; - display: block; -} -.gallery-card-play { - position: absolute; - left: 50%; top: 50%; - transform: translate(-50%, -50%); - width: 36px; height: 36px; - display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.55); - color: #fff; - border-radius: 50%; - pointer-events: none; - z-index: 1; -} -.gallery-card-play svg { margin-left: 2px; } - -.gallery-fav-btn { - position: absolute; top: 0px; right: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; font-size: 14px; - color: rgba(255,255,255,0.6); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s; - padding: 0; -} -.gallery-fav-btn.gallery-fav-active { opacity: 1; color: var(--red); } - -.gallery-dl-btn { - position: absolute; top: 0px; left: 4px; z-index: 2; - background: rgba(0,0,0,0.4); border: none; border-radius: 50%; - width: 26px; height: 26px; - color: rgba(255,255,255,0.75); cursor: pointer; - display: flex; align-items: center; justify-content: center; - opacity: 0; transition: opacity 0.15s, background 0.15s; - padding: 0; -} -.gallery-dl-btn:hover { background: rgba(0,0,0,0.7); color: #fff; } -/* In select mode the per-thumbnail hover buttons (favorite + download) just - get in the way of picking — hide them so the card is a clean select target. - `body.gallery-selecting` is the authoritative signal (one class for the - whole grid) — the per-card .gallery-card-selectable is kept as a backup - in case the body class is missed. */ -body.gallery-selecting .gallery-fav-btn, -body.gallery-selecting .gallery-dl-btn, -.gallery-card-selectable .gallery-fav-btn, -.gallery-card-selectable .gallery-dl-btn { - display: none !important; -} - -/* AI tag chips in detail view */ -.gallery-ai-tags { display: flex; gap: 4px; flex-wrap: wrap; } -.gallery-ai-chip { - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 25%, transparent); - border-radius: 10px; padding: 3px 8px; font-size: 10px; - color: var(--fg); opacity: 0.8; - font: inherit; font-size: 10px; - cursor: pointer; - transition: background 0.12s, opacity 0.12s, border-color 0.12s; -} -.gallery-ai-chip:hover { - opacity: 1; - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 50%, transparent); -} -/* AI-generated tags: muted neutral with a tiny sparkle marker so the - user can tell at a glance these came from the model, not them. */ -.gallery-aitag-chip { - background: color-mix(in srgb, var(--fg) 6%, transparent); - border-color: color-mix(in srgb, var(--fg) 18%, transparent); - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-aitag-chip:hover { - background: color-mix(in srgb, var(--fg) 12%, transparent); - border-color: color-mix(in srgb, var(--fg) 35%, transparent); -} -.gallery-aitag-mark { - font-size: 8px; - opacity: 0.55; - color: var(--accent, var(--red)); -} -/* User-applied tags keep the accent-colored chip — they're the personal/ - curated tags, so they get the strongest visual weight. */ -.gallery-user-chip { - font-weight: 600; -} -/* × to remove a user tag (appears inside the chip). */ -.gallery-tag-x { - display: inline-flex; - align-items: center; - justify-content: center; - margin-left: 5px; - width: 13px; - height: 13px; - border-radius: 50%; - font-size: 12px; - line-height: 1; - opacity: 0.55; - transition: opacity 0.12s, background 0.12s, color 0.12s; -} -.gallery-tag-x:hover { - opacity: 1; - background: color-mix(in srgb, var(--red, #ff5555) 22%, transparent); - color: var(--red, #ff5555); -} - -.gallery-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - align-content: start; - gap: 8px; - max-height: calc(92vh - 165px); - overflow-y: auto; - padding: 2px; - transition: border-color 0.15s, background 0.15s; -} -.gallery-grid.gallery-dragover { - border: 2px dashed var(--red); - border-radius: 8px; - background: color-mix(in srgb, var(--red) 5%, transparent); -} -.gallery-card { - position: relative; - aspect-ratio: 1; - border-radius: 6px; - overflow: hidden; - border: 1px solid var(--border); - cursor: pointer; - transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s; -} -.gallery-card.gallery-card-focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 36%, transparent); -} -/* Upload-affordance tile pinned to the top-left of the Photos grid. Matches - the Upload album tile in the Albums tab — dashed border, centered icon. */ -.gallery-card-upload { - border-style: dashed; - background: color-mix(in srgb, var(--fg) 3%, var(--bg)); - opacity: 0.75; - transition: opacity 0.12s, border-color 0.15s, transform 0.15s; -} -/* The "Start AI tag" button turns into a Cancel control during a tag run. */ -.gallery-tag-cancelling { - color: var(--red) !important; - border-color: color-mix(in srgb, var(--red) 45%, var(--border)) !important; -} -/* Skeleton/shimmer placeholder tiles shown while the first page of photos loads. */ -.gallery-card-skeleton { - cursor: default; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.gallery-card-skeleton:hover { transform: none; box-shadow: none; border-color: var(--border); } -/* Chat attachment image: shimmer skeleton + centered whirlpool shown while a - sent photo uploads / its thumbnail loads (see chatRenderer buildAttachCards). */ -.attach-image-skeleton { - position: relative; - width: 160px; height: 120px; - border-radius: 6px; - display: flex; align-items: center; justify-content: center; - background: linear-gradient(100deg, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 30%, - color-mix(in srgb, var(--fg) 12%, var(--panel)) 50%, - color-mix(in srgb, var(--fg) 4%, var(--panel)) 70%); - background-size: 200% 100%; - animation: gallery-skeleton-shimmer 1.25s ease-in-out infinite; -} -.attach-image-skeleton > .spinner-whirlpool { margin: 0 !important; } - -/* Corner "Aa" button on a chat photo thumbnail — opens the vision/OCR editor - so the user can correct what the vision model fed to the LLM. */ -.attach-image-preview { position: relative; } -.attach-vision-model, -.attach-image-name { - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.attach-vision-model { - font-size: 10px; - color: var(--accent, var(--red)); - opacity: 0.85; - margin-top: 3px; -} -.attach-image-name { - font-size: 10px; - opacity: 0.5; - margin-top: 1px; -} -.attach-ocr-btn { - position: absolute; top: 4px; right: 4px; - z-index: 5; /* sit above msg-action overlays so the click lands here */ - height: 22px; - background: rgba(0,0,0,0.55); color: #fff; - border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; - padding: 0 8px 0 6px; cursor: pointer; - display: inline-flex; align-items: center; justify-content: center; gap: 5px; - font-size: 11px; font-weight: 500; line-height: 1; - opacity: 0.75; transition: opacity 0.15s; -} -.attach-ocr-btn:hover { opacity: 1; } -.attach-ocr-btn svg { display: block; } -/* On mobile keep just the (larger) edit icon — the "Caption" label crowds - the corner of the small chat photo. */ -@media (max-width: 768px) { - .attach-ocr-btn .attach-ocr-label { display: none; } - .attach-ocr-btn { - width: 28px; height: 28px; - padding: 0; - } - .attach-ocr-btn svg { width: 16px; height: 16px; } -} - -/* Vision/OCR text editor modal — opened from the "Aa" button above. */ -.vision-editor-overlay { - position: fixed; inset: 0; z-index: 9999; - background: rgba(0,0,0,0.55); - display: flex; align-items: center; justify-content: center; - padding: 16px; -} -.vision-editor-panel { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 10px; - padding: 14px 16px; - width: min(560px, 92vw); - max-height: 88vh; - display: flex; flex-direction: column; gap: 8px; - box-shadow: 0 14px 40px rgba(0,0,0,0.4); -} -.vision-editor-title { - font-size: 13px; font-weight: 600; color: var(--fg); - display: flex; align-items: center; gap: 6px; -} -.vision-editor-desc { font-size: 11px; opacity: 0.6; line-height: 1.4; } -.vision-editor-text { - width: 100%; min-height: 180px; - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 8px 10px; - font-family: inherit; font-size: 12px; line-height: 1.5; - resize: vertical; outline: none; -} -.vision-editor-text:focus { border-color: var(--accent-primary, var(--red)); } -.vision-editor-hint { font-size: 10.5px; opacity: 0.55; line-height: 1.4; } -.vision-editor-actions { - display: flex; gap: 8px; justify-content: flex-end; - margin-top: 4px; -} -.vision-editor-btn { - background: var(--panel); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 14px; font-size: 12px; cursor: pointer; - transition: border-color 0.15s, background 0.15s; - display: inline-flex; align-items: center; gap: 5px; -} -.vision-editor-btn svg { display: block; } -/* Optical nudge: the button text sits 1px higher than the SVG glyphs - alongside it. Drop the text down 1px so they line up. */ -.vision-editor-btn .vision-btn-label { position: relative; top: 1px; } -.vision-editor-btn:hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); } -.vision-editor-btn-primary { - background: var(--accent-primary, var(--red)); - color: #fff; border-color: transparent; -} -.vision-editor-btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } - -/* Quick full-size lightbox shown when the user taps a chat photo thumbnail. */ -.attach-lightbox { - position: fixed; inset: 0; z-index: 9998; - background: rgba(0, 0, 0, 0.85); - display: flex; align-items: center; justify-content: center; - padding: 16px; cursor: zoom-out; - animation: attach-lightbox-fade 0.12s ease-out; -} -.attach-lightbox img { - max-width: 100%; max-height: 100%; - border-radius: 6px; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); - object-fit: contain; -} -.attach-lightbox-err { - position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); - background: var(--bg); color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; - padding: 6px 10px; font-size: 12px; -} -@keyframes attach-lightbox-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -.gallery-card-upload:hover { - opacity: 1; - border-color: var(--red); - transform: translateY(-1px); -} -.gallery-card-upload-inner { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 6px; - color: var(--fg); -} -.gallery-card-upload-label { - font-size: 12px; - font-weight: 500; - opacity: 0.8; -} -.gallery-card:hover { - border-color: var(--red); - box-shadow: 0 2px 8px color-mix(in srgb, var(--red) 15%, transparent); - transform: translateY(-1px); -} -.gallery-card-overlay { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 8px; - background: linear-gradient(180deg, rgba(0,0,0,0.42), transparent 34%, rgba(0,0,0,0.22)); - color: #fff; - opacity: 0; - pointer-events: none; - transition: opacity 0.15s; -} -.gallery-card-badges { - display: flex; - gap: 4px; - flex-wrap: wrap; - align-items: flex-end; - justify-content: flex-end; - margin-top: auto; - margin-bottom: 34px; -} -.gallery-card-badge { - padding: 2px 6px; - border-radius: 999px; - background: rgba(0, 0, 0, 0.55); - border: 1px solid rgba(255, 255, 255, 0.14); - color: rgba(255, 255, 255, 0.88); - font-size: 9px; - font-weight: 600; - line-height: 1.1; -} -.gallery-card-badge-fav { - color: #ff5f6f; -} -.gallery-select-dot { - position: absolute; top: 6px; left: 6px; z-index: 2; - display: block; - width: 10px; height: 10px; border-radius: 50%; cursor: pointer; - background: color-mix(in srgb, var(--fg) 30%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 50%, transparent); - transition: background 0.15s; -} -.gallery-select-dot.selected { background: var(--red); border-color: var(--red); } -.gallery-select-dot:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -.gallery-select-btn { - padding: 9px 10px 11px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 6px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; transition: all 0.15s; - margin-top: -4px !important; -} -.gallery-select-btn:hover { opacity: 1; } -/* Match the library Select toggle's red tint (.memory-toolbar-btn.active). - Bumped contrast so the toggled state actually reads on mobile — the prior - 15% red on transparent was nearly invisible against the page background. */ -.gallery-select-btn.active { - background: color-mix(in srgb, var(--red) 28%, transparent); - color: var(--red); - border-color: var(--red); - font-weight: 600; - opacity: 1; -} -/* Toolbar action buttons sit 2px lower than the generic select-btn so - they baseline-align with the toolbar's selects/inputs instead of - floating above them. */ -.gallery-toolbar-action { - margin-top: 0 !important; - display: inline-flex; - align-items: center; - gap: 2px; -} -.gallery-bulk-bar { - display: flex; align-items: center; gap: 8px; padding: 6px 0; font-size: 12px; - justify-content: flex-end; -} -.gallery-bulk-bar.hidden { display: none; } -.gallery-bulk-all { - display: inline-flex; align-items: center; gap: 4px; - font-size: 11px; opacity: 0.75; cursor: pointer; margin-right: auto; -} -.gallery-bulk-all input { accent-color: var(--accent, var(--red)); cursor: pointer; } -.gallery-bulk-delete { - padding: 3px 10px; background: transparent; color: var(--red); - border: 1px solid var(--red); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; -} -.gallery-bulk-delete:hover { background: var(--red); color: #fff; } -.gallery-bulk-cancel { - padding: 3px 10px; background: transparent; color: var(--fg); - border: 1px solid var(--border); border-radius: 4px; cursor: pointer; - font-size: 11px; font-family: inherit; opacity: 0.6; -} -.gallery-bulk-cancel:hover { opacity: 1; } -.gallery-card img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; -} -.gallery-card-info { - position: absolute; - bottom: 0; - left: 0; - right: 0; - padding: 6px 8px; - background: linear-gradient(transparent, rgba(0,0,0,0.75)); - color: #fff; -} -.gallery-card-prompt { - font-size: 10px; - line-height: 1.3; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.gallery-card-meta { - display: flex; - align-items: center; - gap: 6px; - margin-top: 2px; - font-size: 9px; - opacity: 0.8; -} -.gallery-card-model { - padding: 1px 5px; - border-radius: 6px; - background: rgba(255,255,255,0.15); -} -.gallery-empty { - text-align: center; - color: color-mix(in srgb, var(--fg) 35%, transparent); - padding: 32px 16px; - font-size: 12px; - font-style: italic; -} -.gallery-load-more { - display: block; - margin: 10px auto 0; - padding: 6px 16px; - background: none; - border: 1px solid var(--border); - border-radius: 6px; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-load-more:hover { - border-color: var(--red); - color: var(--red); -} - -/* Gallery detail overlay */ -.gallery-detail { - position: absolute; - inset: 0; - background: var(--panel); - z-index: 10; - display: flex; - flex-direction: column; - overflow-y: auto; -} -.gallery-detail-header { - display: flex; - align-items: center; - gap: 6px; - padding: 2px 8px 4px; - border-bottom: 1px solid var(--border); - flex-shrink: 0; - /* Pin to top so a tall (portrait) photo can't cover the Back / ⋮ - menu when the detail body grows past the visible area. */ - position: sticky; - top: 0; - background: var(--panel); - /* Above the image's rotate/nav buttons (z-index:3) so they scroll cleanly - behind the header instead of colliding with it. */ - z-index: 5; - min-height: 0; -} -.gallery-detail-title { - max-width: min(360px, 40vw); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-size: 12px; - font-weight: 600; - opacity: 0.82; - position: relative; - top: 1px; -} -.gallery-detail-back { - background: none; - border: none; - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 12px; - padding: 4px 8px; - border-radius: 4px; - position: relative; - top: -2px; -} -/* Slightly larger header icons (back/edit/heart/⋮) — override the inline 14px. */ -.gallery-detail-header svg { width: 16px; height: 16px; } -.gallery-detail-back:hover { - background: color-mix(in srgb, var(--fg) 10%, transparent); -} -.gallery-detail-action { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 11px; - padding: 3px 8px; - border-radius: 4px; - transition: border-color 0.15s; -} -.gallery-detail-action:hover { - border-color: var(--fg); -} -.gallery-detail-action.danger { - color: var(--color-error, #e55); -} -/* Overflow menu — replaces the seven individual action buttons that used - to crowd the right side of the detail header. */ -.gallery-detail-menu-wrap { position: relative; } -.gallery-detail-menu-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 26px; - padding: 0; - position: relative; - top: -2px; -} -.gallery-detail-menu { - position: absolute; - top: calc(100% + 4px); - right: 0; - min-width: 150px; - padding: 3px; - background: var(--panel); - border: 1px solid var(--border); - border-radius: 8px; - box-shadow: 0 4px 14px color-mix(in srgb, var(--fg) 18%, transparent); - z-index: 12; - display: flex; - flex-direction: column; - gap: 0; -} -.gallery-detail-menu[hidden] { display: none; } -.gallery-detail-menu .dropdown-item-compact { - width: 100%; - background: none; - border: none; - text-align: left; - font: inherit; - /* Tighter than the global default — the photo-detail menu has 8 - items so every pixel of vertical padding adds up. */ - padding: 4px 8px; - font-size: 11px; - gap: 8px; - line-height: 1.2; -} -.gallery-detail-menu .dropdown-item-compact .dropdown-icon, -.gallery-detail-menu .dropdown-item-compact .dropdown-icon svg { - width: 12px; - height: 12px; -} -.gallery-detail-body { - display: flex; - gap: 16px; - padding: 12px; - flex: 1; - min-height: 0; - overflow: hidden; -} -.gallery-detail-image { - flex: 1; - display: flex; - align-items: center; - justify-content: center; - min-width: 0; - min-height: 0; - height: 100%; - padding-inline: 56px; - box-sizing: border-box; - overflow: hidden; -} -/* Shrink-wraps the actual image so overlay children (heart, face boxes) - anchor to the IMAGE bounds, not the wider letterboxed wrapper. */ -.gallery-detail-img-frame { - position: relative; - display: inline-flex; - max-width: 100%; - max-height: 100%; -} -.gallery-detail-nav { - position: absolute; - top: 50%; - transform: translateY(-50%); - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-nav:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-nav-prev { left: 8px; } -.gallery-detail-nav-next { right: 8px; } - -/* Rotate buttons — same pattern as the prev/next arrows but pinned to the - top corners. Hover-revealed so they don't clutter the image. */ -.gallery-detail-rotate { - position: absolute; - top: 8px; - background: rgba(0, 0, 0, 0.45); - color: rgba(255, 255, 255, 0.85); - border: none; - border-radius: 50%; - width: 36px; - height: 36px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, background 0.15s; - z-index: 3; - padding: 0; -} -.gallery-detail-rotate:hover { opacity: 1 !important; background: rgba(0, 0, 0, 0.7); } -.gallery-detail-rotate-ccw { left: 8px; } -.gallery-detail-rotate-cw { right: 8px; } -/* Inline heart that lives next to the "Date" label in the sidebar. - Tiny outline icon by default; fills red when favorited. */ -.gallery-detail-date-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 6px; -} -.gallery-detail-fav-inline { - background: none; - border: none; - padding: 0; - /* Nudge up 8px so the heart sits visually above the Date label - baseline rather than centred on it. */ - margin-top: -8px; - border-radius: 4px; - color: var(--fg-muted); - opacity: 0.6; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; - transition: opacity 0.15s, color 0.15s; -} -/* Shrink the SVG so the heart matches the height of the small uppercase - Date label sitting next to it. */ -.gallery-detail-fav-inline svg { - width: 12px; - height: 12px; - display: block; -} -.gallery-detail-fav-inline:hover { - opacity: 1; -} -.gallery-detail-fav-inline.active { - opacity: 1; - color: var(--red); -} -.gallery-detail-sidebar { - width: 290px; - flex-shrink: 0; - /* Vertical scroll for long metadata, but never horizontal — overflow:auto - alone would let a wide child (e.g. long album name in the select) push - the sidebar into horizontal-scroll mode. */ - overflow-x: hidden; - overflow-y: auto; - min-width: 0; -} -.gallery-detail-inspector-head { - padding-bottom: 10px; - margin-bottom: 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); -} -.gallery-detail-eyebrow { - font-size: 9px; - font-weight: 700; - color: var(--accent, var(--red)); - text-transform: uppercase; - letter-spacing: 0.08em; - margin-bottom: 3px; -} -.gallery-detail-inspector-title { - font-size: 14px; - font-weight: 650; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-meta-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 6px; - margin: 4px 0 12px; -} -.gallery-detail-meta-card { - min-width: 0; - border: 1px solid color-mix(in srgb, var(--border) 82%, transparent); - background: color-mix(in srgb, var(--fg) 4%, transparent); - border-radius: 7px; - padding: 7px 8px; -} -.gallery-detail-meta-card span { - display: block; - font-size: 9px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 48%, transparent); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: 3px; -} -.gallery-detail-meta-card strong { - display: block; - min-width: 0; - font-size: 11px; - font-weight: 600; - line-height: 1.25; - word-break: break-word; -} -.gallery-detail-inline-link { - appearance: none; - background: none; - border: none; - color: var(--accent, var(--red)); - padding: 0; - font: inherit; - font-weight: inherit; - cursor: pointer; - text-align: left; -} -.gallery-detail-mini-actions { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin-top: 7px; -} -.gallery-detail-mini-btn { - background: color-mix(in srgb, var(--fg) 5%, transparent); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - padding: 4px 8px; - font: inherit; - font-size: 10.5px; - cursor: pointer; - display: inline-flex; - align-items: center; - gap: 4px; -} -.gallery-detail-mini-btn:hover { - border-color: color-mix(in srgb, var(--fg) 42%, var(--border)); -} -/* Constrain any child that could otherwise stretch the sidebar wider than - its declared width (selects with long option text, long URLs, etc.). */ -.gallery-detail-sidebar select, -.gallery-detail-sidebar input, -.gallery-detail-sidebar .gallery-detail-prompt { - max-width: 100%; - box-sizing: border-box; -} -.gallery-date-rel { - opacity: 0.55; - margin-left: 4px; - font-size: 11px; - font-style: italic; -} -.gallery-detail-section label { - display: block; - font-size: 10px; - font-weight: 600; - opacity: 0.6; - margin-bottom: 3px; - text-transform: uppercase; - letter-spacing: 0.5px; -} -.gallery-detail-section div { - font-size: 12px; - word-break: break-word; -} -.gallery-detail-prompt { - white-space: pre-wrap; - line-height: 1.4; -} -.gallery-tag-input, -.gallery-detail-name-input { - width: 100%; - padding: 5px 8px; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 4px; - font: inherit; - font-size: 11px; - margin-bottom: 4px; -} -.gallery-tag-input:focus, -.gallery-detail-name-input:focus { - border-color: var(--red); - outline: none; -} -/* ↵ enter hint inside the Add-a-tag field (accent), replacing the "press Enter" - placeholder text. */ -.gallery-tag-input-wrap { position: relative; } -.gallery-tag-enter-hint { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; /* the input has 4px bottom margin; center on the box */ - color: var(--accent, var(--red)); - font-size: 13px; - line-height: 1; - pointer-events: none; - opacity: 0.8; -} -.gallery-detail-name-input { font-size: 13px; font-weight: 500; } -/* ↵ enter hint on the image name field — accent, shown while editing to signal - that Enter saves. */ -.gallery-name-wrap { position: relative; } -.gallery-name-enter { - position: absolute; - right: 8px; - top: 50%; - transform: translateY(-50%); - margin-top: -2px; - color: var(--accent, var(--red)); - opacity: 0.85; - pointer-events: none; - display: none; -} -.gallery-tag-save { - background: none; - border: 1px solid var(--border); - color: var(--fg); - cursor: pointer; - font: inherit; - font-size: 10px; - padding: 3px 10px; - border-radius: 4px; - transition: border-color 0.15s, color 0.15s; -} -.gallery-tag-save:hover { - border-color: var(--red); - color: var(--red); -} - -@media (max-width: 600px) { - .gallery-grid { - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - } - /* Single-row toolbar on mobile: search + Sources + Recent (sort) all share - one row. The search bar shrinks so the dropdowns fit beside it. */ - .gallery-toolbar { - flex-wrap: nowrap; - gap: 4px; - } - .gallery-search-wrap { - flex: 1 1 0; - min-width: 0; - } - .gallery-search { - /* Drop the desktop right-padding reserved for the "enter to tag" hint — - hide the hint on mobile (below) so the input can use its full width. */ - padding-right: 8px; - } - .gallery-search-enter-hint { display: none; } - /* Disable the row break so the filters stay inline with search. */ - .gallery-toolbar-break { display: none; } - .gallery-model-filter, - .gallery-sort { - flex: 0 0 auto; - /* Cap each dropdown so search keeps a usable amount of width. */ - max-width: 100px; - font-size: 11px; - padding: 4px 6px; - /* Keep the same vertical alignment as the desktop toolbar. */ - position: relative; - top: 4px; - } - .gallery-select-btn { - padding: 6px 8px 8px; - font-size: 9px; - flex-shrink: 0; - /* Push Select to the far right of the single-row toolbar, regardless - of its DOM position (sits between search and the dropdowns). */ - order: 99; - margin-left: auto; - } - /* Tabs scroll horizontally rather than wrapping when narrow. */ - .gallery-tabs { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - } - .gallery-tabs::-webkit-scrollbar { display: none; } - .gallery-tab { flex-shrink: 0; } - /* Album chips also scroll horizontally so they don't stack. */ - .gallery-album-chips { - overflow-x: auto; - flex-wrap: nowrap; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .gallery-album-chips::-webkit-scrollbar { display: none; } - .gallery-chip { flex-shrink: 0; } - /* Tasks filter chips — same horizontal-swipe behaviour as the gallery - albums / library chips on mobile: one row, slide-to-see-more. */ - .tasks-activity-filters { - overflow-x: auto; - flex-wrap: nowrap !important; - -webkit-overflow-scrolling: touch; - scrollbar-width: none; - padding-bottom: 4px; - } - .tasks-activity-filters::-webkit-scrollbar { display: none; } - .tasks-activity-filters > * { flex-shrink: 0; } - /* Detail view */ - .gallery-detail-body { - flex-direction: column; - overflow: visible; - } - .gallery-detail-image { - flex: 0 0 auto; - width: 100%; - height: clamp(280px, 58vh, 520px); - padding-inline: 48px; - } - .gallery-detail-sidebar { - width: 100%; - } - .gallery-detail-title { - display: none; - } - .gallery-detail-meta-grid { - grid-template-columns: 1fr; - } - /* Album grid: smaller tile minimum on mobile. */ - .gallery-albums-grid { - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 8px; - } -} - - -/* ── Scoreboard table ── */ -.scoreboard-table { - width: 100%; - border-collapse: collapse; - font-size: 0.88em; -} -.scoreboard-table th { - text-align: left; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; - font-size: 0.85em; - color: color-mix(in srgb, var(--fg) 55%, transparent); - text-transform: uppercase; - letter-spacing: 0.5px; -} -.scoreboard-table th:not(:first-child) { - text-align: center; -} -.scoreboard-table td { - padding: 5px 10px; - border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent); - text-align: center; -} -.scoreboard-table td.scoreboard-model { - text-align: left; - font-weight: 500; - max-width: 200px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.scoreboard-table td.scoreboard-pct { - font-weight: 600; - color: var(--red); -} -.scoreboard-table tbody tr:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} - -#scoreboard-overlay svg { - color: var(--accent, var(--red)); -} - -/* ── Compare search results ── */ -.compare-search-results { - display: flex; - flex-direction: column; - gap: 2px; -} -.compare-search-result { - padding: 6px 0; - border-bottom: 1px solid color-mix(in srgb, var(--border) 30%, transparent); -} -.compare-search-result:last-child { - border-bottom: none; -} -.search-result-title { - color: var(--color-accent); - text-decoration: none; - font-weight: 500; - font-size: 0.9em; - display: block; - line-height: 1.3; -} -.search-result-title:hover { - color: var(--color-link-hover); - text-decoration: underline; -} -.search-result-snippet { - color: color-mix(in srgb, var(--fg) 70%, transparent); - font-size: 0.82em; - line-height: 1.4; - margin-top: 2px; -} -.search-result-url { - color: color-mix(in srgb, var(--fg) 35%, transparent); - font-size: 0.75em; - margin-top: 3px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - - -/* ── Cookbook Tool ── */ - -/* ── Cookbook ── */ -.cookbook-serve-preset { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - margin-bottom: 3px; - cursor: pointer; - transition: background 0.15s; -} -.cookbook-serve-preset:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); } -.cookbook-serve-preset-name { - font-size: 11px; - font-weight: 500; - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-serve-preset-meta { - font-size: 9px; - font-family: 'Fira Code', monospace; - color: var(--fg-muted); - opacity: 0.5; -} -.cookbook-serve-preset-launch { - background: none; - border: none; - color: #50fa7b; - cursor: pointer; - font-size: 10px; - opacity: 0.6; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-launch:hover { opacity: 1; } -.cookbook-serve-preset-rm { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 10px; - opacity: 0.3; - padding: 0 2px; - position: relative; - top: -4px; -} -.cookbook-serve-preset-rm:hover { opacity: 0.8; color: var(--red); } -.cookbook-body { - display: flex; - flex-direction: column; - gap: 10px; - overflow-y: auto; - flex: 1; - min-height: 0; - scrollbar-width: thin; -} -#cookbook-modal .modal-content { - display: flex; - flex-direction: column; - overflow: hidden; -} -#cookbook-modal .modal-header { - flex: 0 0 auto; -} -#cookbook-modal .cookbook-body { - min-height: 0; - overflow-y: auto; - overflow-x: hidden; -} -#cookbook-modal .cookbook-group { - min-height: 0; -} -#cookbook-modal .cookbook-group > .admin-card { - min-height: 0; - /* Let .cookbook-body be the SINGLE scroll surface. Nesting another - overflow:auto here trapped the wheel inside the cached-list when a - serve panel expanded — the page couldn't scroll past the panel's - bottom (Launch button got hidden). */ - overflow: visible !important; -} -#cookbook-modal .cookbook-section-body { - min-height: 0; -} -.cookbook-group { - display: flex; - flex-direction: column; - gap: 8px; - flex: 1; - min-height: 0; -} - -/* Cards — match admin-card */ -.cookbook-card { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 8px; - padding: 12px; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-card-header { - display: flex; - align-items: center; - justify-content: space-between; -} -.cookbook-card-header-actions { display: flex; gap: 4px; } -.cookbook-card-title { font-size: 13px; font-weight: 600; } -.cookbook-card-desc { - font-size: 11px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} - -/* Fields grid */ -.cookbook-fields { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 6px; - margin-top: 4px; -} -.cookbook-field-label { - display: flex; - flex-direction: column; - font-size: 11px; - color: color-mix(in srgb, var(--fg) 60%, transparent); - gap: 3px; -} - -/* Inputs — match admin-add-form input */ -.cookbook-field-input { - padding: 5px 8px; - font-size: 12px; - font-family: inherit; - background: var(--bg); - color: var(--fg); - border: 1px solid var(--border); - border-radius: 6px; - outline: none; -} -.cookbook-field-input:focus { border-color: var(--red); } -#hwfit-dl-server, #hwfit-usecase, #hwfit-server-select, #hwfit-cache-server, #serve-sort { height: 28px; width: 88px; } -/* Serve tab — match Documents sizing, but leave room for the active "Cancel" - label and center it vertically so the descenders don't clip. */ -#hwfit-cache-select { - min-width: 58px; - height: 28px; - position: relative; - top: -3px; - display: inline-flex; - align-items: center; - justify-content: center; - line-height: 1; -} - -/* Command preview */ -.cookbook-cmd-preview { - margin: 4px 0 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - color: var(--fg); -} - -/* Buttons — match admin-btn-sm */ -.cookbook-actions { display: flex; gap: 6px; margin-top: 2px; } -.cookbook-btn { - padding: 4px 10px; - min-width: 54px; - text-align: center; - font-size: 11px; - font-family: inherit; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--bg); - color: var(--fg); - cursor: pointer; - transition: all 0.15s; -} -.cookbook-btn:hover { - background: var(--border); - border-color: var(--accent); -} -.cookbook-btn:disabled { opacity: 0.5; cursor: not-allowed; } -.cookbook-run-btn { - background: var(--accent); - color: var(--panel); - border-color: var(--accent); - font-weight: 600; -} -.cookbook-run-btn:hover { opacity: 0.85; } -.cookbook-stop-btn { - background: var(--color-error); - border-color: var(--color-error); -} - -/* Output */ -.cookbook-output-wrap { - position: relative; - margin: 0; -} -.cookbook-output-kill { - position: absolute; - top: 4px; - right: 34px; - width: 22px; - height: 22px; - padding: 0; - background: none; - border: 1px solid transparent; - border-radius: 4px; - color: var(--fg-muted); - font-size: 16px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity .15s, color .15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-output-kill:hover { color: var(--color-error, var(--warn)); border-color: var(--border); } -.cookbook-output-wrap .copy-code { - position: absolute; - top: 6px; - right: 6px; -} -.cookbook-output-wrap:hover .copy-code { opacity: 0.7; } -.cookbook-output-pre { - margin: 0; - padding: 8px 10px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - font-family: 'Fira Code', monospace; - font-size: 11px; - white-space: pre-wrap; - word-break: break-all; - max-height: 180px; - overflow-y: auto; -} -.cookbook-output-error { color: var(--color-error); } - -/* Downloaded/cached models */ -.hwfit-cached-item { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 6px; - padding: 6px 8px; - margin: 2px 0; - border-radius: 6px; - border: 1px solid var(--border); - background: color-mix(in srgb, var(--fg) 3%, transparent); - cursor: default; - font-size: 12px; -} -.hwfit-cached-item .hwfit-serve-panel { - flex-basis: 100%; - margin-top: 4px; -} -.hwfit-cached-item:hover { - background: color-mix(in srgb, var(--fg) 6%, transparent); -} -.hwfit-cached-header { - font-size: 10px; - opacity: 0.4; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - border: none !important; - background: none !important; - padding: 2px 8px; - cursor: default !important; -} -.hwfit-cached-header:hover { background: none !important; } -.hwfit-cached-name { - font-weight: 600; - flex-shrink: 0; -} -.hwfit-cached-repo { - flex: 1; - color: var(--fg-muted); - font-size: 11px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-cached-size, -.hwfit-cached-files { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; -} -.hwfit-cached-serve, -.hwfit-cached-delete { - flex-shrink: 0; -} -.hwfit-cached-delete { - opacity: 0.4; - font-size: 10px; -} -.hwfit-cached-delete:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} -.hwfit-cached-badge { - font-size: 9px; - padding: 1px 6px; - border-radius: 3px; - font-weight: 600; - white-space: nowrap; -} -.hwfit-cached-ready { - background: color-mix(in srgb, var(--color-success, #4caf50) 20%, transparent); - color: var(--color-success, #4caf50); -} -.hwfit-cached-dl { - background: color-mix(in srgb, var(--color-warning, #f0ad4e) 20%, transparent); - color: var(--color-warning, #f0ad4e); -} - -/* Cookbook notification dot */ -#tool-cookbook-btn { - position: relative; -} -#rail-cookbook { - position: relative; -} -.cookbook-open-loading { - display: inline-flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - flex-shrink: 0; - margin-left: 2px; - color: var(--accent, var(--red)); - opacity: 0.9; -} -.cookbook-open-loading .ai-spinner, -.cookbook-open-loading .ai-spinner-whirlpool { - display: inline-flex !important; - width: 14px; - height: 14px; - margin: 0 !important; - gap: 0 !important; -} -.cookbook-open-loading canvas { - display: block; -} -.cookbook-section-loading-wp, -.cookbook-section-loading-wp canvas { - width: 22px !important; - height: 22px !important; - display: block !important; -} -#rail-cookbook .cookbook-open-loading { - position: absolute; - right: 2px; - top: -2px; - width: 12px; - height: 12px; - margin-left: 0; -} -/* The serve-list 'downloading' / 'download stalled' status text sits a hair - low against the rest of the meta line on mobile — nudge it up 2px. */ -@media (max-width: 768px) { - .cookbook-dl-status { - position: relative; - top: -2px; - } -} -.cookbook-notif-dot { - width: 6px; - height: 6px; - flex-shrink: 0; - border-radius: 50%; - background: var(--color-success, #4caf50); - /* Drives the breathing glow in the keyframes (matches email's dot). */ - --notif-glow: var(--color-success, #4caf50); - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -.cookbook-notif-dot.cookbook-notif-error { - background: var(--color-error, #f44); - --notif-glow: var(--color-error, #f44); - position: relative; - left: -2px; - top: -1px; -} -.rail-notify-error { color: var(--color-error, #f44) !important; } -.cookbook-tab-error-dot { - display: inline-block; width: 5px; height: 5px; border-radius: 50%; - background: var(--color-error, #f44); margin-left: 4px; vertical-align: middle; - animation: cookbook-notif-pulse 2s ease-in-out infinite; -} -@keyframes cookbook-notif-pulse { - 0%, 100% { - opacity: 1; - box-shadow: 0 0 0 0 color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 60%, transparent); - } - 50% { - opacity: 0.85; - box-shadow: 0 0 6px 2px color-mix(in srgb, var(--notif-glow, var(--accent, var(--red))) 55%, transparent); - } -} -.cookbook-clear-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-clear-btn:hover { - color: var(--red) !important; - border-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 8%, transparent) !important; -} -.cookbook-bulk-bar { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - margin-bottom: 4px; - background: color-mix(in srgb, var(--red) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--red) 20%, transparent); - border-radius: 6px; - font-size: 11px; -} -.cookbook-bulk-bar.hidden { display: none; } -.serve-select-cb { - width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; cursor: pointer; - background: var(--border); transition: background 0.15s; - align-self: center; - position: relative; - top: -2px; -} -#serve-bulk-bar { - position: relative; - top: -6px; -} -#serve-bulk-bar #serve-bulk-cancel { - top: -2px !important; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 28px; - height: 24px; - line-height: 1; -} -#serve-bulk-bar #serve-bulk-cancel svg { - top: 0; -} -.serve-select-cb.selected { background: var(--red); } -.serve-select-cb:hover { background: color-mix(in srgb, var(--red) 50%, transparent); } -#hwfit-cache-select.active { - background: var(--red); - color: #fff; - border-color: var(--red); -} -.cookbook-serve-dirs { - display: flex; - flex-wrap: wrap; - gap: 4px; - align-items: center; -} -.cookbook-serve-dir-pill { - font-size: 10px; - font-family: 'Fira Code', monospace; - padding: 2px 8px; - border-radius: 4px; - background: color-mix(in srgb, var(--fg) 6%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 10%, transparent); - color: var(--fg-muted); - letter-spacing: 0.2px; -} -/* Status pills shown inline in a Serve-tab card title (next to the model - name) when the model has a live serve / download task. Shared base - class so "running" and "downloading" sit on the same row with the - same chrome; only color varies. */ -.cookbook-serve-running-pill, -.cookbook-serve-downloading-pill { - display: inline-block; - margin-left: 6px; - padding: 1px 7px; - border-radius: 10px; - font-size: 9px; - font-weight: 500; - text-transform: lowercase; - letter-spacing: 0.3px; - vertical-align: 2px; - position: relative; - top: 1px; /* nudged down 2px from the old -1px so it sits - flush with the cap-height of the title text */ -} -.cookbook-serve-running-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); -} -.cookbook-serve-downloading-pill { - color: var(--accent, var(--red)); - background: color-mix(in srgb, var(--accent, var(--red)) 12%, transparent); - border: 1px solid color-mix(in srgb, var(--accent, var(--red)) 35%, transparent); - opacity: 0.85; -} -.cookbook-serve-downloading-pill.is-stalled { - /* Stalled downloads stay visible but read as warning, not progress. */ - color: var(--fg-muted, #888); - background: color-mix(in srgb, var(--fg-muted, #888) 10%, transparent); - border-color: color-mix(in srgb, var(--fg-muted, #888) 30%, transparent); - opacity: 1; -} -.cookbook-serve-running-pill.is-clickable { - cursor: pointer; - transition: background 0.12s, border-color 0.12s; -} -.cookbook-serve-running-pill.is-clickable:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 22%, transparent); - border-color: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); -} - -.cookbook-serve-dir-edit { - font-size: 9px; - color: var(--fg-muted); - opacity: 0.4; - cursor: pointer; - text-decoration: underline; - text-underline-offset: 2px; -} -.cookbook-serve-dir-edit:hover { - opacity: 0.7; - color: var(--accent, var(--red)); -} -.cookbook-gpu-group { - display: flex; - gap: 3px; - margin-top: -4px; -} -.cookbook-gpu-btn { - width: 30px; height: 30px; - font-size: 12px; - font-family: 'Fira Code', monospace; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - color: var(--fg-muted); - cursor: pointer; - opacity: 0.4; - transition: all 0.15s; - padding: 0; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-gpu-btn:hover { - opacity: 0.7; - border-color: var(--fg-muted); -} -.cookbook-gpu-btn.active { - opacity: 1; - background: color-mix(in srgb, var(--fg) 10%, transparent); - border-color: color-mix(in srgb, var(--fg) 55%, transparent); - color: var(--fg); - font-weight: 600; -} -/* Probe annotation states (set by "Free?" button) */ -.cookbook-gpu-btn.gpu-free { - opacity: 1; - border-color: #4ade80; - color: #4ade80; -} -.cookbook-gpu-btn.gpu-busy { - opacity: 0.85; - border-color: color-mix(in srgb, var(--red) 70%, transparent); - color: var(--red); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-btn.gpu-missing { - opacity: 0.2; - text-decoration: line-through; - cursor: not-allowed; -} -/* "Clear Server" lives in the actions row next to "Probe GPUs". - Inherits .cookbook-btn sizing; this rule just adds the destructive - red tint on hover so the "this kills processes" cue stays. */ -.cookbook-gpu-clear:hover:not(:disabled) { - color: var(--red); - border-color: color-mix(in srgb, var(--red) 60%, var(--border)); - background: color-mix(in srgb, var(--red) 8%, transparent); -} -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -.cookbook-gpu-clear:disabled { opacity: 0.4; cursor: wait; } -/* GPU probe popup — per-GPU process list with kill buttons */ -.cookbook-gpu-popup { - /* Fixed positioning (relative to viewport) so we never get pulled into - a scrolling/transform stacking context from an ancestor. Z-index has - to clear the cookbook modal (260) and the rest of the high-z UI - layers (themed-confirm and various overlays sit around 9000-10000). */ - position: fixed; - z-index: 10010; - min-width: 280px; - max-width: 420px; - background: var(--panel, #1a1a1a); - border: 1px solid var(--border); - border-radius: 6px; - box-shadow: 0 8px 24px rgba(0,0,0,0.4); - font-family: 'Fira Code', monospace; - font-size: 11px; - color: var(--fg); -} -.cookbook-gpu-popup-head { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - border-bottom: 1px solid var(--border); - font-weight: 600; -} -.cookbook-gpu-popup-stats { - flex: 1; - font-weight: 400; - color: var(--fg-muted); - font-size: 10px; -} -.cookbook-gpu-popup-close { - background: none; - border: none; - color: var(--fg-muted); - cursor: pointer; - font-size: 14px; - padding: 0 4px; -} -.cookbook-gpu-popup-close:hover { color: var(--fg); } -.cookbook-gpu-popup-body { - padding: 4px 0; - max-height: 320px; - overflow-y: auto; -} -.cookbook-gpu-popup-empty { - padding: 10px; - color: var(--fg-muted); - font-style: italic; -} -.cookbook-gpu-proc { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 10px; -} -.cookbook-gpu-proc:hover { background: color-mix(in srgb, var(--fg) 5%, transparent); } -.cookbook-gpu-proc-info { - flex: 1; - display: flex; - gap: 8px; - align-items: center; - min-width: 0; -} -.cookbook-gpu-proc-pid { - color: var(--fg-muted); - width: 56px; - flex-shrink: 0; -} -.cookbook-gpu-proc-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; -} -.cookbook-gpu-proc-mem { - color: var(--fg-muted); - flex-shrink: 0; -} -.cookbook-gpu-proc-actions { - display: flex; - gap: 4px; - flex-shrink: 0; -} -.cookbook-gpu-kill { - border: 1px solid color-mix(in srgb, var(--red) 60%, transparent); - background: none; - color: var(--red); - border-radius: 3px; - padding: 2px 8px; - cursor: pointer; - font-family: inherit; - font-size: 10px; -} -.cookbook-gpu-kill[data-sig="KILL"] { - background: color-mix(in srgb, var(--red) 12%, transparent); -} -.cookbook-gpu-kill:hover:not(:disabled) { - background: color-mix(in srgb, var(--red) 20%, transparent); -} -.cookbook-gpu-kill:disabled { opacity: 0.4; cursor: wait; } -.cookbook-serve-title { - display: flex; - align-items: center; - gap: 4px; - min-width: 0; -} -.cookbook-serve-title-name { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-hf-link { - font-size: 9px; - text-decoration: none; - color: var(--fg-muted); - opacity: 0.5; - padding: 1px 5px; - border: 1px solid color-mix(in srgb, var(--fg) 12%, transparent); - border-radius: 3px; - background: color-mix(in srgb, var(--fg) 4%, transparent); - vertical-align: 1px; - letter-spacing: 0.3px; - font-weight: 600; - flex-shrink: 0; -} -.cookbook-hf-link:hover { - opacity: 0.8; - border-color: var(--accent, var(--red)); - color: var(--accent, var(--red)); -} -/* In the Trending models list the HF pill is the row's main affordance — - tint it accent so it stands out next to the model name. */ -#cookbook-hf-latest-list .cookbook-hf-link { - color: var(--accent, var(--red)); - opacity: 0.85; - border-color: color-mix(in srgb, var(--accent, var(--red)) 30%, transparent); -} - -/* Running tab sections */ -.cookbook-saved-section, -.cookbook-serve-section, -.cookbook-dl-section { - border: 1px solid var(--border); - border-radius: 6px; - margin-bottom: 6px; - overflow: hidden; -} -/* Divider between server-group sections in the Active tab so the - transition between e.g. local + remote-host server blocks reads - visually instead of running into each other. */ -.cookbook-group[data-backend-group="Running"] > [class*="cookbook-server-section-"] + [class*="cookbook-server-section-"] { - margin-top: 14px; - padding-top: 14px; - border-top: 1px solid color-mix(in srgb, var(--border) 65%, transparent); -} -.cookbook-section-header { - display: flex; - align-items: center; - gap: 6px; - padding: 7px 10px; - cursor: pointer; - user-select: none; - /* Persistent surface + border so it reads as a clickable bar instead of - blending into the panel background. */ - background: color-mix(in srgb, var(--fg) 5%, transparent); - border: 1px solid var(--border); - border-radius: 6px; - transition: background 0.15s, border-color 0.15s; -} -.cookbook-section-header:hover { - background: color-mix(in srgb, var(--fg) 9%, transparent); - border-color: color-mix(in srgb, var(--fg) 22%, transparent); -} -.cookbook-section-header.has-server-color { - color: var(--fg); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 45%, var(--border)); - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 4%, var(--bg)); - box-shadow: inset 6px 0 0 var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color:hover { - background: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 7%, var(--bg)); - border-color: color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 60%, var(--border)); -} -.cookbook-section-header.has-server-color .cookbook-section-title, -.cookbook-section-header.has-server-color .cookbook-section-chevron { - color: var(--cookbook-server-accent, var(--cookbook-server-color)); -} -.cookbook-section-header.has-server-color .cookbook-srv-status.ok { - background: var(--cookbook-server-accent, var(--cookbook-server-color)); - box-shadow: 0 0 0 2px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 18%, transparent), - 0 0 8px color-mix(in srgb, var(--cookbook-server-accent, var(--cookbook-server-color)) 55%, transparent); -} -.cookbook-section-header .cookbook-clear-btn { - margin-left: 0; - position: relative; - top: -3px; -} -@media (max-width: 768px) { - .cookbook-section-header .cookbook-clear-btn { - top: -3px; - } - .cookbook-task-check { - top: 4px; - } -} -/* "Stop all" sits just left of "Clear finished"; it carries the auto margin so - the pair is pushed together to the right of the section title. */ -.cookbook-section-header .cookbook-stop-all-btn { - margin-left: auto; - margin-right: 6px; - position: relative; - top: -3px; -} -.cookbook-stop-all-btn { - font-size: 10px !important; - padding: 0 8px !important; - height: 22px !important; - border-radius: 6px !important; - border: 1px solid var(--border) !important; - color: color-mix(in srgb, var(--fg) 40%, transparent) !important; - opacity: 1 !important; - background: none !important; -} -.cookbook-stop-all-btn:hover { - color: var(--warn, #f0ad4e) !important; - border-color: var(--warn, #f0ad4e) !important; - background: color-mix(in srgb, var(--warn, #f0ad4e) 8%, transparent) !important; -} -.cookbook-section-chevron { - flex-shrink: 0; - opacity: 0.6; - transition: transform 0.15s ease, opacity 0.15s ease; -} -.cookbook-section-body { - padding: 0; -} -.cookbook-saved-toggle-header { - font-size: 10px; - font-weight: 600; - color: var(--fg-muted); - text-transform: uppercase; - letter-spacing: 0.3px; - margin-bottom: 4px; -} -.cookbook-saved-toggle:hover { color: var(--fg); } -.cookbook-saved-item { - margin: 2px 0; - border-radius: 4px; - font-size: 11px; -} -.cookbook-saved-header { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 6px; - border-radius: 4px; - transition: background 0.08s; -} -.cookbook-saved-header:hover { - background: color-mix(in srgb, var(--fg) 5%, transparent); -} -.cookbook-saved-item .hwfit-serve-panel { - margin: 4px 0 4px 6px; -} -.cookbook-saved-host { - font-size: 10px; - color: var(--fg-muted); - font-family: 'Fira Code', monospace; -} -.cookbook-saved-name { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.cookbook-saved-launch { - font-size: 10px; - padding: 2px 8px; -} -.cookbook-saved-del { - background: none; - border: none; - color: var(--fg-muted); - opacity: 0.3; - cursor: pointer; - font-size: 11px; - padding: 0 2px; -} -.cookbook-saved-del:hover { - opacity: 1; - color: var(--color-error, var(--warn)); -} - -/* Serve config panel */ -.hwfit-serve-panel { - margin-top: 6px; - padding: 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: color-mix(in srgb, var(--fg) 3%, transparent); - flex: 1; - display: flex; - flex-direction: column; - position: relative; -} -.cookbook-serve-slots { - display: flex; - gap: 3px; - justify-content: flex-end; - margin-bottom: 4px; -} -/* When the Save split sits inside Row 1 (next to GPUs), align it with the - input baseline (the row's grid cells stretch top-down; without this the - Save buttons sit above the GPU button group). */ -.hwfit-serve-row .cookbook-serve-slots { - align-self: end; - margin-bottom: 4px; -} -.cookbook-slot-btn { - min-width: 22px; height: 28px; - padding: 0 6px; - font-size: 10px; font-weight: 600; - border: 1px solid var(--border); - border-radius: 4px; - background: none; - white-space: nowrap; - max-width: 80px; - overflow: hidden; - text-overflow: ellipsis; - color: var(--fg-muted); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - opacity: 0.5; - transition: all 0.15s; -} -.cookbook-slot-btn:hover { opacity: 0.9; border-color: var(--fg-muted); } -/* Saved-configs split button: "Save" + dropdown-arrow joined into one control, - but each segment keeps its own style — Save uses the filled accent - "Done"-button look; the arrow stays a subtle outlined menu trigger. */ -.cookbook-saved-split { gap: 0; } -.cookbook-saved-save, -.cookbook-saved-arrow { max-width: none; } -.cookbook-saved-save { - padding: 0 10px; - gap: 4px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - font-weight: 600; - opacity: 1; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} -.cookbook-saved-save:hover { - background: var(--border); - border-color: var(--accent); - opacity: 1; -} -.cookbook-saved-arrow { - padding: 0 6px; - background: var(--bg); - color: var(--fg); - border-color: var(--border); - opacity: 1; - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left: none; -} -.cookbook-saved-menu .cookbook-saved-favorite { - border-left: 3px solid var(--red); - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-saved-fav-btn { - width: 20px; - height: 20px; - padding: 0; - border: 1px solid transparent; - border-radius: 6px; - background: none; - color: var(--fg-muted); - opacity: 0.55; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -.cookbook-saved-fav-btn:hover, -.cookbook-saved-fav-btn.active { - color: var(--red); - opacity: 1; - background: color-mix(in srgb, var(--red) 10%, transparent); -} -.cookbook-saved-fav-badge { - flex-shrink: 0; - background: transparent; - padding: 0; -} -.cookbook-serve-favorite-model { - border-left-color: var(--red) !important; - background: color-mix(in srgb, var(--red) 4%, transparent); -} -.cookbook-serve-fav-badge { - display: inline-flex; - align-items: center; - vertical-align: 1px; - margin-left: 5px; - margin-right: 2px; - background: transparent; - padding: 0; -} -.cookbook-slot-saved { background: color-mix(in srgb, var(--accent) 10%, transparent); border-color: color-mix(in srgb, var(--accent) 30%, transparent); color: var(--accent); } -.cookbook-slot-saved:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); } -.cookbook-slot-btn.active { opacity: 1; background: var(--accent); color: #fff; border-color: var(--accent); -} -.cookbook-slot-wrap { - position: relative; - display: inline-flex; -} -.cookbook-slot-del { - position: absolute; - top: -5px; - right: -5px; - width: 14px; - height: 14px; - padding: 0; - border: 1px solid var(--border); - border-radius: 50%; - background: var(--bg, #1a1a1a); - color: var(--fg-muted); - font-size: 12px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity 0.15s, color 0.15s, border-color 0.15s; - display: flex; - align-items: center; - justify-content: center; -} -.cookbook-slot-del:hover { - color: #f44; - border-color: #f44; -} -.cookbook-card-backend { display: none; } -/* Dependencies row — aligned tags */ -.cookbook-dep-row { - display: flex; - align-items: center; - gap: 6px; - padding: 8px 10px; - border: 1px solid var(--border); - border-radius: 6px; - background: var(--panel); -} -/* Let the deps list fill the available window height instead of being - capped at the default .doclib-grid 400-px box. */ -[data-backend-group="Dependencies"] #cookbook-deps-list { - flex: 1; - max-height: none; -} - -/* Settings tab: stack the HF token + Servers cards as distinct blocks - (matches the Download tab's block-per-section layout). */ -.cookbook-settings-stack { - display: flex; - flex-direction: column; - gap: 10px; - flex: 1; - /* Scroll the whole settings panel so the Servers card can grow to hold every - server (it used to be a cramped internal scroll box that clipped them). */ - overflow-y: auto; - min-height: 0; -} -.cookbook-settings-stack.hidden { display: none; } -.cookbook-dep-row.cookbook-dep-blocked { opacity: 0.4; } -.cookbook-dep-info { flex: 1; min-width: 0; } -.cookbook-dep-section { - display: flex; - align-items: baseline; - gap: 8px; - margin: 12px 2px 4px; -} -.cookbook-dep-section-title { - font-size: 11px; - font-weight: 600; - letter-spacing: 0.03em; -} -.cookbook-dep-section-note { - font-size: 10px; - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-subgroup { - margin: 6px 0 8px 12px; - padding: 7px 8px 8px; - border-left: 2px solid color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); - background: color-mix(in srgb, var(--fg) 3%, transparent); - border-radius: 0 6px 6px 0; - display: flex; - flex-direction: column; - gap: 6px; -} -.cookbook-dep-subgroup-title { - display: flex; - align-items: baseline; - gap: 7px; - margin: 0 1px 1px; - font-size: 10.5px; - font-weight: 700; - color: color-mix(in srgb, var(--fg) 78%, transparent); -} -.cookbook-dep-subgroup-title em { - font-size: 9.5px; - font-style: normal; - font-weight: 500; - color: color-mix(in srgb, var(--fg) 48%, transparent); -} -.cookbook-dep-tag { - font-size: 9px; - padding: 0 8px; - border-radius: 4px; - white-space: nowrap; - text-align: center; - min-width: 62px; - box-sizing: border-box; - line-height: 1; - /* Explicit height so the tags (Install / Installed) are the EXACT - same height as the sibling tags — Firefox gives buttons a taller - native box otherwise. Mirrors the server-row tag rule (height:24px). */ - height: 24px; - display: inline-flex; - align-items: center; - justify-content: center; -} -.cookbook-dep-target { - border: 1px solid var(--border); - color: color-mix(in srgb, var(--fg) 50%, transparent); -} -.cookbook-dep-cat { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); -} -/* Rebuild tag — same look as the LLM category tag, sits to its left. */ -.cookbook-dep-rebuild { - background: color-mix(in srgb, var(--fg) 10%, transparent); - color: color-mix(in srgb, var(--fg) 75%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 20%, transparent); - cursor: pointer; - font-family: inherit; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - position: relative; - top: -2px; -} -.cookbook-dep-reinstall { - top: -3px; -} -.cookbook-dep-rebuild:hover { - background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); - color: var(--accent, var(--red)); - border-color: color-mix(in srgb, var(--accent, var(--red)) 45%, transparent); -} -.cookbook-dep-installed { - background: color-mix(in srgb, var(--green, #50fa7b) 18%, transparent); - color: var(--green, #50fa7b); - border: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); - /* Match Install + Installed ▾ so all three variants align in mixed rows. */ - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-na { - background: color-mix(in srgb, var(--fg) 8%, transparent); - color: color-mix(in srgb, var(--fg) 60%, transparent); - border: 1px solid color-mix(in srgb, var(--fg) 16%, transparent); - cursor: help; - /* Match other dep tag widths so N/A rows line up with Install / Installed. */ - min-width: 75.85px; - padding: 0 10px; - box-sizing: border-box; -} -.cookbook-dep-install { - background: var(--accent, var(--red)); - color: #fff; - border: none; - cursor: pointer; - font-family: inherit; - font-weight: 500; - position: relative; - top: -3px; - width: 87.7px; - min-width: 87.7px; - padding: 0 10px; - /* Strip the native button box so it's the same height as the sibling tags - (Firefox renders taller otherwise); height comes from .cookbook-dep-tag. */ - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-build-deps { - width: auto; - min-width: 87.7px; - max-width: 100%; - white-space: normal; - overflow-wrap: anywhere; - line-height: 1.15; - text-align: center; - height: auto; - min-height: 24px; -} -/* The install state is the terminal control in a dependency row. Keep it on - the far edge even when optional rebuild/category controls are present. */ -.cookbook-dep-row > .cookbook-dep-installed, -.cookbook-dep-row > .cookbook-dep-na, -.cookbook-dep-row > .cookbook-dep-install:not(.cookbook-dep-build-deps) { - margin-left: auto; -} -/* Center the "?" glyph inside the help chip. Without text-align it sits 0.5px - left of true center because of the character's natural baseline offset. */ -.hwfit-help-chip { - text-align: center; - padding-left: 0.5px; -} -.cookbook-dep-install:hover { opacity: 0.85; } -/* Installed split button: "Installed" label + separator + ▾ caret; clicking it - opens the actions menu (Update). Replaces the old ⋮ button. */ -.cookbook-dep-installed-btn { - padding: 0; - width: 87.7px; - min-width: 87.7px; - cursor: pointer; - font-family: inherit; - overflow: hidden; - position: relative; - top: -3px; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; -} -.cookbook-dep-installed-btn .cookbook-dep-caret { - display: inline-flex; - align-items: center; - align-self: stretch; - padding: 0 5px; - font-size: 14px; - line-height: 1; - position: relative; - top: 1px; - left: 4px; - opacity: 0.85; - border-left: 1px solid color-mix(in srgb, var(--green, #50fa7b) 35%, transparent); -} -.hwfit-serve-row { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); - gap: 6px; - margin-bottom: 6px; -} -/* Tighter min on the core-knobs row only so Dtype / TP / GPU Mem / Max - Seqs fit on one line without crushing Row 1's GPU button strip or - the Backend picker. */ -.hwfit-serve-row-core { - grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); -} -/* Save / saved-config split aligned to the right end of Row 1 and - nudged up 4px so it sits flush with the rest of the row. */ -.hwfit-serve-row .cookbook-serve-slots { - justify-self: end; - position: relative; - top: -4px; -} -/* The Settings label wraps the Save split — right-align its title text - so the word "Settings" sits above the right-aligned Save button - instead of floating off to the left of its grid cell. 8px right - padding now (16px → 8px) per follow-up tweak, brings the title back - over the actual Save button instead of overshooting it. */ -.hwfit-serve-row label:has(> .cookbook-serve-slots) { - grid-column: -2 / -1; - justify-self: end; - text-align: right; - padding-right: 0; -} -.hwfit-serve-row label:has(> .cookbook-serve-slots) > span { - display: inline-block; - position: relative; - left: -33px; -} -.hwfit-serve-topline { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 8px; - margin: 0 0 6px; -} -.hwfit-serve-topline .hwfit-serve-runtime-text { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.hwfit-serve-preset-row { - display: flex; - justify-content: flex-end; - margin: 0; - flex: 0 0 auto; - position: relative; - top: -6px; -} -.hwfit-serve-row label { - font-size: 10px; - color: var(--fg-muted); - white-space: nowrap; - letter-spacing: 0.3px; -} -.hwfit-serve-row-core .hwfit-context-label { - grid-column: span 2; - width: auto; - min-width: 0; - max-width: none; - justify-self: start; -} -.hwfit-context-control { - display: block; - position: relative; - align-items: center; - margin-top: 2px; - width: 100px; -} -.hwfit-context-control .hwfit-sf[data-field="ctx"] { - min-width: 0; - width: 100%; - padding-right: 40px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]) { - position: relative; - left: -66px; -} -.hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - position: relative; - left: -64px; -} -.hwfit-serve-row-core .hwfit-gpus-label { - position: relative; - left: -70px; -} -.hwfit-context-calc-btn { - position: absolute; - right: 3px; - top: -4px; - width: 34px; - height: 28px; - min-width: 34px; - padding: 0; - font-size: 10px; - line-height: 1; - display: inline-flex; - align-items: center; - justify-content: center; - text-align: center; - overflow: hidden; - white-space: nowrap; -} -.hwfit-context-calc-btn .spinner-whirlpool { - width: 12px !important; - height: 12px !important; - margin: 0 auto !important; -} -.hwfit-context-calc-btn .ai-spinner-whirlpool { - width: 12px !important; - height: 12px !important; -} -@media (min-width: 769px) { - [data-llama-mode-toggle].mode-toggle.mode-toggle-three .mode-toggle-btn > span { - top: -6px !important; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span { - font-size: 0; - } - .mode-toggle.mode-toggle-three [data-llama-mode="unified"] > span::after { - content: 'Unif'; - font-size: 11px; - } -} -@media (max-width: 768px) { - .hwfit-serve-row-core .hwfit-context-label { - display: flex; - flex-direction: column; - align-items: flex-start; - } - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="max_seqs"]), - .hwfit-serve-row-core label:has(.hwfit-sf[data-field="gpu_mem"]) { - left: 0; - } - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_env_preset"]), - details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="extra_env"]) { - grid-column: 1 / -1 !important; - } - .hwfit-context-control { - display: inline-flex; - align-items: center; - gap: 4px; - width: auto; - } - .hwfit-context-control .hwfit-sf[data-field="ctx"] { - width: 100px; - padding-right: 6px; - } - .hwfit-context-calc-btn { - position: relative; - top: -2px; - left: 2px; - right: auto; - flex: 0 0 34px; - width: 34px; - min-width: 34px; - } -} -.hwfit-auto-ctx-note { - display: block; - margin-top: 3px; - font-size: 9px; - opacity: 0.6; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -/* Ctx slider — ported from origin/main. Sits in the Scan/Download toolbar - next to the quant dropdown. Drives _ctxValue() in cookbook-hwfit.js. */ -.hwfit-ctx-control { - height: 28px; - min-width: 134px; - flex-shrink: 0; - display: inline-flex; - align-items: center; - gap: 5px; - padding: 0 7px; - border: 1px solid var(--border); - border-radius: 4px; - background: var(--bg); - font-size: 11px; /* slightly smaller than .cookbook-field-input so Context reads quieter next to Engine/Quant */ -} -.hwfit-ctx-control span { - /* Match Quant/Engine select label style: no uppercase, no letter-spacing. */ - text-transform: none; - letter-spacing: 0; - opacity: 0.9; -} -.hwfit-ctx-control > span:first-child { - font-size: 9.5px; -} -/* Editor-style slider (same look as the gallery editor sliders): thin pill - rail that fattens on interaction, circular red thumb that grows on hover. */ -.hwfit-ctx-control input[type="range"] { - width: 64px; - min-width: 64px; - height: 4px; - padding: 0; - border: 0; - -webkit-appearance: none; - appearance: none; - /* Hard-coded grey so the rail is GUARANTEED visible regardless of theme — - every theme-derived color we tried (--fg-muted, --border, accent-bg mix) - kept blending into the panel background on at least one theme. */ - background: rgba(150, 150, 150, 0.65); - border-radius: 999px; - accent-color: var(--red); - cursor: pointer; - transition: height 0.15s ease, background 0.15s ease; -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - background: var(--fg); -} -.hwfit-ctx-control input[type="range"]:hover, -.hwfit-ctx-control input[type="range"]:focus, -.hwfit-ctx-control input[type="range"]:active { - height: 10px; -} -.hwfit-ctx-control input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]::-moz-range-thumb { - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--red); - border: none; - cursor: pointer; - transition: width 0.12s ease, height 0.12s ease; -} -.hwfit-ctx-control input[type="range"]:hover::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:focus::-webkit-slider-thumb, -.hwfit-ctx-control input[type="range"]:active::-webkit-slider-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control input[type="range"]:hover::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:focus::-moz-range-thumb, -.hwfit-ctx-control input[type="range"]:active::-moz-range-thumb { - width: 18px; - height: 18px; -} -.hwfit-ctx-control output { - min-width: 28px; - text-align: right; - color: var(--fg); - font-weight: 600; -} -.hwfit-sf { - background: var(--bg); - border: 1px solid var(--border); - border-radius: 4px; - color: var(--fg); - font-family: inherit; - font-size: 12px; - padding: 0 6px; - height: 28px; - box-sizing: border-box; -} -.hwfit-sf[data-field="backend"], -.hwfit-sf[data-field="dtype"], -.hwfit-sf[data-field="tp"] { - height: 32px; - box-sizing: border-box; - width: 100%; -} -.hwfit-sf:focus { - border-color: var(--accent, var(--red)); - outline: none; -} -.hwfit-sf[type="text"] { width: 100%; } -.hwfit-sf.hwfit-sf-wide { width: 100%; } -.hwfit-serve-checks { - display: flex; - flex-wrap: wrap; - gap: 10px; - margin-bottom: 6px; -} -.hwfit-sf-cb { - display: flex; - flex-direction: row; - align-items: center; - gap: 6px; - font-size: 11px; - color: var(--fg-muted); - cursor: pointer; - padding: 3px 0; -} diff --git a/static/index.html b/static/index.html index cba90c7c9..ad04daf89 100644 --- a/static/index.html +++ b/static/index.html @@ -246,15 +246,24 @@ real request is discarded and the font fetched a second time. --> - - - - - - - - - + + + + + + + + + + + + + + + + + +