Files
ThothII/frontend/src/index.css
T
User d6cdffea62
Publish documentation / publish (push) Successful in 34s
fix: keep embedded session controls visible and handle empty memory
Cap the embedded shell at its portal container height so steering and stop controls remain accessible. Skip vector retrieval for an empty authoritative Memory archive and compute SQL-rule embeddings lazily.

Validated with 54 Memory tests, 90 frontend tests, five browser scenarios, frontend and Docker builds, and a read-only comparison against the real empty Memory archive.
2026-09-14 17:15:00 +02:00

597 lines
21 KiB
CSS

@import "tw-animate-css";
@import "shadcn/tailwind.css";
/* Omics shares the document. Keep Tailwind's reset inside our mount and portals. */
@thoth-base {
@tailwind base;
}
@tailwind components;
@tailwind utilities;
body.thot-full-document { margin: 0; }
/*
* Product theme — shares the Omics Portal identity (Manrope, warm
* off-white surfaces, #cb333b red). Tokens are RAW OKLCH triplets (L C H) so the
* Tailwind v3 config can wrap them as `oklch(var(--token) / <alpha-value>)` and
* opacity utilities (bg-primary/80, …) keep working. Dark tokens follow either a
* `.dark` class or the portal's `[data-bs-theme="dark"]`, ready for embedding.
* Tokens belong to the React mount and Base UI portal roots, never the host header.
*/
@layer base {
#root, [data-base-ui-portal] {
color-scheme: light;
--font-sans: "Manrope Variable", "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Arial, sans-serif;
--font-heading: var(--font-sans);
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
--text-page: 1.5rem;
--text-section: 1.25rem;
--text-body: 1rem;
--text-control: 0.875rem;
--text-meta: 0.75rem;
/* Layered elevation, tinted with the foreground hue so depth reads warm on
warm surfaces. Borders mark structure; shadows mark elevation. */
--shadow-tint: 0.2678 0.0097 355.6;
--shadow-xs: 0 1px 2px oklch(var(--shadow-tint) / 0.05);
--shadow-sm: 0 1px 2px oklch(var(--shadow-tint) / 0.05), 0 2px 6px -1px oklch(var(--shadow-tint) / 0.05);
--shadow-md: 0 2px 4px -2px oklch(var(--shadow-tint) / 0.06), 0 12px 32px -8px oklch(var(--shadow-tint) / 0.1);
--background: 0.9918 0.0011 17.2; /* surface */
--foreground: 0.2678 0.0097 355.6; /* text */
--card: 0.9985 0.0006 17.2;
--card-foreground: 0.2678 0.0097 355.6;
--popover: 0.9985 0.0006 17.2;
--popover-foreground: 0.2678 0.0097 355.6;
--primary: 0.5587 0.1881 23.2; /* gsd red */
--primary-foreground: 0.9918 0.0011 17.2;
--primary-hover: 0.5095 0.1812 24.1;
--secondary: 0.9709 0.0011 17.2; /* surface-alt */
--secondary-foreground: 0.2678 0.0097 355.6;
--muted: 0.9709 0.0011 17.2;
--muted-foreground: 0.5133 0.0088 345.6;
--accent: 0.9408 0.0011 17.2; /* sunken */
--accent-foreground: 0.2678 0.0097 355.6;
--destructive: 0.5587 0.1881 23.2;
--destructive-foreground: 0.9918 0.0011 17.2;
--border: 0.9093 0.0035 354.7;
--input: 0.64 0.008 354.7;
--ring: 0.5587 0.1881 23.2;
--success: 0.46 0.095 160;
--warning: 0.48 0.09 70;
--info: var(--muted-foreground);
/* Current navigation shares Instrument Red's hue, with lower chroma and
higher lightness so selection reads as a muted location signal. */
--nav-active: 0.9250 0.0520 23.2;
--nav-active-hover: 0.8950 0.0710 23.2;
--nav-active-foreground: 0.3650 0.1100 23.2;
--nav-active-border: 0.6000 0.1350 23.2;
--radius: 0.5rem;
--sidebar: 0.9709 0.0011 17.2;
--sidebar-foreground: 0.2678 0.0097 355.6;
--sidebar-primary: 0.5587 0.1881 23.2;
--sidebar-primary-foreground: 0.9918 0.0011 17.2;
--sidebar-accent: 0.9408 0.0011 17.2;
--sidebar-accent-foreground: 0.2678 0.0097 355.6;
--sidebar-border: 0.9093 0.0035 354.7;
--sidebar-ring: 0.5587 0.1881 23.2;
}
:is(.dark, [data-bs-theme="dark"]) :is(#root, [data-base-ui-portal]) {
color-scheme: dark;
--shadow-tint: 0 0 0;
--background: 0.2178 0 90;
--foreground: 0.9310 0 90;
--card: 0.2350 0 90;
--card-foreground: 0.9310 0 90;
--popover: 0.2350 0 90;
--popover-foreground: 0.9310 0 90;
--primary: 0.73 0.15 23.2;
--primary-foreground: 0.2178 0.005 23.2;
--primary-hover: 0.6897 0.1779 16.9;
--secondary: 0.2972 0 90;
--secondary-foreground: 0.9310 0 90;
--muted: 0.2972 0 90;
--muted-foreground: 0.7826 0 90;
--accent: 0.3485 0 90;
--accent-foreground: 0.9310 0 90;
--destructive: 0.73 0.15 23.2;
--destructive-foreground: 0.9310 0 90;
--border: 0.3715 0 90;
--input: 0.62 0.008 23.2;
--ring: 0.6897 0.1779 16.9;
--success: 0.78 0.10 160;
--warning: 0.82 0.10 75;
--nav-active: 0.3350 0.0550 20.3;
--nav-active-hover: 0.3750 0.0650 20.3;
--nav-active-foreground: 0.8900 0.0700 20.3;
--nav-active-border: 0.6800 0.1350 20.3;
--sidebar: 0.2350 0 90;
--sidebar-foreground: 0.9310 0 90;
--sidebar-primary: 0.6023 0.1848 20.3;
--sidebar-primary-foreground: 0.9310 0 90;
--sidebar-accent: 0.3485 0 90;
--sidebar-accent-foreground: 0.9310 0 90;
--sidebar-border: 0.3715 0 90;
--sidebar-ring: 0.6897 0.1779 16.9;
}
:where(#root, [data-base-ui-portal]), :where(#root, [data-base-ui-portal]) * {
@apply border-border outline-ring/50;
}
/* A click should read as a physical state change, not only as a hover ending.
Keep the response short and compositor-friendly so repeated actions stay fast. */
:where(#root, [data-base-ui-portal]) button:not(:disabled) {
transform-origin: center;
transition-property: color, background-color, border-color, box-shadow, filter, transform;
transition-duration: 140ms;
transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
}
:where(#root, [data-base-ui-portal]) button:not(:disabled):active {
transform: scale(0.97);
filter: brightness(0.94);
box-shadow: none;
}
@media (prefers-reduced-motion: reduce) {
:where(#root, [data-base-ui-portal]) button:not(:disabled) {
transition-duration: 0.01ms;
}
:where(#root, [data-base-ui-portal]) button:not(:disabled):active {
transform: none;
filter: brightness(0.9);
box-shadow: none;
}
}
#root, [data-base-ui-portal] {
@apply font-sans text-foreground antialiased;
line-height: 1.5;
-webkit-text-size-adjust: 100%;
}
#root {
@apply bg-background text-foreground antialiased;
font-feature-settings: "ss01", "cv11";
}
:where(#root, [data-base-ui-portal]) :is(input, textarea)::placeholder {
color: oklch(var(--muted-foreground));
opacity: 1;
}
:where(#root, [data-base-ui-portal]) :is(button, input, select, textarea, summary):focus-visible {
outline: 2px solid oklch(var(--ring));
outline-offset: 2px;
}
/* The composer already exposes keyboard focus on its enclosing focus-within frame. */
#root textarea.thot-composer-input:focus-visible {
outline: none;
}
:where(#root, [data-base-ui-portal]) :where(h1, h2, h3, h4, h5, h6) {
font-family: var(--font-heading);
font-optical-sizing: auto;
letter-spacing: -0.015em;
}
:where(#root, [data-base-ui-portal]) :is(code, pre, kbd, samp) {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
}
:where(#root, [data-base-ui-portal]) ::selection {
background: oklch(var(--primary) / 0.16);
}
/* Thin, brand-tinted scrollbars */
:where(#root, [data-base-ui-portal]) * {
scrollbar-width: thin;
scrollbar-color: oklch(var(--border)) transparent;
}
:where(#root, [data-base-ui-portal]) *::-webkit-scrollbar {
width: 10px;
height: 10px;
}
:where(#root, [data-base-ui-portal]) *::-webkit-scrollbar-thumb {
background: oklch(var(--border));
border-radius: 9999px;
border: 2px solid transparent;
background-clip: padding-box;
}
:where(#root, [data-base-ui-portal]) *::-webkit-scrollbar-thumb:hover {
background: oklch(var(--muted-foreground) / 0.5);
}
}
/*
* Minimal markdown prose for assistant turns (no typography plugin installed).
* Scoped so it never leaks into widgets.
*/
@layer components {
.thot-host {
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
height: var(--thoth-app-height, 100dvh);
color: oklch(var(--foreground));
background: oklch(var(--background));
color-scheme: light;
}
/* Embedded hosts can reserve space for their own header/footer around #root. */
.thot-host--embedded { max-height: 100%; }
.thot-host[data-theme="dark"] { color-scheme: dark; }
.thot-host--full { --thot-shell-gutter: max(20px, 1.5rem); }
.thot-host--full > .thot-context-layout {
margin-inline: var(--thot-shell-gutter);
border-inline: 1px solid oklch(var(--border));
}
.thot-host > .thot-context-layout { flex: 1; min-height: 0; height: auto; }
.thot-full-header {
/* Match Omics static/css/gsd-theme.css --gsd-red-primary in both themes. */
--thot-header-background: #cb333b;
--thot-header-foreground: oklch(99.85% 0.0006 17.2);
display: flex;
flex: none;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 0.5rem 1rem;
min-height: 3.75rem;
padding: 0.625rem var(--thot-shell-gutter);
border-bottom: 1px solid var(--thot-header-background);
background: var(--thot-header-background);
color: var(--thot-header-foreground);
}
.thot-full-header__brand { font: 600 1.25rem/1.2 var(--font-heading); }
.thot-full-header__controls { display: flex; align-items: center; flex-wrap: wrap; gap: 0.25rem; min-width: 0; }
.thot-full-header__user { max-width: 15rem; min-width: 0; font-size: var(--text-control); }
.thot-full-header__error { flex-basis: 100%; font-size: var(--text-control); color: inherit; }
.thot-full-header__controls button { color: var(--thot-header-foreground); }
.thot-full-header__controls button:is(:hover, [aria-expanded="true"]) {
color: var(--thot-header-foreground);
background: color-mix(in srgb, var(--thot-header-foreground) 14%, transparent);
}
.thot-full-header :is(button, select):focus-visible {
outline: 2px solid var(--thot-header-foreground);
outline-offset: 2px;
}
.thot-full-header .thot-shell-select {
color: var(--thot-header-foreground);
background: transparent;
border-color: color-mix(in srgb, var(--thot-header-foreground) 65%, transparent);
}
.thot-full-header .thot-shell-select option {
color: oklch(var(--foreground));
background: oklch(var(--card));
}
/* Native selects can match :focus-visible after a pointer click as well.
Suppress only that outer ring; keyboard focus retains its visible outline. */
.thot-full-header .thot-shell-select[data-pointer-focus="true"]:focus {
outline: none;
box-shadow: none;
}
.thot-shell-select {
min-height: 2rem;
max-width: 9rem;
padding: 0.25rem 0.5rem;
border: 1px solid oklch(var(--input));
border-radius: var(--radius);
background: oklch(var(--background));
color: oklch(var(--foreground));
font: 600 var(--text-control)/1.5 var(--font-sans);
}
.thot-shell-select:focus-visible { outline: 3px solid oklch(var(--ring) / 0.5); outline-offset: 2px; }
.thot-user-menu {
z-index: 50;
min-width: 10rem;
padding: 0.25rem;
border: 1px solid oklch(var(--border));
border-radius: var(--radius);
background: oklch(var(--popover));
color: oklch(var(--popover-foreground));
box-shadow: var(--shadow-md);
font: 500 0.875rem/1.5 var(--font-sans);
outline: none;
}
.thot-user-menu__item { padding: 0.375rem 0.5rem; border-radius: 0.25rem; cursor: default; outline: none; }
.thot-user-menu__item[data-highlighted] { background: oklch(var(--accent)); color: oklch(var(--accent-foreground)); }
.thot-access-state { flex: 1; display: grid; align-content: center; justify-items: center; gap: 1rem; padding: 2rem max(20px, 1.5rem); color: oklch(var(--foreground)); background: oklch(var(--background)); }
@media (max-width: 480px) {
.thot-full-header__controls { flex: 1; justify-content: flex-end; }
.thot-full-header__user { max-width: 9rem; }
}
/* Omics Portal and Thoth share one document in the embedded deployment.
Keep rail geometry on product-specific class names so Hyper/Bootstrap's
global spacing utilities (for example .px-4 !important) cannot resize it. */
.thot-session-navigation__header {
padding: 1.25rem 1rem 0.75rem;
}
.thot-session-navigation__primary-controls {
display: flex;
min-width: 0;
flex-direction: column;
gap: 0.5rem;
padding: 0 1rem 0.75rem;
}
.thot-session-accordion { display: flex; flex-direction: column; }
.thot-session-accordion__item {
display: flex;
flex-direction: column;
flex: 0 0 auto;
border-top: 1px solid oklch(var(--border));
}
.thot-session-accordion__item[data-open] {
flex: 1 1 0;
min-height: 2.5rem;
max-height: calc(min(18rem, 35dvh) + 2.5rem);
}
.thot-session-accordion__item > h3 { flex: none; }
.thot-session-accordion__trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
width: 100%;
min-height: 2.5rem;
padding: 0.5rem;
font: 600 var(--text-control)/1.5 var(--font-sans);
text-align: left;
}
.thot-session-accordion__trigger:hover { background: oklch(var(--muted)); }
.thot-session-accordion__panel {
flex: 1 1 auto;
max-height: min(18rem, 35dvh);
min-height: 0;
overflow-y: auto;
overscroll-behavior-y: contain;
scrollbar-gutter: stable;
padding: 0.25rem;
}
.thot-session-accordion__trigger:focus-visible,
.thot-session-accordion__panel:focus-visible {
outline: 2px solid oklch(var(--ring));
outline-offset: -2px;
}
/* UI labels use the same family as controls; monospace is for technical data. */
.thot-label {
font-family: var(--font-sans);
font-size: var(--text-meta);
font-weight: 600;
letter-spacing: normal;
text-transform: none;
color: oklch(var(--muted-foreground));
}
.thot-prose {
color: oklch(var(--foreground));
font-size: var(--text-body);
line-height: 1.6;
overflow-wrap: anywhere;
}
.thot-prose > :first-child { margin-top: 0; }
.thot-prose > :last-child { margin-bottom: 0; }
.thot-prose p { margin: 0.6em 0; }
.thot-prose h1, .thot-prose h2, .thot-prose h3 {
font-family: var(--font-heading);
font-weight: 600;
line-height: 1.25;
margin: 1.1em 0 0.5em;
letter-spacing: -0.01em;
}
.thot-prose h1 { font-size: var(--text-page); }
.thot-prose h2 { font-size: var(--text-section); }
.thot-prose h3 { font-size: var(--text-body); }
.thot-prose :where(p, ul, ol, blockquote) { max-width: 75ch; }
/* Shared reading contract for Memory and Evidence, independent of UI controls. */
.thot-knowledge-reader,
.thot-knowledge-prose {
font-family: var(--font-sans);
font-size: var(--text-body);
font-weight: 400;
line-height: 1.65;
letter-spacing: normal;
color: oklch(var(--foreground));
width: 100%;
min-width: 0;
max-width: none;
overflow-wrap: anywhere;
}
.thot-knowledge-title {
font: 600 var(--text-page)/1.3 var(--font-sans);
letter-spacing: -0.015em;
margin-top: 0.25rem;
}
.thot-knowledge-heading {
font: 600 var(--text-section)/1.4 var(--font-sans);
letter-spacing: normal;
margin: 0 0 0.5rem;
}
.thot-knowledge-meta {
font: 400 var(--text-control)/1.5 var(--font-sans);
letter-spacing: normal;
}
.thot-knowledge-meta dt { font-weight: 600; margin-bottom: 0.25rem; }
.thot-knowledge-reader code,
.thot-knowledge-reader pre,
.thot-prose.thot-knowledge-prose code {
font-family: var(--font-mono);
font-size: var(--text-control);
line-height: 1.65;
}
.thot-prose.thot-knowledge-prose :where(h1, h2, h3, h4, h5, h6) {
font: 600 var(--text-body)/1.5 var(--font-sans);
letter-spacing: normal;
margin: 1.5rem 0 0.5rem;
}
.thot-prose.thot-knowledge-prose > :first-child { margin-top: 0; }
.thot-knowledge-prose strong { font-weight: 600; }
.thot-knowledge-prose :where(p, ul, ol, blockquote) { max-width: none; }
.thot-prose.thot-knowledge-prose p + p { margin-top: 1.25em; }
.thot-knowledge-prose pre { white-space: pre-wrap; overflow-wrap: anywhere; }
.thot-prose ul, .thot-prose ol { margin: 0.6em 0; padding-left: 1.3em; }
.thot-prose li { margin: 0.25em 0; }
.thot-prose a { color: oklch(var(--primary)); text-decoration: underline; text-underline-offset: 2px; }
.thot-prose code {
font-family: var(--font-mono);
font-size: 0.85em;
background: oklch(var(--muted));
padding: 0.12em 0.4em;
border-radius: 0.3rem;
border: 1px solid oklch(var(--border) / 0.6);
}
.thot-prose pre {
background: oklch(var(--muted));
padding: 0.9rem 1rem;
border-radius: calc(var(--radius) + 2px);
overflow-x: auto;
border: 1px solid oklch(var(--border) / 0.7);
box-shadow: var(--shadow-xs);
}
.thot-prose pre code { background: transparent; padding: 0; }
.thot-prose blockquote {
border: 1px solid oklch(var(--border));
border-radius: var(--radius);
background: oklch(var(--muted) / 0.4);
padding: 0.75em 1em;
color: oklch(var(--muted-foreground));
margin: 0.7em 0;
}
.thot-prose table {
border-collapse: separate;
border-spacing: 0;
width: 100%;
margin: 0.7em 0;
font-size: 0.875rem;
border: 1px solid oklch(var(--border) / 0.8);
border-radius: calc(var(--radius) + 2px);
overflow: hidden;
}
.thot-prose th, .thot-prose td { padding: 0.45em 0.7em; text-align: left; }
.thot-prose td { border-top: 1px solid oklch(var(--border) / 0.6); font-variant-numeric: tabular-nums; }
.thot-prose th {
background: oklch(var(--muted));
font-size: var(--text-control);
font-weight: 600;
letter-spacing: normal;
text-transform: none;
color: oklch(var(--muted-foreground));
}
.thot-activity-prose,
.thot-activity-prose p,
.thot-activity-prose li,
.thot-activity-prose a,
.thot-activity-prose code,
.thot-activity-prose th,
.thot-activity-prose td {
overflow-wrap: anywhere;
word-break: break-word;
}
.thot-activity-prose table { table-layout: fixed; }
.thot-activity-prose pre {
overflow-x: hidden;
white-space: pre-wrap;
overflow-wrap: anywhere;
word-break: break-word;
}
.thot-activity-prose pre code { white-space: inherit; }
}
@layer components {
.thot-awaiting-input {
background: oklch(var(--success) / 0.14);
border-color: oklch(var(--success) / 0.55);
}
.thot-awaiting-input:focus {
box-shadow: 0 0 0 3px oklch(var(--success) / 0.18);
}
/* Live-activity pulse: a soft ring emanates from the red stop dot while the harness
is actively working, signalling "something is happening". Ring only (no layout
properties animated); calm 1.6s ease-out cadence, silenced for reduced motion. */
.thot-pulse {
animation: thot-pulse-ring 1.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.thot-pulse { animation: none; }
}
.thot-prose p + p { margin-top: 1em; }
.thot-prose ul { list-style: disc; }
.thot-prose ol { list-style: decimal; }
.thot-prose ul ul { list-style: circle; }
.thot-prose ol ol { list-style: lower-alpha; }
}
@keyframes thot-pulse-ring {
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 oklch(var(--primary) / 0.4); }
50% { transform: scale(1.15); box-shadow: 0 0 0 5px oklch(var(--primary) / 0); }
}
/* Shiki emits both palettes; select its dark values without rebuilding SQL. */
:is(.dark, [data-bs-theme="dark"]) .shiki,
:is(.dark, [data-bs-theme="dark"]) .shiki span {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
@layer components {
.thot-database-grid, .thot-preview-grid,
:is(.thot-database-grid, .thot-preview-grid) .ag-theme-alpine {
--ag-font-family: var(--font-sans);
--ag-font-size: var(--text-control);
--ag-background-color: oklch(var(--card));
--ag-foreground-color: oklch(var(--foreground));
--ag-header-background-color: oklch(var(--muted) / 0.72);
--ag-header-foreground-color: oklch(var(--foreground));
--ag-border-color: oklch(var(--border));
--ag-row-border-color: oklch(var(--border) / 0.72);
--ag-odd-row-background-color: oklch(var(--muted) / 0.18);
--ag-row-hover-color: oklch(var(--muted) / 0.55);
--ag-selected-row-background-color: oklch(var(--primary) / 0.07);
--ag-input-focus-border-color: oklch(var(--primary) / 0.6);
--ag-range-selection-border-color: oklch(var(--primary) / 0.6);
--ag-header-column-separator-color: oklch(var(--border));
--ag-header-column-separator-display: block;
--ag-wrapper-border-radius: 0;
--ag-cell-horizontal-padding: 12px;
}
.thot-database-grid .ag-root-wrapper {
border: 0;
}
.thot-database-grid .ag-cell {
display: flex;
align-items: center;
}
.thot-database-grid .ag-header-cell-label {
font-weight: 700;
}
.thot-database-grid .ag-cell-focus:not(.ag-cell-range-selected) {
outline: 2px solid oklch(var(--ring) / 0.45);
outline-offset: -2px;
}
.thot-database-grid .ag-cell.thot-database-actions-cell {
padding-inline: 2px;
}
.thot-database-grid .ag-cell.thot-database-actions-cell.ag-cell-focus:not(.ag-cell-range-selected) {
outline: none;
}
.thot-database-grid .ag-cell.thot-database-status-cell {
padding-inline: 4px;
}
.thot-database-grid .ag-pinned-right-header,
.thot-database-grid .ag-pinned-right-cols-container {
box-shadow: -1px 0 0 oklch(var(--border));
}
}