Spaces:
Running
Running
| /* ═══════════════════════════════════════════════════════════════════════ | |
| BLUEPRINT DARK (Awwwards-tier — Linear / Vercel / Liveblocks influence) | |
| Palette: near-pure black base + blueprint blue accent + cool white text. | |
| Background: #09090E — deeper than Linear, creates more atmospheric depth. | |
| Text: cool near-white (#E2E8F8) — crisp against near-black. | |
| Accent: blueprint blue (#5B8FF9) — cartographer/mapmaking identity. | |
| Principle: restraint — accent on <10% of elements, max impact. | |
| ════════════════════════════════════════════════════════════════════════ */ | |
| @font-face { | |
| font-family: "Anthropic Sans"; | |
| src: url(https://cdn.prod.website-files.com/67ce28cfec624e2b733f8a52/69971a00a3295036497e1a28_AnthropicSans-Roman-Web.woff2) format("woff2"); | |
| font-weight: 300 800; | |
| font-style: normal; | |
| font-display: swap; | |
| } | |
| @font-face { | |
| font-family: "Anthropic Sans"; | |
| src: url(https://cdn.prod.website-files.com/67ce28cfec624e2b733f8a52/69971a016067bf14b9b8f48d_AnthropicSans-Italic-Web.woff2) format("woff2"); | |
| font-weight: 300 800; | |
| font-style: italic; | |
| font-display: swap; | |
| } | |
| @font-face { | |
| font-family: "Anthropic Serif"; | |
| src: url(https://cdn.prod.website-files.com/67ce28cfec624e2b733f8a52/69971a1551eb6cda0d656e8a_AnthropicSerif-Roman-Web.woff2) format("woff2"); | |
| font-weight: 300 800; | |
| font-style: normal; | |
| font-display: swap; | |
| } | |
| @font-face { | |
| font-family: "Anthropic Serif"; | |
| src: url(https://cdn.prod.website-files.com/67ce28cfec624e2b733f8a52/69971a15a9fb8c1107a3570e_AnthropicSerif-Italic-Web.woff2) format("woff2"); | |
| font-weight: 300 800; | |
| font-style: italic; | |
| font-display: swap; | |
| } | |
| @font-face { | |
| font-family: "Anthropic Mono"; | |
| src: url(https://cdn.prod.website-files.com/67ce28cfec624e2b733f8a52/69971a2e55d24d61bc045b1a_AnthropicMono-Roman-Web.woff2) format("woff2"); | |
| font-weight: 300 800; | |
| font-style: normal; | |
| font-display: swap; | |
| } | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| html { height: 100dvh; background: #09090E; overflow: hidden; } | |
| /* Hide the OS cursor on pointer-capable devices — the .custom-cursor dot is | |
| the pointer. Touch devices skip this so taps still feel native. Text inputs | |
| keep their native I-beam because a dot over text is harder to aim with. */ | |
| @media (hover: hover) and (pointer: fine) { | |
| html, body, * { cursor: none; } | |
| input[type="text"], input[type="url"], input[type="email"], | |
| input[type="password"], input[type="search"], textarea, | |
| [contenteditable="true"] { cursor: text; } | |
| } | |
| :root { | |
| /* ── Near-pure black backgrounds — blueprint dark base ── | |
| Deeper than Linear (#0A0A0A), creates richer canvas depth. | |
| Each surface uses a subtle blue undertone for cohesion. */ | |
| --bg: #09090E; /* canvas — near-pure black */ | |
| --surface: #0F0F18; /* sidebar, panels — first elevation */ | |
| --surface-2: #151522; /* cards, source items — second elevation */ | |
| --surface-3: #1C1C2E; /* modals, active items — third elevation */ | |
| --surface-4: #23233B; /* hover states, tooltips — highest elevation */ | |
| /* Borders — blue-tinted white for cohesion with blueprint accent */ | |
| --border: rgba(255, 255, 255, 0.09); | |
| --border-subtle: rgba(255, 255, 255, 0.04); | |
| --border-strong: rgba(255, 255, 255, 0.13); | |
| /* Text — cool near-white with blue undertone. | |
| High contrast on near-black without harsh pure white. */ | |
| --text: #F0F6FF; /* near-white — headings, active labels (high contrast) */ | |
| --text-2: #9BAAC8; /* blue-gray body — clearly readable */ | |
| --muted: #5C6E96; /* steel blue — file paths, secondary */ | |
| --faint: #313855; /* dark blue-gray — timestamps, placeholders */ | |
| /* Accent — blueprint blue, the cartographer's ink. | |
| Used sparingly: active states, primary CTAs, glow effects. | |
| Maximum impact through restraint — Linear/Vercel principle. */ | |
| --accent: #5B8FF9; /* blueprint blue — main accent */ | |
| --accent-light: #7DABFF; /* lighter on hover */ | |
| --accent-soft: #A8C5FF; /* pale blue for secondary text */ | |
| --accent-lavender: #C5D8FF; /* high-luminance labels */ | |
| --accent-dim: rgba(91, 143, 249, 0.10); /* very subtle — backgrounds only */ | |
| --accent-glow: rgba(91, 143, 249, 0.22); | |
| --accent-border: rgba(91, 143, 249, 0.35); | |
| /* Secondary accent — teal, for data/service node types */ | |
| --accent-2: #2DD4BF; | |
| --accent-2-dim: rgba(45, 212, 191, 0.12); | |
| /* Semantic */ | |
| --green: #34D399; /* emerald green — success */ | |
| --green-dim: rgba(52, 211, 153, 0.12); | |
| --red: #F87171; /* soft red — errors */ | |
| --red-dim: rgba(248, 113, 113, 0.12); | |
| --warning: #FBBF24; /* amber — warnings */ | |
| --amber: #F59E0B; | |
| /* Legacy alias */ | |
| --pill-bg: var(--surface-3); | |
| /* Typography */ | |
| --sans: "Anthropic Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; | |
| --serif: "Anthropic Serif", "Source Serif Pro", "Iowan Old Style", Georgia, serif; | |
| --mono: "Anthropic Mono", "JetBrains Mono", "Fira Code", monospace; | |
| /* Spacing (8px grid) */ | |
| --space-1: 4px; --space-2: 8px; --space-3: 12px; | |
| --space-4: 16px; --space-5: 20px; --space-6: 24px; | |
| /* Radii */ | |
| --radius-sm: 4px; | |
| --radius: 8px; | |
| --radius-md: 10px; | |
| --radius-lg: 14px; | |
| --radius-xl: 18px; | |
| --radius-full: 9999px; | |
| /* Transitions */ | |
| --transition: 200ms ease; | |
| --transition-slow: 300ms ease; | |
| /* Motion language — one curve everywhere. | |
| --ease-spring is an ease-out-expo: fast start, gentle deceleration, no bounce. | |
| Using it consistently across mounts, tabs, and route changes is what makes | |
| the product feel like one thing instead of many screens. */ | |
| --ease-spring: cubic-bezier(0.22, 1, 0.36, 1); | |
| --ease-spring-snap: cubic-bezier(0.34, 1.15, 0.64, 1); /* subtle overshoot for buttons */ | |
| --dur-fast: 220ms; | |
| --dur-medium: 360ms; | |
| --dur-slow: 520ms; | |
| /* Shadows — cool-tinted deep shadows (no warm brown) */ | |
| --shadow-sm: 0 1px 3px rgba(0,0,16,0.70), 0 1px 2px rgba(0,0,16,0.50); | |
| --shadow: 0 4px 16px rgba(0,0,16,0.80), 0 2px 8px rgba(0,0,16,0.60); | |
| --shadow-lg: 0 12px 40px rgba(0,0,16,0.90), 0 4px 16px rgba(0,0,16,0.70); | |
| --shadow-accent: 0 0 20px rgba(91, 143, 249, 0.25); | |
| /* Glass */ | |
| --glass-bg: rgba(9, 9, 14, 0.85); | |
| --glass-border: rgba(255, 255, 255, 0.06); | |
| --blur: 16px; | |
| } | |
| /* Color-scheme: dark — browser chrome adapts */ | |
| :root { color-scheme: dark; } | |
| /* Custom text selection — default browser blue is generic and clashes with | |
| the accent palette. Use the same blueprint-blue tint at low alpha so any | |
| highlighted text reads as part of the product, not an OS overlay. The | |
| light text colour stays legible on the soft accent fill. */ | |
| ::selection { | |
| background: rgba(91, 143, 249, 0.32); | |
| color: var(--text); | |
| } | |
| ::-moz-selection { | |
| background: rgba(91, 143, 249, 0.32); | |
| color: var(--text); | |
| } | |
| /* CompassSpinner — slow, deliberate rotation. Linear timing because a real | |
| compass needle rotates at constant velocity once disturbed; ease-in-out | |
| would create a "breathing" rhythm that reads as gif-ish. 6s per revolution | |
| is the threshold below which the eye starts registering "spinning" and | |
| above which it reads as "settling" — we want the latter. */ | |
| .compass-spin { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| animation: compass-spin-rotate 6s linear infinite; | |
| transform-origin: 50% 50%; | |
| /* will-change is a paint hint — the browser keeps the element on its own | |
| compositor layer so each rotation frame doesn't trigger a full repaint. | |
| Cheap on memory at this size; matters when many spinners coexist. */ | |
| will-change: transform; | |
| } | |
| .compass-spin svg { | |
| width: 100%; | |
| height: 100%; | |
| display: block; | |
| } | |
| @keyframes compass-spin-rotate { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| .compass-spin { animation: none; } | |
| } | |
| /* ── Smooth page entry ──────────────────────────────────────────────────────── */ | |
| body { | |
| font-family: var(--sans); | |
| font-size: 15px; | |
| line-height: 1.65; | |
| background: #09090E; | |
| color: var(--text); | |
| height: 100%; | |
| overflow: hidden; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| /* Atmospheric bloom — soft static corner glows. The drifting top-left blob | |
| was removed because it competed with the cursor-follow ambient on .main. */ | |
| background-image: | |
| radial-gradient(ellipse 45% 40% at 105% 100%, rgba(10, 30, 90, 0.18) 0%, transparent 60%), | |
| radial-gradient(ellipse 30% 25% at 50% 0%, rgba(91, 143, 249, 0.05) 0%, transparent 50%); | |
| animation: page-in 0.55s cubic-bezier(0.16, 1, 0.3, 1) both; | |
| } | |
| /* Signature dot grid — blueprint-tinted, masked to center, creates spatial texture. | |
| Sits behind all content via z-index. Inspired by Linear/Vercel premium dark aesthetic. | |
| Blue tint ties the grid to the blueprint accent for cohesion. */ | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 0; | |
| background-image: radial-gradient(circle, rgba(91, 143, 249, 0.10) 1px, transparent 1px); | |
| background-size: 24px 24px; | |
| /* Vignette mask — grid strongest at center, invisible at edges */ | |
| mask-image: radial-gradient(ellipse 75% 65% at 50% 50%, rgba(0,0,0,0.50) 0%, transparent 100%); | |
| -webkit-mask-image: radial-gradient(ellipse 75% 65% at 50% 50%, rgba(0,0,0,0.50) 0%, transparent 100%); | |
| } | |
| @keyframes page-in { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| /* ══════════════════════════════════════════════════════════════════════════ | |
| Design primitives — reusable visual moves, consumed across the app. | |
| Keep these small and composable. If a class is used in only one place, | |
| it belongs next to that feature. If it's used in three+, promote here. | |
| ══════════════════════════════════════════════════════════════════════════ */ | |
| /* Typed custom property — needed for transition: --ambient-tint to interpolate | |
| between colours. Without @property the value is treated as an opaque string. */ | |
| @property --ambient-tint { | |
| syntax: "<color>"; | |
| inherits: true; | |
| initial-value: #5B8FF9; | |
| } | |
| /* .display-serif — editorial headline treatment. | |
| Apply to the hero heading of a view (tour title, empty-state, landing). | |
| Signals "this is content to read" vs sans chrome ("things to operate"). */ | |
| .display-serif { | |
| font-family: var(--serif); | |
| font-weight: 600; | |
| letter-spacing: -0.02em; | |
| line-height: 1.1; | |
| } | |
| /* .has-cursor-glow — a soft radial light that follows the pointer. | |
| Requires the host to set --glow-color (defaults to --accent) and to feed | |
| --mx / --my from JS with an onMouseMove handler. The pseudo is below content | |
| (z-index 0) and inside a rounded clip box, so put this on any rounded card. | |
| Paints via the compositor — no React re-renders. */ | |
| .has-cursor-glow { | |
| position: relative; | |
| isolation: isolate; | |
| --mx: 50%; | |
| --my: 50%; | |
| --glow-color: var(--accent); | |
| --glow-size: 380px; | |
| --glow-intensity: 14%; | |
| } | |
| .has-cursor-glow::before { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| border-radius: inherit; | |
| background: radial-gradient( | |
| var(--glow-size) circle at var(--mx) var(--my), | |
| color-mix(in srgb, var(--glow-color) var(--glow-intensity), transparent) 0%, | |
| transparent 55% | |
| ); | |
| pointer-events: none; | |
| opacity: 0; | |
| transition: opacity 480ms ease; | |
| z-index: 0; | |
| } | |
| .has-cursor-glow:hover::before { opacity: 1; } | |
| /* Content under .has-cursor-glow usually needs to sit above the glow */ | |
| .has-cursor-glow > * { position: relative; z-index: 1; } | |
| /* .ambient-tint — a slowly morphing colour wash for a panel background. | |
| Host sets --ambient-tint inline from JS (e.g. active concept colour); the | |
| background interpolates between values because --ambient-tint is @property-typed. */ | |
| .ambient-tint { | |
| position: relative; | |
| --ambient-tint: var(--accent); | |
| background: | |
| radial-gradient(ellipse 70% 55% at 50% 38%, color-mix(in srgb, var(--ambient-tint) 12%, transparent) 0%, transparent 72%), | |
| radial-gradient(ellipse 120% 95% at 50% 55%, color-mix(in srgb, var(--ambient-tint) 4%, transparent) 0%, transparent 80%), | |
| var(--bg); | |
| transition: --ambient-tint 700ms ease; | |
| } | |
| /* .hover-lift — universal button/card lift on hover. | |
| Cheap, composable, and reads as responsive. Prefer over bespoke hover states. */ | |
| .hover-lift { | |
| transition: transform var(--dur-fast) var(--ease-spring-snap), | |
| box-shadow var(--dur-fast) var(--ease-spring-snap), | |
| border-color var(--dur-fast) ease; | |
| } | |
| .hover-lift:hover { | |
| transform: translateY(-1px); | |
| box-shadow: 0 6px 20px rgba(0,0,16,0.55), 0 0 0 1px var(--accent-border); | |
| } | |
| .hover-lift:active { transform: translateY(0); } | |
| /* .view-switch-in — plays on mount. Apply via a keyed container so React | |
| remounts it when the view changes, replaying the animation. Unifies the | |
| feel of Chat↔Diagram, Canvas↔Story, and Explore/Architecture/Class tab | |
| switches so all view transitions share one motion language. */ | |
| .view-switch-in { | |
| /* backwards fill-mode holds the hidden "from" state BEFORE the animation | |
| starts (prevents flash on mount) but releases the transform/filter AFTER | |
| it ends. That matters because a persistent filter/transform creates a | |
| containing block, which would trap position:fixed descendants (like | |
| .diagram-fullscreen) inside this wrapper instead of the viewport. | |
| Similarly, will-change: transform|filter also creates a containing block, | |
| so we omit it — modern browsers auto-promote short animations anyway. */ | |
| animation: view-switch-in var(--dur-slow) var(--ease-spring) backwards; | |
| } | |
| /* Host for top-level view transitions inside .main — passes height through so | |
| DiagramView's height:100% still resolves. */ | |
| .app-view-host { | |
| flex: 1; | |
| min-height: 0; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| @keyframes view-switch-in { | |
| from { opacity: 0; transform: translateY(10px); filter: blur(6px); } | |
| to { opacity: 1; transform: translateY(0); filter: blur(0); } | |
| } | |
| /* .constellation-bg — a dotted lattice that drifts subtly with the cursor. | |
| A thematic backdrop for the Cartographer landing hero: tiny "coordinate" | |
| points evoke the act of mapping. Host element must also set --mx/--my via | |
| onMouseMove (same contract as .has-cursor-glow — reuse the same handler). */ | |
| .constellation-bg { | |
| position: relative; | |
| isolation: isolate; | |
| /* --mx / --my are intentionally NOT declared here so they inherit from a | |
| has-cursor-glow ancestor. Declaring them locally would reset parallax | |
| to centred every time. The calc() fallback covers the no-ancestor case. */ | |
| } | |
| .constellation-bg::before { | |
| content: ""; | |
| position: absolute; | |
| inset: -40px; /* bleed past edges so parallax never reveals an edge */ | |
| background-image: | |
| radial-gradient(rgba(180, 200, 255, 0.18) 1px, transparent 1.2px), | |
| radial-gradient(rgba(180, 200, 255, 0.08) 1px, transparent 1.2px); | |
| background-size: 44px 44px, 88px 88px; | |
| background-position: 0 0, 22px 22px; | |
| mask-image: radial-gradient(ellipse at 50% 45%, black 0%, black 30%, transparent 75%); | |
| -webkit-mask-image: radial-gradient(ellipse at 50% 45%, black 0%, black 30%, transparent 75%); | |
| pointer-events: none; | |
| z-index: 0; | |
| /* Cursor parallax — map 0..100% onto a ±12px offset. calc keeps it smooth | |
| with no JS animation loop; the paint pipeline handles interpolation. */ | |
| transform: translate3d( | |
| calc((var(--mx, 50%) - 50%) * -0.12), | |
| calc((var(--my, 50%) - 50%) * -0.12), | |
| 0 | |
| ); | |
| transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1); | |
| opacity: 0.9; | |
| } | |
| .constellation-bg > * { position: relative; z-index: 1; } | |
| /* .custom-cursor — companion dot that rides alongside the OS cursor. | |
| Position is driven by --x / --y set on the element itself (no React | |
| state), so movement is paint-only. Visibility and "active" state | |
| (near an interactive element) flip via data attributes. */ | |
| .custom-cursor { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| pointer-events: none; | |
| z-index: 9999; | |
| /* centre the dot on the pointer */ | |
| transform: translate3d(calc(var(--x, -100px) - 50%), calc(var(--y, -100px) - 50%), 0); | |
| background: var(--accent-soft); | |
| box-shadow: 0 0 12px rgba(91, 143, 249, 0.55); | |
| opacity: 0; | |
| transition: | |
| opacity var(--dur-fast) ease, | |
| width var(--dur-fast) var(--ease-spring-snap), | |
| height var(--dur-fast) var(--ease-spring-snap), | |
| background var(--dur-fast) ease, | |
| box-shadow var(--dur-fast) ease; | |
| /* Hint the compositor — this element repaints on every mousemove. */ | |
| will-change: transform; | |
| } | |
| .custom-cursor[data-visible="1"] { opacity: 0.85; } | |
| /* Over interactive targets: grow into a hollow ring. Communicates | |
| "this is clickable" without needing a hand-cursor. */ | |
| .custom-cursor[data-active="1"] { | |
| width: 28px; | |
| height: 28px; | |
| background: transparent; | |
| box-shadow: | |
| 0 0 0 1.5px var(--accent-soft), | |
| 0 0 18px rgba(91, 143, 249, 0.45); | |
| } | |
| /* Over text-entry surfaces: hide entirely. The native I-beam does the | |
| pointing job; a companion dot over a placeholder only adds noise. */ | |
| .custom-cursor[data-over-input="1"] { opacity: 0 ; } | |
| /* Respect the user. Disable motion where requested. */ | |
| @media (prefers-reduced-motion: reduce) { | |
| *, *::before, *::after { | |
| animation-duration: 0.01ms ; | |
| animation-iteration-count: 1 ; | |
| transition-duration: 0.01ms ; | |
| scroll-behavior: auto ; | |
| } | |
| } | |
| /* Global focus-visible system */ | |
| *:focus-visible { | |
| outline: 2px solid var(--accent-light); | |
| outline-offset: 2px; | |
| } | |
| input:focus-visible, | |
| textarea:focus-visible { | |
| outline: none; | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px var(--accent-dim), 0 0 0 1px var(--accent-border); | |
| } | |
| /* Reduced motion */ | |
| @media (prefers-reduced-motion: reduce) { | |
| *, *::before, *::after { | |
| animation-duration: 0.01ms ; | |
| transition-duration: 0.01ms ; | |
| } | |
| } | |
| #root { height: 100%; display: flex; flex-direction: column; } | |
| /* ── Layout ──────────────────────────────────────────────────────────────────── */ | |
| .layout { | |
| display: grid; | |
| grid-template-columns: 280px 1fr; | |
| flex: 1; | |
| min-height: 0; | |
| transition: grid-template-columns 200ms cubic-bezier(0.16, 1, 0.3, 1); | |
| position: relative; | |
| z-index: 1; /* sit above body::before dot grid */ | |
| } | |
| /* When sidebar is collapsed, shrink the column to icon-strip width */ | |
| .layout.layout-collapsed { | |
| grid-template-columns: 52px 1fr; | |
| } | |
| /* Fullscreen diagram mode — everything but the focused canvas gets out of the | |
| way. The sidebar column collapses to zero and the chat-header hides, so the | |
| fullscreen overlay has a clean stage to land on (and reflow on exit is | |
| minimal). Kept at the layout level rather than z-index to avoid fighting | |
| stacking contexts. */ | |
| .layout.layout-fullscreen { | |
| grid-template-columns: 0 1fr; | |
| } | |
| .layout.layout-fullscreen .sidebar, | |
| .layout.layout-fullscreen .chat-header { | |
| display: none; | |
| } | |
| /* Landing mode — whole viewport belongs to the hero. We keep the sidebar | |
| column alive (so brand + repo shortcuts remain accessible) but let the | |
| main pane bleed visually. The default collapsed strip is enough chrome | |
| for a first-time visitor. */ | |
| .layout.layout-landing .main { | |
| /* Remove the bottom padding so LandingHero's own spacing dominates. | |
| Also disable the inherited dot grid on body::before to avoid two | |
| textures competing with the hero stage. */ | |
| background: transparent; | |
| } | |
| /* Landing has no repo yet, so the header pill reads as chrome and steals | |
| vertical space (forcing the hero to scroll). Hide header bars entirely | |
| on landing; they reappear the moment any repo is selected. */ | |
| .layout.layout-landing .chat-header, | |
| .layout.layout-landing .messages-header, | |
| .layout.layout-landing .main-header { display: none; } | |
| /* ══════════════════════════════════════════════════════════ | |
| SIDEBAR | |
| ══════════════════════════════════════════════════════════ */ | |
| .sidebar { | |
| background: | |
| /* Top-left bloom — blueprint brand light source */ | |
| radial-gradient(ellipse 180% 45% at 0% 0%, rgba(91, 143, 249, 0.12) 0%, transparent 55%), | |
| /* Bottom-right counter-bloom — depth through color variance */ | |
| radial-gradient(ellipse 120% 30% at 100% 100%, rgba(129, 140, 248, 0.06) 0%, transparent 60%), | |
| #060610; | |
| /* No flat 1px border — the seam is rendered as an absolute pseudo-element | |
| so it can carry a vertical gradient (transparent → faint accent → transparent), | |
| reading as a precision-instrument spine instead of a plain divider. The | |
| ambient-right spill via box-shadow stays for the soft glow into the main pane. */ | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| position: relative; | |
| box-shadow: | |
| inset -1px 0 0 rgba(91, 143, 249, 0.05), | |
| 4px 0 32px rgba(0, 0, 0, 0.60), | |
| 8px 0 64px rgba(0, 0, 0, 0.25); | |
| } | |
| /* Sidebar right-edge spine — replaces a flat 1px border. Vertical gradient | |
| from transparent at the top, faint accent in the middle (where the eye | |
| spends most time), back to transparent at the bottom. The kind of seam | |
| detail you find on premium IDE / instrument UIs (Linear, Raycast). Pseudo | |
| element so it doesn't disturb the layout. */ | |
| .sidebar::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| right: 0; | |
| bottom: 0; | |
| width: 1px; | |
| pointer-events: none; | |
| background: linear-gradient( | |
| 180deg, | |
| transparent 0%, | |
| rgba(91, 143, 249, 0.08) 22%, | |
| rgba(91, 143, 249, 0.18) 50%, | |
| rgba(91, 143, 249, 0.08) 78%, | |
| transparent 100% | |
| ); | |
| } | |
| /* Scrollable content area — everything above the MCP panel */ | |
| .sidebar-scroll { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 12px 14px 14px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0; | |
| min-height: 0; | |
| -webkit-mask-image: linear-gradient(to bottom, black calc(100% - 32px), transparent 100%); | |
| mask-image: linear-gradient(to bottom, black calc(100% - 32px), transparent 100%); | |
| } | |
| /* ── Brand header — gradient bottom edge for premium separation ─────────── */ | |
| .sidebar-brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| padding: 4px 0 14px; | |
| /* Gradient bottom border: accent color → transparent, feels like a light source */ | |
| border-bottom: none; | |
| background: linear-gradient(90deg, rgba(91,143,249,0.30) 0%, rgba(91,143,249,0.12) 40%, transparent 80%) | |
| bottom / 100% 1px no-repeat; | |
| margin-bottom: 0; | |
| } | |
| /* Keep icon styles for the collapsed rail (sidebar-collapsed-brand) */ | |
| .sidebar-brand-icon { | |
| width: 36px; | |
| height: 36px; | |
| /* Dark bg + blue border — matches favicon exactly */ | |
| background: #09090E; | |
| border-radius: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| box-shadow: | |
| 0 0 0 1px rgba(91, 143, 249, 0.22), | |
| 0 2px 8px rgba(91, 143, 249, 0.10); | |
| flex-shrink: 0; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| /* sidebar-brand-icon::after removed — was causing white glare on compass edges */ | |
| /* Superscript label — "GITHUB RAG" small caps above the serif title */ | |
| .sidebar-brand-tag { | |
| font-size: 11px; | |
| font-weight: 500; | |
| color: var(--muted); | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| line-height: 1.2; | |
| display: block; | |
| } | |
| /* Brand name — gradient text for premium feel */ | |
| .sidebar-brand-name { | |
| font-size: 19px; | |
| font-weight: 400; | |
| font-family: var(--serif); | |
| background: linear-gradient(135deg, #FFFFFF 0%, #C8DAFF 60%, #7DABFF 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| color: #FFFFFF; | |
| letter-spacing: -0.02em; | |
| line-height: 1.2; | |
| padding-bottom: 0.08em; /* gradient clip mask cuts descenders — padding restores them */ | |
| } | |
| /* ── Sidebar sections — gradient dividers instead of harsh solid lines ─────── */ | |
| .sidebar-section { | |
| display: flex; | |
| flex-direction: column; | |
| padding-top: 16px; | |
| padding-bottom: 10px; | |
| /* Gradient divider: fades from accent color → transparent → transparent. | |
| More premium than a solid 1px border. */ | |
| border-top: none; | |
| background: | |
| linear-gradient(90deg, rgba(91, 143, 249, 0.20) 0%, rgba(91, 143, 249, 0.08) 40%, transparent 100%) | |
| top / 100% 1px no-repeat; | |
| } | |
| /* ── Section labels — Raycast-style with trailing gradient rule ───────────── */ | |
| .section-label { | |
| font-size: 10px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| color: rgba(200, 210, 240, 0.45); | |
| margin-bottom: 8px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| /* Gradient rule line dissolving to the right — premium structural separator */ | |
| .section-label::after { | |
| content: ''; | |
| flex: 1; | |
| height: 1px; | |
| background: linear-gradient(90deg, rgba(91, 143, 249, 0.25) 0%, transparent 100%); | |
| } | |
| /* ── Ingest card — frameless, the form IS the UI. No box needed. ──────────── */ | |
| .ingest-card { | |
| position: relative; | |
| } | |
| /* ── Ingest form — search-bar style: input + button in one row ──────────── */ | |
| .ingest-form { | |
| position: relative; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0; | |
| } | |
| .ingest-form input { | |
| /* Premium URL input: monospace, dark, minimal border — the developer aesthetic */ | |
| background: rgba(255, 255, 255, 0.045); | |
| border: 1px solid rgba(255, 255, 255, 0.10); | |
| border-radius: 11px; | |
| color: var(--text); | |
| font-size: 12px; | |
| font-family: var(--mono); | |
| /* Right padding clears the icon button (36px) + gap */ | |
| padding: 11px 48px 11px 13px; | |
| outline: none; | |
| transition: border-color 180ms ease, box-shadow 180ms ease; | |
| width: 100%; | |
| letter-spacing: -0.01em; | |
| } | |
| .ingest-form input:focus { | |
| border-color: rgba(91, 143, 249, 0.50); | |
| box-shadow: 0 0 0 3px rgba(91, 143, 249, 0.10); | |
| background: rgba(255, 255, 255, 0.06); | |
| } | |
| .ingest-form input::placeholder { | |
| color: rgba(200, 210, 240, 0.28); | |
| font-family: var(--mono); | |
| } | |
| /* Submit button — icon-only square button inside the input ─────────────── */ | |
| .ingest-form .btn { | |
| position: absolute ; | |
| right: 5px; | |
| top: 5px; | |
| bottom: 5px; | |
| height: auto; | |
| width: 36px; | |
| padding: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 7px; | |
| color: #ffffff ; | |
| /* Solid enough to read white text clearly, with top-edge shimmer */ | |
| background: linear-gradient(160deg, #4A7FE8 0%, #5B8FF9 100%); | |
| border: 1px solid rgba(91, 143, 249, 0.55); | |
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.20), 0 2px 8px rgba(91, 143, 249, 0.25); | |
| transform: none; | |
| transition: background 150ms ease, box-shadow 150ms ease; | |
| } | |
| .ingest-form .btn:hover { | |
| background: linear-gradient(160deg, #5A8FF8 0%, #6B9FFF 100%); | |
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 4px 16px rgba(91, 143, 249, 0.40); | |
| transform: none; | |
| opacity: 1; | |
| color: #ffffff ; | |
| } | |
| .ingest-form .btn:disabled { | |
| background: rgba(255, 255, 255, 0.08); | |
| border-color: rgba(255, 255, 255, 0.07); | |
| color: rgba(255, 255, 255, 0.25) ; | |
| box-shadow: none; | |
| opacity: 1; | |
| } | |
| /* ── Buttons ──────────────────────────────────────────────── */ | |
| .btn { | |
| /* Blueprint blue gradient — solid, confident, pops on dark background */ | |
| background: linear-gradient(160deg, #3A72E8 0%, #5B8FF9 55%, #7DABFF 100%); | |
| border: 1px solid rgba(91, 143, 249, 0.50); | |
| border-radius: var(--radius); | |
| color: #fff; | |
| cursor: pointer; | |
| font-family: var(--sans); | |
| font-size: 13px; | |
| font-weight: 600; | |
| padding: 9px 14px; | |
| transition: opacity var(--transition), transform var(--transition), box-shadow var(--transition); | |
| box-shadow: 0 2px 20px rgba(91, 143, 249, 0.50), inset 0 1px 0 rgba(255, 255, 255, 0.25); | |
| letter-spacing: -0.01em; | |
| } | |
| .btn:hover { | |
| opacity: 0.95; | |
| box-shadow: | |
| 0 4px 24px rgba(91, 143, 249, 0.55), | |
| 0 0 12px rgba(91, 143, 249, 0.30), | |
| inset 0 1px 0 rgba(200, 220, 255, 0.25); | |
| transform: translateY(-1px); | |
| } | |
| .btn:active { transform: scale(0.97) translateY(0); box-shadow: none; } | |
| .btn:disabled { opacity: 0.35; cursor: not-allowed; transform: none; } | |
| /* Stop button — shown instead of send while streaming; use warm red token */ | |
| .btn.btn-stop { | |
| background: var(--red-dim); | |
| border-color: rgba(248,113,113,0.40); | |
| box-shadow: none; | |
| color: var(--red); | |
| } | |
| .btn.btn-stop:hover { | |
| background: rgba(248,113,113,0.15); | |
| box-shadow: 0 2px 12px rgba(248,113,113,0.22); | |
| opacity: 1; | |
| } | |
| .btn.secondary { | |
| background: var(--surface-3); | |
| border: 1px solid var(--border-strong); | |
| color: var(--text-2); | |
| box-shadow: none; | |
| font-weight: 500; | |
| } | |
| .btn.secondary:hover { | |
| background: var(--surface-4); | |
| border-color: var(--accent-border); | |
| color: var(--text); | |
| box-shadow: none; | |
| transform: none; | |
| } | |
| /* ── Repo list ────────────────────────────────────────────── */ | |
| .repo-list { display: flex; flex-direction: column; gap: 2px; } | |
| .repo-item { | |
| /* Positioning context — actions overlay the right edge when hovered */ | |
| position: relative; | |
| display: flex; | |
| align-items: center; | |
| padding: 8px 12px; | |
| border-radius: var(--radius); | |
| cursor: pointer; | |
| /* 2px left accent — cleaner, less dominant */ | |
| border-left: 2px solid transparent; | |
| /* Spring easing — feels physical, not mechanical */ | |
| transition: background 150ms cubic-bezier(0.34,1.56,0.64,1), | |
| border-left-color 150ms ease, | |
| transform 150ms cubic-bezier(0.34,1.56,0.64,1), | |
| box-shadow 150ms ease; | |
| gap: 8px; | |
| } | |
| /* Single-row layout: slug on the left, meta (chunk count + staleness) on the right */ | |
| .repo-item-main { | |
| flex: 1; | |
| min-width: 0; | |
| display: flex; | |
| flex-direction: row; | |
| align-items: center; | |
| gap: 7px; | |
| } | |
| .repo-item-meta { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| flex-shrink: 0; | |
| margin-left: auto; | |
| transition: opacity 140ms ease; | |
| } | |
| .repo-item:hover .repo-item-meta { opacity: 0; } | |
| /* Actions overlay the meta area on hover — meta fades out, actions fade in. | |
| Absolute positioning means the slug gets the FULL row width when not hovered, | |
| killing the ellipsis that was eating "karpathy/micrograd" → "karpathy/mi…". */ | |
| .repo-item-actions { | |
| position: absolute; | |
| right: 8px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| display: flex; | |
| align-items: center; | |
| gap: 2px; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 140ms ease; | |
| } | |
| .repo-item:hover .repo-item-actions { | |
| opacity: 1; | |
| pointer-events: auto; | |
| } | |
| .repo-item:hover { | |
| background: rgba(91, 143, 249, 0.06); | |
| border-left-color: rgba(91, 143, 249, 0.55); | |
| /* Spring XY lift: right + up simultaneously */ | |
| transform: translate(2px, -1px); | |
| box-shadow: 0 4px 16px rgba(0,0,0,0.25); | |
| } | |
| .repo-item.active { | |
| background: var(--accent-dim); | |
| border-left-color: var(--accent); | |
| /* Inset ambient glow reinforces the selected state without being loud */ | |
| box-shadow: inset 2px 0 14px rgba(91, 143, 249, 0.07); | |
| } | |
| /* Active repo: text pops to full brightness */ | |
| .repo-item.active .repo-slug { color: var(--text); font-weight: 600; } | |
| .repo-slug { | |
| font-size: 13px; | |
| font-weight: 500; | |
| color: var(--text-2); | |
| flex: 1; | |
| min-width: 0; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| letter-spacing: -0.01em; | |
| } | |
| /* owner/ prefix — dimmed so the eye jumps straight to the repo name */ | |
| .repo-owner { | |
| color: var(--muted); | |
| font-weight: 400; | |
| } | |
| .repo-count { | |
| font-size: 10px; | |
| color: rgba(200, 210, 240, 0.60); | |
| font-family: var(--mono); | |
| font-weight: 500; | |
| flex-shrink: 0; | |
| letter-spacing: 0.02em; | |
| /* Tabular numerals: every digit takes the same advance width, so when | |
| counts of different magnitudes (27, 681, 13561) stack in a list they | |
| align cleanly on the right edge. Reads instrument-like, not webpage-like. | |
| Single declaration, but it's the kind of detail that separates a | |
| dashboard from a control panel. */ | |
| font-variant-numeric: tabular-nums; | |
| /* Soft pill on a sheer backdrop — feels premium, not "dev-tool" */ | |
| background: rgba(255,255,255,0.04); | |
| border: 1px solid rgba(255,255,255,0.06); | |
| border-radius: 999px; | |
| padding: 1px 7px; | |
| min-width: 24px; | |
| text-align: center; | |
| cursor: default; | |
| } | |
| .repo-item.active .repo-count { | |
| background: rgba(91, 143, 249, 0.12); | |
| border-color: rgba(91, 143, 249, 0.28); | |
| color: rgba(200, 220, 255, 0.85); | |
| } | |
| .repo-delete { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| color: var(--muted); | |
| font-size: 14px; | |
| width: 26px; | |
| height: 26px; | |
| padding: 0; | |
| border-radius: var(--radius-sm); | |
| flex-shrink: 0; | |
| transition: color var(--transition), background var(--transition); | |
| } | |
| .repo-delete:hover { color: var(--red); background: var(--red-dim); } | |
| /* Confirm-delete inline buttons (Delete / Cancel) */ | |
| .repo-confirm-yes, | |
| .repo-confirm-no { | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| font-size: 11px; | |
| font-weight: 600; | |
| padding: 2px 7px; | |
| border-radius: var(--radius-sm); | |
| flex-shrink: 0; | |
| transition: color var(--transition), background var(--transition); | |
| white-space: nowrap; | |
| } | |
| .repo-confirm-yes { color: var(--red); } | |
| .repo-confirm-yes:hover { background: var(--red-dim); } | |
| .repo-confirm-no { color: var(--muted); } | |
| .repo-confirm-no:hover { color: var(--text); background: var(--surface-3); } | |
| /* ── Mode pills — Raycast-style dark track container with springy active state ─ */ | |
| .mode-pills { | |
| display: inline-flex; | |
| gap: 2px; | |
| background: rgba(255,255,255,0.04); | |
| border: 1px solid rgba(255,255,255,0.08); | |
| border-radius: var(--radius-md); | |
| padding: 3px; | |
| width: fit-content; | |
| box-shadow: inset 0 1px 3px rgba(0,0,0,0.30); | |
| } | |
| .pill { | |
| background: none; | |
| border: none; | |
| border-radius: calc(var(--radius-md) - 3px); | |
| color: var(--text-2); | |
| cursor: pointer; | |
| font-family: var(--sans); | |
| font-size: 11.5px; | |
| font-weight: 500; | |
| padding: 4px 11px; | |
| transition: background 150ms ease, color 150ms ease; | |
| letter-spacing: -0.01em; | |
| white-space: nowrap; | |
| } | |
| .pill:hover { | |
| background: rgba(255, 255, 255, 0.08); | |
| color: rgba(255, 255, 255, 0.80); | |
| } | |
| .pill.active { | |
| background: rgba(255,255,255,0.10); | |
| border: none; | |
| color: var(--text); | |
| font-weight: 600; | |
| /* Three-layer depth: stronger top inner highlight + subtle drop shadow | |
| pulls the active pill ABOVE the wrapper's inset (which makes inactive | |
| pills read as "pressed-in"), and a 1px hairline outline gives the | |
| raised edge definition. iOS/macOS segmented-control trick — the | |
| active segment looks physically raised, not just a different colour. */ | |
| box-shadow: | |
| inset 0 1px 0 rgba(255,255,255,0.18), | |
| 0 1px 2px rgba(0,0,0,0.35), | |
| 0 0 0 1px rgba(255,255,255,0.06); | |
| } | |
| /* Agent pill — distinct accent treatment so the mode reads clearly at a | |
| glance, not like a twin of the RAG pill. The sparkle mark is the same | |
| motif as the header agent-badge and the session-mode-badge. */ | |
| .pill--agent { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| .pill--agent .pill-mark { | |
| color: var(--accent); | |
| font-size: 11px; | |
| line-height: 1; | |
| opacity: 0.6; | |
| transition: opacity var(--transition), transform var(--transition); | |
| } | |
| .pill--agent:hover .pill-mark { opacity: 0.9; } | |
| .pill--agent.active { | |
| background: var(--accent-dim); | |
| color: var(--accent-light); | |
| /* Same three-layer depth as the default .pill.active, but the hairline | |
| edge is the accent border so the agent pill carries its accent | |
| vocabulary into its raised state. */ | |
| box-shadow: | |
| inset 0 1px 0 rgba(200, 220, 255, 0.18), | |
| 0 1px 2px rgba(0,0,0,0.40), | |
| 0 0 0 1px var(--accent-border); | |
| } | |
| .pill--agent.active .pill-mark { | |
| color: var(--accent); | |
| opacity: 1; | |
| transform: scale(1.1); | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| MAIN CHAT AREA | |
| ══════════════════════════════════════════════════════════ */ | |
| .main { | |
| display: flex; | |
| flex-direction: column; | |
| height: 100%; | |
| min-height: 0; | |
| overflow: hidden; | |
| /* Subtle ambient light from top — "the conversation space" */ | |
| background: | |
| radial-gradient(ellipse 60% 30% at 50% -5%, rgba(91, 143, 249, 0.10) 0%, transparent 50%), | |
| var(--bg); | |
| } | |
| /* ── Universal ambient layer on .main ───────────────────────── | |
| The same cursor-glow + constellation primitives used on landing are applied | |
| to .main so every view shares one ambient voice. We tune them softer here | |
| than their per-card default because they cover the whole viewport — what | |
| reads right on a 400px card would be oppressive at 1200px wide. Children | |
| can still set their own card-level cursor-glow for tighter accent. | |
| ------------------------------------------------------------- */ | |
| .main.has-cursor-glow { | |
| --glow-size: 820px; | |
| --glow-intensity: 7%; | |
| --glow-color: var(--accent); | |
| } | |
| /* The constellation-bg and has-cursor-glow primitives both paint via ::before. | |
| When both classes are applied to the SAME element, the constellation's | |
| background-image wins and the glow disappears. On .main we want BOTH — so | |
| ::before keeps the constellation dots, and ::after hosts the cursor glow. */ | |
| .main.constellation-bg::before { | |
| /* Dot lattice — bright enough to read as texture on every view, soft enough | |
| not to compete with content. Mask extends past the viewport so the feathered | |
| edge isn't visible as a ring. */ | |
| opacity: 0.85; | |
| background-image: | |
| radial-gradient(rgba(180, 200, 255, 0.18) 1px, transparent 1.2px), | |
| radial-gradient(rgba(180, 200, 255, 0.08) 1px, transparent 1.2px); | |
| background-size: 48px 48px, 96px 96px; | |
| mask-image: radial-gradient(ellipse at 50% 40%, black 0%, black 55%, transparent 95%); | |
| -webkit-mask-image: radial-gradient(ellipse at 50% 40%, black 0%, black 55%, transparent 95%); | |
| } | |
| .main.has-cursor-glow::after { | |
| /* Cursor-follow ambient glow — same contract as .has-cursor-glow::before | |
| but lives on ::after so it can coexist with the constellation on ::before. | |
| Always visible (not hover-gated) so every view reads as lit. */ | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| background: radial-gradient( | |
| var(--glow-size) circle at var(--mx) var(--my), | |
| color-mix(in srgb, var(--glow-color) var(--glow-intensity), transparent) 0%, | |
| transparent 55% | |
| ); | |
| pointer-events: none; | |
| opacity: 1; | |
| z-index: 0; | |
| } | |
| /* Landing is the entry moment — push the ambient to full strength here so | |
| the hero sits in a lit room, not a dim one. The stage's own demo-accent | |
| glow is localised to .lh-stage and doesn't clash with the room light. */ | |
| .layout.layout-landing .main.has-cursor-glow { | |
| --glow-size: 980px; | |
| --glow-intensity: 11%; | |
| } | |
| .layout.layout-landing .main.has-cursor-glow::after { | |
| opacity: 1; | |
| } | |
| .layout.layout-landing .main.constellation-bg::before { | |
| opacity: 1; /* keep the dot grid — it IS the landing's texture */ | |
| } | |
| /* ── Chat header ──────────────────────────────────────────── */ | |
| .chat-header { | |
| padding: 0 24px; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.07); | |
| /* 3-column grid: equal 1fr flanks + auto centre. | |
| The centre column only takes as much width as its content, and the two | |
| equal 1fr sides consume the rest — so the toggle is always pixel-perfect | |
| centred regardless of what's on either flank. */ | |
| display: grid; | |
| grid-template-columns: 1fr auto 1fr; | |
| align-items: center; | |
| gap: 0; | |
| /* Frosted glass header */ | |
| background: rgba(9, 9, 14, 0.85); | |
| backdrop-filter: blur(16px) saturate(160%); | |
| -webkit-backdrop-filter: blur(16px) saturate(160%); | |
| min-height: 52px; | |
| position: sticky; | |
| top: 0; | |
| z-index: 40; | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.06), | |
| 0 1px 0 rgba(0, 0, 0, 0.4), | |
| 0 4px 24px rgba(0, 0, 0, 0.30); | |
| } | |
| .header-left { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .header-center { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| /* Bottom glow line on header */ | |
| .chat-header::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: -1px; | |
| left: 0; | |
| right: 0; | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, rgba(91, 143, 249,0.20), transparent); | |
| } | |
| .chat-header .repo-badge { | |
| background: rgba(255,255,255,0.05); | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--radius); | |
| color: var(--accent-soft); | |
| font-size: 12px; | |
| font-weight: 600; | |
| padding: 4px 10px; | |
| letter-spacing: -0.01em; | |
| /* Glow + top-edge inner highlight for a lit badge feel */ | |
| box-shadow: | |
| 0 2px 10px rgba(91, 143, 249,0.18), | |
| inset 0 1px 0 rgba(200, 220, 255,0.15); | |
| } | |
| /* Agent mode indicator — persistent header badge so "you are in agent mode" | |
| is legible from any view. The violet accent-dim fill + accent border make | |
| it visually distinct from the neutral repo-badge beside it. | |
| `data-thinking` flips the label to "Agent thinking…" during streaming and | |
| adds a subtle pulse animation on the sparkle so the badge feels alive. */ | |
| .agent-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| background: var(--accent-dim); | |
| border: 1px solid var(--accent-border); | |
| border-radius: var(--radius); | |
| color: var(--accent-soft); | |
| font-family: var(--sans); | |
| font-size: 12px; | |
| font-weight: 600; | |
| padding: 4px 10px; | |
| letter-spacing: -0.01em; | |
| transition: background var(--transition), color var(--transition), box-shadow var(--transition); | |
| } | |
| .agent-badge-mark { | |
| color: var(--accent); | |
| font-size: 11px; | |
| line-height: 1; | |
| } | |
| .agent-badge[data-thinking] { | |
| /* Active-investigation state — brighter text, softer accent glow. */ | |
| color: var(--accent-light); | |
| background: color-mix(in srgb, var(--accent) 14%, transparent); | |
| box-shadow: 0 0 0 1px var(--accent-border), 0 4px 18px rgba(91, 143, 249, 0.22); | |
| } | |
| .agent-badge[data-thinking] .agent-badge-mark { | |
| animation: agent-pulse 1.4s cubic-bezier(0.22, 1, 0.36, 1) infinite; | |
| } | |
| @keyframes agent-pulse { | |
| 0%, 100% { opacity: 0.55; transform: scale(1); } | |
| 50% { opacity: 1; transform: scale(1.14); } | |
| } | |
| .chat-header .clear-btn { | |
| margin-left: auto; | |
| background: none; | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--radius); | |
| color: var(--muted); | |
| cursor: pointer; | |
| font-family: var(--sans); | |
| font-size: 12px; | |
| font-weight: 500; | |
| padding: 5px 12px; | |
| transition: color var(--transition), border-color var(--transition), background var(--transition); | |
| } | |
| .chat-header .clear-btn:hover { | |
| color: var(--text); | |
| border-color: var(--border-strong); | |
| background: rgba(255, 255, 255,0.05); | |
| } | |
| /* ── Messages ─────────────────────────────────────────────── */ | |
| .messages { | |
| flex: 1; | |
| min-height: 0; | |
| overflow-y: auto; | |
| padding: 24px 28px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 24px; | |
| /* Center column — all bubbles confined to 760px for consistent reading line */ | |
| align-items: center; | |
| } | |
| /* Inner wrapper keeps messages at max readable width */ | |
| .messages > * { | |
| width: 100%; | |
| max-width: 760px; | |
| } | |
| .message { | |
| display: flex; | |
| flex-direction: column; | |
| animation: message-in 0.45s cubic-bezier(0.16, 1, 0.3, 1) both; | |
| gap: 8px; | |
| } | |
| @keyframes message-in { | |
| from { opacity: 0; transform: translateY(14px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .message.user { align-self: flex-end; align-items: flex-end; max-width: 580px; } | |
| /* Assistant messages: row layout with avatar */ | |
| .message.assistant { | |
| flex-direction: row; | |
| align-items: flex-start; | |
| gap: 14px; | |
| align-self: flex-start; | |
| } | |
| .message.assistant .message-content { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| flex: 1; | |
| min-width: 0; | |
| } | |
| /* ── Mode tag (RAG / Agent label) ─────────────────────────── */ | |
| /* Compact label above each assistant response — makes chat history scannable. | |
| "✦ Agent · 4 steps" vs "◎ RAG · hybrid" at a glance. */ | |
| .msg-mode-tag { | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| font-size: 11px; | |
| color: var(--muted); | |
| user-select: none; | |
| } | |
| .msg-mode-icon { | |
| font-size: 10px; | |
| opacity: 0.7; | |
| } | |
| .msg-mode-label { | |
| font-weight: 600; | |
| letter-spacing: 0.03em; | |
| color: var(--text-2); | |
| text-transform: uppercase; | |
| font-size: 10px; | |
| } | |
| .msg-mode-detail { | |
| color: var(--muted); | |
| } | |
| .msg-mode-model { | |
| color: var(--faint); | |
| font-style: italic; | |
| } | |
| /* ── Avatar ───────────────────────────────────────────────── */ | |
| .message-avatar { | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| margin-top: 2px; | |
| } | |
| .message-avatar.assistant { | |
| /* Blueprint gradient avatar — matches brand icon */ | |
| background: linear-gradient(135deg, #3D6EE8, #5B8FF9); | |
| color: #fff; | |
| font-size: 13px; | |
| box-shadow: | |
| 0 0 0 2px rgba(91, 143, 249, 0.25), | |
| 0 4px 12px rgba(91, 143, 249, 0.30); | |
| } | |
| /* ── GFM table styles (remark-gfm renders <table> inside .bubble) ── */ | |
| .bubble table { | |
| border-collapse: collapse; | |
| width: 100%; | |
| font-size: 13px; | |
| } | |
| .bubble th, .bubble td { | |
| border: 1px solid var(--border); | |
| padding: 6px 10px; | |
| text-align: left; | |
| line-height: 1.5; | |
| white-space: nowrap; | |
| } | |
| .bubble th { | |
| background: rgba(255, 255, 255,0.06); | |
| font-weight: 600; | |
| color: var(--text); | |
| } | |
| .bubble tr:nth-child(even) td { | |
| background: rgba(255, 255, 255,0.03); | |
| } | |
| /* ── Message bubbles ──────────────────────────────────────── */ | |
| .bubble { | |
| padding: 15px 19px; | |
| border-radius: var(--radius-md); | |
| font-size: 14.5px; | |
| line-height: 1.72; | |
| word-break: break-word; | |
| letter-spacing: -0.012em; | |
| } | |
| .message.user .bubble { | |
| /* User bubble — blueprint blue gradient, confident and distinct */ | |
| background: linear-gradient(145deg, #1E3A7A 0%, #2B52B0 40%, #3D6EE8 100%); | |
| color: rgba(235, 243, 255, 0.97); | |
| border-bottom-right-radius: 4px; | |
| border: 1px solid rgba(91, 143, 249, 0.40); | |
| box-shadow: | |
| 0 4px 24px rgba(91, 143, 249, 0.30), | |
| inset 0 1px 0 rgba(255, 255, 255, 0.20); | |
| font-weight: 450; | |
| } | |
| .message.assistant .bubble { | |
| /* Glass assistant bubble — elevated dark surface */ | |
| background: rgba(15, 15, 24, 0.95); | |
| border: 1px solid rgba(255,255,255,0.09); | |
| border-bottom-left-radius: 4px; | |
| box-shadow: 0 2px 20px rgba(0,0,16,0.35), inset 0 1px 0 rgba(255,255,255,0.05); | |
| border-left: 2px solid rgba(91, 143, 249, 0.60); | |
| } | |
| /* Markdown inside assistant bubble */ | |
| .bubble p { margin-bottom: 10px; } | |
| .bubble p:last-child { margin-bottom: 0; } | |
| .bubble ul, .bubble ol { margin: 8px 0 8px 20px; } | |
| .bubble li { margin-bottom: 5px; line-height: 1.65; } | |
| .bubble h1, .bubble h2, .bubble h3 { | |
| font-weight: 700; | |
| letter-spacing: -0.03em; | |
| margin-bottom: 8px; | |
| margin-top: 16px; | |
| color: var(--text); | |
| } | |
| .bubble h1:first-child, .bubble h2:first-child, .bubble h3:first-child { margin-top: 0; } | |
| .bubble pre { | |
| /* Better code block — darker, more contrast */ | |
| background: #090A0F ; | |
| border: 1px solid var(--border) ; | |
| border-radius: var(--radius) ; | |
| margin: 12px 0 ; | |
| overflow-x: auto; | |
| /* Left accent on code blocks */ | |
| border-left: 2px solid var(--accent-border) ; | |
| } | |
| .bubble code { | |
| font-family: var(--mono); | |
| font-size: 13px; | |
| } | |
| /* Inline code — blueprint tint on dark well */ | |
| .bubble :not(pre) > code { | |
| background: rgba(91, 143, 249,0.12); | |
| border: 1px solid var(--accent-border); | |
| color: var(--accent-lavender); | |
| border-radius: var(--radius-sm); | |
| padding: 2px 7px; | |
| font-family: var(--mono); | |
| font-size: 12.5px; | |
| /* Subtle top-edge inner light so it reads as slightly raised */ | |
| box-shadow: inset 0 1px 0 rgba(200, 220, 255,0.10); | |
| } | |
| .bubble strong { | |
| font-weight: 700; | |
| color: var(--text); | |
| } | |
| /* ── Query type badge ─────────────────────────────────────── */ | |
| .query-type-badge { | |
| font-size: 11px; | |
| font-weight: 500; | |
| text-transform: none; | |
| letter-spacing: -0.01em; | |
| color: var(--accent-soft); | |
| padding: 3px 9px; | |
| border: 1px solid var(--accent-border); | |
| border-radius: 10px; | |
| background: var(--accent-dim); | |
| width: fit-content; | |
| font-family: var(--sans); | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| SOURCE CARDS (Glassmorphism treatment) | |
| ══════════════════════════════════════════════════════════ */ | |
| .sources { display: flex; flex-direction: column; gap: 6px; width: 100%; max-width: 800px; } | |
| .sources-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 10px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.07em; | |
| color: var(--muted); | |
| margin-bottom: 6px; | |
| padding-left: 2px; | |
| } | |
| .source-item { | |
| /* Warm charcoal source card */ | |
| background: rgba(20, 20, 32, 0.90); | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--radius-md); | |
| overflow: hidden; | |
| backdrop-filter: blur(8px); | |
| -webkit-backdrop-filter: blur(8px); | |
| transition: border-color var(--transition), box-shadow var(--transition), background var(--transition); | |
| } | |
| .source-item:hover { | |
| border-color: var(--accent-border); | |
| background: rgba(15, 18, 35, 0.98); | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.3), 0 0 0 1px rgba(91, 143, 249,0.08); | |
| } | |
| /* Accent left border on source cards */ | |
| .source-item::before { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| bottom: 0; | |
| width: 2px; | |
| background: linear-gradient(to bottom, var(--accent), transparent); | |
| opacity: 0; | |
| transition: opacity var(--transition); | |
| } | |
| .source-item { position: relative; } | |
| .source-item:hover::before, | |
| .source-item:focus-within::before { | |
| opacity: 1; | |
| } | |
| .source-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 9px; | |
| padding: 8px 13px; | |
| cursor: pointer; | |
| user-select: none; | |
| transition: background var(--transition); | |
| } | |
| .source-header:hover { background: rgba(255, 255, 255,0.04); } | |
| .source-num { | |
| font-size: 10px; | |
| font-weight: 700; | |
| color: var(--accent-soft); | |
| min-width: 18px; | |
| font-family: var(--mono); | |
| } | |
| .source-path { | |
| font-size: 12px; | |
| font-family: var(--mono); | |
| color: var(--text-2); | |
| flex: 1; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .source-name { | |
| font-size: 11px; | |
| color: var(--accent-soft); | |
| font-family: var(--mono); | |
| white-space: nowrap; | |
| font-weight: 600; | |
| } | |
| .source-lines { | |
| font-size: 11px; | |
| color: var(--muted); | |
| white-space: nowrap; | |
| font-family: var(--mono); | |
| } | |
| /* Parent-doc expansion badge — shown when a function was expanded to its class */ | |
| .source-lines-expanded { | |
| font-size: 10px; | |
| color: var(--accent); | |
| white-space: nowrap; | |
| font-family: var(--mono); | |
| border: 1px solid var(--accent-border); | |
| background: var(--accent-dim); | |
| border-radius: var(--radius-sm); | |
| padding: 1px 5px; | |
| cursor: help; | |
| } | |
| .source-score { | |
| font-size: 10px; | |
| color: var(--green); | |
| white-space: nowrap; | |
| font-family: var(--mono); | |
| font-weight: 600; | |
| /* Pill border matching the score color — set via inline style in SourceCard.jsx */ | |
| border: 1px solid rgba(52,211,153,0.35); | |
| background: rgba(52,211,153,0.10); | |
| padding: 1px 6px; | |
| border-radius: var(--radius-full); | |
| } | |
| .source-chevron { | |
| color: var(--muted); | |
| transition: transform var(--transition), color var(--transition); | |
| flex-shrink: 0; | |
| opacity: 0.8; | |
| } | |
| .source-chevron.open { | |
| transform: rotate(90deg); | |
| color: var(--accent-soft); | |
| opacity: 1; | |
| } | |
| .source-code { border-top: 1px solid var(--border-subtle); } | |
| .source-code pre { margin: 0 ; border-radius: 0 ; } | |
| /* Repo badge — shown on source cards when querying across all repos */ | |
| .source-repo-badge { | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| font-weight: 600; | |
| padding: 2px 7px; | |
| border-radius: var(--radius); | |
| color: var(--accent-soft); | |
| background: rgba(91, 143, 249,0.12); | |
| border: 1px solid rgba(91, 143, 249,0.25); | |
| flex-shrink: 0; | |
| white-space: nowrap; | |
| } | |
| /* Language badge */ | |
| .source-lang-badge { | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| font-weight: 600; | |
| padding: 2px 7px; | |
| border-radius: 10px; /* pill shape — more polished than square */ | |
| text-transform: lowercase; | |
| background: rgba(255, 255, 255,0.06); | |
| color: var(--muted); | |
| border: 1px solid var(--border); | |
| flex-shrink: 0; | |
| white-space: nowrap; | |
| } | |
| /* Language badge colors — all warm-palette, no cold blues/cyans */ | |
| .source-lang-badge[data-lang="python"] { color: #C9A96E; background: rgba(201,169,110,0.12); border-color: rgba(201,169,110,0.28); } | |
| .source-lang-badge[data-lang="javascript"] { color: #D4B84A; background: rgba(212,184,74,0.10); border-color: rgba(212,184,74,0.22); } | |
| .source-lang-badge[data-lang="typescript"] { color: #C8A0A8; background: rgba(200,160,168,0.10); border-color: rgba(200,160,168,0.24); } | |
| .source-lang-badge[data-lang="go"] { color: var(--accent-2); background: rgba(58,158,140,0.10); border-color: rgba(58,158,140,0.24); } | |
| .source-lang-badge[data-lang="rust"] { color: var(--accent-light); background: rgba(91, 143, 249,0.12); border-color: var(--accent-border); } | |
| .source-lang-badge[data-lang="java"] { color: #C9933A; background: rgba(201,147,58,0.12); border-color: rgba(201,147,58,0.28); } | |
| /* Copy button */ | |
| .source-copy-btn { | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| color: var(--muted); | |
| padding: 3px 6px; | |
| border-radius: var(--radius-sm); | |
| flex-shrink: 0; | |
| transition: color var(--transition), background var(--transition); | |
| opacity: 0; | |
| display: flex; | |
| align-items: center; | |
| } | |
| .source-header:hover .source-copy-btn { opacity: 1; } | |
| /* On touch devices (no hover) always show the copy button */ | |
| @media (hover: none) { .source-copy-btn { opacity: 1; } } | |
| .source-copy-btn:hover { | |
| color: var(--text); | |
| background: rgba(255, 255, 255,0.07); | |
| } | |
| /* GitHub link — always styled as a link so it's obviously clickable */ | |
| .source-github-link { | |
| color: var(--accent-soft); | |
| text-decoration: none; | |
| font-family: var(--mono); | |
| font-size: 12px; | |
| flex: 1; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .source-github-link:hover { color: var(--accent-light); text-decoration: underline; text-decoration-color: var(--accent-border); } | |
| /* "↗" open-on-GitHub button — always visible, positioned after the line numbers */ | |
| .source-open-btn { | |
| color: var(--muted); | |
| text-decoration: none; | |
| font-size: 12px; | |
| line-height: 1; | |
| padding: 2px 5px; | |
| border-radius: 4px; | |
| border: 1px solid transparent; | |
| transition: color var(--transition), border-color var(--transition), background var(--transition); | |
| flex-shrink: 0; | |
| margin-left: auto; /* push to the right edge */ | |
| } | |
| .source-open-btn:hover { | |
| color: var(--accent-soft); | |
| border-color: var(--accent-border); | |
| background: rgba(91, 143, 249,0.10); | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| INPUT BAR | |
| ══════════════════════════════════════════════════════════ */ | |
| .input-bar { | |
| padding: 10px 20px 14px; | |
| border-top: none; | |
| /* Very faint bg gradient to separate input zone from messages */ | |
| background: linear-gradient(to top, rgba(10,11,16,0.85) 0%, transparent 100%); | |
| display: flex; | |
| flex-direction: column; | |
| position: relative; | |
| flex-shrink: 0; | |
| } | |
| /* Elevated inner-glow input tray — LiteLLM framer card technique */ | |
| .input-row { | |
| position: relative; | |
| display: flex; | |
| align-items: flex-end; | |
| background: #13131E; | |
| border-radius: 16px; | |
| padding: 4px; | |
| box-shadow: | |
| rgba(91, 143, 249, 0.06) 0px 0px 40px 0px inset, | |
| rgba(0, 0, 16, 0.60) 0px 8px 32px 0px, | |
| rgba(255, 255, 255, 0.18) 0px 0px 0px 1px; | |
| transition: box-shadow 220ms ease; | |
| } | |
| .input-row:focus-within { | |
| box-shadow: | |
| rgba(91, 143, 249, 0.18) 0px 0px 50px 0px inset, | |
| rgba(91, 143, 249, 0.28) 0px 8px 40px 0px, | |
| rgba(91, 143, 249, 0.65) 0px 0px 0px 1.5px; | |
| } | |
| /* Top glow line on input bar */ | |
| .input-bar::before { | |
| content: ''; | |
| position: absolute; | |
| top: -1px; | |
| left: 0; | |
| right: 0; | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, rgba(91, 143, 249,0.40), transparent); | |
| } | |
| .input-bar textarea { | |
| flex: 1; | |
| background: transparent; | |
| border: none; | |
| border-radius: var(--radius-md); | |
| color: var(--text); | |
| font-family: var(--sans); | |
| font-size: 14px; | |
| line-height: 1.55; | |
| max-height: 180px; | |
| min-height: 52px; | |
| outline: none; | |
| padding: 13px 56px 13px 15px; | |
| resize: none; | |
| transition: border-color var(--transition), background var(--transition), box-shadow var(--transition); | |
| letter-spacing: -0.01em; | |
| width: 100%; | |
| } | |
| .input-bar textarea:focus { | |
| outline: none; | |
| background: transparent; | |
| box-shadow: none; | |
| } | |
| .input-bar textarea::placeholder { color: var(--muted); opacity: 0.6; } | |
| /* Send button embedded inside textarea — positioned absolutely bottom-right */ | |
| .input-row .btn { | |
| position: absolute; | |
| right: 10px; | |
| bottom: 10px; | |
| width: 34px; | |
| height: 34px; | |
| min-width: unset; | |
| padding: 0; | |
| border-radius: var(--radius); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| LANDING HERO — full-bleed first impression | |
| ══════════════════════════════════════════════════════════ | |
| The landing hero performs the product: a concept graph draws itself | |
| while the user reads. Composition: headline → live stage → repo tiles | |
| → URL input. Centred, generous whitespace, one focal colour per demo. */ | |
| .landing-root { | |
| flex: 1; | |
| min-height: 0; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: stretch; | |
| /* Center the hero vertically — on tall viewports it floats, on short | |
| ones it hugs the top. overflow: hidden enforces no-scroll (the whole | |
| pitch is designed to fit the first fold). */ | |
| justify-content: center; | |
| overflow: hidden; | |
| position: relative; | |
| /* Gentle vignette — keeps eyes centred on the stage without the heavy | |
| blue blobs that competed with the constellation backdrop before. */ | |
| background: radial-gradient(ellipse at 50% 35%, rgba(91, 143, 249, 0.10) 0%, transparent 55%); | |
| } | |
| /* ── Iris reveal — landing → tour transition ──────────────────────────── | |
| When the user picks a tile (or presses "Map it"), we capture the click | |
| point as --reveal-x/--reveal-y on <html>, add .is-revealing, and hold | |
| it for ~950ms. During that window: | |
| - A full-viewport scrim (html::before) expands from the click point | |
| as a darkening radial gradient, then fades out — the visual "zoom | |
| out of the landing, into the tour" beat. | |
| - The newly-mounted explore view enters with a clip-path iris grow | |
| from the same origin, so the product literally *opens* out of | |
| wherever the user pressed. | |
| Why coordinate the two layers: the scrim hides the abrupt unmount of | |
| the landing; the iris gives the new view a feeling of emergence | |
| instead of a hard swap. Together they read as one continuous gesture. */ | |
| html.is-revealing::before { | |
| content: ""; | |
| position: fixed; | |
| inset: 0; | |
| z-index: 90; | |
| pointer-events: none; | |
| background: radial-gradient( | |
| circle at var(--reveal-x, 50%) var(--reveal-y, 50%), | |
| rgba(12, 14, 26, 0) 0%, | |
| rgba(12, 14, 26, 0) 8%, | |
| rgba(110, 231, 183, 0.35) 14%, | |
| rgba(12, 14, 26, 0.9) 24%, | |
| rgba(12, 14, 26, 1) 60% | |
| ); | |
| animation: reveal-scrim 1200ms cubic-bezier(0.22, 1, 0.36, 1) both; | |
| } | |
| @keyframes reveal-scrim { | |
| 0% { opacity: 1; transform: scale(1); } | |
| 40% { opacity: 1; } | |
| 100% { opacity: 0; transform: scale(2.4); } | |
| } | |
| html.is-revealing .app-view-host { | |
| animation: reveal-iris 1200ms cubic-bezier(0.22, 1, 0.36, 1) both; | |
| } | |
| @keyframes reveal-iris { | |
| from { | |
| clip-path: circle(0 at var(--reveal-x, 50%) var(--reveal-y, 50%)); | |
| opacity: 0; | |
| } | |
| 25% { opacity: 1; } | |
| to { | |
| clip-path: circle(170% at var(--reveal-x, 50%) var(--reveal-y, 50%)); | |
| opacity: 1; | |
| } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| html.is-revealing::before, | |
| html.is-revealing .app-view-host { animation: none ; clip-path: none ; } | |
| } | |
| .lh-root { | |
| max-width: 880px; | |
| width: 100%; | |
| margin: 0 auto; | |
| /* Vertical rhythm is a clamp so the hero breathes on tall screens but | |
| collapses tight on laptop viewports — everything must stay above the | |
| fold. Horizontal padding is fixed; vertical padding and gap scale with | |
| available height. */ | |
| padding: clamp(16px, 3vh, 48px) 32px clamp(14px, 2.5vh, 36px); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: clamp(12px, 2.2vh, 24px); | |
| } | |
| .lh-title-wrap { | |
| text-align: center; | |
| max-width: 720px; | |
| animation: onboardIn var(--dur-slow) var(--ease-spring) both; | |
| } | |
| .lh-title { | |
| /* Also scale with viewport height — on short screens we want the | |
| headline to compress vertically, not just squish horizontally. */ | |
| font-size: clamp(40px, min(6vw, 9vh), 78px); | |
| line-height: 1.02; | |
| letter-spacing: -0.035em; | |
| margin: 0 0 clamp(6px, 1.2vh, 14px); | |
| color: #FFFFFF; | |
| /* The serif already communicates "craft"; avoid extra weight. */ | |
| font-weight: 300; | |
| } | |
| .lh-title em { | |
| font-style: italic; | |
| background: linear-gradient(110deg, #BAD4FF 0%, #7DABFF 40%, #5B8FF9 75%, #818CF8 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| filter: drop-shadow(0 0 28px rgba(91,143,249,0.70)); | |
| } | |
| .lh-sub { | |
| font-size: clamp(13px, 1.6vh, 15px); | |
| color: var(--text-2); | |
| line-height: 1.55; | |
| letter-spacing: -0.01em; | |
| margin: 0 auto; | |
| max-width: 560px; | |
| } | |
| /* The stage — aspect-ratio preserves the composition, but we also cap its | |
| height so short viewports never push the tiles/URL input off-screen. | |
| The stage is the most compressible element on this page — shrinking it | |
| preserves the headline and CTAs. */ | |
| .lh-stage { | |
| position: relative; | |
| width: 100%; | |
| max-width: 760px; | |
| aspect-ratio: 680 / 340; | |
| max-height: 38vh; | |
| border-radius: var(--radius-lg); | |
| background: linear-gradient(180deg, rgba(14, 18, 34, 0.75), rgba(8, 10, 22, 0.95)); | |
| border: 1px solid rgba(255, 255, 255, 0.06); | |
| overflow: hidden; | |
| box-shadow: | |
| /* Top inner highlight — one hairline of light along the upper edge | |
| completes the "lit object under skylight" effect. The card already | |
| has the outer accent ring and drop shadows; this is the missing | |
| piece that makes it read as a window with material weight. */ | |
| inset 0 1px 0 rgba(255, 255, 255, 0.06), | |
| 0 0 0 1px color-mix(in srgb, var(--demo-accent, #5B8FF9) 22%, transparent), | |
| 0 30px 90px rgba(0, 0, 16, 0.55), | |
| 0 0 120px color-mix(in srgb, var(--demo-accent, #5B8FF9) 14%, transparent); | |
| animation: onboardIn var(--dur-slow) var(--ease-spring) 120ms both; | |
| transition: box-shadow var(--dur-slow) var(--ease-spring); | |
| } | |
| .lh-svg { | |
| width: 100%; | |
| height: 100%; | |
| display: block; | |
| } | |
| /* Edges draw themselves via stroke-dasharray + dashoffset. The length | |
| doesn't need to be precise — 1000 is larger than any curve on this | |
| canvas, so animating to 0 reveals the whole line. */ | |
| .lh-edge { | |
| stroke-dasharray: 1000; | |
| stroke-dashoffset: 1000; | |
| animation: lh-edge-draw 900ms var(--ease-spring) both; | |
| } | |
| @keyframes lh-edge-draw { | |
| to { stroke-dashoffset: 0; } | |
| } | |
| .lh-node { | |
| opacity: 0; | |
| transform: scale(0.6); | |
| animation: lh-node-in 520ms var(--ease-spring-snap) both; | |
| } | |
| @keyframes lh-node-in { | |
| from { opacity: 0; transform: scale(0.4); } | |
| 70% { opacity: 1; transform: scale(1.08); } | |
| to { opacity: 1; transform: scale(1); } | |
| } | |
| .lh-label { | |
| opacity: 0; | |
| animation: lh-label-in 500ms ease both; | |
| } | |
| @keyframes lh-label-in { | |
| from { opacity: 0; transform: translateY(4px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Edge pulses — bright data-dot traveling along each curve. Fades in after | |
| the edge has finished drawing so the eye reads the sequence as "line | |
| appears, then data starts flowing through it." */ | |
| .lh-pulse { | |
| opacity: 0; | |
| animation: lh-pulse-in 600ms ease 1.2s forwards; | |
| } | |
| @keyframes lh-pulse-in { to { opacity: 0.95; } } | |
| /* Stage breathing — the whole graph gently expands/contracts so the page | |
| never feels inert. Scale is tiny (1.005) — noticeable only subconsciously. */ | |
| .lh-svg { animation: lh-stage-breathe 6.5s ease-in-out infinite; transform-origin: 50% 50%; } | |
| @keyframes lh-stage-breathe { | |
| 0%, 100% { transform: scale(1); filter: brightness(1); } | |
| 50% { transform: scale(1.006); filter: brightness(1.06); } | |
| } | |
| /* Respect reduced-motion — pulses and breathing both go silent. */ | |
| @media (prefers-reduced-motion: reduce) { | |
| .lh-pulse, .lh-svg { animation: none ; } | |
| } | |
| .lh-stage-meta { | |
| position: absolute; | |
| bottom: 12px; | |
| left: 16px; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 11px; | |
| font-family: var(--mono); | |
| color: var(--text-2); | |
| background: rgba(8, 10, 22, 0.55); | |
| backdrop-filter: blur(8px); | |
| -webkit-backdrop-filter: blur(8px); | |
| padding: 4px 10px; | |
| border-radius: var(--radius-full); | |
| border: 1px solid rgba(255, 255, 255, 0.06); | |
| } | |
| .lh-stage-dot { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| box-shadow: 0 0 8px currentColor; | |
| } | |
| .lh-stage-slug { color: var(--text); } | |
| .lh-stage-dash { opacity: 0.4; } | |
| .lh-stage-tagline { opacity: 0.7; } | |
| /* Tiles — real CTAs, not decoration. Three-up grid; on narrow screens stacks. */ | |
| .lh-tiles { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 12px; | |
| width: 100%; | |
| max-width: 760px; | |
| } | |
| .lh-tile { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 2px; | |
| padding: 12px 16px; | |
| background: rgba(255, 255, 255, 0.03); | |
| border: 1px solid rgba(255, 255, 255, 0.07); | |
| border-radius: var(--radius); | |
| color: var(--text); | |
| cursor: pointer; | |
| text-align: left; | |
| position: relative; | |
| /* hover-lift primitive provides the translate; we extend it with a | |
| border-accent reveal tied to this tile's demo accent. */ | |
| animation: onboardIn var(--dur-slow) var(--ease-spring) 240ms both; | |
| } | |
| .lh-tile:hover { | |
| border-color: color-mix(in srgb, var(--tile-accent, #5B8FF9) 55%, transparent); | |
| background: color-mix(in srgb, var(--tile-accent, #5B8FF9) 8%, rgba(255,255,255,0.03)); | |
| } | |
| .lh-tile.is-active { | |
| border-color: color-mix(in srgb, var(--tile-accent, #5B8FF9) 65%, transparent); | |
| background: color-mix(in srgb, var(--tile-accent, #5B8FF9) 10%, rgba(255,255,255,0.03)); | |
| box-shadow: 0 0 0 1px color-mix(in srgb, var(--tile-accent, #5B8FF9) 35%, transparent); | |
| } | |
| .lh-tile-kicker { | |
| font-size: 10px; | |
| font-family: var(--mono); | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| color: var(--tile-accent, var(--accent-soft)); | |
| opacity: 0.85; | |
| } | |
| .lh-tile-name { | |
| font-size: 17px; | |
| font-weight: 600; | |
| letter-spacing: -0.018em; | |
| line-height: 1.2; | |
| color: var(--text); | |
| margin-top: 2px; | |
| } | |
| .lh-tile-tagline { | |
| font-size: 12px; | |
| color: var(--muted); | |
| line-height: 1.4; | |
| } | |
| .lh-tile-arrow { | |
| position: absolute; | |
| top: 14px; | |
| right: 14px; | |
| opacity: 0.35; | |
| color: var(--text-2); | |
| transition: transform var(--dur-fast) var(--ease-spring-snap), opacity var(--dur-fast) ease, color var(--dur-fast) ease; | |
| } | |
| .lh-tile:hover .lh-tile-arrow { | |
| opacity: 1; | |
| transform: translateX(2px); | |
| color: var(--tile-accent, var(--accent-soft)); | |
| } | |
| /* URL input — reads as a mono terminal prompt. Single primary action | |
| with a big "Map it" affordance on the right. */ | |
| .lh-url { | |
| display: flex; | |
| align-items: stretch; | |
| gap: 0; | |
| width: 100%; | |
| max-width: 560px; | |
| background: rgba(12, 14, 28, 0.72); | |
| border: 1px solid rgba(255, 255, 255, 0.08); | |
| border-radius: var(--radius); | |
| padding: 4px 4px 4px 14px; | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.04), | |
| 0 1px 0 rgba(0, 0, 0, 0.25); | |
| transition: | |
| border-color var(--dur-fast) ease, | |
| box-shadow var(--dur-fast) ease, | |
| background var(--dur-fast) ease; | |
| animation: onboardIn var(--dur-slow) var(--ease-spring) 320ms both; | |
| } | |
| .lh-url:hover { | |
| border-color: rgba(255, 255, 255, 0.14); | |
| background: rgba(12, 14, 28, 0.82); | |
| } | |
| .lh-url:focus-within { | |
| border-color: color-mix(in srgb, var(--accent) 55%, transparent); | |
| background: rgba(14, 16, 32, 0.88); | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.06), | |
| 0 0 0 4px color-mix(in srgb, var(--accent) 10%, transparent), | |
| 0 8px 32px -8px color-mix(in srgb, var(--accent) 25%, transparent); | |
| } | |
| .lh-url-prefix { | |
| font-family: var(--mono); | |
| font-size: 13px; | |
| color: var(--muted); | |
| align-self: center; | |
| user-select: none; | |
| } | |
| .lh-url-input { | |
| flex: 1; | |
| min-width: 0; | |
| border: none; | |
| background: transparent; | |
| color: var(--text); | |
| font-family: var(--mono); | |
| font-size: 13px; | |
| padding: 10px 8px; | |
| outline: none; | |
| box-shadow: none; | |
| } | |
| /* Suppress the global input focus halo — the wrapper's :focus-within | |
| state IS the focus indicator here. A second halo inside creates the | |
| double-box effect. */ | |
| .lh-url-input:focus, | |
| .lh-url-input:focus-visible { | |
| outline: none; | |
| box-shadow: none; | |
| border-color: transparent; | |
| } | |
| .lh-url-btn:focus-visible { | |
| outline: none; | |
| box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.35); | |
| } | |
| .lh-url-input::placeholder { color: var(--muted); } | |
| .lh-url-btn { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 7px; | |
| padding: 0 18px; | |
| border: none; | |
| border-radius: calc(var(--radius) - 2px); | |
| /* Top-lit gradient: accent-light at the top edge → accent at the bottom. | |
| With the inner top highlight + outer accent glow this reads as a real | |
| button under implied "skylight" — a primary CTA, not a flat rectangle. */ | |
| background: linear-gradient(180deg, var(--accent-light) 0%, var(--accent) 100%); | |
| color: #fff; | |
| font-size: 13px; | |
| font-weight: 600; | |
| letter-spacing: -0.01em; | |
| cursor: pointer; | |
| /* Three-layer presence: inner top highlight (light source visible on the | |
| top edge), bottom-edge inner shadow for depth, soft outer accent glow | |
| for "this is the primary action you should click." */ | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.25), | |
| inset 0 -1px 0 rgba(0, 0, 0, 0.15), | |
| 0 4px 14px -4px rgba(91, 143, 249, 0.45); | |
| transition: | |
| transform var(--dur-fast) var(--ease-spring-snap), | |
| box-shadow var(--dur-fast) ease, | |
| filter var(--dur-fast) ease; | |
| } | |
| .lh-url-btn:hover { | |
| /* Brighten via filter (cheaper than re-blending a new gradient) and | |
| swell the outer glow. Feels like the button is reaching toward the | |
| cursor without actually moving it (jumpy hover translates feel cheap). */ | |
| filter: brightness(1.08); | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.30), | |
| inset 0 -1px 0 rgba(0, 0, 0, 0.15), | |
| 0 6px 22px -4px rgba(91, 143, 249, 0.65); | |
| } | |
| .lh-url-btn:active { transform: translateY(0.5px); filter: brightness(0.95); } | |
| .lh-url-btn:disabled { | |
| opacity: 0.45; | |
| cursor: not-allowed; | |
| filter: saturate(0.6); | |
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10); | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| LANDING INGESTION — the journey | |
| The signature moment: tile click → the hero stage stays and a live map | |
| forms inside it, driven by real SSE events from /ingest/stream. Shares | |
| the .lh-stage / .lh-title styling so the transition from LandingHero | |
| reads as one continuous moment, not two separate screens. | |
| ══════════════════════════════════════════════════════════ */ | |
| .li-root { | |
| max-width: 880px; | |
| width: 100%; | |
| margin: 0 auto; | |
| padding: clamp(16px, 3vh, 48px) 32px clamp(14px, 2.5vh, 36px); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: clamp(14px, 2.4vh, 26px); | |
| animation: onboardIn var(--dur-slow) var(--ease-spring) both; | |
| } | |
| .li-title { | |
| font-size: clamp(36px, min(5.2vw, 7.6vh), 66px); | |
| } | |
| .li-slug { | |
| background: none; | |
| -webkit-background-clip: unset; | |
| -webkit-text-fill-color: currentColor; | |
| background-clip: unset; | |
| color: var(--demo-accent, #5B8FF9); | |
| filter: drop-shadow(0 0 24px color-mix(in srgb, var(--demo-accent, #5B8FF9) 55%, transparent)); | |
| font-style: italic; | |
| font-family: inherit; | |
| } | |
| /* The copy line mounts with its own key on every variant, so it replays | |
| the fade whenever the narrative changes. Subtle — just enough to feel | |
| "alive" without being distracting. */ | |
| .li-sub { | |
| animation: li-sub-in 420ms var(--ease-spring) both; | |
| } | |
| @keyframes li-sub-in { | |
| from { opacity: 0; transform: translateY(4px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Stage — same shell as .lh-stage. Add a progress-aware box-shadow so the | |
| glow intensifies as ingestion advances. --li-progress is a number 0..1 | |
| written by JS. */ | |
| .li-stage { | |
| box-shadow: | |
| 0 0 0 1px color-mix(in srgb, var(--demo-accent, #5B8FF9) calc(22% + 20% * var(--li-progress, 0)), transparent), | |
| 0 30px 90px rgba(0, 0, 16, 0.55), | |
| 0 0 calc(120px + 80px * var(--li-progress, 0)) color-mix(in srgb, var(--demo-accent, #5B8FF9) calc(14% + 14% * var(--li-progress, 0)), transparent); | |
| transition: box-shadow var(--dur-slow) var(--ease-spring); | |
| } | |
| .li-stage.is-done { | |
| /* On completion, push the glow harder for a "reveal" beat. */ | |
| box-shadow: | |
| 0 0 0 1px color-mix(in srgb, var(--demo-accent, #5B8FF9) 60%, transparent), | |
| 0 30px 90px rgba(0, 0, 16, 0.55), | |
| 0 0 240px color-mix(in srgb, var(--demo-accent, #5B8FF9) 32%, transparent); | |
| } | |
| .li-stage.is-error { | |
| box-shadow: | |
| 0 0 0 1px rgba(220, 120, 120, 0.45), | |
| 0 30px 90px rgba(0, 0, 16, 0.55); | |
| } | |
| /* Core node — pulses gently through all phases, "settles" on done */ | |
| .li-core { | |
| animation: li-core-pulse 2600ms var(--ease-spring) infinite; | |
| transform-origin: 340px 170px; | |
| } | |
| .li-stage.is-done .li-core { | |
| animation: li-core-settle 900ms var(--ease-spring) both; | |
| } | |
| @keyframes li-core-pulse { | |
| 0%, 100% { transform: scale(1); opacity: 0.95; } | |
| 50% { transform: scale(1.06); opacity: 1; } | |
| } | |
| @keyframes li-core-settle { | |
| from { transform: scale(1.08); } | |
| to { transform: scale(1); } | |
| } | |
| /* File dots bloom in one by one */ | |
| .li-dot { | |
| opacity: 0; | |
| transform: scale(0.4); | |
| animation: li-dot-in 520ms var(--ease-spring-snap) both; | |
| } | |
| @keyframes li-dot-in { | |
| 0% { opacity: 0; transform: scale(0.2); } | |
| 70% { opacity: 1; transform: scale(1.15); } | |
| 100% { opacity: 1; transform: scale(1); } | |
| } | |
| /* Edges appear softly — their own simple fade */ | |
| .li-edge { | |
| opacity: 0; | |
| animation: li-edge-in 700ms ease 200ms both; | |
| } | |
| @keyframes li-edge-in { | |
| to { opacity: 0.28; } | |
| } | |
| /* Concept flash — a brief ring + label that appears and recedes */ | |
| .li-flash { | |
| animation: li-flash-life 1400ms ease both; | |
| } | |
| @keyframes li-flash-life { | |
| 0% { opacity: 0; transform: scale(0.2); } | |
| 25% { opacity: 1; transform: scale(1.2); } | |
| 75% { opacity: 1; transform: scale(1.0); } | |
| 100% { opacity: 0; transform: scale(0.9); } | |
| } | |
| /* Progress strip at the bottom of the stage — replaces the .lh-stage-meta | |
| pill with one that includes a live bar fill. */ | |
| .li-meta { | |
| position: absolute; | |
| left: 16px; | |
| right: 16px; | |
| bottom: 12px; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-size: 11px; | |
| font-family: var(--mono); | |
| color: var(--text-2); | |
| background: rgba(8, 10, 22, 0.6); | |
| backdrop-filter: blur(10px); | |
| -webkit-backdrop-filter: blur(10px); | |
| padding: 6px 12px; | |
| border-radius: var(--radius-full); | |
| border: 1px solid rgba(255, 255, 255, 0.06); | |
| } | |
| .li-meta-dot { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| box-shadow: 0 0 8px currentColor; | |
| flex-shrink: 0; | |
| } | |
| .li-meta-label { | |
| flex-shrink: 0; | |
| color: var(--text); | |
| } | |
| .li-bar { | |
| flex: 1; | |
| height: 3px; | |
| background: rgba(255, 255, 255, 0.06); | |
| border-radius: var(--radius-full); | |
| overflow: hidden; | |
| margin-left: 4px; | |
| } | |
| .li-bar-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, | |
| color-mix(in srgb, var(--demo-accent, #5B8FF9) 60%, transparent), | |
| var(--demo-accent, #5B8FF9)); | |
| border-radius: var(--radius-full); | |
| transition: width 420ms var(--ease-spring); | |
| } | |
| /* Bottom CTA row — fixed height so the layout doesn't jump when the | |
| CTA appears on completion. The CTA itself blooms in when rendered. */ | |
| .li-cta-row { | |
| min-height: 44px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| } | |
| .li-cta { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 12px 24px; | |
| border-radius: var(--radius); | |
| border: 1px solid color-mix(in srgb, var(--demo-accent, #5B8FF9) 55%, transparent); | |
| background: color-mix(in srgb, var(--demo-accent, #5B8FF9) 18%, rgba(12, 14, 28, 0.6)); | |
| color: #fff; | |
| font-size: 14px; | |
| font-weight: 600; | |
| letter-spacing: -0.01em; | |
| cursor: pointer; | |
| transition: transform var(--dur-fast) var(--ease-spring-snap), | |
| background var(--dur-fast) ease, | |
| box-shadow var(--dur-medium) ease; | |
| box-shadow: 0 0 0 0 color-mix(in srgb, var(--demo-accent, #5B8FF9) 30%, transparent); | |
| animation: li-cta-bloom 720ms var(--ease-spring-snap) both; | |
| } | |
| .li-cta:hover { | |
| background: color-mix(in srgb, var(--demo-accent, #5B8FF9) 26%, rgba(12, 14, 28, 0.6)); | |
| box-shadow: 0 0 28px 2px color-mix(in srgb, var(--demo-accent, #5B8FF9) 45%, transparent); | |
| } | |
| .li-cta:focus-visible { | |
| outline: 2px solid color-mix(in srgb, var(--demo-accent, #5B8FF9) 70%, transparent); | |
| outline-offset: 3px; | |
| } | |
| @keyframes li-cta-bloom { | |
| 0% { opacity: 0; transform: translateY(10px) scale(0.94); } | |
| 60% { opacity: 1; transform: translateY(-2px) scale(1.04); } | |
| 100% { opacity: 1; transform: translateY(0) scale(1); } | |
| } | |
| .li-cta-ghost { | |
| background: transparent; | |
| border: 1px solid rgba(255, 255, 255, 0.08); | |
| color: var(--muted); | |
| padding: 8px 14px; | |
| border-radius: var(--radius); | |
| font-size: 12.5px; | |
| cursor: pointer; | |
| transition: color var(--dur-fast) ease, border-color var(--dur-fast) ease; | |
| } | |
| .li-cta-ghost:hover { | |
| color: var(--text); | |
| border-color: rgba(255, 255, 255, 0.16); | |
| } | |
| .li-cancel { opacity: 0.6; } | |
| /* Reduced motion — cut every journey animation to a simple opacity swap. | |
| The progress bar still fills because that's meaningful (not decorative). */ | |
| @media (prefers-reduced-motion: reduce) { | |
| .li-core, | |
| .li-dot, | |
| .li-edge, | |
| .li-flash, | |
| .li-cta, | |
| .li-sub { animation: none ; } | |
| .li-dot { opacity: 1 ; transform: none ; } | |
| .li-edge { opacity: 0.28 ; } | |
| .li-cta { opacity: 1; transform: none ; } | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| EMPTY STATE & ONBOARDING | |
| ══════════════════════════════════════════════════════════ */ | |
| .empty-state { | |
| flex: 1; | |
| min-height: 0; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| /* justify-content: center — vertically centres the onboarding/suggest content. | |
| overflow: hidden — blobs (::before/::after) are position:absolute and their | |
| geometry would trigger a spurious scrollbar with overflow:auto. hidden clips | |
| them cleanly without any scroll. Content fits the viewport; clipping is safe. */ | |
| justify-content: center; | |
| padding: 0 24px; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .empty-state::before { | |
| content: ''; | |
| position: absolute; | |
| top: 10%; | |
| left: 5%; | |
| width: 520px; | |
| height: 420px; | |
| border-radius: 50%; | |
| background: radial-gradient(ellipse, rgba(91, 143, 249, 0.30) 0%, rgba(91, 143, 249, 0.10) 50%, transparent 70%); | |
| filter: blur(55px); | |
| pointer-events: none; | |
| z-index: 0; | |
| animation: blob-drift-1 18s ease-in-out infinite alternate; | |
| } | |
| .empty-state::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 5%; | |
| right: 5%; | |
| width: 460px; | |
| height: 380px; | |
| border-radius: 50%; | |
| background: radial-gradient(ellipse, rgba(129, 140, 248, 0.22) 0%, rgba(91, 143, 249, 0.08) 50%, transparent 70%); | |
| filter: blur(65px); | |
| pointer-events: none; | |
| z-index: 0; | |
| animation: blob-drift-2 22s ease-in-out infinite alternate; | |
| } | |
| /* Blob 1: violet — drifts diagonally toward bottom-right */ | |
| @keyframes blob-drift-1 { | |
| 0% { transform: translate(0px, 0px) scale(1); } | |
| 33% { transform: translate(120px, 60px) scale(1.08); } | |
| 66% { transform: translate(60px, 140px) scale(0.95); } | |
| 100% { transform: translate(180px, 90px) scale(1.05); } | |
| } | |
| /* Blob 2: indigo — counter-drifts toward top-left, out of phase with blob 1 */ | |
| @keyframes blob-drift-2 { | |
| 0% { transform: translate(0px, 0px) scale(1); } | |
| 33% { transform: translate(-90px, -70px) scale(1.06); } | |
| 66% { transform: translate(-40px, -130px) scale(0.93);} | |
| 100% { transform: translate(-140px, -60px) scale(1.04);} | |
| } | |
| .empty-state > * { position: relative; z-index: 1; } | |
| /* Onboarding steps */ | |
| .onboarding-steps { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0; | |
| max-width: 640px; | |
| width: 100%; | |
| padding-top: 12px; | |
| padding-bottom: 24px; | |
| } | |
| /* 3-column grid for the three steps */ | |
| .onboarding-grid { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 10px; | |
| width: 100%; | |
| } | |
| /* Staggered entrance for onboarding header + steps */ | |
| @keyframes onboardIn { | |
| from { opacity: 0; transform: translateY(14px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .onboarding-header { | |
| animation: onboardIn var(--dur-slow) var(--ease-spring) both; | |
| animation-delay: 0ms; | |
| } | |
| .onboarding-steps .onboarding-step:nth-child(2) { animation: onboardIn var(--dur-medium) var(--ease-spring) 120ms both; } | |
| .onboarding-steps .onboarding-step:nth-child(3) { animation: onboardIn var(--dur-medium) var(--ease-spring) 210ms both; } | |
| .onboarding-steps .onboarding-step:nth-child(4) { animation: onboardIn var(--dur-medium) var(--ease-spring) 300ms both; } | |
| /* Header for onboarding section */ | |
| .onboarding-header { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; /* centres SVG + headline + subtitle along the cross-axis */ | |
| text-align: center; | |
| margin-bottom: 14px; | |
| } | |
| /* North point pulses — the other three points fade in sequence */ | |
| .compass-north { | |
| animation: compass-pulse-n 3s ease-in-out infinite; | |
| } | |
| .compass-south { | |
| animation: compass-pulse-dim 3s ease-in-out infinite 0.75s; | |
| } | |
| .compass-east { | |
| animation: compass-pulse-dim 3s ease-in-out infinite 1.5s; | |
| } | |
| .compass-west { | |
| animation: compass-pulse-dim 3s ease-in-out infinite 2.25s; | |
| } | |
| @keyframes compass-pulse-n { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.35; } | |
| } | |
| @keyframes compass-pulse-dim { | |
| 0%, 100% { opacity: 0.15; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .onboarding-headline { | |
| font-size: 70px; | |
| font-weight: 300; | |
| font-family: var(--serif); | |
| letter-spacing: -0.040em; | |
| line-height: 1.08; | |
| padding-bottom: 0.05em; /* stops descenders (g, y, p) clipping at line-height edges */ | |
| margin-bottom: 10px; | |
| color: #FFFFFF; | |
| } | |
| /* "any" in "Map any codebase" — gradient accent highlights the key idea: | |
| that Cartographer works on every repo, not just pre-selected ones */ | |
| .onboarding-headline em { | |
| font-style: italic; | |
| background: linear-gradient(110deg, #BAD4FF 0%, #7DABFF 40%, #5B8FF9 75%, #818CF8 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| filter: drop-shadow(0 0 28px rgba(91,143,249,0.70)); | |
| } | |
| .onboarding-sub { | |
| font-size: 15px; | |
| color: var(--text-2); | |
| line-height: 1.55; | |
| letter-spacing: -0.012em; | |
| max-width: 400px; | |
| margin: 0 auto; | |
| margin-top: 4px; | |
| } | |
| .onboarding-step { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| background: rgba(15, 15, 24, 0.80); | |
| border: 1px solid rgba(255,255,255,0.07); | |
| border-radius: 14px; | |
| padding: 16px; | |
| backdrop-filter: blur(12px); | |
| -webkit-backdrop-filter: blur(12px); | |
| transition: border-color 200ms ease, background 200ms ease, box-shadow 200ms ease, transform 200ms ease; | |
| } | |
| .onboarding-step:hover { | |
| border-color: rgba(91,143,249,0.35); | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 24px rgba(0,0,16,0.4), 0 0 0 1px rgba(91,143,249,0.10); | |
| } | |
| /* Completed steps compress — description hidden, only title remains */ | |
| .onboarding-step.done { | |
| padding: 8px 14px; | |
| background: transparent; | |
| border-color: var(--border-subtle); | |
| } | |
| .onboarding-step.done p { display: none; } | |
| .onboarding-step.done strong { | |
| font-size: 12px; | |
| font-weight: 500; | |
| color: var(--muted); | |
| } | |
| .onboarding-step.done .step-num { | |
| width: 20px; height: 20px; | |
| font-size: 9px; | |
| background: var(--green-dim); | |
| border-color: rgba(52,211,153,0.30); | |
| color: var(--green); | |
| box-shadow: none; | |
| } | |
| .onboarding-step:not(.active):not(.done) strong { color: var(--text-2); } | |
| .onboarding-step:not(.active):not(.done) p { color: var(--muted); } | |
| .onboarding-step:not(.active):not(.done) .step-num { | |
| background: rgba(255, 255, 255,0.06); | |
| color: var(--muted); | |
| border-color: var(--border); | |
| box-shadow: none; | |
| } | |
| .onboarding-step.active { | |
| border-color: rgba(91, 143, 249, 0.45); | |
| border-left-color: var(--accent); | |
| border-left-width: 2px; | |
| background: rgba(91, 143, 249, 0.07); | |
| box-shadow: | |
| 0 0 0 0 transparent, | |
| 0 8px 32px rgba(91, 143, 249,0.12), | |
| var(--shadow-sm); | |
| } | |
| .step-num { | |
| background: linear-gradient(135deg, #3A6FE8 0%, #5B8FF9 55%, #7DABFF 100%); | |
| border: 1px solid rgba(91, 143, 249,0.60); | |
| color: #fff; | |
| border-radius: 50%; | |
| width: 28px; height: 28px; | |
| display: flex; align-items: center; justify-content: center; | |
| font-size: 11px; font-weight: 700; | |
| flex-shrink: 0; | |
| margin-top: 1px; | |
| box-shadow: | |
| 0 0 0 3px rgba(91, 143, 249,0.15), | |
| 0 2px 14px rgba(91, 143, 249,0.55), | |
| inset 0 1px 0 rgba(255, 255, 255, 0.30); | |
| font-family: var(--mono); | |
| } | |
| .onboarding-step strong { | |
| font-size: 13px; | |
| font-weight: 650; | |
| color: #FFFFFF; | |
| display: block; | |
| margin-bottom: 4px; | |
| letter-spacing: -0.018em; | |
| } | |
| .onboarding-step p { | |
| font-size: 11.5px; | |
| color: var(--muted); | |
| margin: 2px 0; | |
| line-height: 1.55; | |
| } | |
| .onboarding-step code { | |
| font-family: var(--mono); | |
| color: var(--accent-lavender); | |
| font-size: 12px; | |
| background: rgba(91, 143, 249, 0.14); | |
| border: 1px solid var(--accent-border); | |
| border-radius: var(--radius-sm); | |
| padding: 2px 6px; | |
| /* Subtle glow so code snippets catch the eye */ | |
| box-shadow: inset 0 1px 0 rgba(200, 220, 255, 0.12); | |
| } | |
| /* Suggest state */ | |
| .suggest-state { max-width: 620px; width: 100%; text-align: center; padding-top: 16px; padding-bottom: 24px; } | |
| .suggest-state h2 { | |
| animation: onboardIn var(--dur-slow) var(--ease-spring) both; | |
| font-size: 48px; | |
| font-weight: 300; | |
| font-family: var(--serif); | |
| letter-spacing: -0.035em; | |
| line-height: 0.98; | |
| margin-bottom: 12px; | |
| color: #FFFFFF; | |
| text-shadow: 0 0 80px rgba(91,143,249,0.20); | |
| } | |
| .suggest-state > p { | |
| animation: onboardIn var(--dur-medium) var(--ease-spring) 120ms both; | |
| font-size: 13.5px; | |
| color: var(--text-2); | |
| margin-bottom: 20px; | |
| line-height: 1.6; | |
| } | |
| .suggestions { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 7px; | |
| margin-bottom: 18px; | |
| } | |
| /* Staggered entrance — delay set inline via animationDelay style prop in App.jsx | |
| so index-based order is always guaranteed correct */ | |
| @keyframes suggestionIn { | |
| from { opacity: 0; transform: translateY(16px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .suggestion-btn { | |
| display: flex; | |
| align-items: center; | |
| gap: 14px; | |
| background: rgba(255, 255, 255, 0.03); | |
| border: 1px solid rgba(255, 255, 255, 0.08); | |
| border-bottom-color: rgba(255, 255, 255, 0.12); | |
| border-radius: 14px; | |
| color: var(--text); | |
| cursor: pointer; | |
| font-family: var(--sans); | |
| padding: 13px 14px 13px 14px; | |
| text-align: left; | |
| transition: | |
| border-color var(--dur-fast) var(--ease-spring), | |
| background var(--dur-fast) var(--ease-spring), | |
| transform var(--dur-fast) var(--ease-spring-snap), | |
| box-shadow var(--dur-fast) var(--ease-spring-snap); | |
| animation: suggestionIn var(--dur-slow) var(--ease-spring) both; | |
| box-shadow: 0 1px 3px rgba(0,0,0,0.25), 0 4px 16px rgba(0,0,0,0.15); | |
| } | |
| .suggestion-btn:hover { | |
| border-color: rgba(91, 143, 249, 0.50); | |
| background: rgba(91, 143, 249, 0.08); | |
| transform: translateY(-2px) translateX(1px); | |
| box-shadow: | |
| 0 0 0 1px rgba(91, 143, 249, 0.45), | |
| 0 0 24px rgba(91, 143, 249, 0.18), | |
| 0 8px 24px rgba(0, 0, 0, 0.35); | |
| } | |
| .suggestion-btn:hover .suggestion-icon { | |
| background: rgba(91, 143, 249, 0.18); | |
| border-color: rgba(91, 143, 249, 0.40); | |
| color: var(--accent-soft); | |
| } | |
| .suggestion-btn:hover .suggestion-arrow { | |
| opacity: 1; | |
| transform: translateX(2px); | |
| color: var(--accent-soft); | |
| } | |
| /* Icon container — rounded square with subtle accent tint */ | |
| .suggestion-icon { | |
| width: 36px; | |
| height: 36px; | |
| min-width: 36px; | |
| border-radius: 10px; | |
| background: rgba(255, 255, 255, 0.06); | |
| border: 1px solid rgba(255, 255, 255, 0.10); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--text-2); | |
| transition: background var(--transition), border-color var(--transition), color var(--transition); | |
| } | |
| /* Text content — title + subtitle */ | |
| .suggestion-content { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2px; | |
| flex: 1; | |
| min-width: 0; | |
| } | |
| .suggestion-title { | |
| font-size: 13.5px; | |
| font-weight: 600; | |
| color: var(--text); | |
| letter-spacing: -0.018em; | |
| line-height: 1.3; | |
| } | |
| .suggestion-body { | |
| font-size: 12px; | |
| color: var(--muted); | |
| letter-spacing: -0.008em; | |
| line-height: 1.4; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| /* Arrow — dim by default, slides right on hover */ | |
| .suggestion-arrow { | |
| opacity: 0.30; | |
| flex-shrink: 0; | |
| transition: opacity var(--transition), transform var(--transition), color var(--transition); | |
| color: var(--muted); | |
| } | |
| /* ── Mode description — one-line explanation below each pill group ── */ | |
| .mode-description { | |
| font-size: 11.5px; | |
| color: rgba(200, 210, 240, 0.45); | |
| line-height: 1.5; | |
| margin-top: 8px; | |
| padding: 0 2px; | |
| } | |
| /* ── Try-these repo chips — tiny monospace chips, utility not decoration ── */ | |
| .try-repo-chips { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 4px; | |
| margin-top: 8px; | |
| } | |
| .try-repo-chip { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 4px; | |
| background: rgba(255, 255, 255, 0.04); | |
| border: 1px solid rgba(91, 143, 249, 0.30); | |
| border-radius: var(--radius-full); | |
| padding: 3px 9px; | |
| font-size: 11px; | |
| font-family: var(--mono); | |
| color: var(--accent-soft); | |
| cursor: pointer; | |
| transition: | |
| background var(--dur-fast) var(--ease-spring), | |
| border-color var(--dur-fast) var(--ease-spring), | |
| color var(--dur-fast) var(--ease-spring), | |
| transform var(--dur-fast) var(--ease-spring-snap), | |
| box-shadow var(--dur-fast) var(--ease-spring-snap); | |
| white-space: nowrap; | |
| } | |
| .try-repo-chip:hover { | |
| background: rgba(91, 143, 249, 0.12); | |
| border-color: rgba(91, 143, 249, 0.55); | |
| color: var(--text); | |
| transform: translateY(-1px); | |
| box-shadow: 0 4px 12px rgba(91, 143, 249, 0.18); | |
| } | |
| .try-repo-chip:active { transform: translateY(0); } | |
| /* ── Contextual retrieval tip — sits inside the repos section ── */ | |
| .ctip { | |
| padding: 6px 0 2px; | |
| } | |
| .ctip-trigger { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| width: 100%; | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| font-size: 11px; | |
| /* Slightly more visible than pure faint — "Improve search quality" is a feature CTA */ | |
| color: var(--muted); | |
| padding: 0; | |
| text-align: left; | |
| transition: color var(--transition); | |
| font-weight: 500; | |
| } | |
| .ctip-trigger:hover { color: var(--text-2); } | |
| .ctip-body { | |
| font-size: 12px; | |
| color: var(--muted); | |
| line-height: 1.65; | |
| margin-top: 9px; | |
| padding-left: 16px; | |
| border-left: 2px solid var(--accent-border); | |
| } | |
| .ctip-body strong { color: var(--text-2); font-weight: 600; } | |
| .repo-contextual { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--accent-soft); | |
| cursor: default; | |
| line-height: 0; | |
| /* Slow glow-beat: signals "this repo has AI context embedded" without | |
| demanding attention. Opacity oscillates between dim and full while | |
| drop-shadow breathes the accent glow in and out. 8s period keeps | |
| it calm — closer to a sleeping MacBook indicator than a notification. */ | |
| animation: contextual-beat 8s ease-in-out infinite; | |
| } | |
| @keyframes contextual-beat { | |
| 0% { opacity: 0.45; color: var(--accent-soft); filter: none; } | |
| 50% { opacity: 1; color: rgba(200, 130, 255, 1); filter: drop-shadow(0 0 6px rgba(200, 130, 255, 0.75)); } | |
| 100% { opacity: 0.45; color: var(--accent-soft); filter: none; } | |
| } | |
| .quality-tip-key { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: var(--surface-4); | |
| border: 1px solid var(--border); | |
| border-radius: 4px; | |
| padding: 0 4px; | |
| font-size: 10px; | |
| font-family: var(--mono); | |
| color: var(--text-2); | |
| vertical-align: middle; | |
| } | |
| /* ── Pipeline provenance — label strip showing how the answer was built ── */ | |
| .pipeline-provenance { | |
| display: flex; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| gap: 5px; | |
| font-size: 10.5px; | |
| font-family: var(--mono); | |
| margin-bottom: 12px; | |
| padding: 7px 12px; | |
| background: rgba(26,23,20,0.85); | |
| border: 1px solid var(--border-strong); | |
| border-left: 2px solid rgba(91, 143, 249, 0.55); | |
| border-radius: var(--radius); | |
| box-shadow: 0 1px 0 rgba(255, 255, 255,0.04), 0 2px 10px rgba(0,0,0,0.20); | |
| } | |
| .pipeline-stage { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 4px; | |
| /* Bump from muted to text-2 so it's actually readable */ | |
| color: var(--text-2); | |
| letter-spacing: 0.01em; | |
| } | |
| .pipeline-sep { color: var(--muted); opacity: 0.6; } | |
| .pipeline-grade-high { color: var(--green); } | |
| .pipeline-grade-medium { color: var(--warning); } | |
| .pipeline-grade-low { color: var(--red); } | |
| /* Quality feature stages (HyDE, expansion) — blueprint-tinted to signal extra work */ | |
| .pipeline-quality { | |
| color: var(--accent-soft); | |
| opacity: 0.9; | |
| } | |
| .mode-hint { | |
| background: rgba(91, 143, 249,0.07); | |
| border: 1px solid var(--accent-border); | |
| border-left: 3px solid var(--accent); | |
| border-radius: var(--radius-md); | |
| color: var(--text-2); | |
| font-size: 13px; | |
| line-height: 1.6; | |
| margin-bottom: 14px; | |
| padding: 11px 15px; | |
| text-align: left; | |
| box-shadow: 0 2px 12px rgba(91, 143, 249,0.08); | |
| } | |
| .mode-hint strong { color: var(--accent-soft); } | |
| .graph-hint-btn { | |
| background: none; | |
| border: none; | |
| color: var(--muted); | |
| cursor: pointer; | |
| font-family: var(--sans); | |
| font-size: 13px; | |
| padding: 4px; | |
| text-decoration: underline; | |
| text-decoration-style: dotted; | |
| text-decoration-color: rgba(138,122,100,0.55); | |
| letter-spacing: -0.01em; | |
| transition: color var(--transition); | |
| } | |
| .graph-hint-btn:hover { color: var(--text); } | |
| /* ══════════════════════════════════════════════════════════ | |
| INPUT MODE BADGE — small status line below the textarea | |
| ══════════════════════════════════════════════════════════ */ | |
| .input-mode-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| font-size: 10px; | |
| font-weight: 600; | |
| color: var(--accent-soft); | |
| letter-spacing: -0.01em; | |
| font-family: var(--sans); | |
| margin-top: 5px; | |
| padding: 0 2px; | |
| gap: 4px; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| INPUT FOOTER ROW — wraps agent badge + model selector | |
| ══════════════════════════════════════════════════════════ */ | |
| .input-footer-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| margin-top: 5px; | |
| /* Remove the margin-top from badge when it's inside this row */ | |
| } | |
| .input-footer-row .input-mode-badge { | |
| margin-top: 0; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| MODEL SELECTOR — dropdown button + floating menu | |
| ══════════════════════════════════════════════════════════ */ | |
| .model-selector { | |
| position: relative; | |
| } | |
| .model-selector-btn { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 5px; | |
| background: none; | |
| border: 1px solid rgba(255, 255, 255,0.10); | |
| border-radius: var(--radius-sm); | |
| padding: 2px 7px 2px 6px; | |
| cursor: pointer; | |
| font-family: var(--sans); | |
| font-size: 10px; | |
| font-weight: 500; | |
| color: var(--muted); | |
| transition: color var(--transition), border-color var(--transition), background var(--transition); | |
| white-space: nowrap; | |
| line-height: 1.6; | |
| } | |
| .model-selector-btn:hover { | |
| color: var(--text-2); | |
| border-color: rgba(255, 255, 255,0.20); | |
| background: rgba(255, 255, 255,0.04); | |
| } | |
| .model-selector-name { | |
| font-weight: 600; | |
| letter-spacing: -0.01em; | |
| } | |
| /* Speed badge: fast = green-ish, slow = amber */ | |
| .model-speed-badge { | |
| font-size: 9px; | |
| font-weight: 600; | |
| padding: 0px 4px; | |
| border-radius: 3px; | |
| letter-spacing: 0; | |
| } | |
| .model-speed-fast { | |
| background: rgba(52,211,153,0.15); | |
| color: var(--green, #72b87e); | |
| border: 1px solid rgba(52,211,153,0.25); | |
| } | |
| .model-speed-slow { | |
| background: rgba(212,171,90,0.12); | |
| color: #c9a85a; | |
| border: 1px solid rgba(212,171,90,0.22); | |
| } | |
| .model-chevron { | |
| color: var(--muted); | |
| transition: transform var(--transition); | |
| flex-shrink: 0; | |
| } | |
| .model-chevron.open { | |
| transform: rotate(180deg); | |
| } | |
| /* The dropdown panel — opens above the button */ | |
| .model-menu { | |
| position: absolute; | |
| bottom: calc(100% + 6px); | |
| left: 0; | |
| min-width: 300px; | |
| background: var(--surface-3); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| box-shadow: | |
| 0 -4px 24px rgba(0,0,8,0.55), | |
| 0 0 0 1px rgba(255, 255, 255,0.06); | |
| z-index: 200; | |
| overflow: hidden; | |
| padding: 4px; | |
| } | |
| .model-menu-item { | |
| display: block; | |
| width: 100%; | |
| text-align: left; | |
| background: none; | |
| border: none; | |
| border-radius: calc(var(--radius) - 3px); | |
| padding: 8px 10px; | |
| cursor: pointer; | |
| font-family: var(--sans); | |
| transition: background var(--transition); | |
| } | |
| .model-menu-item:hover:not(:disabled) { | |
| background: rgba(255, 255, 255,0.06); | |
| } | |
| .model-menu-item.active { | |
| background: rgba(91, 143, 249,0.10); | |
| } | |
| .model-menu-item.unavailable { | |
| opacity: 0.45; | |
| cursor: not-allowed; | |
| } | |
| .model-menu-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| margin-bottom: 3px; | |
| } | |
| .model-menu-name { | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: var(--text); | |
| letter-spacing: -0.01em; | |
| } | |
| .model-menu-note { | |
| font-size: 11px; | |
| color: var(--muted); | |
| line-height: 1.45; | |
| letter-spacing: -0.01em; | |
| } | |
| .model-menu-unavail { | |
| font-size: 10px; | |
| color: var(--red, #c86858); | |
| margin-top: 2px; | |
| font-style: italic; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| STATUS BAR | |
| ══════════════════════════════════════════════════════════ */ | |
| .status-bar { | |
| font-size: 12px; | |
| padding: 7px 12px; | |
| border-radius: var(--radius); | |
| border: 1px solid; | |
| font-weight: 500; | |
| } | |
| .status-bar.info { background: var(--accent-dim); border-color: var(--accent-border); color: var(--accent-soft); } | |
| .status-bar.success { background: var(--green-dim); border-color: rgba(52,211,153,0.35); color: var(--green); } | |
| .status-bar.error { background: var(--red-dim); border-color: rgba(200,104,88,0.35); color: var(--red); } | |
| /* ══════════════════════════════════════════════════════════ | |
| SPINNER & CURSOR | |
| ══════════════════════════════════════════════════════════ */ | |
| .spinner { | |
| width: 14px; height: 14px; | |
| border: 2px solid rgba(255, 255, 255,0.08); | |
| border-top-color: var(--accent-light); | |
| border-radius: 50%; | |
| animation: spin 0.75s linear infinite; | |
| display: inline-block; | |
| flex-shrink: 0; | |
| } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| .cursor { | |
| display: inline-block; | |
| width: 2px; height: 15px; | |
| background: var(--accent-light); | |
| margin-left: 2px; | |
| vertical-align: text-bottom; | |
| animation: blink 1s step-end infinite; | |
| box-shadow: 0 0 8px rgba(91, 143, 249, 0.8); | |
| } | |
| @keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } | |
| /* ══════════════════════════════════════════════════════════ | |
| VIEW TOGGLE (Chat | Graph) | |
| ══════════════════════════════════════════════════════════ */ | |
| .header-actions { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| justify-content: flex-end; | |
| } | |
| .view-toggle { | |
| display: flex; | |
| background: rgba(255,255,255,0.04); | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--radius-md); | |
| overflow: hidden; | |
| padding: 3px; | |
| gap: 2px; | |
| box-shadow: inset 0 1px 3px rgba(0,0,0,0.30); | |
| } | |
| .view-btn { | |
| background: none; | |
| border: none; | |
| color: var(--text-2); | |
| cursor: pointer; | |
| font-family: var(--sans); | |
| /* Slightly larger than before — this is the primary view switcher */ | |
| font-size: 13px; | |
| font-weight: 500; | |
| padding: 5px 18px; | |
| border-radius: calc(var(--radius-md) - 3px); | |
| transition: background var(--transition), color var(--transition); | |
| letter-spacing: -0.01em; | |
| white-space: nowrap; | |
| } | |
| .view-btn:hover { background: rgba(255,255,255,0.06); color: var(--text); } | |
| .view-btn.active { | |
| background: rgba(255,255,255,0.10); | |
| color: var(--text); | |
| box-shadow: inset 0 1px 0 rgba(255,255,255,0.12); | |
| font-weight: 600; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| CODE GRAPH | |
| ══════════════════════════════════════════════════════════ */ | |
| .graph-container { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .graph-header { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 11px 24px; | |
| border-bottom: 1px solid var(--border); | |
| flex-shrink: 0; | |
| background: rgba(10, 11, 16, 0.85); | |
| backdrop-filter: blur(8px); | |
| } | |
| .graph-title { | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: var(--text-2); | |
| font-family: var(--mono); | |
| letter-spacing: -0.02em; | |
| } | |
| .graph-stats { | |
| font-size: 11px; | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| } | |
| .graph-legend { | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| padding: 8px 24px; | |
| border-bottom: 1px solid var(--border-subtle); | |
| flex-shrink: 0; | |
| background: rgba(10, 11, 16, 0.55); | |
| } | |
| .legend-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 11px; | |
| color: var(--muted); | |
| font-weight: 500; | |
| } | |
| .legend-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| box-shadow: 0 0 4px currentColor; | |
| } | |
| .graph-canvas { | |
| flex: 1; | |
| position: relative; | |
| overflow: hidden; | |
| background: | |
| radial-gradient(ellipse 50% 40% at 50% 50%, rgba(91, 143, 249,0.04) 0%, transparent 70%), | |
| var(--bg); | |
| } | |
| .graph-loading, .graph-error { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| color: var(--muted); | |
| font-size: 13px; | |
| } | |
| .graph-error { color: var(--red); } | |
| /* Tooltip */ | |
| .graph-tooltip { | |
| position: absolute; | |
| background: var(--surface-3); | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--radius-md); | |
| padding: 12px 16px; | |
| pointer-events: none; | |
| z-index: 100; | |
| min-width: 210px; | |
| max-width: 290px; | |
| box-shadow: var(--shadow-lg), 0 0 0 1px rgba(91, 143, 249,0.1); | |
| animation: tooltip-in 0.12s ease-out both; | |
| backdrop-filter: blur(12px); | |
| } | |
| @keyframes tooltip-in { | |
| from { opacity: 0; transform: scale(0.95) translateY(-4px); } | |
| to { opacity: 1; transform: scale(1) translateY(0); } | |
| } | |
| .graph-tooltip-name { | |
| font-family: var(--mono); | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: var(--accent-soft); | |
| margin-bottom: 4px; | |
| } | |
| .graph-tooltip-path { | |
| font-size: 11px; | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| margin-bottom: 5px; | |
| word-break: break-all; | |
| } | |
| .graph-tooltip-meta { | |
| font-size: 11px; | |
| color: var(--muted); | |
| margin-bottom: 10px; | |
| font-family: var(--mono); | |
| } | |
| .graph-tooltip-ask { | |
| pointer-events: all; | |
| background: linear-gradient(135deg, #3D6EE8, #7DABFF); | |
| border: 1px solid rgba(91, 143, 249,0.5); | |
| border-radius: var(--radius); | |
| color: #fff; | |
| cursor: pointer; | |
| font-family: var(--sans); | |
| font-size: 12px; | |
| font-weight: 600; | |
| padding: 6px 12px; | |
| transition: opacity var(--transition), transform var(--transition); | |
| width: 100%; | |
| letter-spacing: -0.01em; | |
| box-shadow: 0 2px 8px rgba(91, 143, 249,0.3); | |
| } | |
| .graph-tooltip-ask:hover { opacity: 0.88; transform: translateY(-1px); } | |
| /* ══════════════════════════════════════════════════════════ | |
| DIAGRAM VIEW — Mermaid system design diagrams | |
| ══════════════════════════════════════════════════════════ */ | |
| .diagram-container { | |
| display: flex; | |
| flex-direction: column; | |
| height: 100%; | |
| /* Transparent so the .main ambient layer (cursor-glow + constellation) shows | |
| through. Fullscreen variant below opts back into var(--bg) because it | |
| breaks out of .main with position:fixed. */ | |
| background: transparent; | |
| overflow: hidden; | |
| } | |
| .diagram-header { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 14px 24px; | |
| border-bottom: 1px solid var(--border); | |
| flex-shrink: 0; | |
| } | |
| .diagram-title { | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: var(--text); | |
| font-family: var(--mono); | |
| } | |
| .diagram-ask-btn { | |
| font-size: 12px; | |
| font-family: var(--sans); | |
| font-weight: 600; | |
| color: #fff; | |
| background: linear-gradient(135deg, var(--accent) 0%, #5B8FF9 100%); | |
| border: 1px solid rgba(91, 143, 249,0.5); | |
| border-radius: 6px; | |
| padding: 5px 12px; | |
| cursor: pointer; | |
| box-shadow: 0 1px 6px rgba(91, 143, 249,0.25), inset 0 1px 0 rgba(255,255,255,0.10); | |
| transition: background var(--transition), box-shadow var(--transition), transform 80ms ease; | |
| } | |
| .diagram-ask-btn:hover { | |
| background: linear-gradient(135deg, #A8C5FF 0%, var(--accent) 100%); | |
| box-shadow: 0 2px 12px rgba(91, 143, 249,0.40), inset 0 1px 0 rgba(255,255,255,0.12); | |
| transform: translateY(-1px); | |
| } | |
| .diagram-ask-btn:active { transform: translateY(0); } | |
| /* ── Type selector bar ── */ | |
| .diagram-type-bar { | |
| display: flex; | |
| gap: 8px; | |
| padding: 14px 24px; | |
| border-bottom: 1px solid var(--border-subtle); | |
| flex-shrink: 0; | |
| background: rgba(9, 9, 14, 0.80); | |
| } | |
| .diagram-type-btn { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 3px; | |
| /* More generous padding — these are feature entry points, not list items */ | |
| padding: 11px 18px; | |
| border-radius: var(--radius-md); | |
| border: 1px solid rgba(255,255,255,0.09); | |
| background: rgba(255,255,255,0.03); | |
| cursor: pointer; | |
| /* position: relative anchors the ::before gradient top-edge line */ | |
| position: relative; | |
| overflow: hidden; | |
| transition: | |
| background var(--dur-fast) var(--ease-spring), | |
| border-color var(--dur-fast) var(--ease-spring), | |
| box-shadow var(--dur-fast) var(--ease-spring-snap), | |
| transform var(--dur-fast) var(--ease-spring-snap); | |
| min-width: 150px; | |
| } | |
| /* "Lit edge" technique — a gradient line materialises at the top of the | |
| active card, mimicking light catching a raised edge. Seen on Linear, | |
| Vercel, Raycast. Invisible by default; only the active card shows it. */ | |
| .diagram-type-btn::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; left: 12px; right: 12px; | |
| height: 2px; | |
| border-radius: 0 0 2px 2px; | |
| background: linear-gradient(90deg, transparent, var(--accent), var(--accent-soft), transparent); | |
| opacity: 0; | |
| transition: opacity var(--transition); | |
| } | |
| .diagram-type-btn:hover { | |
| background: rgba(255, 255, 255,0.05); | |
| border-color: var(--accent-border); | |
| transform: translateY(-1px); | |
| box-shadow: 0 6px 20px rgba(91, 143, 249, 0.10); | |
| } | |
| .diagram-type-btn:active { transform: translateY(0); } | |
| .diagram-type-btn.active { | |
| background: rgba(91, 143, 249,0.12); | |
| border-color: rgba(91, 143, 249,0.45); | |
| box-shadow: 0 4px 20px rgba(91, 143, 249,0.08), inset 0 0 0 0 transparent; | |
| } | |
| .diagram-type-btn.active::before { opacity: 1; } | |
| .diagram-type-icon { | |
| /* Block display so the SVG sits on its own line above the label */ | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 4px; | |
| color: var(--muted); | |
| transition: color var(--transition); | |
| } | |
| .diagram-type-btn.active .diagram-type-icon, | |
| .diagram-type-btn:hover .diagram-type-icon { color: var(--accent-soft); } | |
| .diagram-type-label { | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: var(--text); | |
| font-family: var(--mono); | |
| letter-spacing: -0.01em; | |
| } | |
| .diagram-type-btn.active .diagram-type-label { color: var(--accent); } | |
| /* text-2 not muted — the description is the context, not decoration */ | |
| .diagram-type-desc { | |
| font-size: 11px; | |
| color: var(--text-2); | |
| letter-spacing: -0.01em; | |
| } | |
| /* ── Canvas ── */ | |
| .diagram-canvas { | |
| flex: 1; | |
| position: relative; | |
| overflow: auto; | |
| display: flex; | |
| align-items: flex-start; | |
| justify-content: center; | |
| padding: 24px; | |
| } | |
| .diagram-loading { | |
| position: absolute; | |
| inset: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| font-size: 13px; | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| } | |
| .diagram-error { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 12px; | |
| color: var(--red); | |
| font-size: 13px; | |
| font-family: var(--mono); | |
| padding: 24px; | |
| text-align: center; | |
| } | |
| .diagram-retry-btn { | |
| font-size: 12px; | |
| font-family: var(--sans); | |
| font-weight: 500; | |
| color: var(--text-2); | |
| background: var(--surface-3); | |
| border: 1px solid var(--border-strong); | |
| border-radius: 6px; | |
| padding: 5px 12px; | |
| cursor: pointer; | |
| transition: color var(--transition), background var(--transition), border-color var(--transition); | |
| } | |
| .diagram-retry-btn:hover { | |
| color: var(--text); | |
| background: var(--surface-4); | |
| border-color: var(--accent-border); | |
| } | |
| /* The div mermaid renders its SVG into. | |
| min-width: 100% so small diagrams still fill the canvas. | |
| width: fit-content allows wide diagrams (graph LR) to overflow | |
| and be scrolled horizontally rather than compressed. */ | |
| .diagram-render { | |
| min-width: 100%; | |
| width: fit-content; | |
| min-height: 200px; | |
| padding-bottom: 16px; | |
| } | |
| .diagram-render svg { | |
| display: block; | |
| margin: 0 auto; | |
| } | |
| /* Nodes are clickable — show pointer cursor and a brightness highlight on hover */ | |
| .diagram-render svg .node, | |
| .diagram-render svg .actor { cursor: pointer; } | |
| .diagram-render svg .node:hover rect, | |
| .diagram-render svg .node:hover circle, | |
| .diagram-render svg .node:hover polygon, | |
| .diagram-render svg .actor:hover rect { | |
| filter: brightness(1.25); | |
| transition: filter 0.15s; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| AGENT TRACE — Claude Code-style connected timeline | |
| ══════════════════════════════════════════════════════════ */ | |
| .agent-trace { | |
| width: 100%; | |
| max-width: 800px; | |
| margin-bottom: 10px; | |
| } | |
| /* Toggle row: "Agent ●——● 3 steps ▾" */ | |
| .agent-trace-toggle { | |
| display: flex; | |
| align-items: center; | |
| gap: 7px; | |
| background: none; | |
| border: none; | |
| padding: 0; | |
| font-family: var(--sans); | |
| font-size: 12px; | |
| font-weight: 500; | |
| color: var(--muted); | |
| letter-spacing: -0.01em; | |
| width: 100%; | |
| text-align: left; | |
| margin-bottom: 4px; | |
| } | |
| .agent-trace-toggle:hover { color: var(--text-2); } | |
| /* The "Agent" root node — filled violet circle */ | |
| .agent-trace-root-dot { | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| background: var(--accent); | |
| box-shadow: 0 0 8px rgba(91, 143, 249,0.5); | |
| flex-shrink: 0; | |
| } | |
| .agent-trace-root-label { | |
| font-weight: 600; | |
| color: var(--accent-soft); | |
| font-size: 12px; | |
| } | |
| .agent-trace-count { | |
| color: var(--muted); | |
| font-size: 11px; | |
| } | |
| .agent-trace-chevron { | |
| font-size: 11px; | |
| color: var(--muted); | |
| margin-left: auto; | |
| } | |
| /* Steps container — relative so the vertical line can be positioned inside */ | |
| .agent-trace-steps { | |
| position: relative; | |
| margin-top: 4px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0; | |
| padding-left: 5px; | |
| } | |
| /* The continuous vertical line connecting all nodes */ | |
| .agent-trace-line { | |
| position: absolute; | |
| left: 4px; /* center of the 10px dot */ | |
| top: 6px; | |
| bottom: 12px; | |
| width: 2px; | |
| background: linear-gradient(to bottom, var(--accent-border), rgba(91, 143, 249,0.08)); | |
| pointer-events: none; | |
| } | |
| /* Individual step row */ | |
| .agent-step { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 0; | |
| padding: 5px 0 5px 0; | |
| animation: step-in 0.18s cubic-bezier(0.16, 1, 0.3, 1) both; | |
| position: relative; | |
| } | |
| @keyframes step-in { | |
| from { opacity: 0; transform: translateX(-6px); } | |
| to { opacity: 1; transform: translateX(0); } | |
| } | |
| /* Node dot + arrow: sits on top of the vertical line */ | |
| .agent-step-node { | |
| display: flex; | |
| align-items: center; | |
| flex-shrink: 0; | |
| gap: 5px; | |
| padding-top: 2px; | |
| } | |
| .agent-step-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background: var(--surface-3); | |
| border: 1.5px solid var(--accent-border); | |
| flex-shrink: 0; | |
| z-index: 1; | |
| transition: background var(--transition), border-color var(--transition); | |
| } | |
| .agent-step.done .agent-step-dot { | |
| background: var(--accent-dim); | |
| border-color: var(--accent-soft); | |
| } | |
| .agent-step.pending .agent-step-dot { | |
| background: var(--accent); | |
| border-color: var(--accent-light); | |
| box-shadow: 0 0 6px rgba(91, 143, 249,0.6); | |
| } | |
| .agent-step-arrow { | |
| font-size: 11px; | |
| color: var(--muted); | |
| line-height: 1; | |
| } | |
| /* Step body: tool name + query + output */ | |
| .agent-step-body { | |
| flex: 1; | |
| min-width: 0; | |
| background: rgba(10,11,16,0.75); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| padding: 7px 11px; | |
| margin-left: 4px; | |
| font-size: 12px; | |
| transition: border-color var(--transition); | |
| } | |
| .agent-step.pending .agent-step-body { | |
| border-color: var(--accent-border); | |
| background: rgba(91, 143, 249,0.04); | |
| } | |
| .agent-step.done .agent-step-body { opacity: 0.8; } | |
| .agent-step.collapsed .agent-step-body { opacity: 0.55; transition: opacity var(--transition), border-color var(--transition); } | |
| .agent-step.collapsed .agent-step-body:hover { opacity: 0.85; border-color: var(--accent-border); } | |
| .agent-step-header { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 6px; | |
| flex-wrap: wrap; | |
| } | |
| /* SVG icon — rendered inline with color inherited */ | |
| .agent-step-icon { | |
| color: var(--accent-soft); | |
| flex-shrink: 0; | |
| margin-top: 1px; | |
| display: flex; | |
| } | |
| .agent-step-tool { | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: var(--accent-soft); | |
| white-space: nowrap; | |
| } | |
| .agent-step-query { | |
| color: var(--text-2); | |
| font-size: 11px; | |
| white-space: normal; | |
| word-break: break-all; | |
| flex: 1; | |
| } | |
| .agent-step-output { | |
| margin-top: 7px; | |
| padding-top: 7px; | |
| border-top: 1px solid var(--border-subtle); | |
| color: var(--muted); | |
| font-size: 11px; | |
| font-family: var(--mono); | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| } | |
| /* Only clip/fade when content is long enough to need it */ | |
| .agent-step-output.clipped { | |
| max-height: 120px; | |
| overflow: hidden; | |
| mask-image: linear-gradient(to bottom, black 60%, transparent 100%); | |
| -webkit-mask-image: linear-gradient(to bottom, black 60%, transparent 100%); | |
| cursor: pointer; | |
| } | |
| .agent-step-output.expanded { | |
| max-height: none; | |
| overflow: visible; | |
| mask-image: none; | |
| -webkit-mask-image: none; | |
| cursor: default; | |
| } | |
| .agent-step-expand { | |
| background: rgba(91, 143, 249,0.08); | |
| border: 1px solid var(--accent-border); | |
| border-radius: 4px; | |
| color: var(--accent-soft); | |
| font-size: 10px; | |
| font-family: var(--sans); | |
| cursor: pointer; | |
| padding: 3px 8px; | |
| margin-top: 6px; | |
| opacity: 0.9; | |
| letter-spacing: 0.01em; | |
| display: inline-block; | |
| transition: background var(--transition), opacity var(--transition); | |
| } | |
| .agent-step-expand:hover { | |
| opacity: 1; | |
| background: rgba(91, 143, 249,0.18); | |
| } | |
| /* Agent thought bubble — reasoning text emitted before a tool call. | |
| gap: 0 matches agent-step so the body margin-left places content at the same x. */ | |
| .agent-thought { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 0; | |
| padding: 2px 0 6px 0; | |
| } | |
| /* Body wrapper — mirrors agent-step-body positioning (margin-left: 4px) so | |
| thought text starts at the exact same column as tool step card content. */ | |
| .agent-thought-body { | |
| flex: 1; | |
| min-width: 0; | |
| margin-left: 4px; | |
| } | |
| /* Header row inside body: text + chevron side by side */ | |
| .agent-thought-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| cursor: pointer; | |
| } | |
| .thought-dot { | |
| background: transparent ; | |
| border-color: var(--muted) ; | |
| margin-top: 3px; | |
| } | |
| .agent-thought-text { | |
| flex: 1; | |
| min-width: 0; | |
| font-size: 11.5px; | |
| font-style: italic; | |
| color: var(--text-2); | |
| line-height: 1.5; | |
| opacity: 0.8; | |
| padding-top: 1px; | |
| } | |
| /* Past (completed) thoughts — collapsed to one line, click to expand */ | |
| .agent-thought-past { | |
| padding: 1px 0 1px 0; | |
| border-radius: var(--radius-sm); | |
| transition: background var(--transition); | |
| cursor: pointer; | |
| } | |
| .agent-thought-past:hover .agent-thought-body { | |
| opacity: 0.8; | |
| } | |
| .agent-thought-past .agent-thought-text { | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| opacity: 0.45; | |
| } | |
| .agent-thought-past.agent-thought-open .agent-thought-text { | |
| white-space: normal; | |
| overflow: visible; | |
| text-overflow: unset; | |
| opacity: 0.7; | |
| } | |
| .agent-thought-chevron { | |
| flex-shrink: 0; | |
| color: var(--muted); | |
| opacity: 0.5; | |
| margin-left: 6px; | |
| display: flex; | |
| align-items: center; | |
| } | |
| .agent-thinking { | |
| display: flex; | |
| align-items: center; | |
| gap: 9px; | |
| font-size: 12px; | |
| color: var(--text-2); | |
| margin-bottom: 8px; | |
| width: 100%; | |
| max-width: 800px; | |
| font-weight: 500; | |
| letter-spacing: -0.01em; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| INGEST PROGRESS | |
| ══════════════════════════════════════════════════════════ */ | |
| .ingest-progress { | |
| margin-top: 10px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| } | |
| .ingest-step { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 8px; | |
| font-size: 0.78rem; | |
| padding: 5px 8px; | |
| border-radius: var(--radius); | |
| color: var(--muted); | |
| animation: step-in var(--transition) both; | |
| } | |
| .ingest-step.active { | |
| color: var(--text); | |
| background: rgba(91, 143, 249,0.08); | |
| border: 1px solid var(--accent-border); | |
| box-shadow: inset 3px 0 0 var(--accent), 0 2px 8px rgba(91, 143, 249,0.10); | |
| } | |
| .ingest-step.done { color: var(--faint); } | |
| .ingest-step.error { color: var(--red); } | |
| .ingest-step-icon { | |
| flex-shrink: 0; | |
| width: 14px; | |
| display: flex; | |
| align-items: center; | |
| padding-top: 1px; | |
| } | |
| .ingest-step.active .ingest-step-icon { animation: pulse 1.2s ease-in-out infinite; } | |
| .ingest-step.done .ingest-step-icon { color: var(--green); } | |
| .ingest-step-detail { flex: 1; line-height: 1.45; } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.3; } | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| KEYBOARD SHORTCUT HINT | |
| ══════════════════════════════════════════════════════════ */ | |
| .input-hint { | |
| position: absolute; | |
| right: 54px; /* just left of the send button (34px wide + 10px from right + 10px gap) */ | |
| top: 50%; | |
| transform: translateY(-50%); | |
| font-size: 11px; | |
| color: var(--faint); | |
| font-family: var(--mono); | |
| pointer-events: none; | |
| background: rgba(255, 255, 255,0.04); | |
| border: 1px solid var(--border); | |
| padding: 2px 7px; | |
| border-radius: var(--radius-sm); | |
| letter-spacing: 0.02em; | |
| user-select: none; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| SCROLLBAR | |
| ══════════════════════════════════════════════════════════ */ | |
| ::-webkit-scrollbar { width: 5px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { | |
| /* warm parchment tint — no cold white */ | |
| background: rgba(255, 255, 255, 0.12); | |
| border-radius: 3px; | |
| transition: background var(--transition); | |
| } | |
| ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.25); } | |
| /* ══════════════════════════════════════════════════════════ | |
| MCP SERVER STATUS PANEL | |
| ══════════════════════════════════════════════════════════ */ | |
| .mcp-panel { | |
| /* Pinned at bottom of sidebar — frosted glass treatment like Raycast status bar */ | |
| margin: 0 14px 14px; | |
| border: 1px solid rgba(91, 143, 249, 0.15); | |
| border-radius: var(--radius-md); | |
| overflow: clip; | |
| font-size: 12px; | |
| background: rgba(8, 8, 18, 0.80); | |
| backdrop-filter: blur(12px); | |
| -webkit-backdrop-filter: blur(12px); | |
| flex-shrink: 0; | |
| box-shadow: | |
| inset 0 1px 0 rgba(91, 143, 249, 0.22), | |
| inset 0 0 0 1px rgba(255, 255, 255, 0.04), | |
| 0 -2px 16px rgba(0, 0, 0, 0.30); | |
| } | |
| .mcp-panel-header { | |
| width: 100%; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 9px 11px; | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| color: var(--text-2); | |
| text-align: left; | |
| font-family: var(--sans); | |
| font-size: 12px; | |
| transition: background var(--transition); | |
| } | |
| .mcp-panel-header:hover { background: rgba(255, 255, 255,0.04); } | |
| .mcp-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| } | |
| .mcp-dot.connected { | |
| background: #34D399; | |
| box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.70); | |
| animation: dot-pulse 2s ease-out infinite; | |
| } | |
| @keyframes dot-pulse { | |
| 0% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.70); } | |
| 70% { box-shadow: 0 0 0 6px rgba(52, 211, 153, 0.00); } | |
| 100% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.00); } | |
| } | |
| .mcp-dot.disconnected { background: var(--faint); } | |
| .mcp-panel-title { font-weight: 600; flex: 1; letter-spacing: -0.01em; } | |
| .mcp-counts { | |
| font-size: 11px; | |
| color: var(--muted); | |
| font-variant-numeric: tabular-nums; | |
| font-family: var(--mono); | |
| } | |
| .mcp-chevron { color: var(--muted); flex-shrink: 0; } | |
| .mcp-panel-body { | |
| border-top: 1px solid var(--border-subtle); | |
| padding: 9px 11px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| max-height: 240px; | |
| overflow-y: auto; | |
| } | |
| .mcp-error { color: var(--muted); font-size: 11px; margin: 0; } | |
| /* Primer — a single line that turns a debug list into part of the product story. | |
| Sets context once at the top of the expanded panel. */ | |
| .mcp-primer { | |
| margin: 0 0 4px; | |
| padding: 6px 8px; | |
| font-size: 10.5px; | |
| line-height: 1.45; | |
| color: var(--muted); | |
| background: rgba(91, 143, 249, 0.04); | |
| border: 1px solid rgba(91, 143, 249, 0.10); | |
| border-radius: var(--radius-sm); | |
| } | |
| .mcp-section { display: flex; flex-direction: column; gap: 4px; } | |
| .mcp-section-label { | |
| display: flex; | |
| align-items: baseline; | |
| justify-content: space-between; | |
| gap: 6px; | |
| font-size: 10px; | |
| font-weight: 600; | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| color: var(--faint); | |
| margin-bottom: 2px; | |
| font-family: var(--sans); | |
| } | |
| .mcp-section-count { | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| color: rgba(180, 200, 255, 0.45); | |
| font-variant-numeric: tabular-nums; | |
| letter-spacing: 0; | |
| text-transform: none; | |
| } | |
| /* Container row — holds the clickable header and (when open) the detail panel. */ | |
| .mcp-row { | |
| display: flex; | |
| flex-direction: column; | |
| border-radius: var(--radius-sm); | |
| transition: background var(--transition); | |
| } | |
| .mcp-row.is-open { background: rgba(91, 143, 249, 0.05); } | |
| /* Rich list row — small category icon + name + one-line description. | |
| Reads as a capabilities gallery rather than a symbol dump. */ | |
| .mcp-item { | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 8px; | |
| padding: 5px 6px; | |
| border-radius: var(--radius-sm); | |
| transition: background var(--transition); | |
| cursor: pointer; | |
| width: 100%; | |
| background: none; | |
| border: none; | |
| text-align: left; | |
| font: inherit; | |
| color: inherit; | |
| } | |
| .mcp-item:hover { background: rgba(91, 143, 249, 0.06); } | |
| .mcp-item:focus-visible { outline: none; background: rgba(91, 143, 249, 0.08); } | |
| .mcp-item-chevron { | |
| flex-shrink: 0; | |
| color: var(--faint); | |
| opacity: 0.6; | |
| margin-top: 2px; | |
| transition: transform var(--transition), opacity var(--transition); | |
| } | |
| .mcp-row.is-open .mcp-item-chevron { | |
| transform: rotate(180deg); | |
| opacity: 1; | |
| } | |
| /* Expanded detail — full description + argument signature. Appears below the | |
| row it belongs to, visually linked by shared rounded container + subtle tint. */ | |
| .mcp-detail { | |
| padding: 6px 10px 10px 30px; /* align left edge with the icon column */ | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| border-top: 1px dashed rgba(255, 255, 255, 0.06); | |
| margin: 2px 4px 0; | |
| } | |
| .mcp-detail-desc { | |
| margin: 0; | |
| font-size: 11px; | |
| line-height: 1.5; | |
| color: var(--text-2); | |
| } | |
| .mcp-detail-empty { | |
| margin: 0; | |
| font-size: 10.5px; | |
| color: var(--muted); | |
| font-style: italic; | |
| } | |
| .mcp-detail-hint { | |
| margin: 0; | |
| font-size: 10.5px; | |
| color: var(--muted); | |
| line-height: 1.5; | |
| } | |
| .mcp-detail-hint code { | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| padding: 1px 4px; | |
| background: rgba(91, 143, 249, 0.12); | |
| border-radius: 3px; | |
| color: var(--accent-soft); | |
| } | |
| .mcp-detail-preview { | |
| margin: 0; | |
| padding: 8px 10px; | |
| font-family: var(--mono); | |
| font-size: 10.5px; | |
| line-height: 1.5; | |
| color: var(--text-2); | |
| background: rgba(8, 10, 20, 0.65); | |
| border: 1px solid rgba(255, 255, 255, 0.05); | |
| border-radius: var(--radius-sm); | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| max-height: 200px; | |
| overflow-y: auto; | |
| } | |
| .mcp-detail-action { | |
| align-self: flex-start; | |
| padding: 4px 10px; | |
| font-size: 10.5px; | |
| font-family: var(--sans); | |
| color: var(--accent-soft); | |
| background: rgba(91, 143, 249, 0.10); | |
| border: 1px solid rgba(91, 143, 249, 0.25); | |
| border-radius: var(--radius-sm); | |
| cursor: pointer; | |
| transition: background var(--transition), border-color var(--transition); | |
| } | |
| .mcp-detail-action:hover { | |
| background: rgba(91, 143, 249, 0.18); | |
| border-color: rgba(91, 143, 249, 0.45); | |
| } | |
| /* Argument signature — compact table-like list of parameters. */ | |
| .mcp-sig { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| } | |
| .mcp-sig-label { | |
| font-size: 9.5px; | |
| font-weight: 600; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: var(--faint); | |
| } | |
| .mcp-sig-arg { | |
| display: flex; | |
| flex-wrap: wrap; | |
| align-items: baseline; | |
| gap: 6px; | |
| font-size: 10.5px; | |
| line-height: 1.4; | |
| } | |
| .mcp-sig-name { | |
| font-family: var(--mono); | |
| font-size: 10.5px; | |
| color: var(--accent-soft); | |
| } | |
| .mcp-sig-type { | |
| font-family: var(--mono); | |
| font-size: 9.5px; | |
| color: var(--muted); | |
| padding: 0 5px; | |
| border: 1px solid rgba(255, 255, 255, 0.08); | |
| border-radius: 3px; | |
| } | |
| .mcp-sig-req { | |
| font-size: 9px; | |
| letter-spacing: 0.06em; | |
| text-transform: uppercase; | |
| color: #F59E0B; | |
| background: rgba(245, 158, 11, 0.08); | |
| border: 1px solid rgba(245, 158, 11, 0.18); | |
| padding: 0 5px; | |
| border-radius: 3px; | |
| font-weight: 600; | |
| } | |
| .mcp-sig-desc { | |
| flex-basis: 100%; | |
| color: var(--muted); | |
| font-size: 10.5px; | |
| line-height: 1.45; | |
| padding-left: 2px; | |
| } | |
| .mcp-kind { | |
| flex-shrink: 0; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 16px; | |
| height: 16px; | |
| border-radius: 4px; | |
| margin-top: 1px; | |
| border: 1px solid rgba(255, 255, 255, 0.06); | |
| } | |
| .mcp-kind-tool { color: #7DABFF; background: rgba(125, 171, 255, 0.10); } | |
| .mcp-kind-resource { color: #6EE7B7; background: rgba(110, 231, 183, 0.08); } | |
| .mcp-kind-prompt { color: #C4B5FD; background: rgba(196, 181, 253, 0.08); } | |
| .mcp-item-content { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1px; | |
| min-width: 0; | |
| flex: 1; | |
| } | |
| .mcp-item-name { | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| color: var(--accent-soft); | |
| line-height: 1.3; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| .mcp-item-desc { | |
| font-size: 10.5px; | |
| color: var(--muted); | |
| line-height: 1.35; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| font-family: var(--sans); | |
| } | |
| .mcp-uri { | |
| color: var(--green); | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| PROMPT AUTOCOMPLETE MENU | |
| ══════════════════════════════════════════════════════════ */ | |
| .prompt-menu { | |
| position: absolute; | |
| bottom: calc(100% + 8px); | |
| left: 0; | |
| right: 0; | |
| /* Glassmorphism menu */ | |
| background: var(--surface-3); | |
| border: 1px solid var(--accent-border); | |
| border-radius: var(--radius-md); | |
| overflow: hidden; | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(91, 143, 249,0.10); | |
| z-index: 100; | |
| backdrop-filter: blur(16px); | |
| } | |
| .prompt-menu-label { | |
| font-size: 10px; | |
| font-weight: 700; | |
| letter-spacing: 0.10em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| padding: 7px 13px 4px; | |
| font-family: var(--mono); | |
| } | |
| .prompt-menu-item { | |
| width: 100%; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-start; | |
| gap: 2px; | |
| padding: 9px 13px; | |
| background: none; | |
| border: none; | |
| border-top: 1px solid var(--border-subtle); | |
| cursor: pointer; | |
| text-align: left; | |
| transition: background var(--transition); | |
| font-family: var(--sans); | |
| } | |
| .prompt-menu-item:hover { background: var(--accent-dim); } | |
| .prompt-menu-name { | |
| font-family: var(--mono); | |
| font-size: 13px; | |
| color: var(--accent-soft); | |
| font-weight: 600; | |
| } | |
| .prompt-menu-desc { | |
| font-size: 11px; | |
| color: var(--muted); | |
| line-height: 1.4; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| MOBILE | |
| ══════════════════════════════════════════════════════════ */ | |
| .mobile-menu-btn { | |
| display: none; | |
| align-items: center; | |
| justify-content: center; | |
| width: 32px; | |
| height: 32px; | |
| background: none; | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| color: var(--muted); | |
| cursor: pointer; | |
| flex-shrink: 0; | |
| transition: color var(--transition), border-color var(--transition); | |
| } | |
| .mobile-menu-btn:hover { color: var(--text); border-color: var(--border-strong); } | |
| .sidebar-overlay { | |
| display: none; | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0,0,0,0.65); | |
| z-index: 199; | |
| backdrop-filter: blur(4px); | |
| } | |
| @media (max-width: 768px) { | |
| /* Touch scroll fix: html overflow:hidden blocks momentum scroll on iOS. | |
| Switch to clip (visual only, no scroll suppression) so the messages | |
| area can scroll normally via touch. */ | |
| html { overflow: clip; } | |
| .layout { grid-template-columns: 1fr; } | |
| .sidebar { | |
| position: fixed; | |
| inset: 0 auto 0 0; | |
| width: min(280px, 85vw); | |
| z-index: 200; | |
| transform: translateX(-100%); | |
| transition: transform var(--transition-slow); | |
| height: 100dvh; | |
| } | |
| .sidebar.open { transform: translateX(0); } | |
| .sidebar-overlay { display: block; } | |
| .mobile-menu-btn { display: flex; } | |
| .message.user, .message.assistant { max-width: 100%; } | |
| .messages { padding: 14px 16px; gap: 16px; -webkit-overflow-scrolling: touch; } | |
| .bubble { padding: 11px 14px; } | |
| .input-bar { padding: 9px 14px 12px; } | |
| .chat-header { padding: 10px 16px; } | |
| /* Hide ⌘K hint on mobile — not relevant for touch devices */ | |
| .input-hint { display: none; } | |
| /* Onboarding: scale down the large headline and collapse the 3-col grid */ | |
| .onboarding-headline { font-size: 36px; letter-spacing: -0.025em; } | |
| .onboarding-sub { font-size: 13px; } | |
| .onboarding-grid { grid-template-columns: 1fr; gap: 8px; } | |
| /* Blobs are oversized and cause layout issues at 390px — shrink them */ | |
| .empty-state::before { width: 280px; height: 220px; } | |
| .empty-state::after { width: 240px; height: 200px; } | |
| /* Allow onboarding content to scroll if it overflows on short screens */ | |
| .empty-state { justify-content: flex-start; padding-top: 32px; overflow-y: auto; } | |
| /* ── Diagram header ── */ | |
| /* Title ("Explore — owner/repo") takes too much width — context is clear from tabs */ | |
| .diagram-title { display: none; } | |
| .diagram-header { padding: 10px 14px; } | |
| /* Shrink action buttons to icon+symbol only — hide the text labels */ | |
| .diagram-retry-btn { font-size: 11px; padding: 5px 8px; } | |
| .diagram-ask-btn { font-size: 10px; padding: 5px 8px; } | |
| /* ── Tab bar ── */ | |
| /* Horizontal scroll so tabs never wrap or overflow */ | |
| .diagram-type-bar { overflow-x: auto; padding: 8px 14px; gap: 6px; -webkit-overflow-scrolling: touch; } | |
| /* Switch each tab from column (icon / label / desc) to row (icon + label) */ | |
| .diagram-type-btn { | |
| flex-direction: row; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 7px 10px; | |
| min-width: unset; | |
| flex-shrink: 0; | |
| } | |
| /* Hide the subtitle description line — label alone is enough on mobile */ | |
| .diagram-type-desc { display: none; } | |
| .diagram-type-icon { margin-bottom: 0; } | |
| /* Source card header: let filepath truncate early so the row doesn't overflow */ | |
| .source-github-link { max-width: 120px; } | |
| /* Hide the open-in-github icon button on mobile — the filename link covers it */ | |
| .source-open-btn { display: none; } | |
| /* Cap textarea growth on mobile so it doesn't consume the whole screen */ | |
| .input-bar textarea { max-height: 120px; overflow-y: auto; } | |
| /* Diagram/Explore canvas: hint that it's pannable/zoomable on touch */ | |
| .ec-canvas-wrapper { touch-action: none; } | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| MESSAGE ENTRY ANIMATION | |
| ══════════════════════════════════════════════════════════ */ | |
| @keyframes msgIn { | |
| from { opacity: 0; transform: translateY(6px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| STREAMING PHASE INDICATOR | |
| Makes the invisible retrieval step visible: | |
| "Searching code…" → "Found 4 sources · Generating answer…" | |
| ══════════════════════════════════════════════════════════ */ | |
| @keyframes phasePulse { | |
| 0%, 100% { opacity: 1; transform: scale(1); } | |
| 50% { opacity: 0.3; transform: scale(0.75); } | |
| } | |
| .stream-phase { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 12px; | |
| font-weight: 500; | |
| /* text-2 = readable warm cream rather than near-invisible muted */ | |
| color: var(--text-2); | |
| margin-bottom: 8px; | |
| width: 100%; | |
| max-width: 800px; | |
| letter-spacing: -0.01em; | |
| } | |
| .stream-phase--generating { | |
| color: var(--accent-soft); | |
| } | |
| .stream-phase-dot { | |
| width: 7px; | |
| height: 7px; | |
| border-radius: 50%; | |
| background: currentColor; | |
| flex-shrink: 0; | |
| animation: phasePulse 1s ease-in-out infinite; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| COPY ANSWER BUTTON | |
| Appears on hover over the assistant bubble — lets devs | |
| copy the full markdown text to clipboard in one click. | |
| ══════════════════════════════════════════════════════════ */ | |
| .copy-answer-btn { | |
| position: absolute; | |
| top: 10px; | |
| right: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 28px; | |
| height: 28px; | |
| background: var(--surface-3); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| color: var(--muted); | |
| cursor: pointer; | |
| opacity: 0; | |
| transition: opacity var(--transition), background var(--transition), color var(--transition); | |
| z-index: 2; | |
| } | |
| .bubble:hover .copy-answer-btn { opacity: 1; } | |
| .copy-answer-btn:hover { | |
| color: var(--text); | |
| background: var(--surface-4); | |
| border-color: var(--border-strong); | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| BACKEND HEALTH DOT | |
| Green = connected, Red = unreachable | |
| ══════════════════════════════════════════════════════════ */ | |
| .backend-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| box-shadow: 0 0 8px currentColor; | |
| opacity: 0.9; | |
| } | |
| /* Pulse ring — only on the connected (green) state — signals "live" */ | |
| .backend-dot[style*="var(--green)"], | |
| .backend-dot[title="Backend connected"] { | |
| animation: dot-pulse 2s ease-out infinite; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| RECENT CHATS — session list in sidebar | |
| ══════════════════════════════════════════════════════════ */ | |
| .session-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1px; | |
| } | |
| .session-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| border-radius: var(--radius); | |
| border-left: 2px solid transparent; | |
| transition: background var(--transition), border-left-color var(--transition); | |
| } | |
| .session-item:hover { | |
| background: var(--surface-2); | |
| border-left-color: rgba(91, 143, 249, 0.4); | |
| } | |
| .session-item.active { | |
| background: var(--accent-dim); | |
| border-left-color: var(--accent); | |
| } | |
| .session-item.active .session-title { color: var(--accent-soft); } | |
| .session-btn { | |
| flex: 1; | |
| min-width: 0; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| padding: 8px 10px; | |
| border-radius: var(--radius); | |
| text-align: left; | |
| font-family: var(--sans); | |
| } | |
| .session-title { | |
| flex: 1; | |
| font-size: 12px; | |
| color: var(--text-2); | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| letter-spacing: -0.01em; | |
| } | |
| .session-btn:hover .session-title { color: var(--text); } | |
| .session-time { | |
| font-size: 10px; | |
| color: var(--muted); | |
| opacity: 0.65; | |
| flex-shrink: 0; | |
| font-family: var(--mono); | |
| /* Tabular nums so "5m ago" / "12m ago" / "2h ago" align consistently | |
| down the session list — same craft signal as .repo-count. */ | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .session-delete { | |
| background: none; | |
| border: none; | |
| color: var(--muted); | |
| cursor: pointer; | |
| font-size: 14px; | |
| line-height: 1; | |
| padding: 5px 7px; | |
| min-width: 26px; | |
| min-height: 26px; | |
| border-radius: var(--radius-sm); | |
| opacity: 0; | |
| transition: opacity var(--transition), color var(--transition); | |
| flex-shrink: 0; | |
| } | |
| .session-item:hover .session-delete { opacity: 1; } | |
| .session-delete:hover { color: var(--red); } | |
| .session-confirm { | |
| display: flex; | |
| gap: 4px; | |
| flex-shrink: 0; | |
| padding-right: 4px; | |
| } | |
| .session-confirm-yes, | |
| .session-confirm-no { | |
| background: none; | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-sm); | |
| font-size: 10px; | |
| font-family: var(--sans); | |
| cursor: pointer; | |
| padding: 2px 7px; | |
| transition: color var(--transition), border-color var(--transition); | |
| } | |
| .session-confirm-yes { color: var(--red); } | |
| .session-confirm-yes:hover { border-color: var(--red); } | |
| .session-confirm-no { color: var(--muted); } | |
| /* ══════════════════════════════════════════════════════════ | |
| FEATURE 2: STALENESS INDICATOR + RE-INDEX BUTTON | |
| ══════════════════════════════════════════════════════════ */ | |
| /* Staleness badge — small pill next to the chunk count */ | |
| .repo-staleness { | |
| font-size: 10px; | |
| font-family: var(--mono); | |
| padding: 1px 5px; | |
| border-radius: var(--radius-full); | |
| border: 1px solid; | |
| flex-shrink: 0; | |
| } | |
| .repo-staleness--warn { | |
| color: var(--warning); | |
| border-color: rgba(201, 147, 58, 0.38); | |
| background: rgba(201, 147, 58, 0.10); | |
| } | |
| .repo-staleness--stale { | |
| color: var(--red); | |
| border-color: rgba(200, 104, 88, 0.38); | |
| background: rgba(200, 104, 88, 0.10); | |
| } | |
| .repo-staleness--fresh { | |
| color: var(--green); | |
| border-color: rgba(114, 184, 126, 0.35); | |
| background: rgba(114, 184, 126, 0.08); | |
| } | |
| /* Re-index button ⟳ */ | |
| /* README icon — same hover-reveal pattern as reindex/delete */ | |
| .repo-readme-btn { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| color: var(--muted); | |
| width: 26px; | |
| height: 26px; | |
| padding: 0; | |
| border-radius: var(--radius-sm); | |
| flex-shrink: 0; | |
| transition: color var(--transition), background var(--transition); | |
| } | |
| .repo-readme-btn:hover { color: var(--accent-soft); background: var(--accent-dim); } | |
| .repo-reindex { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| color: var(--muted); | |
| font-size: 15px; | |
| line-height: 1; | |
| width: 26px; | |
| height: 26px; | |
| padding: 0; | |
| border-radius: var(--radius-sm); | |
| flex-shrink: 0; | |
| transition: color var(--transition), background var(--transition), transform 400ms linear; | |
| } | |
| .repo-reindex:hover { color: var(--accent-soft); background: var(--accent-dim); } | |
| .repo-reindex:disabled { opacity: 0.4; cursor: not-allowed; } | |
| .repo-reindex.spinning { | |
| animation: spin 0.8s linear infinite; | |
| color: var(--accent-soft); | |
| } | |
| @keyframes spin { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| /* Glow pulse on the re-index button after a successful re-index. | |
| Fades over 3s (matching the reindexDone timeout in Sidebar.jsx). */ | |
| .repo-reindex.done-glow { | |
| color: var(--accent-soft); | |
| animation: reindex-done-pulse 3s ease-out forwards; | |
| } | |
| @keyframes reindex-done-pulse { | |
| 0% { color: var(--accent-soft); text-shadow: 0 0 8px var(--accent), 0 0 16px var(--accent-glow); } | |
| 40% { color: #fff; text-shadow: 0 0 12px var(--accent), 0 0 24px var(--accent-glow); } | |
| 100% { color: var(--muted); text-shadow: none; } | |
| } | |
| /* Re-index progress bar — sits flush at the bottom of the repo card */ | |
| .repo-reindex-progress { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background: var(--surface-4); | |
| } | |
| .repo-reindex-progress-bar { | |
| height: 100%; | |
| background: var(--accent); | |
| border-radius: 0 1px 1px 0; | |
| transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1); | |
| box-shadow: 0 0 6px var(--accent); | |
| /* Shimmer shows the bar is alive even when progress is slow */ | |
| background-image: linear-gradient( | |
| 90deg, | |
| var(--accent) 0%, | |
| rgba(160, 120, 255, 0.9) 45%, | |
| var(--accent) 100% | |
| ); | |
| background-size: 200% 100%; | |
| animation: reindex-shimmer 1.8s ease-in-out infinite; | |
| } | |
| @keyframes reindex-shimmer { | |
| 0% { background-position: 200% center; } | |
| 100% { background-position: -200% center; } | |
| } | |
| /* When re-index completes: bar stays at 100% and slowly exhales its glow | |
| over 8s before fading out. Matches the reindexDone timeout in Sidebar.jsx. | |
| The glow intensity peaks early (pulse) then drifts to nothing — like a | |
| heartbeat flatlining gracefully. */ | |
| .repo-reindex-progress-bar.done { | |
| animation: reindex-done-fade 8s ease-out forwards; | |
| background: var(--accent); | |
| background-image: none; /* stop shimmer */ | |
| } | |
| @keyframes reindex-done-fade { | |
| 0% { opacity: 1; box-shadow: 0 0 10px 2px var(--accent), 0 0 20px var(--accent-glow); } | |
| 15% { opacity: 1; box-shadow: 0 0 14px 3px var(--accent), 0 0 28px var(--accent-glow); } | |
| 60% { opacity: 0.6; box-shadow: 0 0 6px var(--accent); } | |
| 100% { opacity: 0; box-shadow: none; } | |
| } | |
| /* ── Concept tour arrow travel animation ─────────────────────────────────── */ | |
| /* Arrows use SVG <animateMotion> for a traveling dot (direction is communicated | |
| by the dot's movement, not CSS). The glow dot is rendered inline in ExploreView. | |
| Highlighted arrows on hover use a drop-shadow filter instead of the dot. */ | |
| /* ── Agent trace panel ───────────────────────────────────────────────────── */ | |
| .ec-trace-panel { | |
| margin: 0 0 12px; | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| overflow: hidden; | |
| background: var(--surface-2); | |
| } | |
| .ec-trace-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 8px 12px; | |
| cursor: pointer; | |
| user-select: none; | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: var(--text-2); | |
| letter-spacing: 0.04em; | |
| text-transform: uppercase; | |
| } | |
| .ec-trace-header:hover { color: var(--text); } | |
| .ec-trace-header svg { flex-shrink: 0; color: var(--accent); } | |
| .ec-trace-body { | |
| padding: 0 12px 10px; | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| line-height: 1.7; | |
| max-height: 260px; | |
| overflow-y: auto; | |
| } | |
| .ec-trace-line { | |
| display: flex; | |
| gap: 8px; | |
| align-items: flex-start; | |
| padding: 1px 0; | |
| } | |
| .ec-trace-icon { flex-shrink: 0; width: 14px; color: var(--muted); margin-top: 2px; } | |
| .ec-trace-icon.thinking { color: var(--accent); } | |
| .ec-trace-icon.react { color: #fb923c; } /* orange — tool call */ | |
| .ec-trace-icon.found { color: #4ade80; } | |
| .ec-trace-icon.file { color: var(--accent-soft); } | |
| .ec-trace-icon.finding { color: #facc15; } | |
| .ec-trace-icon.info { color: var(--muted); } | |
| .ec-trace-text { color: var(--text-2); flex: 1; min-width: 0; word-break: break-word; } | |
| .ec-trace-name { color: var(--text); font-weight: 600; } | |
| .ec-trace-sub { color: var(--muted); font-size: 10px; } | |
| /* ReAct trace entries: tool call on its own line (mono, accent), THINK below (faint, tiny) */ | |
| .ec-trace-react-tool { | |
| display: block; | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| color: var(--accent-soft); | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .ec-trace-react-think { | |
| display: block; | |
| font-size: 9px; | |
| color: var(--faint); | |
| margin-top: 1px; | |
| line-height: 1.3; | |
| } | |
| .ec-trace-stages { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 4px; | |
| margin-top: 2px; | |
| } | |
| .ec-trace-stage-pill { | |
| padding: 1px 6px; | |
| border-radius: 10px; | |
| background: var(--surface-4); | |
| color: var(--accent-soft); | |
| font-size: 10px; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| FEATURE 3: SESSION SEARCH + EDITABLE TITLE | |
| ══════════════════════════════════════════════════════════ */ | |
| /* Session search input — compact, matches sidebar aesthetic */ | |
| .session-search { | |
| width: 100%; | |
| padding: 5px 10px; | |
| background: rgba(255, 255, 255,0.04); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| color: var(--text-2); | |
| font-family: var(--sans); | |
| font-size: 12px; | |
| outline: none; | |
| margin-bottom: 6px; | |
| transition: border-color var(--transition), background var(--transition); | |
| } | |
| .session-search::placeholder { color: var(--faint); } | |
| .session-search:focus { | |
| border-color: var(--accent-border); | |
| background: rgba(91, 143, 249, 0.06); | |
| } | |
| /* Inline title edit input — takes the place of the session-btn */ | |
| .session-title-input { | |
| flex: 1; | |
| min-width: 0; | |
| padding: 6px 8px; | |
| background: rgba(91, 143, 249, 0.10); | |
| border: 1px solid var(--accent-border); | |
| border-radius: var(--radius); | |
| color: var(--text); | |
| font-family: var(--sans); | |
| font-size: 12px; | |
| outline: none; | |
| transition: border-color var(--transition); | |
| } | |
| .session-title-input:focus { | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 2px var(--accent-dim); | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| FEATURE 4: CODE MAP | |
| ══════════════════════════════════════════════════════════ */ | |
| /* Container fills the diagram-canvas space — stretch to full width/height */ | |
| .codemap-container { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| width: 100%; | |
| height: 100%; | |
| align-self: stretch; | |
| padding: 0 4px; | |
| box-sizing: border-box; | |
| } | |
| .codemap-header { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 3px; | |
| } | |
| .codemap-title { | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: var(--text); | |
| letter-spacing: -0.01em; | |
| } | |
| .codemap-subtitle { | |
| font-size: 11px; | |
| color: var(--muted); | |
| } | |
| /* Legend row */ | |
| .codemap-legend { | |
| display: flex; | |
| gap: 14px; | |
| flex-wrap: wrap; | |
| } | |
| .codemap-legend-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .codemap-legend-dot { | |
| width: 9px; | |
| height: 9px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| opacity: 0.85; | |
| } | |
| .codemap-legend-label { | |
| font-size: 11px; | |
| color: var(--text-2); | |
| font-family: var(--mono); | |
| } | |
| /* SVG wrapper — fills remaining vertical space in the codemap-container */ | |
| .codemap-svg-wrapper { | |
| flex: 1; | |
| min-height: 300px; | |
| overflow: hidden; | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--border); | |
| background: rgba(0,0,0,0.25); | |
| } | |
| .codemap-svg { | |
| width: 100%; | |
| height: 100%; | |
| display: block; | |
| cursor: crosshair; | |
| } | |
| /* Tooltip */ | |
| .codemap-tooltip { | |
| position: absolute; | |
| pointer-events: none; | |
| background: var(--surface-3); | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--radius-md); | |
| padding: 9px 12px; | |
| box-shadow: var(--shadow); | |
| z-index: 10; | |
| min-width: 180px; | |
| max-width: 260px; | |
| } | |
| .codemap-tooltip-name { | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: var(--text); | |
| font-family: var(--mono); | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .codemap-tooltip-type { | |
| font-size: 10px; | |
| color: var(--accent-soft); | |
| font-family: var(--mono); | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin-top: 2px; | |
| } | |
| .codemap-tooltip-file { | |
| font-size: 11px; | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| margin-top: 4px; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .codemap-tooltip-lines { | |
| font-size: 10px; | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| margin-top: 2px; | |
| } | |
| .codemap-tooltip-hint { | |
| font-size: 10px; | |
| color: var(--accent-soft); | |
| margin-top: 6px; | |
| border-top: 1px solid var(--border-subtle); | |
| padding-top: 5px; | |
| } | |
| /* States */ | |
| .codemap-loading { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| height: 300px; | |
| color: var(--muted); | |
| font-size: 13px; | |
| } | |
| .codemap-loading-sub { | |
| font-size: 11px; | |
| color: var(--muted); | |
| } | |
| .codemap-error { | |
| color: var(--red); | |
| font-size: 13px; | |
| padding: 20px; | |
| } | |
| .codemap-empty { | |
| color: var(--muted); | |
| font-size: 13px; | |
| padding: 20px; | |
| } | |
| /* Code Map toolbar row */ | |
| .codemap-header-row { | |
| display: flex; | |
| align-items: flex-start; | |
| justify-content: space-between; | |
| gap: 12px; | |
| } | |
| .codemap-toolbar { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| flex-shrink: 0; | |
| } | |
| .codemap-search { | |
| background: var(--surface-2); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| color: var(--text); | |
| font-size: 11px; | |
| font-family: var(--mono); | |
| padding: 4px 9px; | |
| width: 140px; | |
| outline: none; | |
| transition: border-color var(--transition); | |
| } | |
| .codemap-search::placeholder { color: var(--faint); } | |
| .codemap-search:focus { border-color: var(--accent-border); } | |
| .codemap-reset-btn { | |
| background: transparent; | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| color: var(--muted); | |
| font-size: 11px; | |
| font-family: var(--mono); | |
| padding: 4px 9px; | |
| cursor: pointer; | |
| transition: color var(--transition), border-color var(--transition); | |
| white-space: nowrap; | |
| } | |
| .codemap-reset-btn:hover { color: var(--text); border-color: var(--accent-border); } | |
| /* ── Rate-limit countdown banner — warm amber, not cold lemon ── */ | |
| .rate-limit-banner { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| padding: 10px 14px; | |
| background: rgba(201,147,58,0.10); | |
| border: 1px solid rgba(201,147,58,0.28); | |
| border-left: 2px solid var(--warning); | |
| border-radius: var(--radius-md); | |
| font-size: 13px; | |
| color: var(--warning); | |
| font-family: var(--mono); | |
| margin-bottom: 2px; | |
| } | |
| .rate-limit-spinner { | |
| display: inline-block; | |
| width: 13px; | |
| height: 13px; | |
| border: 2px solid rgba(201,147,58,0.25); | |
| border-top-color: var(--warning); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| flex-shrink: 0; | |
| } | |
| .rate-limit-text { flex: 1; } | |
| .rate-limit-retry-btn { | |
| background: rgba(201,147,58,0.12); | |
| border: 1px solid rgba(201,147,58,0.30); | |
| border-radius: var(--radius); | |
| color: var(--warning); | |
| font-size: 11px; | |
| font-family: var(--mono); | |
| padding: 3px 10px; | |
| cursor: pointer; | |
| transition: background var(--transition); | |
| white-space: nowrap; | |
| flex-shrink: 0; | |
| } | |
| .rate-limit-retry-btn:hover { background: rgba(201,147,58,0.22); } | |
| /* ── Suggest footer — secondary actions below suggestions ── */ | |
| .suggest-footer { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| margin-top: 14px; | |
| } | |
| .suggest-footer-btn { | |
| background: rgba(255, 255, 255, 0.04); | |
| border: 1px solid rgba(255, 255, 255, 0.09); | |
| color: var(--muted); | |
| font-size: 12px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| padding: 4px 10px; | |
| border-radius: var(--radius-full); | |
| transition: color var(--transition), border-color var(--transition), background var(--transition); | |
| font-family: inherit; | |
| letter-spacing: -0.01em; | |
| } | |
| .suggest-footer-btn:hover { | |
| color: var(--accent-soft); | |
| border-color: var(--accent-border); | |
| background: var(--accent-dim); | |
| } | |
| .suggest-footer-sep { | |
| color: var(--faint); | |
| font-size: 12px; | |
| pointer-events: none; | |
| user-select: none; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| FEATURE 4 (cont.): DIAGRAM FOCUS BANNER | |
| Shown when user navigated from "Diagram this →" | |
| ══════════════════════════════════════════════════════════ */ | |
| .diagram-focus-banner { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 7px 14px; | |
| margin: 10px 24px; | |
| background: rgba(91, 143, 249, 0.08); | |
| border: 1px solid var(--accent-border); | |
| border-radius: var(--radius); | |
| font-size: 12px; | |
| color: var(--accent-soft); | |
| flex-shrink: 0; | |
| } | |
| .diagram-focus-icon { | |
| font-size: 14px; | |
| color: var(--accent); | |
| flex-shrink: 0; | |
| } | |
| .diagram-focus-files { | |
| font-family: var(--mono); | |
| font-size: 11px; | |
| color: var(--text-2); | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| /* ══════════════════════════════════════════════════════════ | |
| FEATURE 5: "DIAGRAM THIS →" BUTTON | |
| ══════════════════════════════════════════════════════════ */ | |
| .diagram-this-btn { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 5px; | |
| margin-top: 10px; | |
| padding: 6px 14px; | |
| background: rgba(91, 143, 249, 0.10); | |
| border: 1px solid var(--accent-border); | |
| border-radius: var(--radius-full); | |
| color: var(--accent-soft); | |
| font-size: 12px; | |
| font-weight: 500; | |
| font-family: var(--sans); | |
| cursor: pointer; | |
| transition: background var(--transition), color var(--transition), border-color var(--transition); | |
| letter-spacing: -0.01em; | |
| } | |
| .diagram-this-btn:hover { | |
| background: rgba(91, 143, 249, 0.20); | |
| color: var(--text); | |
| border-color: var(--accent); | |
| } | |
| /* Agent trace: collapsed thought summary — one-line italic hint */ | |
| .agent-trace-thought-summary { | |
| font-size: 11px; | |
| color: var(--muted); | |
| font-style: italic; | |
| padding: 4px 10px 6px 32px; | |
| line-height: 1.4; | |
| cursor: default; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| /* Session mode badge — ✦ indicator for agent-mode sessions */ | |
| .session-mode-badge { | |
| font-size: 9px; | |
| color: var(--accent-soft); | |
| opacity: 0.8; | |
| flex-shrink: 0; | |
| } | |
| /* ═══════════════════════════════════════════════════════════════════════ | |
| EXPLORE VIEW — Interactive Codebase Tour | |
| Concept cards + bezier arrows forming a learning path through the repo. | |
| ════════════════════════════════════════════════════════════════════════ */ | |
| .ec-container { | |
| display: flex; | |
| flex-direction: column; | |
| height: 100%; | |
| overflow: hidden; | |
| } | |
| /* Summary header */ | |
| .ec-header { | |
| padding: 4px 16px 8px; | |
| border-bottom: 1px solid rgba(255, 255, 255,0.06); | |
| flex-shrink: 0; | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 18px; | |
| flex-wrap: wrap; | |
| } | |
| /* Right-aligned cluster: mode toggle + start-reading hint. | |
| Grouped so they read as one "view controls" block instead of two | |
| stray chips. A thin divider between them reinforces the grouping. */ | |
| .ec-controls { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 12px; | |
| flex-shrink: 0; | |
| margin-left: auto; | |
| padding-top: 2px; | |
| } | |
| .ec-controls .ec-mode-toggle + .ec-entry-hint { | |
| position: relative; | |
| padding-left: 12px; | |
| } | |
| .ec-controls .ec-mode-toggle + .ec-entry-hint::before { | |
| content: ""; | |
| position: absolute; | |
| left: 0; | |
| top: 50%; | |
| width: 1px; | |
| height: 14px; | |
| background: var(--border); | |
| transform: translateY(-50%); | |
| } | |
| .ec-summary { | |
| font-size: 13px; | |
| color: var(--text); | |
| line-height: 1.5; | |
| flex: 1; | |
| min-width: 200px; | |
| } | |
| .ec-entry-hint { | |
| font-size: 11.5px; | |
| color: var(--muted); | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| white-space: nowrap; | |
| flex-shrink: 0; | |
| } | |
| .ec-entry-hint code { | |
| background: rgba(91,143,249,0.12); | |
| border: 1px solid rgba(91,143,249,0.28); | |
| color: var(--accent-soft); | |
| padding: 1px 6px; | |
| border-radius: 4px; | |
| font-size: 11px; | |
| font-family: var(--mono); | |
| } | |
| /* Canvas wrapper — handles pan/zoom events */ | |
| .ec-canvas-wrapper { | |
| flex: 1; | |
| overflow: hidden; | |
| cursor: grab; | |
| position: relative; | |
| /* Atmospheric glow — spotlight from top-center bleeds behind the graph, | |
| making nodes pop against the dark canvas. Inspired by Raycast/Arc splash screens. | |
| Two layers: a warm blue core + a cooler wide bloom that fills the canvas. | |
| No solid var(--bg) base — the .main ambient layer shows through so Explore | |
| matches every other view. */ | |
| background: | |
| radial-gradient(ellipse 70% 55% at 50% 50%, rgba(91, 143, 249, 0.13) 0%, transparent 70%), | |
| radial-gradient(ellipse 110% 90% at 50% 50%, rgba(91, 143, 249, 0.05) 0%, transparent 80%); | |
| } | |
| .ec-canvas-wrapper:active { | |
| cursor: grabbing; | |
| } | |
| /* Canvas inner div — contains SVG layer + cards, transforms for zoom/pan */ | |
| .ec-canvas { | |
| position: relative; | |
| will-change: transform; | |
| user-select: none; | |
| } | |
| /* ── Concept cards — Arc/Raycast-inspired dark navy with glowing border ── */ | |
| .ec-card { | |
| background: rgba(9, 10, 20, 0.97); | |
| border: 1px solid rgba(255, 255, 255, 0.08); | |
| box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 4px 20px rgba(0, 0, 0, 0.50); | |
| border-radius: 10px; | |
| padding: 13px 14px 11px; | |
| cursor: pointer; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| backdrop-filter: blur(12px); | |
| /* `top` transitions so cards in lower rows glide down when an upper-row | |
| card is hovered (instead of jumping). The same easing curve as the | |
| content-expansion transitions, so the whole reflow reads as one motion. */ | |
| transition: | |
| border-color 0.15s, | |
| box-shadow 0.15s, | |
| opacity 0.15s, | |
| top 240ms cubic-bezier(0.22, 1, 0.36, 1); | |
| } | |
| .ec-card:hover { | |
| border-color: rgba(91, 143, 249, 0.65); | |
| box-shadow: | |
| inset 0 1px 0 rgba(255, 255, 255, 0.08), | |
| 0 0 0 1px rgba(91, 143, 249, 0.45), | |
| 0 8px 40px rgba(91, 143, 249, 0.22), | |
| 0 4px 20px rgba(0, 0, 0, 0.50); | |
| } | |
| .ec-card.ec-dimmed { | |
| opacity: 0.25; | |
| } | |
| /* Top row: order badge + entry badge + type tag */ | |
| .ec-card-top { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 5px; | |
| } | |
| .ec-order { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: rgba(91, 143, 249,0.18); | |
| border: 1px solid rgba(91, 143, 249,0.4); | |
| color: var(--accent-soft); | |
| font-size: 10px; | |
| font-weight: 700; | |
| font-family: var(--mono); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| } | |
| .ec-entry-tag { | |
| font-size: 10px; | |
| color: var(--green); | |
| background: var(--green-dim); | |
| border: 1px solid rgba(52,211,153,0.30); | |
| border-radius: 4px; | |
| padding: 1px 6px; | |
| font-weight: 600; | |
| letter-spacing: 0.02em; | |
| } | |
| .ec-type-tag { | |
| font-size: 10px; | |
| border: 1px solid; | |
| border-radius: 4px; | |
| padding: 1px 6px; | |
| font-family: var(--mono); | |
| font-weight: 500; | |
| letter-spacing: 0.02em; | |
| } | |
| /* Concept name */ | |
| .ec-name { | |
| font-size: 14.5px; | |
| font-weight: 700; | |
| color: var(--text); /* --fg is undefined; use correct token */ | |
| line-height: 1.2; | |
| letter-spacing: -0.01em; | |
| } | |
| /* Subtitle */ | |
| .ec-subtitle { | |
| font-size: 11.5px; | |
| color: var(--muted); | |
| line-height: 1.4; | |
| } | |
| /* File pill */ | |
| .ec-file { | |
| font-size: 10.5px; | |
| color: rgba(91, 143, 249,0.65); | |
| font-family: var(--mono); | |
| margin-top: 3px; | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| /* Key items — collapsed by default, revealed on hover. The card stays | |
| minimal at rest (name + subtitle + file + ask button) and reveals its | |
| relational details on cursor approach. The combination of max-height | |
| transition + opacity gives a smooth grow/fade rather than a hard show. | |
| .ec-card raises z-index when hovered (set inline by ConceptCard), so | |
| the expanded card pops above its neighbours instead of pushing them. */ | |
| .ec-items { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 4px; | |
| max-height: 0; | |
| margin-top: 0; | |
| opacity: 0; | |
| overflow: hidden; | |
| transition: | |
| max-height 240ms cubic-bezier(0.22, 1, 0.36, 1), | |
| margin-top 240ms cubic-bezier(0.22, 1, 0.36, 1), | |
| opacity 180ms ease; | |
| } | |
| .ec-card:hover .ec-items, | |
| .ec-card.ec-hover .ec-items { | |
| max-height: 80px; | |
| margin-top: 6px; | |
| opacity: 1; | |
| } | |
| .ec-item { | |
| font-size: 10px; | |
| background: rgba(255, 255, 255,0.05); | |
| border: 1px solid rgba(255, 255, 255,0.1); | |
| border-radius: 4px; | |
| padding: 2px 7px; | |
| color: var(--accent-soft); | |
| font-family: var(--mono); | |
| } | |
| .ec-item-more { | |
| font-size: 10px; | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| align-self: center; | |
| padding-left: 2px; | |
| } | |
| /* Builds-on row — surfaces dependency edges as readable text so the user | |
| can scan a card and see where to navigate next without tracing arrows | |
| visually. Mirrors the depends_on data already used to draw the blue | |
| connection arrows on the canvas. Sits between key items and the Ask | |
| button — last "real content" before the action affordance. */ | |
| /* Builds-on row — same hover-to-reveal pattern as .ec-items, so the card | |
| has a single "expanded vs at-rest" mode rather than mixing always-on | |
| and on-hover content. */ | |
| .ec-connects { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 3px; | |
| max-height: 0; | |
| margin-top: 0; | |
| padding-top: 0; | |
| border-top: 1px solid transparent; | |
| opacity: 0; | |
| overflow: hidden; | |
| transition: | |
| max-height 240ms cubic-bezier(0.22, 1, 0.36, 1), | |
| margin-top 240ms cubic-bezier(0.22, 1, 0.36, 1), | |
| padding-top 240ms cubic-bezier(0.22, 1, 0.36, 1), | |
| border-top-color 240ms ease, | |
| opacity 180ms ease; | |
| } | |
| .ec-card:hover .ec-connects, | |
| .ec-card.ec-hover .ec-connects { | |
| max-height: 96px; | |
| margin-top: 6px; | |
| padding-top: 6px; | |
| border-top-color: rgba(255, 255, 255, 0.05); | |
| opacity: 1; | |
| } | |
| .ec-connects-label { | |
| font-family: var(--mono); | |
| font-size: 9px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| color: var(--faint); | |
| } | |
| .ec-connects-list { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 4px; | |
| } | |
| .ec-connects-item { | |
| font-size: 10.5px; | |
| color: var(--text-2); | |
| background: rgba(255, 255, 255, 0.03); | |
| border: 1px solid rgba(255, 255, 255, 0.06); | |
| border-radius: 4px; | |
| padding: 1px 6px; | |
| /* Truncate so a long neighbour name doesn't blow the card width */ | |
| max-width: 100%; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| .ec-connects-more { | |
| font-size: 10px; | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| align-self: center; | |
| padding-left: 2px; | |
| } | |
| /* Ask button */ | |
| .ec-ask { | |
| margin-top: 9px; | |
| background: transparent; | |
| border: 1px solid rgba(91, 143, 249,0.28); | |
| border-radius: 5px; | |
| color: rgba(91, 143, 249,0.8); | |
| font-size: 11px; | |
| padding: 5px 10px; | |
| cursor: pointer; | |
| text-align: left; | |
| transition: background 0.12s, border-color 0.12s, color 0.12s; | |
| font-family: var(--sans); /* --ui is undefined; use correct token */ | |
| } | |
| .ec-ask:hover { | |
| background: rgba(91, 143, 249,0.10); | |
| border-color: rgba(91, 143, 249,0.55); | |
| color: var(--accent-soft); | |
| } | |
| /* Legend */ | |
| .ec-legend { | |
| display: flex; | |
| align-items: center; | |
| gap: 16px; | |
| padding: 9px 16px; | |
| border-top: 1px solid rgba(255, 255, 255,0.06); | |
| flex-shrink: 0; | |
| flex-wrap: wrap; | |
| } | |
| .ec-legend-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 12px; | |
| color: var(--text-2); | |
| } | |
| .ec-legend-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| } | |
| .ec-legend-hint { | |
| font-size: 11px; | |
| color: var(--muted); | |
| margin-left: auto; | |
| } | |
| /* Loading state */ | |
| .ec-loading { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 14px; | |
| height: 100%; | |
| color: var(--muted); | |
| font-size: 14px; | |
| } | |
| /* Error state */ | |
| .ec-error { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| height: 100%; | |
| gap: 12px; | |
| } | |
| /* ── Canvas/Story mode toggle — segmented control in the tour header ───── */ | |
| .ec-mode-toggle { | |
| display: inline-flex; | |
| padding: 2px; | |
| background: rgba(255, 255, 255, 0.03); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| flex-shrink: 0; | |
| } | |
| .ec-mode-btn { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-family: var(--sans); | |
| font-size: 11.5px; | |
| font-weight: 500; | |
| color: var(--text-2); | |
| background: transparent; | |
| border: none; | |
| border-radius: 5px; | |
| padding: 4px 10px; | |
| cursor: pointer; | |
| transition: background var(--transition), color var(--transition); | |
| } | |
| .ec-mode-btn:hover { color: var(--text); } | |
| .ec-mode-btn.is-active { | |
| background: rgba(91, 143, 249, 0.14); | |
| color: var(--accent-soft); | |
| box-shadow: inset 0 0 0 1px rgba(91, 143, 249, 0.30); | |
| } | |
| .ec-mode-btn svg { opacity: 0.85; } | |
| /* ══════════════════════════════════════════════════════════════════════════ | |
| TOUR STORY MODE — one concept at a time, keyboard-scrub, step rail | |
| Lives as a sibling of .ec-canvas-wrapper inside .ec-container. | |
| Goal: pace the reading. Large hero concept, peripheral neighbors, fluid | |
| transition, progress rail at the bottom. Same data as Canvas mode. | |
| ══════════════════════════════════════════════════════════════════════════ */ | |
| /* .ts-root is .ambient-tint (primitive) + story-specific layout. | |
| --ambient-tint is set inline from JSX per active concept — the colour | |
| interpolates smoothly because the custom property is @property-typed. */ | |
| .ts-root { | |
| flex: 1; | |
| min-height: 0; | |
| display: grid; | |
| grid-template-rows: auto 1fr auto; | |
| gap: 0; | |
| overflow: hidden; | |
| } | |
| /* ── Flow strip — horizontal map of every step ─────────────────────────── */ | |
| .ts-flow { | |
| display: flex; | |
| gap: 6px; | |
| padding: 12px 24px; | |
| overflow-x: auto; | |
| border-bottom: 1px solid var(--border-subtle); | |
| scrollbar-width: thin; | |
| } | |
| .ts-flow-step { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 5px 10px 5px 7px; | |
| border: 1px solid var(--border); | |
| border-radius: 999px; | |
| background: rgba(255, 255, 255, 0.02); | |
| color: var(--muted); | |
| font-family: var(--sans); | |
| font-size: 11.5px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| white-space: nowrap; | |
| transition: background var(--transition), color var(--transition), border-color var(--transition), transform var(--transition); | |
| flex-shrink: 0; | |
| } | |
| .ts-flow-step:hover { | |
| color: var(--text-2); | |
| background: rgba(255, 255, 255, 0.04); | |
| } | |
| .ts-flow-step.is-done { | |
| color: var(--text-2); | |
| opacity: 0.7; | |
| } | |
| .ts-flow-step.is-active { | |
| color: var(--text); | |
| background: rgba(91, 143, 249, 0.10); | |
| box-shadow: 0 0 18px rgba(91, 143, 249, 0.15); | |
| transform: translateY(-1px); | |
| } | |
| .ts-flow-num { | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| font-weight: 700; | |
| color: var(--faint); | |
| background: rgba(255, 255, 255, 0.04); | |
| border-radius: 999px; | |
| padding: 1px 6px; | |
| } | |
| .ts-flow-step.is-active .ts-flow-num { | |
| color: var(--accent-soft); | |
| background: rgba(91, 143, 249, 0.18); | |
| } | |
| .ts-flow-label { | |
| max-width: 180px; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| /* ── Stage — centered card flanked by prev/next nav buttons ─────────────── */ | |
| .ts-stage { | |
| position: relative; | |
| display: grid; | |
| grid-template-columns: minmax(56px, 1fr) minmax(480px, 760px) minmax(56px, 1fr); | |
| align-items: center; | |
| gap: 16px; | |
| padding: 32px 24px; | |
| overflow: hidden; | |
| min-height: 0; | |
| } | |
| .ts-nav { | |
| display: inline-flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| width: 100%; | |
| max-width: 160px; | |
| height: 120px; | |
| margin: 0 auto; | |
| border: 1px dashed var(--border-subtle); | |
| border-radius: var(--radius-lg); | |
| background: transparent; | |
| color: var(--muted); | |
| cursor: pointer; | |
| transition: color var(--transition), border-color var(--transition), background var(--transition); | |
| padding: 10px 12px; | |
| } | |
| .ts-nav:hover:not(:disabled) { | |
| color: var(--accent-soft); | |
| border-color: var(--accent-border); | |
| background: rgba(91, 143, 249, 0.04); | |
| } | |
| .ts-nav:disabled { | |
| opacity: 0.25; | |
| cursor: not-allowed; | |
| } | |
| .ts-nav-peek { | |
| font-size: 10.5px; | |
| color: inherit; | |
| opacity: 0.75; | |
| text-align: center; | |
| max-width: 100%; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| display: -webkit-box; | |
| -webkit-line-clamp: 2; | |
| -webkit-box-orient: vertical; | |
| line-height: 1.3; | |
| } | |
| /* ── The focus card — hero surface ─────────────────────────────────────── */ | |
| .ts-card { | |
| background: linear-gradient(180deg, rgba(21, 21, 34, 0.85) 0%, rgba(15, 15, 24, 0.90) 100%); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-xl); | |
| display: flex; | |
| flex-direction: column; | |
| backdrop-filter: blur(12px); | |
| /* Keep overflow hidden at the card level so border-radius clips ALL | |
| descendants (accent rail, glow pseudo). The inner .ts-card-body owns | |
| the scroll context so long content still scrolls. */ | |
| overflow: hidden; | |
| max-height: 100%; | |
| animation: ts-card-in var(--dur-slow) var(--ease-spring) both; | |
| } | |
| /* Inner scrolling body — content lives here so the scroll thumb doesn't | |
| cut across the accent rail or rounded corners. */ | |
| .ts-card-body { | |
| padding: 32px 36px 24px 42px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 14px; | |
| overflow-y: auto; | |
| min-height: 0; | |
| flex: 1; | |
| } | |
| /* Clip layer for the accent rail — kept as a dedicated z-indexed overlay so | |
| the rail sits above the cursor glow but below the body content. */ | |
| .ts-card-clip { | |
| position: absolute; | |
| inset: 0; | |
| border-radius: inherit; | |
| overflow: hidden; | |
| pointer-events: none; | |
| z-index: 2; | |
| } | |
| @keyframes ts-card-in { | |
| from { opacity: 0; transform: translateY(18px) scale(0.982); filter: blur(8px); } | |
| to { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); } | |
| } | |
| /* Accent rail along the left edge of the card — color-coded by concept type */ | |
| .ts-rail-accent { | |
| position: absolute; | |
| top: 0; bottom: 0; left: 0; | |
| width: 3px; | |
| opacity: 0.85; | |
| } | |
| .ts-card-head { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .ts-head-left { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .ts-num { | |
| font-family: var(--mono); | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: var(--muted); | |
| letter-spacing: 0.05em; | |
| } | |
| .ts-num-sep { | |
| display: inline-block; | |
| margin: 0 4px; | |
| color: var(--faint); | |
| } | |
| .ts-entry-badge { | |
| font-size: 10.5px; | |
| font-weight: 600; | |
| color: var(--green); | |
| background: var(--green-dim); | |
| border: 1px solid rgba(52, 211, 153, 0.30); | |
| border-radius: 4px; | |
| padding: 2px 7px; | |
| letter-spacing: 0.02em; | |
| } | |
| .ts-type { | |
| font-family: var(--mono); | |
| font-size: 10.5px; | |
| font-weight: 500; | |
| border: 1px solid; | |
| border-radius: 4px; | |
| padding: 2px 7px; | |
| letter-spacing: 0.02em; | |
| } | |
| /* Story title — composes .display-serif on the element for font + tracking, | |
| this rule just sets scale and colour. */ | |
| .ts-title { | |
| font-size: 30px; | |
| line-height: 1.12; | |
| color: var(--text); | |
| margin: 0; | |
| } | |
| .ts-subtitle { | |
| font-size: 14px; | |
| color: var(--text-2); | |
| line-height: 1.45; | |
| margin: 0; | |
| } | |
| .ts-desc { | |
| font-size: 14px; | |
| color: var(--text-2); | |
| line-height: 1.7; | |
| margin: 0; | |
| } | |
| .ts-items { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 6px; | |
| margin-top: 2px; | |
| } | |
| .ts-item { | |
| font-family: var(--mono); | |
| font-size: 11.5px; | |
| color: var(--accent-soft); | |
| background: rgba(91, 143, 249, 0.08); | |
| border: 1px solid rgba(91, 143, 249, 0.22); | |
| border-radius: 5px; | |
| padding: 3px 8px; | |
| } | |
| .ts-deps { | |
| display: flex; | |
| flex-wrap: wrap; | |
| align-items: center; | |
| gap: 6px; | |
| padding-top: 6px; | |
| border-top: 1px dashed var(--border-subtle); | |
| } | |
| .ts-deps-label { | |
| font-size: 10.5px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| color: var(--muted); | |
| margin-right: 2px; | |
| } | |
| .ts-dep-pill { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| border: 1px solid var(--border); | |
| border-radius: 999px; | |
| background: transparent; | |
| color: var(--text-2); | |
| font-family: var(--sans); | |
| font-size: 11px; | |
| padding: 3px 9px 3px 4px; | |
| cursor: pointer; | |
| transition: color var(--transition), border-color var(--transition), background var(--transition); | |
| } | |
| .ts-dep-pill:hover { | |
| color: var(--accent-soft); | |
| border-color: var(--accent-border); | |
| background: rgba(91, 143, 249, 0.06); | |
| } | |
| .ts-dep-num { | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| font-weight: 700; | |
| color: var(--faint); | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 999px; | |
| padding: 1px 5px; | |
| } | |
| .ts-card-foot { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| gap: 12px; | |
| margin-top: 4px; | |
| padding-top: 14px; | |
| border-top: 1px solid var(--border-subtle); | |
| } | |
| .ts-file { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| font-size: 11.5px; | |
| text-decoration: none; | |
| background: rgba(255, 255, 255, 0.02); | |
| border: 1px solid var(--border); | |
| border-radius: 6px; | |
| padding: 5px 10px; | |
| transition: color var(--transition), border-color var(--transition), background var(--transition); | |
| max-width: 60%; | |
| overflow: hidden; | |
| } | |
| .ts-file:hover { | |
| color: var(--accent-soft); | |
| border-color: var(--accent-border); | |
| background: rgba(91, 143, 249, 0.06); | |
| } | |
| .ts-file-path { | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| } | |
| .ts-ask { | |
| background: rgba(91, 143, 249, 0.14); | |
| border: 1px solid rgba(91, 143, 249, 0.35); | |
| color: var(--accent-soft); | |
| font-family: var(--sans); | |
| font-size: 12.5px; | |
| font-weight: 500; | |
| border-radius: 7px; | |
| padding: 7px 14px; | |
| cursor: pointer; | |
| transition: background var(--transition), border-color var(--transition), color var(--transition), transform var(--transition); | |
| } | |
| .ts-ask:hover { | |
| background: rgba(91, 143, 249, 0.22); | |
| border-color: var(--accent); | |
| color: var(--text); | |
| transform: translateY(-1px); | |
| } | |
| /* ── Bottom rail — progress line + clickable dots + keyboard hint ──────── */ | |
| .ts-rail { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| padding: 10px 28px 14px; | |
| border-top: 1px solid var(--border-subtle); | |
| background: rgba(9, 9, 14, 0.6); | |
| } | |
| .ts-rail-track { | |
| position: relative; | |
| height: 22px; | |
| display: flex; | |
| align-items: center; | |
| } | |
| /* Base line behind the dots — full width, faint */ | |
| .ts-rail-track::before { | |
| content: ""; | |
| position: absolute; | |
| left: 0; right: 0; top: 50%; | |
| height: 1px; | |
| background: var(--border); | |
| transform: translateY(-50%); | |
| } | |
| .ts-rail-fill { | |
| position: absolute; | |
| left: 0; top: 50%; | |
| height: 2px; | |
| background: linear-gradient(90deg, var(--accent-soft), var(--accent)); | |
| box-shadow: 0 0 12px rgba(91, 143, 249, 0.45); | |
| transform: translateY(-50%); | |
| transition: width 480ms var(--ease-spring); | |
| overflow: visible; | |
| } | |
| /* Travelling comet at the leading edge of the progress fill. | |
| ::after rides on the right edge of the fill — moves with it because | |
| it's positioned relative to the fill, and pulses with a heartbeat keyframe. */ | |
| .ts-rail-fill::after { | |
| content: ""; | |
| position: absolute; | |
| right: -5px; | |
| top: 50%; | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| background: var(--accent); | |
| transform: translateY(-50%); | |
| box-shadow: | |
| 0 0 0 0 rgba(91, 143, 249, 0.55), | |
| 0 0 18px 3px rgba(91, 143, 249, 0.65); | |
| animation: ts-heartbeat 2400ms ease-in-out infinite; | |
| } | |
| @keyframes ts-heartbeat { | |
| 0%, 100% { box-shadow: 0 0 0 0 rgba(91, 143, 249, 0.55), 0 0 14px 2px rgba(91, 143, 249, 0.55); } | |
| 50% { box-shadow: 0 0 0 6px rgba(91, 143, 249, 0.00), 0 0 22px 5px rgba(91, 143, 249, 0.80); } | |
| } | |
| .ts-rail-dots { | |
| position: relative; | |
| width: 100%; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .ts-rail-dot { | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| border: 1px solid var(--border-strong); | |
| background: var(--bg); | |
| cursor: pointer; | |
| padding: 0; | |
| transition: transform var(--transition), border-color var(--transition), background var(--transition), box-shadow var(--transition); | |
| } | |
| .ts-rail-dot:hover { | |
| border-color: var(--accent-soft); | |
| transform: scale(1.2); | |
| } | |
| .ts-rail-dot.is-done { | |
| background: var(--accent); | |
| border-color: var(--accent); | |
| } | |
| .ts-rail-dot.is-active { | |
| background: var(--accent); | |
| border-color: var(--accent-light); | |
| box-shadow: 0 0 0 3px rgba(91, 143, 249, 0.22), 0 0 14px rgba(91, 143, 249, 0.55); | |
| transform: scale(1.35); | |
| } | |
| .ts-rail-hint { | |
| font-size: 10.5px; | |
| color: var(--muted); | |
| text-align: center; | |
| letter-spacing: 0.02em; | |
| } | |
| .ts-rail-hint kbd { | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| background: rgba(255, 255, 255, 0.05); | |
| border: 1px solid var(--border); | |
| border-radius: 3px; | |
| padding: 1px 5px; | |
| margin: 0 2px; | |
| color: var(--text-2); | |
| } | |
| /* Narrow viewports — hide peripheral peeks, tighten padding */ | |
| @media (max-width: 820px) { | |
| .ts-stage { | |
| grid-template-columns: 40px 1fr 40px; | |
| padding: 20px 12px; | |
| } | |
| .ts-nav { height: 80px; max-width: 44px; } | |
| .ts-nav-peek { display: none; } | |
| .ts-card-body { padding: 22px 20px 18px 26px; } | |
| .ts-title { font-size: 22px; } | |
| } | |
| /* ── Collapsible Sidebar ─────────────────────────────────────────────────── */ | |
| /* Width is controlled by the grid column (.layout.layout-collapsed), not here */ | |
| .sidebar-collapsed { | |
| width: 100%; | |
| padding: 12px 0; | |
| align-items: center; | |
| gap: 8px; | |
| overflow: hidden; | |
| } | |
| .sidebar-collapsed-brand { | |
| width: 30px; | |
| height: 30px; | |
| border-radius: 7px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| /* Suppress the large glow from .sidebar-brand-icon in collapsed state */ | |
| box-shadow: 0 0 0 1px rgba(91, 143, 249, 0.3); | |
| } | |
| .sidebar-collapsed-item { | |
| position: relative; | |
| width: 34px; | |
| height: 34px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--muted); | |
| } | |
| .sidebar-collapsed-badge { | |
| position: absolute; | |
| top: 2px; | |
| right: 2px; | |
| background: var(--accent); | |
| color: #fff; | |
| border-radius: 10px; | |
| font-size: 9px; | |
| font-weight: 700; | |
| padding: 1px 4px; | |
| line-height: 1.4; | |
| font-family: var(--mono); | |
| } | |
| .sidebar-collapsed-expand { | |
| margin-top: auto; | |
| margin-bottom: 12px; | |
| width: 34px; | |
| height: 34px; | |
| background: none; | |
| border: 1px solid rgba(255, 255, 255,0.1); | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| color: var(--muted); | |
| transition: border-color 0.15s, color 0.15s; | |
| } | |
| .sidebar-collapsed-expand:hover { | |
| border-color: rgba(255, 255, 255, 0.30); | |
| color: var(--text); | |
| } | |
| /* Collapse button inside expanded sidebar brand row */ | |
| .sidebar-collapse-btn { | |
| margin-left: auto; | |
| background: none; | |
| border: 1px solid rgba(255, 255, 255,0.1); | |
| border-radius: 6px; | |
| width: 28px; | |
| height: 28px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| color: var(--muted); | |
| flex-shrink: 0; | |
| transition: border-color 0.15s, color 0.15s; | |
| } | |
| .sidebar-collapse-btn:hover { | |
| border-color: rgba(255, 255, 255, 0.30); | |
| color: var(--text); | |
| } | |
| /* ── Diagram Fullscreen ───────────────────────────────────────────────────── */ | |
| .diagram-container.diagram-fullscreen { | |
| position: fixed; | |
| inset: 0; | |
| z-index: 300; /* above mobile sidebar (200) and everything in .main */ | |
| background: var(--bg); | |
| border-radius: 0; | |
| display: flex; | |
| flex-direction: column; | |
| padding: 12px 28px 8px; | |
| gap: 6px; | |
| /* Dim the surrounding ambient so the focused content leads. The same | |
| cursor-glow still reads through the transparent body below. */ | |
| background: | |
| radial-gradient(ellipse 60% 40% at 50% 0%, rgba(91, 143, 249, 0.05), transparent 60%), | |
| var(--bg); | |
| animation: fullscreen-in 220ms var(--ease-spring) both; | |
| } | |
| @keyframes fullscreen-in { | |
| from { opacity: 0; transform: scale(0.985); } | |
| to { opacity: 1; transform: scale(1); } | |
| } | |
| .diagram-container.diagram-fullscreen .diagram-header { | |
| /* Slim header bar — only the exit + action buttons matter here. */ | |
| padding: 0; | |
| border-bottom: none; | |
| min-height: auto; | |
| } | |
| .diagram-container.diagram-fullscreen .diagram-body { | |
| flex: 1; | |
| min-height: 0; | |
| overflow: hidden; | |
| } | |
| /* Fullscreen breadcrumb — sits in the leftover space on the left of the | |
| action buttons. Same visual voice as the chat-header's repo-badge so it | |
| reads as continuation, not a new element. */ | |
| .diagram-fs-crumbs { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 12px; | |
| color: var(--text-2); | |
| font-family: var(--mono); | |
| padding: 5px 12px 5px 10px; | |
| border-radius: var(--radius-sm); | |
| background: rgba(255, 255, 255, 0.02); | |
| border: 1px solid rgba(255, 255, 255, 0.05); | |
| letter-spacing: -0.01em; | |
| animation: crumbs-in 260ms var(--ease-spring) 80ms both; | |
| } | |
| @keyframes crumbs-in { | |
| from { opacity: 0; transform: translateX(-6px); } | |
| to { opacity: 1; transform: translateX(0); } | |
| } | |
| .diagram-fs-slug { display: inline-flex; align-items: baseline; } | |
| .diagram-fs-owner { color: var(--muted); font-weight: 400; } | |
| .diagram-fs-name { color: var(--text); font-weight: 600; } | |
| .diagram-fs-sep { color: var(--faint); font-family: var(--sans); } | |
| .diagram-fs-view { | |
| font-family: var(--sans); | |
| font-size: 11.5px; | |
| color: var(--accent-soft); | |
| padding: 2px 8px; | |
| background: rgba(91, 143, 249, 0.10); | |
| border: 1px solid rgba(91, 143, 249, 0.22); | |
| border-radius: 4px; | |
| letter-spacing: 0; | |
| } | |
| .diagram-fullscreen-btn { | |
| background: var(--surface-3); | |
| border: 1px solid var(--border-strong); | |
| border-radius: 6px; | |
| width: 30px; | |
| height: 30px; | |
| padding: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| line-height: 0; | |
| font-size: 0; | |
| cursor: pointer; | |
| color: var(--text-2); | |
| flex-shrink: 0; | |
| transition: background 0.15s, border-color 0.15s, color 0.15s; | |
| } | |
| .diagram-fullscreen-btn:hover { | |
| background: var(--surface-4); | |
| border-color: var(--accent-border); | |
| color: var(--accent-soft); | |
| } | |
| .diagram-canvas { | |
| min-height: 500px; | |
| } | |
| /* ── README View ─────────────────────────────────────────────────────────── */ | |
| /* Fills the main pane exactly like DiagramView — no modal, no overlay */ | |
| .readme-view { | |
| display: flex; | |
| flex-direction: column; | |
| flex: 1; | |
| min-height: 0; | |
| overflow: hidden; | |
| } | |
| .readme-view-bar { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 10px 20px; | |
| border-bottom: 1px solid var(--border); | |
| background: var(--surface-2); | |
| flex-shrink: 0; | |
| gap: 12px; | |
| min-height: 44px; | |
| } | |
| .readme-view-bar-left { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| min-width: 0; | |
| } | |
| .readme-view-bar-right { | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| flex-shrink: 0; | |
| } | |
| /* README action bar buttons — same visual system as clear-btn / header actions */ | |
| .readme-bar-btn { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| padding: 5px 10px; | |
| font-size: 12px; | |
| font-family: var(--sans); | |
| font-weight: 500; | |
| color: var(--text-2); | |
| background: var(--surface-3); | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--radius-sm); | |
| cursor: pointer; | |
| transition: color var(--transition), background var(--transition), border-color var(--transition); | |
| white-space: nowrap; | |
| } | |
| .readme-bar-btn:hover { | |
| color: var(--text); | |
| background: var(--surface-4); | |
| border-color: var(--accent-border); | |
| } | |
| .readme-view-status { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 12px; | |
| color: var(--muted); | |
| } | |
| .readme-view-body { | |
| flex: 1; | |
| overflow-y: auto; | |
| /* Transparent — lets the .main ambient layer show through. */ | |
| background: transparent; | |
| } | |
| /* Prose column — GitHub/Linear-style: clean, constrained, no visual tricks */ | |
| .readme-content { | |
| max-width: 680px; | |
| margin: 0 auto; | |
| padding: 44px clamp(24px, 6%, 72px) 72px; | |
| font-size: 14.5px; | |
| line-height: 1.78; | |
| color: var(--text-2); | |
| } | |
| /* H1 — document heading, not a hero banner. | |
| GitHub renders it at ~28px clean white; we match that discipline. */ | |
| .readme-content h1 { | |
| font-size: 28px; | |
| font-weight: 700; | |
| letter-spacing: -0.025em; | |
| line-height: 1.2; | |
| margin: 0 0 6px; | |
| color: var(--text); | |
| } | |
| /* Thin accent rule beneath the title — the one visual signature */ | |
| .readme-content h1::after { | |
| content: ''; | |
| display: block; | |
| margin-top: 14px; | |
| height: 1px; | |
| width: 100%; | |
| background: linear-gradient(to right, var(--accent-border), transparent 60%); | |
| } | |
| /* Lead paragraph immediately after H1 — slightly larger, acts as subtitle */ | |
| .readme-content h1 + p { | |
| font-size: 15.5px; | |
| line-height: 1.65; | |
| color: var(--text-2); | |
| margin: 16px 0 44px; | |
| font-weight: 400; | |
| } | |
| /* H2 — small-caps section label with glowing dot + fading rule */ | |
| .readme-content h2 { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: 0.12em; | |
| text-transform: uppercase; | |
| color: var(--muted); | |
| margin: 48px 0 18px; | |
| padding: 0; | |
| border: none; | |
| } | |
| .readme-content h2::before { | |
| content: ''; | |
| display: inline-block; | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: var(--accent); | |
| box-shadow: 0 0 12px var(--accent-glow), 0 0 4px var(--accent); | |
| flex-shrink: 0; | |
| } | |
| .readme-content h2::after { | |
| content: ''; | |
| flex: 1; | |
| height: 1px; | |
| background: linear-gradient(to right, var(--border-strong), transparent); | |
| } | |
| /* H3 */ | |
| .readme-content h3 { | |
| font-size: 13.5px; | |
| font-weight: 600; | |
| color: var(--text); | |
| margin: 24px 0 8px; | |
| letter-spacing: -0.01em; | |
| } | |
| /* Paragraphs */ | |
| .readme-content p { margin: 0 0 14px; } | |
| /* Lists — blueprint dots instead of browser bullets */ | |
| .readme-content ul { | |
| margin: 0 0 16px; | |
| padding-left: 0; | |
| list-style: none; | |
| } | |
| .readme-content ol { margin: 0 0 16px; padding-left: 22px; } | |
| .readme-content li { | |
| margin-bottom: 7px; | |
| padding-left: 18px; | |
| position: relative; | |
| } | |
| .readme-content ul > li::before { | |
| content: ''; | |
| position: absolute; | |
| left: 3px; | |
| top: 10px; | |
| width: 4px; | |
| height: 4px; | |
| background: var(--accent); | |
| border-radius: 50%; | |
| opacity: 0.6; | |
| } | |
| /* Inline code — looks like code, not a button. | |
| Surface-4 bg + subtle border is how GitHub/Notion style it. */ | |
| .readme-content code { | |
| font-family: var(--mono); | |
| font-size: 12.5px; | |
| background: var(--surface-3); | |
| border: 1px solid var(--border-strong); | |
| border-radius: 4px; | |
| padding: 1px 6px; | |
| color: var(--text-2); | |
| } | |
| /* Standalone pre blocks (not inside TerminalBlock) — fallback only */ | |
| .readme-content > pre { | |
| background: #050510; | |
| border: 1px solid var(--border-strong); | |
| border-top: 1.5px solid var(--accent-border); | |
| border-radius: var(--radius); | |
| padding: 18px; | |
| overflow-x: auto; | |
| margin: 16px 0; | |
| } | |
| .readme-content > pre code { | |
| background: none; border: none; padding: 0; | |
| font-size: 12.5px; color: var(--text-2); line-height: 1.65; | |
| } | |
| /* Horizontal rule — gradient fade instead of hard line */ | |
| .readme-content hr { | |
| border: none; | |
| height: 1px; | |
| background: linear-gradient(to right, transparent, var(--accent-border), transparent); | |
| margin: 40px 0; | |
| } | |
| /* Strong */ | |
| .readme-content strong { color: var(--accent-lavender); font-weight: 600; } | |
| /* Links */ | |
| .readme-content a { color: var(--accent-soft); text-decoration: none; } | |
| .readme-content a:hover { color: var(--accent-light); text-decoration: underline; } | |
| /* Blockquotes */ | |
| .readme-content blockquote { | |
| border-left: 2px solid var(--accent-border); | |
| margin: 16px 0; | |
| padding: 4px 0 4px 16px; | |
| color: var(--muted); | |
| font-style: italic; | |
| } | |
| /* ── Terminal chrome for fenced code blocks ─────────────────────────────── */ | |
| /* macOS-style window: traffic-light dots + language badge + dark body */ | |
| .readme-terminal { | |
| margin: 20px 0; | |
| border-radius: var(--radius); | |
| overflow: hidden; | |
| border: 1px solid var(--border-strong); | |
| box-shadow: | |
| 0 12px 40px rgba(0,0,0,0.6), | |
| inset 0 1px 0 rgba(255,255,255,0.04); | |
| } | |
| .readme-terminal-bar { | |
| display: flex; | |
| align-items: center; | |
| padding: 9px 14px; | |
| background: rgba(255,255,255,0.03); | |
| border-bottom: 1px solid var(--border); | |
| gap: 6px; | |
| } | |
| /* Traffic-light dots — authentic macOS feel */ | |
| .readme-terminal-dots { | |
| display: flex; | |
| gap: 6px; | |
| } | |
| .readme-terminal-dots span { | |
| width: 10px; | |
| height: 10px; | |
| border-radius: 50%; | |
| } | |
| .readme-terminal-dots span:nth-child(1) { background: #FF5F57; } | |
| .readme-terminal-dots span:nth-child(2) { background: #FEBC2E; } | |
| .readme-terminal-dots span:nth-child(3) { background: #28C840; } | |
| .readme-terminal-lang { | |
| margin-left: auto; | |
| font-family: var(--mono); | |
| font-size: 10px; | |
| color: var(--faint); | |
| letter-spacing: 0.06em; | |
| text-transform: lowercase; | |
| } | |
| /* Pre inside the terminal — no extra chrome, just content */ | |
| .readme-terminal-pre { | |
| margin: 0; | |
| padding: 18px; | |
| background: #050510; | |
| border: none; | |
| border-radius: 0; | |
| overflow-x: auto; | |
| } | |
| .readme-terminal-pre code { | |
| background: none; | |
| border: none; | |
| padding: 0; | |
| font-size: 12.5px; | |
| color: var(--text-2); | |
| line-height: 1.65; | |
| } | |
| /* Quality tip strip — one-liner between action bar and content */ | |
| .readme-quality-note { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 6px 16px; | |
| font-size: 11px; | |
| color: var(--muted); | |
| background: var(--surface-2); | |
| border-bottom: 1px solid var(--border); | |
| line-height: 1.5; | |
| } | |
| .readme-quality-note code { | |
| font-family: var(--mono); | |
| font-size: 10.5px; | |
| background: var(--surface-4); | |
| padding: 1px 4px; | |
| border-radius: 3px; | |
| color: var(--text-2); | |
| } | |
| /* In the README action bar the toggle is smaller — tone down the active glow */ | |
| .readme-view-bar .view-btn.active { | |
| background: rgba(255,255,255,0.07); | |
| box-shadow: none; | |
| } | |
| /* Preview/Markdown toggle reuses the app-wide .view-toggle / .view-btn system */ | |
| /* Raw markdown source panel — monospace, line-height matches code blocks */ | |
| .readme-raw { | |
| margin: 0; | |
| padding: 32px clamp(24px, 8%, 120px); | |
| font-family: var(--mono); | |
| font-size: 12.5px; | |
| line-height: 1.7; | |
| color: var(--text-2); | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| overflow-x: hidden; | |
| tab-size: 2; | |
| } | |
| .readme-view-placeholder { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 12px; | |
| height: 100%; | |
| min-height: 300px; | |
| color: var(--muted); | |
| font-size: 13px; | |
| text-align: center; | |
| } | |
| .readme-view-placeholder-icon { | |
| margin-bottom: 4px; | |
| } | |
| /* ── README Modal (unused, kept for reference) ───────────────────────────── */ | |
| .readme-overlay { | |
| position: fixed; | |
| inset: 0; | |
| z-index: 200; | |
| background: rgba(0, 0, 0, 0.72); | |
| backdrop-filter: blur(6px); | |
| -webkit-backdrop-filter: blur(6px); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 24px 16px; | |
| } | |
| .readme-panel { | |
| background: var(--surface-2); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-lg); | |
| width: 100%; | |
| max-width: 760px; | |
| max-height: 88vh; | |
| display: flex; | |
| flex-direction: column; | |
| box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6); | |
| overflow: hidden; | |
| } | |
| .readme-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| padding: 14px 18px; | |
| border-bottom: 1px solid var(--border); | |
| flex-shrink: 0; | |
| } | |
| .readme-header-left { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| flex: 1; | |
| min-width: 0; | |
| } | |
| .readme-title { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: var(--text); | |
| white-space: nowrap; | |
| } | |
| .readme-repo { | |
| font-size: 11.5px; | |
| color: var(--muted); | |
| font-family: var(--mono); | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| } | |
| .readme-cache-badge { | |
| font-size: 10px; | |
| font-family: var(--mono); | |
| color: var(--muted); | |
| background: var(--surface-3); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-sm); | |
| padding: 1px 6px; | |
| white-space: nowrap; | |
| } | |
| .readme-header-actions { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| flex-shrink: 0; | |
| } | |
| .readme-action-btn { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| padding: 5px 10px; | |
| background: var(--surface-3); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-sm); | |
| color: var(--text-2); | |
| font-size: 12px; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| white-space: nowrap; | |
| } | |
| .readme-action-btn:hover { | |
| background: var(--surface-4); | |
| border-color: var(--accent-border); | |
| color: var(--accent-soft); | |
| } | |
| .readme-close-btn { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 28px; | |
| height: 28px; | |
| background: transparent; | |
| border: 1px solid transparent; | |
| border-radius: var(--radius-sm); | |
| color: var(--muted); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| flex-shrink: 0; | |
| } | |
| .readme-close-btn:hover { | |
| background: var(--surface-3); | |
| border-color: var(--border); | |
| color: var(--text); | |
| } | |
| /* Thin accent progress bar below the header */ | |
| .readme-progress-wrap { | |
| height: 2px; | |
| background: var(--surface-3); | |
| flex-shrink: 0; | |
| } | |
| .readme-progress-bar { | |
| height: 100%; | |
| background: var(--accent-soft); | |
| border-radius: 1px; | |
| transition: width 0.4s ease; | |
| } | |
| .readme-body { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 28px 32px; | |
| min-height: 0; | |
| } | |
| .readme-loading { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| color: var(--muted); | |
| font-size: 13px; | |
| padding: 40px 0; | |
| justify-content: center; | |
| } | |
| .readme-error { | |
| padding: 40px 0; | |
| text-align: center; | |
| color: var(--text-2); | |
| font-size: 13px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 16px; | |
| } | |
| /* README trigger button in header */ | |
| .readme-trigger-btn { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| padding: 5px 10px; | |
| background: transparent; | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-sm); | |
| color: var(--muted); | |
| font-size: 12px; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| white-space: nowrap; | |
| } | |
| .readme-trigger-btn:hover { | |
| background: var(--surface-3); | |
| border-color: var(--accent-border); | |
| color: var(--accent-soft); | |
| } | |
| @media (max-width: 768px) { | |
| .readme-panel { max-height: 94vh; } | |
| .readme-body { padding: 20px 18px; } | |
| .readme-action-btn span { display: none; } /* icon-only on mobile */ | |
| } | |