/* ========================================================== Synthetic Sciences — Thesis-style design system Adapted from Thesis "Obsidian Glass" tokens. Frosted-glass panels over a quiet background mesh. Mono-first typography for instrument-readout density. 4px controls, 8px surfaces. ========================================================== */ /* Computer Modern (SIL OFL, see /fonts/cmc/OFL.md). Self-hosted, regular + bold. */ @font-face { font-family: "Computer Modern"; font-style: normal; font-weight: 400; font-display: swap; src: url("/fonts/cmc/cmunorm.woff2") format("woff2"); } @font-face { font-family: "Computer Modern"; font-style: normal; font-weight: 700; font-display: swap; src: url("/fonts/cmc/cmunobx.woff2") format("woff2"); } :root { /* The app's --color-* namespace is a thin ALIAS layer over the @synsci/ui semantic tokens (theme.css statics + the runtime-injected theme, default frontend/ui/src/theme/themes/openscience.json). One palette, one source of truth — the shell, the transcript, and Settings all move together, and picking a different theme restyles the whole app coherently. */ --color-bg: var(--background-base); --color-bg-subtle: var(--background-weak); --color-bg-elevated: var(--input-disabled); --color-surface: var(--surface-raised-strong); --color-surface-solid: var(--surface-raised-stronger-non-alpha); --color-border: var(--border-base); --color-border-strong: var(--border-strong-base); --color-text: var(--text-strong); --color-text-muted: var(--text-weak); --color-text-faint: var(--text-weaker); --color-accent: var(--text-strong); --color-accent-hover: var(--text-base); --color-accent-subtle: var(--surface-base-hover); --color-focus: var(--border-selected); --color-on-accent: var(--background-base); --glass-bg: rgba(255, 255, 255, 0.85); --glass-border: rgba(0, 0, 0, 0.1); --glass-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 4px 12px rgba(0, 0, 0, 0.03); --glass-blur: 20px; /* Radius — one crisp system: 4px controls, 8px overlays (modals, dialogs, drawers); circular only for toggles/dots/avatars. Sharp + consistent is the product's identity — refined, not rounded into a generic look. */ --radius: 4px; --radius-sm: 4px; --radius-xs: 4px; --radius-lg: 8px; --color-success: var(--surface-success-strong); --color-success-muted: var(--surface-success-weak); --color-error: var(--surface-critical-strong); --color-error-muted: var(--surface-critical-weak); --color-warning: var(--surface-warning-strong); --color-warning-muted: var(--surface-warning-weak); --kind-insight: #7e6b8f; --kind-insight-bg: rgba(126, 107, 143, 0.08); --kind-empirical: #f97316; --kind-empirical-bg: rgba(249, 115, 22, 0.08); --kind-untyped: #71717a; --kind-untyped-bg: rgba(113, 113, 122, 0.08); --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 24px; --space-6: 32px; --space-7: 48px; --space-8: 64px; --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04); --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04); --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0, 0, 0, 0.06); --text-2xs: 9px; --text-xs: 10px; --text-sm: 11px; --text-base: 12px; --text-md: 13px; --text-lg: 14px; --text-xl: 16px; --text-2xl: 20px; --font-sans: "Computer Modern", "Latin Modern Roman", Georgia, "Times New Roman", serif; --font-serif: "Computer Modern", "Latin Modern Roman", Georgia, "Times New Roman", serif; /* Code font: single source. --font-family-mono is owned by @synsci/ui theme.css and written at runtime by the Settings mono-font picker (self-hosted Nerd Font families) — everything code-shaped routes through it. */ --font-code: var(--font-family-mono); --font-ui-sans: "Computer Modern", "Latin Modern Roman", Georgia, "Times New Roman", serif; /* Bridge @synsci/ui (SessionTurn chat transcript) typography to Computer Modern */ --font-family-sans: "Computer Modern", "Latin Modern Roman", Georgia, serif; --z-header: 20; --z-sticky: 50; --z-fab: 100; --z-overlay: 200; --z-modal: 300; --z-toast: 400; --agent-bubble-radius: 4px; --agent-bubble-pad-x: 14px; --agent-bubble-pad-y: 11px; --agent-gap: 14px; --agent-trace-w: 1px; --agent-ease: cubic-bezier(0.16, 1, 0.3, 1); --agent-signal: var(--color-text-muted); --agent-signal-soft: var(--color-accent-subtle); --agent-record: var(--color-text-muted); --agent-code-bg: #0c0c0d; --agent-code-bg-soft: #15151a; --agent-code-fg: #e6e6e6; --agent-code-fg-faint: #6e6e76; --agent-code-border: rgba(255, 255, 255, 0.06); --logo-filter: none; } /* The one keyboard focus ring. !important so it survives inline style resets (`all: unset` on hand-rolled buttons) and component-level outline:none — keyboard users always get the same accent ring. */ :where(button, [role="button"], a, input, textarea, select, [tabindex]):focus-visible { outline: 2px solid var(--color-focus) !important; outline-offset: 2px; } /* The old light-mode "cream neutralisation" remap block is gone: the default openscience theme now ships the product's neutral greys directly, so the ui semantic layer needs no app-side patching (and non-default themes render exactly as authored). */ /* Settings reads as a full surface, not a small floating card. */ [data-component="dialog"]:has(.settings-dialog) [data-slot="dialog-container"] { width: min(calc(100vw - 48px), 1060px); height: min(calc(100vh - 48px), 800px); } :root.dark, .dark, html[data-color-scheme="dark"] { /* --color-* aliases resolve through the mode-aware semantic tokens, so only the values with no semantic source live here. */ --glass-bg: rgba(23, 23, 23, 0.9); --glass-border: rgba(255, 255, 255, 0.12); --glass-shadow: 0 1px 3px rgba(0, 0, 0, 0.3), 0 4px 12px rgba(0, 0, 0, 0.2); --kind-insight: #a596b8; --kind-insight-bg: rgba(165, 150, 184, 0.12); --kind-empirical: #fb923c; --kind-empirical-bg: rgba(251, 146, 60, 0.1); --kind-untyped: #a1a1aa; --kind-untyped-bg: rgba(161, 161, 170, 0.1); --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.2); --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2); --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.3); --agent-code-bg: #060608; --agent-code-bg-soft: #0d0d11; --agent-code-border: rgba(255, 255, 255, 0.05); --logo-filter: invert(1); } /* ========================================================== Base — flat, quiet canvas. (The colored ambient mesh that used to sit here read as a random gradient bleeding around the chat and message boxes, so it's gone.) ========================================================== */ .thesis-root { position: relative; background: var(--color-bg); color: var(--color-text); font-family: var(--font-sans); letter-spacing: -0.011em; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-feature-settings: "ss01", "ss02"; } /* The 40px ruled grid overlay that used to live in .thesis-root::before was removed — it read as distracting horizontal lines in empty areas (e.g. the chat), and its full-viewport masked repaint was costly during layout changes like opening the right pane. */ .thesis-root > * { position: relative; z-index: 1; } .thesis-root code, .thesis-root pre, .thesis-root [data-mono], .thesis-root .font-mono { font-family: var(--font-code); font-feature-settings: "liga" 0; } /* ========================================================== Glass panels — the core design primitive ========================================================== */ .g-card { background: var(--color-surface-solid); border: 1px solid var(--color-border); border-radius: var(--radius); box-shadow: var(--shadow-xs); } .g-card-muted { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: var(--radius); } .g-assistant { background: var(--color-surface-solid); border: 1px solid var(--color-border); border-left: 2px solid var(--color-text-faint); border-radius: var(--radius); } .g-strip { background: rgba(255, 255, 255, 0.55); backdrop-filter: blur(14px) saturate(1.2); -webkit-backdrop-filter: blur(14px) saturate(1.2); border-bottom: 1px solid var(--color-border); } .dark .g-strip, html[data-color-scheme="dark"] .g-strip { background: rgba(23, 23, 23, 0.7); } .g-composer { background: var(--color-surface-solid); border: 1px solid var(--color-border); border-radius: 4px; transition: border-color 120ms ease, box-shadow 160ms ease; box-shadow: var(--shadow-xs); } .g-composer:focus-within { border-color: var(--color-focus); box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-focus) 15%, transparent), var(--shadow-xs); } .g-divider { height: 1px; background: var(--color-border); } .glass { background: var(--glass-bg); backdrop-filter: blur(var(--glass-blur)) saturate(1.5); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.5); border: 1px solid var(--glass-border); box-shadow: var(--glass-shadow); border-radius: var(--radius); } .glass-flat { background: var(--glass-bg); backdrop-filter: blur(var(--glass-blur)) saturate(1.5); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.5); border-right: 1px solid var(--glass-border); } /* ========================================================== Tabular figures ========================================================== */ .tab-fig { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1, "ss01" 1, "ss02" 1; } /* ========================================================== Animations ========================================================== */ @keyframes thesis-spin { to { transform: rotate(360deg); } } @keyframes thesis-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } @keyframes thesis-stagger-fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } } @keyframes thesis-slide-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } @keyframes thesis-fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes thesis-blink-cursor { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } } @keyframes thesis-scale-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } } @keyframes thesis-loading-slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(433%); } } .thesis-spin { animation: thesis-spin 0.9s linear infinite; } .thesis-pulse { animation: thesis-pulse 1.6s ease-in-out infinite; } .thesis-fade-in { animation: thesis-fade-in 200ms var(--agent-ease); } .thesis-slide-up { animation: thesis-slide-up 240ms var(--agent-ease); } .thesis-stagger { animation: thesis-stagger-fade-in 240ms var(--agent-ease); animation-fill-mode: backwards; } .thesis-blink { animation: thesis-blink-cursor 1.1s steps(1) infinite; } @keyframes thesis-pop-up { from { opacity: 0; transform: translateY(6px) scale(0.985); } to { opacity: 1; transform: translateY(0) scale(1); } } /* Smooth pop for upward-opening menus (model/agent/effort/graph pickers). */ .thesis-pop-up { animation: thesis-pop-up 150ms var(--agent-ease); transform-origin: bottom left; will-change: transform, opacity; } /* Reduced-motion: mount/enter animations replay every time a component mounts or a keep-alive surface is first revealed, which reads as a jarring flash when switching tabs / files / folders / terminal / atlas. Honor the OS preference by making them instant (final state, no transform/opacity ramp) while keeping functional loaders (spin/blink) intact. */ @media (prefers-reduced-motion: reduce) { .thesis-fade-in, .thesis-slide-up, .thesis-stagger, .thesis-pop-up, .thesis-modal, .thesis-overlay, .thesis-drawer-right { animation: none !important; } .thesis-skeleton, .thesis-glow, .thesis-pulse, .thesis-orbit-ring-1, .thesis-orbit-ring-2 { animation: none !important; } } /* ========================================================== Soft glow on the orbit ring + frame brackets on transcript ========================================================== */ .thesis-agent-orbit { filter: drop-shadow(0 0 6px color-mix(in srgb, var(--color-focus) 25%, transparent)); } .dark .thesis-agent-orbit, html[data-color-scheme="dark"] .thesis-agent-orbit { filter: drop-shadow(0 0 8px color-mix(in srgb, var(--color-focus) 35%, transparent)); } @keyframes thesis-glow { 0%, 100% { opacity: 0.6; } 50% { opacity: 0.95; } } .thesis-glow { animation: thesis-glow 2.6s ease-in-out infinite; } /* ========================================================== Buttons & chips ========================================================== */ .thesis-btn { font-family: var(--font-sans); font-size: 11px; font-weight: 500; padding: 5px 10px; border-radius: var(--radius); border: 1px solid var(--color-border); background: var(--color-surface-solid); color: var(--color-text); cursor: pointer; transition: all 120ms ease; display: inline-flex; align-items: center; gap: 6px; } .thesis-btn:hover { border-color: var(--color-border-strong); background: var(--color-bg-elevated); } .thesis-btn:disabled { opacity: 0.5; cursor: not-allowed; } .thesis-btn-primary { background: var(--color-accent); color: var(--color-bg); border-color: var(--color-accent); } .thesis-btn-primary:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); } .thesis-btn-ghost { background: transparent; border: 1px solid transparent; color: var(--color-text-muted); } .thesis-btn-ghost:hover { background: var(--color-accent-subtle); color: var(--color-text); } .thesis-chip { font-family: var(--font-sans); font-size: 10px; font-weight: 400; letter-spacing: 0.08em; text-transform: uppercase; padding: 1px 5px; border-radius: var(--radius); background: var(--color-bg-elevated); color: var(--color-text-muted); display: inline-flex; align-items: center; gap: 3px; } .thesis-chip-success { background: var(--color-success-muted); color: var(--color-success); } .thesis-chip-warning { background: var(--color-warning-muted); color: var(--color-warning); } .thesis-chip-error { background: var(--color-error-muted); color: var(--color-error); } .thesis-chip-empirical { background: var(--kind-empirical-bg); color: var(--kind-empirical); } .thesis-chip-insight { background: var(--kind-insight-bg); color: var(--kind-insight); } /* ========================================================== Inputs ========================================================== */ .thesis-input { font-family: var(--font-sans); font-size: 12px; padding: 8px 12px; border-radius: var(--radius); border: 1px solid var(--color-border); background: var(--color-accent-subtle); color: var(--color-text); outline: none; width: 100%; transition: border-color 120ms ease; } .thesis-input:focus { border-color: var(--color-text-faint); } .thesis-input::placeholder { color: var(--color-text-faint); } .thesis-textarea { font-family: var(--font-sans); font-size: 13px; padding: 12px 14px; border-radius: var(--radius); border: 1px solid var(--color-border); background: var(--color-surface-solid); color: var(--color-text); outline: none; width: 100%; resize: none; line-height: 1.55; transition: border-color 120ms ease; } .thesis-textarea:focus { border-color: var(--color-text-faint); } .thesis-textarea::placeholder { color: var(--color-text-faint); } /* ========================================================== Sidebar nav items ========================================================== */ .thesis-nav-item { display: flex; align-items: center; gap: 10px; padding: 6px 10px; border-radius: var(--radius); font-family: var(--font-sans); font-size: 12px; font-weight: 500; color: var(--color-text-muted); cursor: pointer; background: transparent; border: 0; width: 100%; text-align: left; transition: all 120ms ease; } .thesis-nav-item:hover { background: var(--color-accent-subtle); color: var(--color-text); } .thesis-nav-item[data-active="true"] { background: var(--color-accent-subtle); color: var(--color-text); } /* ========================================================== Section labels ========================================================== */ .thesis-section-label { font-family: var(--font-sans); font-size: 10px; font-weight: 400; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-faint); } /* ========================================================== Research node card (.rn-style) ========================================================== */ .thesis-rn { width: 280px; min-height: 88px; background: linear-gradient(180deg, var(--color-surface-solid), var(--color-bg-subtle)); border: 1px solid var(--color-border); border-radius: 4px; padding: 14px 16px; display: flex; flex-direction: column; gap: 6px; cursor: pointer; transition: all 160ms cubic-bezier(0.16, 1, 0.3, 1); box-shadow: var(--shadow-xs); } .thesis-rn:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--color-border-strong); } .thesis-rn[data-active="true"] { box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-focus) 30%, transparent), var(--shadow-sm); border-color: var(--color-focus); } .thesis-rn[data-staged="true"] { opacity: 0.65; } .thesis-rn-title { font-family: var(--font-sans); font-size: 13px; font-weight: 500; color: var(--color-text); line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .thesis-rn-summary { font-family: var(--font-sans); font-size: 11px; color: var(--color-text-muted); line-height: 1.5; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } /* ========================================================== Scrollbar ========================================================== */ .thesis-scroll::-webkit-scrollbar { width: 8px; height: 8px; } .thesis-scroll::-webkit-scrollbar-track { background: transparent; } .thesis-scroll::-webkit-scrollbar-thumb { background: var(--color-border); border-radius: 4px; } .thesis-scroll::-webkit-scrollbar-thumb:hover { background: var(--color-border-strong); } /* When SessionTurn is rendered inside our chat scroller, each turn must size to its own content rather than to the parent (its default `height: 100%` produces one viewport of empty space per message). */ .thesis-chat-scroll [data-component="session-turn"] { height: auto; min-height: 0; display: block; } .thesis-chat-scroll [data-component="session-turn"] > [data-slot="session-turn-content"] { height: auto; min-height: 0; flex-grow: 0; overflow-y: visible; overflow-x: visible; } /* ========================================================== Chat markdown ========================================================== */ .thesis-md { color: var(--color-text); font-family: var(--font-sans); } .thesis-md h1, .thesis-md h2, .thesis-md h3, .thesis-md h4 { font-family: var(--font-sans); font-weight: 600; letter-spacing: -0.015em; margin: 14px 0 6px; line-height: 1.25; color: var(--color-text); } .thesis-md h1 { font-size: 18px; } .thesis-md h2 { font-size: 16px; } .thesis-md h3 { font-size: 14px; } .thesis-md h4 { font-size: 13px; } .thesis-md p { margin: 0 0 10px; } .thesis-md p:last-child { margin-bottom: 0; } .thesis-md ul, .thesis-md ol { margin: 4px 0 10px; padding-left: 22px; } .thesis-md li { margin: 2px 0; line-height: 1.55; } .thesis-md ol li::marker { color: var(--color-text-faint); font-family: var(--font-sans); font-size: 11px; } .thesis-md ul li::marker { color: var(--color-text-faint); } .thesis-md a { color: var(--color-text); text-decoration: underline; text-decoration-color: var(--color-text-faint); text-underline-offset: 3px; transition: text-decoration-color 120ms ease; } .thesis-md a:hover { text-decoration-color: var(--color-text); } .thesis-md strong, .thesis-md b { font-weight: 600; color: var(--color-text); } .thesis-md em { color: var(--color-text-muted); font-style: italic; } .thesis-md code { font-family: var(--font-code); font-size: 11.5px; background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: var(--radius); padding: 0 4px; color: var(--color-text); letter-spacing: 0.01em; } .thesis-md pre, .thesis-md pre.thesis-code-block { font-family: var(--font-code); font-size: 11.5px; background: var(--agent-code-bg); color: var(--agent-code-fg); border: 1px solid var(--agent-code-border); border-radius: var(--radius); padding: 12px 14px; margin: 10px 0; overflow-x: auto; line-height: 1.5; position: relative; } .thesis-md pre code { background: transparent; border: none; padding: 0; color: inherit; } .thesis-code-lang { position: absolute; top: 6px; right: 8px; font-family: var(--font-code); font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--agent-code-fg-faint); padding: 1px 5px; background: rgba(255, 255, 255, 0.04); border-radius: var(--radius); border: 1px solid rgba(255, 255, 255, 0.06); } /* very lightweight token classes — set selectively by future shiki pass */ .thesis-md .tok-keyword { color: #f2f2f2; font-weight: 600; } .thesis-md .tok-string { color: #cfcfd4; } .thesis-md .tok-number { color: #b8b8bf; } .thesis-md .tok-comment { color: #6e6e76; font-style: italic; } .thesis-md .tok-fn { color: #ffffff; } .thesis-md .tok-punct { color: #888892; } .thesis-md blockquote { margin: 10px 0; padding: 4px 0 4px 12px; border-left: 2px solid var(--color-text-faint); color: var(--color-text-muted); font-style: italic; } .thesis-md hr { border: 0; border-top: 1px solid var(--color-border); margin: 16px 0; } .thesis-md table { font-family: var(--font-sans); font-size: 11px; border-collapse: collapse; margin: 10px 0; width: 100%; } .thesis-md th, .thesis-md td { border: 1px solid var(--color-border); padding: 5px 8px; text-align: left; } .thesis-md th { background: var(--color-bg-subtle); font-weight: 600; font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--color-text-faint); } .thesis-md kbd { font-family: var(--font-code); font-size: 10px; padding: 0 5px; border: 1px solid var(--color-border); border-bottom-width: 2px; border-radius: var(--radius); background: var(--color-bg-subtle); color: var(--color-text-muted); } /* ========================================================== Suggestion tile (welcome screen) ========================================================== */ .thesis-tile { font-family: var(--font-sans); font-size: 12.5px; line-height: 1.5; color: var(--color-text-muted); padding: 10px 14px; border-radius: var(--radius); border: 1px solid var(--color-border); background: var(--color-surface-solid); cursor: pointer; transition: all 120ms ease; text-align: left; } .thesis-tile:hover { border-color: var(--color-border-strong); color: var(--color-text); } /* ========================================================== Modal / drawer ========================================================== */ .thesis-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.32); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); z-index: var(--z-overlay); animation: thesis-fade-in 200ms ease; } .dark .thesis-overlay, html[data-color-scheme="dark"] .thesis-overlay { background: rgba(0, 0, 0, 0.55); } .thesis-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: var(--z-modal); background: var(--color-surface-solid); border: 1px solid var(--color-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-md); animation: thesis-scale-in 240ms var(--agent-ease); max-width: 90vw; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column; } .thesis-drawer-right { position: fixed; top: 0; right: 0; bottom: 0; width: 460px; max-width: 92vw; background: var(--color-surface-solid); border-left: 1px solid var(--color-border); box-shadow: var(--shadow-md); z-index: var(--z-modal); animation: thesis-slide-in-right 240ms var(--agent-ease); display: flex; flex-direction: column; } @keyframes thesis-slide-in-right { from { transform: translateX(8px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } /* ========================================================== Approval card (in chat) ========================================================== */ .thesis-proposal { background: var(--color-surface-solid); border: 1px solid var(--color-border); border-left: 3px solid var(--kind-empirical); border-radius: var(--radius); padding: 12px 14px; display: flex; flex-direction: column; gap: 10px; } .thesis-proposal-row { display: grid; grid-template-columns: 88px 1fr; gap: 10px; font-family: var(--font-sans); font-size: 11px; align-items: center; } .thesis-proposal-row > .label { color: var(--color-text-faint); text-transform: uppercase; letter-spacing: 0.08em; font-size: 10px; } .thesis-proposal-row > .value { color: var(--color-text); } /* ========================================================== Agent orbital glyph ========================================================== */ .thesis-agent-orbit { position: relative; transition: transform 200ms ease; } .thesis-agent-orbit:hover { transform: scale(1.06); } .thesis-orbit-ring-1 { transform-origin: 12px 12px; animation: thesis-orbit-1 12s linear infinite; } .thesis-orbit-ring-2 { transform-origin: 12px 12px; animation: thesis-orbit-2 16s linear infinite reverse; } @keyframes thesis-orbit-1 { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes thesis-orbit-2 { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* ========================================================== Skeleton loader ========================================================== */ .thesis-skeleton { background: linear-gradient( 90deg, var(--color-bg-subtle) 0%, var(--color-bg-elevated) 50%, var(--color-bg-subtle) 100% ); background-size: 200% 100%; animation: thesis-skeleton-shimmer 1.4s infinite ease-in-out; border-radius: var(--radius); } @keyframes thesis-skeleton-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } /* ========================================================== Frame brackets — instrument readout ========================================================== */ .thesis-frame { position: relative; } .thesis-frame::before, .thesis-frame::after { content: ""; position: absolute; width: 14px; height: 14px; border-color: var(--color-border-strong); pointer-events: none; } .thesis-frame::before { top: 6px; left: 6px; border-top: 1px solid; border-left: 1px solid; } .thesis-frame::after { bottom: 6px; right: 6px; border-bottom: 1px solid; border-right: 1px solid; }