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