/* 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 , 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 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; }