diff --git a/static/css/cookbook-research-memory-settings.css b/static/css/cookbook-research-memory-settings.css
new file mode 100644
index 000000000..3bcbb08cd
--- /dev/null
+++ b/static/css/cookbook-research-memory-settings.css
@@ -0,0 +1,1423 @@
+/* Cookbook (including What Fits?), deep research, memory, settings and admin.
+ *
+ * Split out of style.css for maintainability, not for performance: this file
+ * is loaded eagerly from index.html immediately after style.css, so the rules
+ * land in the cascade in exactly the order they had inside the single file.
+ * Nothing here is lazy-loaded.
+ *
+ * LOAD ORDER IS PART OF THE CONTRACT. The tags in static/index.html and
+ * the PRECACHE list in static/sw.js must both list the stylesheets in this
+ * order:
+ * 1. static/style.css
+ * 2. static/css/documents-gallery-editor.css
+ * 3. static/css/email-calendar-notes-tasks.css
+ * 4. static/css/cookbook-research-memory-settings.css
+ *
+ * Rules were moved verbatim. A rule stays in style.css when its selector group
+ * covers more than one panel, when the class is shared design language used by
+ * modules outside this panel, or when moving it would change which of two
+ * equally specific rules wins.
+ */
+
+/* Cookbook already shows its own running/served-status dot
+ (#cookbook-notif-dot, toggled with .cookbook-notif-active on the
+ button). Don't stack the tabbed-down pulse on top of it — the two
+ dots overlap. Suppress the minimized dot while the status dot is up. */
+.list-item.rail-minimized.cookbook-notif-active::after { display: none; }
+@keyframes research-done-pulse {
+ 0%, 100% { transform: scale(1); opacity: 1; }
+ 50% { transform: scale(1.5); opacity: 0.7; }
+ }
+@keyframes research-pulse {
+ 0%, 100% { background: color-mix(in srgb, var(--red) 12%, transparent); }
+ 50% { background: color-mix(in srgb, var(--red) 22%, transparent); }
+ }
+#cookbook-gguf-delete-overlay {
+ background:rgba(0,0,0,0.5);
+ backdrop-filter:blur(4px);
+ pointer-events:auto !important;
+ z-index:99999 !important;
+ position:fixed !important;
+ inset:0 !important;
+ }
+.cookbook-gguf-delete-actions {
+ flex-wrap:wrap;
+ }
+/* ── Processing pulse animation (reused by session-star) ── */
+@keyframes research-pulse {
+ 0%, 100% { opacity: 0.3; transform: scale(0.8); }
+ 50% { opacity: 1; transform: scale(1.2); }
+}
+@keyframes memory-synapse-pulse {
+ 0%, 100% { opacity: 0.35; transform: scale(1); }
+ 50% { opacity: 0.65; transform: scale(1.02); }
+}
+.memory-add-input::placeholder {
+ color: color-mix(in srgb, var(--fg) 40%, transparent);
+}
+.memory-toolbar-toggle:has(input:not(:checked)) > span {
+ text-decoration: line-through;
+ text-decoration-color: color-mix(in srgb, var(--fg) 30%, transparent);
+}
+.memory-sort-label { white-space: nowrap; }
+.memory-sort-btn[aria-expanded="true"] .memory-sort-caret { transform: rotate(180deg); }
+@keyframes memory-synapse-sweep {
+ /* Sweep traverses left → right in the first ~12% of the cycle (≈0.7s of
+ a 6.2s loop), then waits offscreen. */
+ 0% { --sweep: -20%; }
+ 12% { --sweep: 120%; }
+ 13%, 100% { --sweep: 120%; }
+}
+.memory-item-edit-input:focus {
+ outline: none;
+}
+.memory-edit-cat-select:focus {
+ outline: none;
+}
+.memory-add-row .memory-edit-cat-select:focus {
+ border-color: var(--red);
+}
+.memory-item-source::before,
+.memory-item-time::before {
+ content: '\00b7 ';
+}
+.adm-check-dot::after {
+ content: '';
+ position: absolute;
+ left: 50%;
+ top: 45%;
+ width: 5px;
+ height: 2.5px;
+ border-left: 1.5px solid #fff;
+ border-bottom: 1.5px solid #fff;
+ transform: translate(-50%, -50%) rotate(-45deg) scale(0);
+ transform-origin: center;
+ transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+.adm-model-row:active .adm-check-dot {
+ transform: scale(0.9);
+}
+.adm-cb-hidden:checked + .adm-check-dot::after {
+ transform: translate(-50%, -50%) rotate(-45deg) scale(1);
+}
+.adm-cb-hidden:checked + .adm-endpoint-select-dot::after {
+ display: none;
+}
+/* Most recently added endpoint — brief accent glow so the user can
+ spot the new row immediately after Adding / Find. Fades out cleanly. */
+@keyframes adm-ep-just-added-glow {
+ 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent, var(--red)) 55%, transparent); background: color-mix(in srgb, var(--accent, var(--red)) 18%, transparent); }
+ 60% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 0%, transparent); background: color-mix(in srgb, var(--accent, var(--red)) 8%, transparent); }
+ 100% { box-shadow: 0 0 0 0 transparent; background: transparent; }
+}
+.adm-section-toggle:focus-visible { outline: 1px solid var(--red); outline-offset: 1px; }
+/* Collapsed: hide the form body and point the caret right. */
+.adm-add-section.collapsed .admin-model-form { display: none; }
+.adm-add-section.collapsed .adm-section-caret { transform: rotate(-90deg); }
+.adm-quickstart-section {
+ margin-top: 7px;
+}
+.adm-quickstart-section:not(.collapsed) .adm-quickstart-toggle {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+.adm-quickstart-section.collapsed .adm-quickstart-body { display: none; }
+.adm-quickstart-section.collapsed .adm-section-caret { transform: rotate(-90deg); }
+.adm-provider-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+@keyframes admin-spin {
+ to { transform: rotate(360deg); }
+}
+.admin-rag-item .admin-btn-delete { font-size: 10px; padding: 2px 6px; }
+/* Cookbook's cached-model list: NO inner-scroll cap. Two nested scroll
+ surfaces (this + the outer .admin-card) trapped the wheel so an expanded
+ serve panel couldn't be reached on tall content. Let the outer
+ .admin-card (overflow-y:auto) be the single scroll surface. */
+.hwfit-cached-list {
+ max-height: none !important;
+ overflow-y: visible !important;
+}
+/* The Servers list reuses .memory-toolbar for layout but must grow with every
+ added server — the 120px cap above was clipping manually-added servers. */
+.memory-toolbar.cookbook-servers-toolbar {
+ max-height: none;
+ overflow: visible;
+}
+#hwfit-cached-list {
+ position: relative;
+ top: -6px;
+}
+#hwfit-server-select,
+#hwfit-search {
+ position: relative;
+ top: -2px !important;
+}
+#hwfit-search {
+ height: 30px;
+ min-height: 30px;
+ 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-body::-webkit-scrollbar {
+ width: 4px;
+}
+.cookbook-body::-webkit-scrollbar-thumb {
+ background: color-mix(in srgb, var(--fg) 15%, transparent);
+ border-radius: 4px;
+}
+.cookbook-body::-webkit-scrollbar-track {
+ background: transparent;
+ padding: 4px 0;
+}
+#hwfit-dl-server.cookbook-server-select-colored,
+#hwfit-server-select.cookbook-server-select-colored,
+#hwfit-cache-server.cookbook-server-select-colored,
+#hwfit-deps-server.cookbook-server-select-colored {
+ color: var(--cookbook-server-color, var(--fg));
+ border-color: color-mix(in srgb, var(--cookbook-server-color) 55%, var(--border));
+ background-color: color-mix(in srgb, var(--cookbook-server-color) 12%, var(--bg));
+}
+.cookbook-output-wrap:hover .cookbook-output-kill { opacity: 0.7; }
+.cookbook-tab-error-dot { --notif-glow: var(--color-error, #f44); }
+/* Brief highlight on the matched task card when jumping from the
+ running pill, so the user can spot it among a long list. */
+.cookbook-task-flash {
+ animation: cookbook-task-flash-anim 1.6s ease-out;
+}
+@keyframes cookbook-task-flash-anim {
+ 0% { box-shadow: 0 0 0 2px var(--accent, var(--red)); }
+ 100% { box-shadow: 0 0 0 2px transparent; }
+}
+/* Cookbook header "downloading" status label sits 2px too far left
+ against the rest of the cookbook chrome — nudge it right. */
+#cookbook-bg-status {
+ left: 2px;
+}
+/* Keep both GPU action labels on one line — they wrapped to two rows on
+ mobile, which looked broken. */
+.cookbook-gpu-probe, .cookbook-gpu-clear { white-space: nowrap; }
+#cookbook-hf-latest-list .cookbook-hf-link:hover {
+ opacity: 1;
+}
+#cookbook-hf-latest-list {
+ min-height: 0;
+ max-height: min(52vh, 480px);
+ overflow-y: auto;
+ overflow-x: hidden;
+ overscroll-behavior: contain;
+ scrollbar-width: thin;
+}
+.cookbook-section-header .cookbook-section-title {
+ flex: 1;
+}
+.cookbook-section-header:hover .cookbook-section-chevron { opacity: 1; }
+.cookbook-slot-wrap:hover .cookbook-slot-del { opacity: 1; }
+/* Keep dependency pills visually centered against the package description.
+ The row itself stays stable; only the controls move down slightly. */
+.cookbook-dep-row > .cookbook-dep-tag {
+ transform: translateY(2px);
+}
+/* Conditional line under the Download h2: only when the section is folded
+ (collapsed). When expanded, the body content provides separation; the
+ underline reads as clutter. */
+#cookbook-dl-tab-fold { border-bottom: none !important; padding-bottom: 0 !important; }
+#cookbook-dl-tab-fold.is-folded {
+ border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent) !important;
+ padding-bottom: 6px !important;
+}
+/* Smooth open/close for the Direct Download body — replaces the
+ instant display:none toggle with a max-height + opacity slide so
+ auto-fold and auto-expand don't feel jarring. 1200px is a safe
+ upper bound for the body height; the slide only fires up to the
+ actual content height because content stops there. */
+#cookbook-dl-tab-fold-body {
+ overflow: hidden;
+ max-height: 1200px;
+ opacity: 1;
+ transition: max-height 0.28s ease, opacity 0.18s ease, margin 0.28s ease;
+}
+#cookbook-dl-tab-fold-body.is-folded {
+ max-height: 0;
+ opacity: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ pointer-events: none;
+}
+.cookbook-dep-installed-btn .cookbook-dep-installed-label { padding: 0 8px; }
+.cookbook-dep-installed-btn:hover { filter: brightness(1.15); }
+.hwfit-serve-topline .hwfit-serve-runtime-note {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+/* Expanded serve panel — make sure it can be scrolled past when it
+ grows taller than the visible viewport. Caps panel height to viewport
+ minus chrome and gives it its own internal scroll, so the surrounding
+ cached-list scroll isn't the only escape route. */
+.hwfit-cached-item .hwfit-serve-panel {
+ max-height: calc(100vh - 180px);
+ overflow-y: auto;
+ overscroll-behavior: contain;
+}
+@media (max-width: 768px) {
+ .hwfit-cached-item .hwfit-serve-panel {
+ max-height: calc(100svh - 120px);
+ }
+ .hwfit-serve-preset-row {
+ justify-content: flex-end;
+ margin: -2px 0 0;
+ }
+ .hwfit-serve-topline {
+ flex-wrap: wrap;
+ }
+ .hwfit-serve-topline .hwfit-serve-runtime-note {
+ flex-basis: 100%;
+ }
+ .hwfit-serve-topline .hwfit-serve-runtime-text {
+ white-space: normal;
+ }
+}
+.hwfit-serve-row label select,
+.hwfit-serve-row label input {
+ display: block;
+ margin-top: 2px;
+}
+.hwfit-extra-env-label {
+ grid-column: 1 / -1;
+}
+.hwfit-serve-panel[data-backend-active="vllm"] .hwfit-extra-env-label {
+ grid-column: 2 / -1;
+}
+.hwfit-sf.hwfit-sf-full { grid-column: 1 / -1; }
+.hwfit-spec-tokens-bare::-webkit-outer-spin-button,
+.hwfit-spec-tokens-bare::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+ display: none;
+}
+.hwfit-numstep .hwfit-spec-tokens::-webkit-outer-spin-button,
+.hwfit-numstep .hwfit-spec-tokens::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+.hwfit-numstep-btn:focus { outline: none; }
+.hwfit-spec-group:has(input[type="checkbox"]:not(:checked)) .hwfit-spec-method,
+.hwfit-spec-group:has(input[type="checkbox"]:not(:checked)) .hwfit-spec-tokens,
+.hwfit-spec-group:has(input[type="checkbox"]:not(:checked)) .hwfit-numstep {
+ opacity: 0.45;
+ pointer-events: none;
+}
+.hwfit-serve-extra {
+ margin-bottom: 6px;
+}
+.hwfit-serve-cmd-details {
+ margin: 4px 0 0;
+}
+.hwfit-serve-cmd-summary::-webkit-details-marker {
+ display: none;
+}
+.hwfit-serve-cmd-summary::after {
+ content: '';
+ margin-left: auto;
+ width: 0;
+ height: 0;
+ border-left: 4px solid currentColor;
+ border-top: 3px solid transparent;
+ border-bottom: 3px solid transparent;
+ opacity: 0.6;
+ transform: rotate(0deg);
+ transition: transform 0.18s ease;
+}
+.hwfit-serve-cmd-details[open] > .hwfit-serve-cmd-summary::after {
+ transform: rotate(90deg);
+}
+.hwfit-serve-cmd-details[open] > .hwfit-serve-cmd-summary {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+/* Same z-index treatment as .cookbook-task-dropdown — cookbook modal's
+ auto-stack climbs past low values; popups append to body and need to
+ sit above the modal regardless. */
+:where(.hwfit-cached-dropdown,
+.cookbook-gpu-split-menu,
+.cookbook-launch-actions-menu,
+.cookbook-saved-menu,
+.cookbook-dep-menu) {
+ z-index: 10000;
+}
+.hwfit-serve-cmd-wrap .hwfit-serve-cmd {
+ /* Just enough breathing room so a cursor at line-end doesn't actually
+ touch the Copy icon — text otherwise uses the full width of the box. */
+ padding-right: 32px;
+}
+.hwfit-serve-cmd-details[open] .hwfit-serve-cmd {
+ border-color: color-mix(in srgb, var(--accent, var(--red)) 48%, var(--border));
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent, var(--red)) 16%, transparent),
+ 0 0 12px color-mix(in srgb, var(--accent, var(--red)) 13%, transparent);
+}
+.cookbook-gpu-split .cookbook-gpu-split-main {
+ border-top-right-radius: 0 !important;
+ border-bottom-right-radius: 0 !important;
+}
+.cookbook-gpu-split .cookbook-gpu-split-arrow {
+ border-top-left-radius: 0 !important;
+ border-bottom-left-radius: 0 !important;
+ border-left-width: 0 !important;
+ padding: 0 6px !important;
+ min-width: 24px;
+}
+.cookbook-task[data-type="download"][data-status="running"] .cookbook-task-type[data-type="download"] {
+ background: color-mix(in srgb, var(--color-accent, #00aaff) 18%, transparent);
+ color: var(--color-accent, #00aaff);
+}
+@keyframes cookbook-wave-pulse {
+ 0%, 100% { opacity: 0.45; }
+ 50% { opacity: 1; }
+}
+.cookbook-task[data-status="done"] .cookbook-task-check-ico { display: inline; }
+.cookbook-task[data-status="done"] .cookbook-task-clear-ico { display: none; }
+.cookbook-task-header {
+ cursor: pointer;
+}
+.cookbook-env-row > .cookbook-field-label {
+ flex: 1 1 140px;
+ min-width: 120px;
+}
+.cookbook-extra-label { grid-column: 1 / -1; }
+.cookbook-slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+ background: var(--fg);
+ border: 2px solid var(--panel);
+ box-shadow: 0 1px 4px rgba(0,0,0,0.3);
+ cursor: pointer;
+ transition: transform 0.1s, box-shadow 0.1s;
+}
+.cookbook-slider:hover::-webkit-slider-thumb {
+ transform: scale(1.15);
+ box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent, var(--red)) 20%, transparent);
+}
+.cookbook-slider:active::-webkit-slider-thumb {
+ transform: scale(1.25);
+ box-shadow: 0 0 0 6px color-mix(in srgb, var(--accent, var(--red)) 25%, transparent);
+}
+.cookbook-slider::-moz-range-thumb {
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: var(--fg);
+ border: 2px solid var(--panel);
+ box-shadow: 0 1px 4px rgba(0,0,0,0.3);
+ cursor: pointer;
+}
+.cookbook-slider::-moz-range-progress {
+ background: var(--accent, var(--red));
+ height: 6px;
+ border-radius: 4px;
+}
+.cookbook-slider::-webkit-slider-runnable-track {
+ height: 6px;
+ border-radius: 4px;
+}
+.cookbook-slider::-moz-range-track {
+ height: 6px;
+ border-radius: 4px;
+ background: color-mix(in srgb, var(--fg) 12%, transparent);
+}
+.cookbook-checkbox:checked::after {
+ transform: translateX(14px);
+}
+/* Sliders span full row for alignment */
+.cookbook-field-slider {
+ grid-column: 1 / -1;
+}
+.cookbook-diag-body {
+ margin-top: 7px;
+}
+/* Add Server collapsible */
+.cookbook-server-details {
+ margin: 4px 0 6px;
+}
+.cookbook-server-toggle::-webkit-details-marker { display: none; }
+.cookbook-srv-status:hover { transform: scale(1.3); }
+@keyframes cookbook-srv-pulse {
+ 0%, 100% { opacity: 0.45; }
+ 50% { opacity: 1; }
+}
+.cookbook-server-entry::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ z-index: 2;
+ 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(--cookbook-server-color, 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;
+}
+#cookbook-servers-list .cookbook-server-entry:nth-child(2n)::after { animation-duration: 7.4s; animation-delay: 1.6s; }
+#cookbook-servers-list .cookbook-server-entry:nth-child(3n)::after { animation-duration: 8.8s; animation-delay: 3.2s; }
+#cookbook-servers-list .cookbook-server-entry:nth-child(5n)::after { animation-duration: 9.3s; animation-delay: 4.7s; }
+#cookbook-servers-list .cookbook-server-entry:nth-child(7n)::after { animation-duration: 5.5s; animation-delay: 2.3s; }
+.cookbook-server-entry:hover::after { animation: none; opacity: 0; }
+@media (prefers-reduced-motion: reduce) {
+ .cookbook-server-entry::after { animation: none; opacity: 0; }
+}
+/* Keep the row inside the card on narrow screens — the fixed-width host/path
+ inputs would otherwise overflow the card's background to the right. */
+@media (max-width: 768px) {
+ .cookbook-server-row .cookbook-srv-host,
+ .cookbook-server-row .cookbook-srv-path { flex: 1 1 100% !important; width: auto !important; min-width: 0 !important; }
+}
+.cookbook-server-row .cookbook-srv-name { width: 60px; flex-shrink: 0; flex-grow: 0; }
+.cookbook-server-row select.cookbook-srv-color {
+ display: none !important;
+}
+.cookbook-server-row .cookbook-srv-color-btn:focus-visible {
+ outline: 2px solid color-mix(in srgb, var(--cookbook-server-color, var(--accent, var(--red))) 70%, transparent);
+ outline-offset: 2px;
+}
+.cookbook-server-row .cookbook-srv-host { flex: 1; min-width: 100px; }
+.cookbook-server-row .cookbook-srv-host[readonly] { opacity: 0.4; cursor: default; }
+.cookbook-server-row .cookbook-srv-port { width: 40px; flex-shrink: 0; flex-grow: 0; }
+.cookbook-server-row .cookbook-srv-env { width: 65px; flex-shrink: 0; flex-grow: 0; }
+.cookbook-server-row .cookbook-srv-path { flex: 1; min-width: 80px; }
+.cookbook-server-row .cookbook-srv-status { width: 8px; height: 8px; align-self: center; }
+/* Brief highlight on the serve command box when a saved config is loaded, so
+ the click clearly registers (loading is otherwise silent). */
+.cookbook-cmd-flash {
+ animation: cookbookCmdFlash 0.6s ease;
+}
+.hwfit-toolbar:has(.hwfit-hw-manual-btn) {
+ gap: 8px;
+}
+.hwfit-toolbar .hwfit-server-select { min-width: 70px; flex-shrink: 0; }
+.hwfit-toolbar .hwfit-usecase { min-width: 70px; flex-shrink: 0; }
+.hwfit-toolbar .hwfit-engine { min-width: 0; width: 86px; flex-shrink: 0; font-size: 10px; }
+.hwfit-toolbar .hwfit-search { flex: 1; min-width: 80px; }
+.hwfit-advanced-panel .hwfit-engine-menu {
+ min-width: 132px;
+}
+/* Lower-opacity "Search models..." placeholder so it reads as a hint, not
+ a label — matches the muted form-field feel of the inline filters. */
+.hwfit-search::placeholder { opacity: 0.5; }
+.hwfit-search::-webkit-input-placeholder { opacity: 0.5; }
+.hwfit-search::-moz-placeholder { opacity: 0.5; }
+.hwfit-quant-wrap .hwfit-quant,
+.hwfit-engine-wrap .hwfit-engine {
+ /* Make room for the ? on the right edge, in addition to the native chevron. */
+ padding-right: 32px;
+}
+.hwfit-toolbar .hwfit-host { width: 110px; flex-shrink: 0; }
+.hwfit-env-row .hwfit-envtype { width: auto; min-width: 70px; flex-shrink: 0; }
+.hwfit-env-row .hwfit-envpath { flex: 1; min-width: 100px; }
+.hwfit-env-row .hwfit-gpus { width: 90px; flex-shrink: 0; }
+.hwfit-hw-chip-dismiss { cursor: pointer; }
+.hwfit-hw-chip-row[data-hw-chip="gpu"] .hwfit-hw-chip-x {
+ transform: translateY(-3px);
+}
+.hwfit-manual-panel .hwfit-hw-manual-save,
+.hwfit-manual-panel .hwfit-hw-manual-clear {
+ /* -3 (was -2) — 1px more up to optically align with the labeled
+ inputs to their left. */
+ transform: translateY(-3px);
+}
+.cookbook-model-list-fade {
+ animation: cookbook-model-list-fade-in 180ms ease-out both;
+}
+@keyframes cookbook-model-list-fade-in {
+ from { opacity: 0; transform: translateY(3px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+@media (prefers-reduced-motion: reduce) {
+ .cookbook-model-list-fade,
+ .hwfit-list.cookbook-hwfit-models-just-loaded > .hwfit-row:not(.hwfit-header) { animation: none; }
+}
+.hwfit-row:has(.hwfit-dl-dot):hover { opacity: 0.9; }
+.hwfit-row:has(.hwfit-dl-dot) .hwfit-dl-dot { opacity: 1; }
+.hwfit-header .hwfit-name { font-size: 9px; }
+.hwfit-sortable { cursor: pointer; user-select: none; }
+.hwfit-c-params { width: 42px; }
+.hwfit-c-quant { width: 52px; }
+.hwfit-c-vram { width: 42px; }
+.hwfit-c-ctx { width: 32px; }
+.hwfit-c-speed { width: 44px; }
+.hwfit-c-mode { width: 72px; }
+@keyframes hwfit-panel-in {
+ from { opacity: 0; transform: translateY(-4px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+.hwfit-cb { cursor: pointer; }
+.settings-appearance-panel {
+ flex-direction: column;
+}
+@keyframes cookbook-modal-enter {
+ 0% {
+ opacity: 0;
+ transform: translateY(12px) scale(0.94);
+ filter: saturate(0.85);
+ }
+ 65% {
+ opacity: 1;
+ transform: translateY(-2px) scale(1.012);
+ filter: saturate(1.05);
+ }
+ 100% {
+ opacity: 1;
+ transform: translateY(0) scale(1);
+ filter: none;
+ }
+}
+@keyframes cookbook-modal-enter-mobile {
+ 0% { opacity: 0; transform: translateY(100%); }
+ 100% { opacity: 1; transform: translateY(0); }
+}
+/* Cookbook Serve Advanced fold — wraps the rarely-touched tuning rows
+ (KV/Attention/Swap/Env for vLLM, llama.cpp batch/cache/split, VRAM
+ monitor, speculative, extra args). Matches the existing .hwfit-panel-
+ advanced look: muted-gray label, no caps, no letter-spacing, no
+ warning-y opacity. Content flows into the parent's existing scroll
+ surface (no inner max-height) and inner rows reset their margin so
+ stacking gaps don't double when the fold opens. */
+/* Styled to match the Add Models page collapsible sections
+ (.adm-section-toggle) — same border/background/caret pattern, so the
+ two folds across the app read consistently. */
+details.hwfit-serve-advanced {
+ margin-top: 8px;
+ overflow: visible;
+}
+details.hwfit-serve-advanced > summary.hwfit-serve-advanced-summary {
+ cursor: pointer;
+ user-select: none;
+ list-style: none;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 11px;
+ color: var(--fg);
+ opacity: 0.8;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 6px 9px;
+ background: color-mix(in srgb, var(--fg) 4%, transparent);
+ transition: border-color 0.12s, background 0.12s, opacity 0.12s, border-radius 0s;
+}
+details.hwfit-serve-advanced > summary.hwfit-serve-advanced-summary::-webkit-details-marker {
+ display: none;
+}
+details.hwfit-serve-advanced > summary.hwfit-serve-advanced-summary:hover {
+ opacity: 1;
+ border-color: var(--red);
+ background: color-mix(in srgb, var(--red) 8%, transparent);
+}
+/* Caret on the right, rotates open/closed. SVG-style rectangles via
+ borders keep this glyph-free + crisp at small sizes. */
+details.hwfit-serve-advanced > summary.hwfit-serve-advanced-summary::after {
+ content: '';
+ margin-left: auto;
+ width: 0;
+ height: 0;
+ border-left: 4px solid currentColor;
+ border-top: 3px solid transparent;
+ border-bottom: 3px solid transparent;
+ opacity: 0.6;
+ transform: rotate(90deg);
+ transition: transform 0.18s ease;
+}
+details.hwfit-serve-advanced:not([open]) > summary.hwfit-serve-advanced-summary::after {
+ transform: rotate(0deg);
+}
+/* Body rows below the header — tight rhythm so the fold doesn't
+ feel airy. The cookbook modal's existing .cookbook-body is the
+ scroll surface; nothing inside the fold should add its own scroll. */
+details.hwfit-serve-advanced[open] > summary.hwfit-serve-advanced-summary {
+ margin-bottom: 6px;
+}
+details.hwfit-serve-advanced > .hwfit-serve-row.hwfit-vram-monitor .hwfit-vram-readout {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+details.hwfit-serve-advanced > .hwfit-serve-row label select,
+details.hwfit-serve-advanced > .hwfit-serve-row label input {
+ margin-top: 1px;
+}
+details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_attn_backend"]) {
+ position: relative;
+ left: -63px;
+}
+details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_block_size"]) {
+ position: relative;
+ left: -75px;
+}
+details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="swap"]) {
+ position: relative;
+ left: -73px;
+}
+details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_kv_cache_dtype"]) {
+ position: relative;
+ left: 2px;
+}
+@media (max-width: 768px) {
+ details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="vllm_block_size"]) {
+ left: 1px;
+ }
+ details.hwfit-serve-advanced label:has(.hwfit-sf[data-field="swap"]) {
+ left: -3px;
+ }
+ details.hwfit-serve-advanced > .hwfit-serve-checks .hwfit-sf-cb {
+ flex: 1 1 100%;
+ }
+}
+/* Hide the native number-input spinner arrows (e.g. SMTP/IMAP Port) — they
+ render unstyled and ugly. Field still accepts only numbers. */
+.settings-input[type="number"]::-webkit-outer-spin-button,
+.settings-input[type="number"]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+.settings-input[type="number"] {
+ -moz-appearance: textfield;
+ appearance: textfield;
+}
+.cookbook-output-wrap:hover .cookbook-output-copy {
+ opacity: 0.5;
+}
+.cookbook-output-wrap .cookbook-output-copy:hover {
+ opacity: 1;
+}
+#cookbook-dl-btn-search {
+ position: relative;
+ top: -4px;
+ padding-top: 0;
+ padding-bottom: 0;
+ height: 28px;
+}
+#cookbook-dl-btn {
+ position: relative;
+ top: -4px;
+}
+.research-synapse-compact .rs-stage { height: 288px; }
+@keyframes rs-live {
+ 0%, 100% { opacity: 0.45; }
+ 50% { opacity: 1; }
+}
+.research-synapse .rs-guide ellipse {
+ fill: none;
+ stroke: color-mix(in srgb, var(--fg) 7%, transparent);
+ stroke-width: 1;
+ stroke-dasharray: 2 7;
+}
+.research-synapse .rs-edge-tone-1 { stroke: color-mix(in srgb, var(--gold, #d4a72c) 45%, var(--border)); }
+.research-synapse .rs-edge-tone-2 { stroke: color-mix(in srgb, #5aa7c7 42%, var(--border)); }
+.research-synapse .rs-edge.rs-edge-firing {
+ stroke: var(--accent, var(--red));
+ stroke-width: 2;
+ opacity: 1;
+ filter: drop-shadow(0 0 4px var(--accent, var(--red)));
+ animation: rs-fire 1.1s ease-out;
+}
+@keyframes rs-fire {
+ 0% { stroke-dasharray: 4 200; stroke-dashoffset: 200; opacity: 0; }
+ 20% { opacity: 1; }
+ 100% { stroke-dasharray: 200 4; stroke-dashoffset: -200; opacity: 0.55; }
+}
+.research-synapse .rs-node-root {
+ fill: var(--accent, var(--red));
+ stroke: var(--accent, var(--red));
+ filter: drop-shadow(0 0 5px color-mix(in srgb, var(--accent, var(--red)) 65%, transparent));
+}
+.research-synapse .rs-root-halo {
+ fill: none;
+ stroke: color-mix(in srgb, var(--accent, var(--red)) 28%, transparent);
+ stroke-width: 1;
+}
+/* Sub & leaf nodes stay inside the accent palette so the whole graph
+ reads as one organism. Leaves are softer/lower-contrast dots that
+ float around their sub — not green-on-stem (which read as palm fronds). */
+.research-synapse .rs-node-sub {
+ fill: color-mix(in srgb, var(--accent, var(--red)) 10%, var(--bg));
+ stroke: color-mix(in srgb, var(--accent, var(--red)) 70%, var(--fg));
+}
+.research-synapse .rs-node-sub.rs-node-tone-1 {
+ fill: color-mix(in srgb, var(--gold, #d4a72c) 11%, var(--bg));
+ stroke: color-mix(in srgb, var(--gold, #d4a72c) 76%, var(--fg));
+}
+.research-synapse .rs-node-sub.rs-node-tone-2 {
+ fill: color-mix(in srgb, #5aa7c7 11%, var(--bg));
+ stroke: color-mix(in srgb, #5aa7c7 72%, var(--fg));
+}
+.research-synapse .rs-node-leaf {
+ stroke: color-mix(in srgb, var(--accent, var(--red)) 55%, transparent);
+ fill: color-mix(in srgb, var(--accent, var(--red)) 42%, var(--bg));
+}
+.research-synapse .rs-node-leaf.rs-node-tone-1 { fill: color-mix(in srgb, var(--gold, #d4a72c) 48%, var(--bg)); }
+.research-synapse .rs-node-leaf.rs-node-tone-2 { fill: color-mix(in srgb, #5aa7c7 48%, var(--bg)); }
+.research-synapse .rs-source-node { cursor: help; }
+@keyframes rs-hub-expand {
+ 0% { filter: drop-shadow(0 0 0 transparent); }
+ 35% { filter: drop-shadow(0 0 7px var(--accent, var(--red))); }
+ 100% { filter: drop-shadow(0 0 0 transparent); }
+}
+@keyframes rs-pop {
+ 0% { transform: scale(0); opacity: 0; }
+ 60% { transform: scale(1.25); opacity: 1; }
+ 100% { opacity: 1; }
+}
+@keyframes rs-pulse {
+ 0% { transform: scale(1); opacity: 0.65; }
+ 100% { transform: scale(5); opacity: 0; }
+}
+.research-synapse .rs-meta .rs-sep { display: none; }
+.research-synapse.rs-complete .rs-pulse { animation: none; opacity: 0; }
+.research-synapse.rs-complete .rs-node-root {
+ fill: var(--color-success, #4caf50);
+ stroke: var(--color-success, #4caf50);
+}
+@media (max-width: 480px) {
+ .research-synapse .rs-meta { grid-template-columns: 1fr auto auto; row-gap: 6px; }
+ .research-synapse .rs-meta .rs-status { display: none; }
+}
+.hwfit-cached-menu-btn svg { fill: currentColor; }
+.settings-email-preferences-card > h2 {
+ display: none;
+}
+[data-settings-panel="email"] > .settings-email-preferences-card {
+ padding-top: 0;
+}
+.cookbook-task-menu-btn {
+ top: 0 !important;
+ transform: translateY(2px) scale(1.15) !important;
+}
+/* Settings "+ Add server" matches the model-dir "+ Add" path button (22px). */
+#cookbook-server-add.cal-add-btn-text {
+ height: 28px;
+ min-width: 62px;
+ border-radius: 14px;
+ padding: 0 12px 0 8px;
+ position: relative;
+ top: 1px;
+ font-size: 12px;
+}
+@keyframes research-pane-enter {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+.research-pane-header:active { cursor:grabbing; }
+.research-pane-header h4 {
+ margin: 0;
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--red);
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}
+.research-tab-panel.hidden,
+.research-tab-panel[hidden] { display: none; }
+.research-tab-heading h2 {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 1;
+}
+.research-tab-panel:not([data-research-panel="research"]) .research-jobs-list {
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow-y: auto;
+}
+/* Hide the in-body "Research" subtitle on mobile to save vertical space —
+ the toolbar/header carries enough context already. */
+/* Match the .admin-card h2 styling used in Documents/Library so the
+ "Research" sub-title reads the same as those modals' titles. */
+.research-new-job h2 {
+ font-size: 14px;
+ font-weight: 600;
+ letter-spacing: 0;
+}
+.research-new-job > .research-new-job-desc {
+ margin: 6px 0 0;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ flex-wrap: wrap;
+}
+@media (max-width: 600px) {
+ /* Keep the "Research" title visible on mobile (matches the Cookbook tab
+ titles, which show on mobile). It used to be hidden here. */
+ .research-new-job h2 {
+ font-size: 14px;
+ }
+}
+.research-setting:has(#research-model) .research-setting-label {
+ position: relative;
+ top: 1px;
+}
+.research-setting:has(#research-rounds) select,
+.research-setting:has(#research-category) select {
+ position: relative;
+ top: -4px;
+}
+.research-setting:has(#research-rounds) .research-setting-label,
+.research-setting:has(#research-category) .research-setting-label {
+ position: relative;
+ top: 4px;
+}
+.research-setting select {
+ font-size: 11px; padding: 4px 6px;
+ background: var(--bg);
+ color: var(--fg);
+ border: 1px solid var(--border); border-radius: 4px;
+}
+.research-picker-btn:focus-visible,
+.research-picker-option:focus-visible {
+ outline: 2px solid color-mix(in srgb, var(--accent, var(--red)) 65%, transparent);
+ outline-offset: 1px;
+}
+.research-picker-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.research-picker.open .research-picker-btn > svg { transform: rotate(180deg); }
+.research-picker-menu[hidden] { display: none; }
+.research-picker-option > span:nth-child(2):not(.research-picker-check),
+.research-picker-option > span:first-child:not(.research-picker-icon) {
+ overflow: hidden; text-overflow: ellipsis;
+}
+.research-picker-option.active .research-picker-check { opacity: 1; }
+.research-run-mode-popover.rrm-up { transform-origin: bottom right; }
+.research-run-mode-popover .research-run-mode-row:hover {
+ background: color-mix(in srgb, var(--accent-primary, var(--red)) 14%, transparent);
+}
+.research-job-card.done { cursor:pointer; }
+/* Past (library) research used to be dimmed to 0.65 which read as "greyed out".
+ They now look the same as the rest — folding them under "Past research"
+ handles the de-clutter instead. */
+.research-job-card.done.from-library { opacity:1; }
+.research-job-thumb-frame[role="button"] { cursor: pointer; }
+.research-job-thumb-frame[role="button"]:hover {
+ outline: 1px solid color-mix(in srgb, var(--accent, var(--red)) 65%, transparent);
+ outline-offset: 1px;
+}
+.research-job-report-body h1, .research-job-report-body h2, .research-job-report-body h3 {
+ color: var(--cat-color, var(--accent, var(--fg)));
+}
+.research-hero::after {
+ content: ''; position: absolute; right: -40px; top: -40px;
+ width: 160px; height: 160px; border-radius: 50%;
+ background: radial-gradient(circle, color-mix(in srgb, var(--cat-color, var(--accent)) 15%, transparent) 0%, transparent 60%);
+ pointer-events: none;
+}
+.research-hero-icon svg { width: 100%; height: 100%; }
+/* Product: callout-style bullets */
+.research-body-product ul { list-style: none; padding-left: 0; }
+.research-body-product ul li {
+ padding: 6px 10px 6px 28px; margin: 4px 0;
+ border-left: 2px solid color-mix(in srgb, #5b8abf 40%, transparent);
+ background: color-mix(in srgb, #5b8abf 4%, transparent);
+ border-radius: 0 4px 4px 0; position: relative;
+}
+.research-body-product ul li::before {
+ content: '▸'; position: absolute; left: 10px;
+ color: #5b8abf; font-weight: bold;
+}
+/* Comparison: styled table */
+.research-body-comparison table {
+ width: 100%; border-collapse: collapse; margin: 12px 0;
+ border: 1px solid color-mix(in srgb, #e5a33a 25%, transparent);
+ border-radius: 6px; overflow: hidden;
+}
+.research-body-comparison th {
+ background: color-mix(in srgb, #e5a33a 18%, transparent);
+ color: #e5a33a; font-weight: 700;
+ padding: 8px 12px; text-align: left;
+ border-bottom: 2px solid color-mix(in srgb, #e5a33a 40%, transparent);
+}
+.research-body-comparison td {
+ padding: 8px 12px;
+ border-bottom: 1px solid color-mix(in srgb, #e5a33a 12%, transparent);
+}
+.research-body-comparison tr:nth-child(even) td {
+ background: color-mix(in srgb, #e5a33a 3%, transparent);
+}
+/* How-to: big numbered steps */
+.research-body-howto ol { counter-reset: howto-step; list-style: none; padding-left: 0; }
+.research-body-howto ol > li {
+ counter-increment: howto-step; position: relative;
+ padding: 10px 12px 10px 52px; margin: 8px 0;
+ background: color-mix(in srgb, #82c882 5%, transparent);
+ border-radius: 8px; border-left: 2px solid #82c882;
+}
+.research-body-howto ol > li::before {
+ content: counter(howto-step);
+ position: absolute; left: 10px; top: 14px;
+ width: 30px; height: 30px; border-radius: 50%;
+ background: #82c882; color: white;
+ font-weight: 700; font-size: 13px;
+ display: flex; align-items: center; justify-content: center;
+ box-shadow: 0 2px 6px color-mix(in srgb, #82c882 40%, transparent);
+}
+/* Landscape: section banners */
+.research-body-landscape h3 {
+ padding: 8px 14px;
+ background: linear-gradient(90deg, color-mix(in srgb, #a07ae0 15%, transparent), transparent);
+ border-left: 3px solid #a07ae0;
+ border-radius: 0 6px 6px 0;
+ margin: 14px 0 8px;
+}
+/* Fact-check: verdict emphasis */
+.research-body-factcheck blockquote {
+ border-left: 3px solid var(--red);
+ background: color-mix(in srgb, var(--red) 6%, transparent);
+ padding: 10px 14px; margin: 10px 0;
+ border-radius: 0 6px 6px 0;
+}
+.research-body-factcheck strong {
+ color: var(--red);
+ padding: 1px 6px; border-radius: 4px;
+ background: color-mix(in srgb, var(--red) 12%, transparent);
+}
+.research-job-cancel:hover,
+.research-job-remove:hover,
+.research-job-report-link:hover { opacity:1; }
+.research-synapse-toggle:hover,
+.research-synapse-toggle.active { opacity:1; }
+.research-job-synapse-host.synapse-collapsed { display:none; }
+.research-job-summary .research-job-thumb-frame {
+ transform: translateY(-4px);
+}
+.research-job-summary .research-job-failnote {
+ margin: 2px 0 0;
+}
+.research-job-thumb-frame::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ background: radial-gradient(ellipse at center,
+ transparent 42%,
+ color-mix(in srgb, var(--accent, var(--red)) 28%, transparent) 100%);
+ opacity: 0.75;
+}
+/* Push the first dim (dismiss/delete) button — and everything after it — to
+ the right edge of the actions row. `:first-of-type` would match the very
+ first