/* ═══════════════════════════════════════════════════════════════════════ 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: ""; 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 !important; } /* Respect the user. Disable motion where requested. */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } /* 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 !important; transition-duration: 0.01ms !important; } } #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 !important; 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 !important; /* 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 !important; } .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) !important; 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 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 !important; border: 1px solid var(--border) !important; border-radius: var(--radius) !important; margin: 12px 0 !important; overflow-x: auto; /* Left accent on code blocks */ border-left: 2px solid var(--accent-border) !important; } .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 !important; border-radius: 0 !important; } /* 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 , 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 !important; clip-path: none !important; } } .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 !important; } } .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 !important; } .li-dot { opacity: 1 !important; transform: none !important; } .li-edge { opacity: 0.28 !important; } .li-cta { opacity: 1; transform: none !important; } } /* ══════════════════════════════════════════════════════════ 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 !important; border-color: var(--muted) !important; 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 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 */ }