Add full shell, replaceable Omics adapter and bilingual interaction
Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
This commit is contained in:
+104
-20
@@ -1,18 +1,25 @@
|
||||
@import "tw-animate-css";
|
||||
@import "shadcn/tailwind.css";
|
||||
@tailwind base;
|
||||
/* 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; }
|
||||
|
||||
/*
|
||||
* GSD theme — mirrors the Omics Portal design system (Manrope + Fraunces, 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 {
|
||||
#root, [data-base-ui-portal] {
|
||||
color-scheme: light;
|
||||
--font-sans: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Arial, sans-serif;
|
||||
--font-heading: "Fraunces", "Source Serif Pro", Georgia, "Times New Roman", serif;
|
||||
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", Menlo, Consolas, monospace;
|
||||
@@ -64,8 +71,8 @@
|
||||
--sidebar-ring: 0.5587 0.1881 23.2;
|
||||
}
|
||||
|
||||
.dark,
|
||||
[data-bs-theme="dark"] {
|
||||
: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;
|
||||
@@ -101,77 +108,79 @@
|
||||
--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. */
|
||||
button:not(:disabled) {
|
||||
: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);
|
||||
}
|
||||
|
||||
button:not(:disabled):active {
|
||||
: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) {
|
||||
button:not(:disabled) {
|
||||
:where(#root, [data-base-ui-portal]) button:not(:disabled) {
|
||||
transition-duration: 0.01ms;
|
||||
}
|
||||
|
||||
button:not(:disabled):active {
|
||||
:where(#root, [data-base-ui-portal]) button:not(:disabled):active {
|
||||
transform: none;
|
||||
filter: brightness(0.9);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
@apply font-sans;
|
||||
#root, [data-base-ui-portal] {
|
||||
@apply font-sans text-foreground antialiased;
|
||||
line-height: 1.5;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
#root {
|
||||
@apply bg-background text-foreground antialiased;
|
||||
font-feature-settings: "ss01", "cv11";
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
:where(#root, [data-base-ui-portal]) :is(h1, h2, h3, h4) {
|
||||
font-family: var(--font-heading);
|
||||
font-optical-sizing: auto;
|
||||
letter-spacing: -0.015em;
|
||||
}
|
||||
|
||||
code, pre, kbd, samp {
|
||||
:where(#root, [data-base-ui-portal]) :is(code, pre, kbd, samp) {
|
||||
font-family: var(--font-mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
::selection {
|
||||
: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;
|
||||
}
|
||||
*::-webkit-scrollbar {
|
||||
:where(#root, [data-base-ui-portal]) *::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
*::-webkit-scrollbar-thumb {
|
||||
:where(#root, [data-base-ui-portal]) *::-webkit-scrollbar-thumb {
|
||||
background: oklch(var(--border));
|
||||
border-radius: 9999px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
:where(#root, [data-base-ui-portal]) *::-webkit-scrollbar-thumb:hover {
|
||||
background: oklch(var(--muted-foreground) / 0.5);
|
||||
}
|
||||
}
|
||||
@@ -181,6 +190,70 @@
|
||||
* 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;
|
||||
}
|
||||
.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 {
|
||||
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 oklch(var(--border));
|
||||
background: oklch(var(--card));
|
||||
}
|
||||
.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: 0.8125rem; }
|
||||
.thot-full-header__error { flex-basis: 100%; font-size: 0.8125rem; color: oklch(var(--destructive)); }
|
||||
.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 0.8125rem/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. */
|
||||
@@ -322,8 +395,19 @@
|
||||
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-database-grid, .thot-preview-grid,
|
||||
:is(.thot-database-grid, .thot-preview-grid) .ag-theme-alpine {
|
||||
--ag-font-family: var(--font-sans);
|
||||
--ag-font-size: 0.8125rem;
|
||||
--ag-background-color: oklch(var(--card));
|
||||
|
||||
Reference in New Issue
Block a user