@tailwind base; @tailwind components; @tailwind utilities; /* The app has no light theme, so the UA is told so once, at the root. Without this every browser-drawn control defaults to its light palette — most visibly the window and pane scrollbars, which rendered as light grey over the near-black surfaces. It also darkens native selects, date pickers and autofill, so it is the single declaration that keeps UA chrome on-theme. `scrollbar-color` is inherited, so setting it here reaches every scroll container that does not opt into the `::-webkit-scrollbar` styling below; it is honoured by Firefox and Chromium 121+, while older WebKit and Safari fall back to the dark UA scrollbar `color-scheme` already gives them. Thumb is `border.bright`, track is `surface` — the same tokens the panes use. */ :root { color-scheme: dark; scrollbar-width: thin; scrollbar-color: #3d3d5c #12121c; } html, body { height: 100%; } body { @apply bg-background font-mono text-slate-200 antialiased; } ::selection { @apply bg-primary text-black; } /* Thin scrollbars for log/stream panes. Defining any `::-webkit-scrollbar` pseudo-element makes Chromium ignore `scrollbar-color` for that element and draw a classic (non-overlay) bar, so the track is painted explicitly here rather than inherited. */ .scrollbar-thin::-webkit-scrollbar { width: 8px; height: 8px; } .scrollbar-thin::-webkit-scrollbar-track { @apply bg-transparent; } .scrollbar-thin::-webkit-scrollbar-thumb { @apply rounded-full bg-border-bright; } .scrollbar-thin::-webkit-scrollbar-corner { @apply bg-transparent; } /* Scrollable but chromeless — the landing nav's tab strip on narrow screens. */ .scrollbar-none { scrollbar-width: none; } .scrollbar-none::-webkit-scrollbar { display: none; } @layer utilities { /* Faint grid backdrop, tintable per module via `--grid-rgb` (inline style). Defaults to neutral slate; auth/landing set periwinkle, architect sets violet. */ .bg-grid { background-image: linear-gradient(to right, rgb(var(--grid-rgb, 148 163 184) / 0.05) 1px, transparent 1px), linear-gradient(to bottom, rgb(var(--grid-rgb, 148 163 184) / 0.05) 1px, transparent 1px); background-size: 32px 32px; } /* Lit blueprint plane — the architect canvas backdrop. Three static layers on one element, split this way on purpose: `mask-image` applies to an element AND its pseudo-elements, so the grid's dissolve mask lives on ::before alone. Putting the vignette under that mask would erase it exactly at the edges where it has to be strongest. Hue via `--grid-rgb`, same contract as `.bg-grid`. Host element must be positioned. Nothing animates, so there is no reduced-motion entry. */ .canvas-depth { pointer-events: none; background-image: radial-gradient( ellipse 90% 70% at 50% 34%, rgb(var(--grid-rgb, 148 163 184) / 0.05), transparent 70% ); } .canvas-depth::before, .canvas-depth::after { content: ''; position: absolute; inset: 0; border-radius: inherit; } /* Two-tier grid: 128px majors over 32px minors. The 32px cadence and its 0.05 alpha are shared with `.bg-grid` so the two backdrops stay one family; 128 is 4x32, so every major lands on a minor and composites brighter. */ .canvas-depth::before { background-image: linear-gradient(to right, rgb(var(--grid-rgb, 148 163 184) / 0.09) 1px, transparent 1px), linear-gradient(to bottom, rgb(var(--grid-rgb, 148 163 184) / 0.09) 1px, transparent 1px), linear-gradient(to right, rgb(var(--grid-rgb, 148 163 184) / 0.05) 1px, transparent 1px), linear-gradient(to bottom, rgb(var(--grid-rgb, 148 163 184) / 0.05) 1px, transparent 1px); background-size: 128px 128px, 128px 128px, 32px 32px, 32px 32px; -webkit-mask-image: radial-gradient(ellipse 88% 78% at 50% 40%, #000 45%, transparent 100%); mask-image: radial-gradient(ellipse 88% 78% at 50% 40%, #000 45%, transparent 100%); } /* Edge vignette — deliberately outside the mask above. Darkens toward the page `background` token, not a new hue. */ .canvas-depth::after { background: radial-gradient( ellipse 80% 80% at 50% 45%, transparent 45%, rgb(10 10 16 / 0.5) 100% ); } /* Colored spotlight beam for the landing stage. A beam is three stacked parts — source hotspot, coned shaft, hot core — each hued via its inline gradient. The shaft and core get a slow opacity breathe plus a gentle sway around the top apex; the source only breathes (swaying it would drift the lamp off the apex). Each part carries its own duration/delay so beams never move in lockstep. Breathe touches only opacity and sway only transform, so they never fight over the same property on one element. */ .spotlight-beam, .spotlight-core { animation: spotlight-breathe var(--beam-dur, 8s) ease-in-out infinite, spotlight-sway var(--beam-sway, 16s) ease-in-out infinite; } .spotlight-source { animation: spotlight-breathe var(--beam-dur, 8s) ease-in-out infinite; } /* Uppercase micro-label used for stat labels and section eyebrows. */ .text-micro { @apply text-[10px] font-semibold uppercase tracking-micro; } /* Vertical label for a collapsed side rail — reads bottom-to-top. The rotate is what flips `vertical-rl` from top-down to bottom-up, which is the conventional direction for a Latin tab label. Compose with `text-micro`. */ .text-vertical { writing-mode: vertical-rl; transform: rotate(180deg); } /* Architect step-2 column resize when a side panel collapses to its rail. A tween, not a spring: FlowLayer re-measures the whole agent graph on every frame of this transition, so overshoot would cost real work. */ .panel-grid { transition: grid-template-columns 250ms cubic-bezier(0.16, 1, 0.3, 1); } /* Animated neon gradient border for featured cards — double-background technique, hue via `--gb-rgb` (inline style). Degrades to a static gradient border without animation support. */ .gradient-border { border: 1px solid transparent; background: linear-gradient(#12121c, #12121c) padding-box, linear-gradient( 90deg, rgb(var(--gb-rgb, 211 203 192) / 0.7), rgb(var(--gb-rgb, 211 203 192) / 0.1) 40%, rgb(var(--gb-rgb, 211 203 192) / 0.7) ) border-box; background-size: 100% 100%, 200% 100%; animation: border-pan 6s linear infinite; } /* Diagonal light sweep on hover — for solid CTAs and featured cards. */ .shimmer-hover { position: relative; overflow: hidden; } .shimmer-hover::after { content: ''; position: absolute; inset: 0; width: 40%; transform: translateX(-150%) skewX(-12deg); background: linear-gradient(110deg, transparent, rgb(255 255 255 / 0.14), transparent); pointer-events: none; } .shimmer-hover:hover::after { animation: shimmer 0.9s ease-out; } /* 1px neon line sweeping down once on route entry — tint via `--pt-rgb`. Fixed so it crosses the whole viewport regardless of scroll position. */ .page-scanline { position: fixed; top: 0; left: 0; right: 0; height: 1px; z-index: 60; pointer-events: none; background: linear-gradient( 90deg, transparent, rgb(var(--pt-rgb, 211 203 192) / 0.9) 30%, rgb(var(--pt-rgb, 211 203 192) / 0.9) 70%, transparent ); box-shadow: 0 0 12px 1px rgb(var(--pt-rgb, 211 203 192) / 0.55); animation: scanline 0.35s ease-in both; } /* Sparkline entrance — bars grow from the baseline, staggered per bar via inline animation-delay. */ .spark-grow rect { transform-box: fill-box; transform-origin: bottom; animation: spark-grow 0.5s ease-out both; } /* Staggered blur-in for a container's direct children (forms, card innards). Caps at 10 children; later ones share the last beat. */ .stagger-children > * { animation: stagger-in 0.45s cubic-bezier(0.16, 1, 0.3, 1) both; } .stagger-children > *:nth-child(2) { animation-delay: 0.06s; } .stagger-children > *:nth-child(3) { animation-delay: 0.12s; } .stagger-children > *:nth-child(4) { animation-delay: 0.18s; } .stagger-children > *:nth-child(5) { animation-delay: 0.24s; } .stagger-children > *:nth-child(6) { animation-delay: 0.3s; } .stagger-children > *:nth-child(7) { animation-delay: 0.36s; } .stagger-children > *:nth-child(n + 8) { animation-delay: 0.42s; } /* One-shot light pan along the TopBar bottom edge — tint via `--tb-rgb`. */ .topbar-pan { background: linear-gradient( 90deg, transparent, rgb(var(--tb-rgb, 211 203 192) / 0.8), transparent ); background-size: 40% 100%; background-repeat: no-repeat; animation: topbar-pan 0.7s ease-out both; } /* Horizontal bar entrance (BarList) — pair with inline animation-delay. */ .bar-grow { transform-origin: left; animation: bar-grow 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; } /* Power-on line sweeping down a panel once per task start — `--ps-rgb`. */ .panel-scan { position: absolute; inset: 0; pointer-events: none; background-image: linear-gradient( 90deg, transparent, rgb(var(--ps-rgb, 167 139 250) / 0.8), transparent ); background-size: 100% 2px; background-repeat: no-repeat; animation: panel-scan 0.6s ease-in both; } /* Architect task rail — one status bar per task, hue via `--bar-rgb`. The glow is what makes a 6px bar read as neon rather than as a plain divider. */ .rail-bar { background-color: rgb(var(--bar-rgb, 139 143 163)); box-shadow: 0 0 8px 0 rgb(var(--bar-rgb, 139 143 163) / 0.45); } /* Selected task: `--glow-rgb` (the architect hue) blooms around the bar, which keeps its own status color. */ .rail-bar-active { box-shadow: 0 0 10px 0 rgb(var(--bar-rgb, 139 143 163) / 0.6), 0 0 20px 4px rgb(var(--glow-rgb, 157 77 255) / 0.5); } /* Fresh EventLog row: tinted highlight decaying to transparent — `--lg-rgb`. */ .log-flash { animation: log-flash 1.2s ease-out both; } /* Loading placeholder sheen — module tint via `--sk-rgb`. */ .skeleton { @apply rounded-md bg-surface-2; background-image: linear-gradient( 100deg, transparent 30%, rgb(var(--sk-rgb, 148 163 184) / 0.08) 50%, transparent 70% ); background-size: 200% 100%; background-repeat: no-repeat; animation: skeleton-sheen 1.6s ease-in-out infinite; } /* Toast entrance — a short fade + slide from the right edge. */ .toast-in { animation: toast-in 0.22s ease-out both; } /* Sponsor like-burst — a particle flies out along its own `--tx`/`--ty`. */ .like-particle { animation: like-particle-fly 0.5s cubic-bezier(0.16, 1, 0.3, 1) both; } /* Sponsor like-burst — the filled heart pops, then fades to the outline. */ .like-heart-pop { animation: like-heart-pop 0.45s cubic-bezier(0.16, 1, 0.3, 1) both; } @media (prefers-reduced-motion: reduce) { .gradient-border, .shimmer-hover:hover::after, .skeleton, .spark-grow rect, .stagger-children > *, .topbar-pan, .bar-grow, .log-flash, .toast-in, .like-particle, .like-heart-pop { animation: none; } .spotlight-beam, .spotlight-source, .spotlight-core { animation: none; } .page-scanline, .panel-scan { display: none; } .panel-grid { transition: none; } } } @keyframes spotlight-breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } } @keyframes spotlight-sway { 0%, 100% { transform: rotate(-1.5deg); } 50% { transform: rotate(1.5deg); } } @keyframes spark-grow { from { transform: scaleY(0); } } @keyframes toast-in { from { opacity: 0; transform: translateX(12px); } } @keyframes like-particle-fly { from { transform: translate(-50%, -50%) scale(0.2); opacity: 1; } 60% { opacity: 1; } to { transform: translate(calc(-50% + var(--tx)), calc(-50% + var(--ty))) scale(0.5); opacity: 0; } } @keyframes like-heart-pop { 0% { transform: scale(0.6); opacity: 0; } 40% { transform: scale(1.35); opacity: 1; } 70% { transform: scale(0.95); opacity: 1; } 100% { transform: scale(1); opacity: 0; } } @keyframes stagger-in { from { opacity: 0; transform: translateY(8px); filter: blur(4px); } } @keyframes topbar-pan { from { background-position: -40% 0; } to { background-position: 140% 0; } } @keyframes bar-grow { from { transform: scaleX(0); } } @keyframes skeleton-sheen { from { background-position: -150% 0; } to { background-position: 250% 0; } } @keyframes scanline { from { transform: translateY(0); opacity: 0.9; } to { transform: translateY(100vh); opacity: 0; } } @keyframes panel-scan { from { background-position: 0 0; opacity: 1; } to { background-position: 0 100%; opacity: 0; } } @keyframes log-flash { 0% { background-color: rgb(var(--lg-rgb, 167 139 250) / 0.14); opacity: 0; transform: translateY(6px); } 25% { opacity: 1; transform: translateY(0); } 100% { background-color: transparent; opacity: 1; transform: translateY(0); } } /* ---- Per-domain squad-card hover motifs ---------------------------------- */ /* Decorative loop timings (~2s) sit outside motion.ts's DUR scale (one-shots, ≤0.8s), so they are CSS literals here — consistent with the keyframes above. The shared EASE.out curve is reused as the cubic-bezier literal. Every motif applies to nodes tagged [data-play], which are paused until the card hovers. These rules are intentionally unlayered: the `.motif-*` classes below use the `animation` shorthand, which resets `animation-play-state` to `running`. An unlayered rule beats Tailwind's layered utilities, and the higher-specificity `.card-motif [data-play]` selector re-asserts `paused` over that shorthand, so the grid stays idle until a card is hovered. */ /* Rendered always, invisible + paused at rest; fades to a faint watermark and resumes only while the parent card (a `.group`) is hovered. */ .card-motif { opacity: 0; transition: opacity 0.3s ease-out; } .group:hover .card-motif { opacity: 0.16; } .card-motif [data-play] { transform-box: fill-box; /* transform-origin relative to each node's own box */ animation-play-state: paused; } .group:hover .card-motif [data-play] { animation-play-state: running; } /* Rotations/tilts that must pivot on a point in the SVG, not each node's box. */ .card-motif .motif-spin { transform-box: view-box; transform-origin: 50% 50%; } .card-motif .motif-tip { transform-box: view-box; transform-origin: 50% 28%; } .card-motif .motif-swing { transform-box: view-box; transform-origin: 75% 41%; /* the tassel's attachment point on the cap */ } @media (prefers-reduced-motion: reduce) { /* Freeze at the static frame; the hover opacity fade (a transition) stays. */ .card-motif [data-play] { animation: none; } } /* Software: each code line wipes in from the left, holds, then retypes. */ .motif-codeline { animation: motif-codeline 2.4s cubic-bezier(0.16, 1, 0.3, 1) infinite; } @keyframes motif-codeline { 0% { transform: scaleX(0); opacity: 0; } 20% { opacity: 1; } 40%, 80% { transform: scaleX(1); opacity: 1; } 100% { transform: scaleX(0); opacity: 0; } } /* Blinking text caret. */ .motif-caret { animation: motif-caret 1s steps(1) infinite; } @keyframes motif-caret { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } } /* Finance: a candle grows from the baseline, holds, then the oldest fades out. */ .motif-candle { animation: motif-candle 2s cubic-bezier(0.16, 1, 0.3, 1) infinite; } @keyframes motif-candle { 0% { transform: scaleY(0); opacity: 0; } 25%, 80% { transform: scaleY(1); opacity: 1; } 100% { transform: scaleY(1); opacity: 0; } } /* Expanding signal ring / ripple (marketing rings, searching + opensource + local). */ .motif-ping { animation: motif-ping 1.8s ease-out infinite; transform-origin: center; } @keyframes motif-ping { 0% { transform: scale(0.3); opacity: 0.9; } 100% { transform: scale(1.9); opacity: 0; } } /* SEO: the magnifier sweeps back and forth across the result rows. */ .motif-scan { animation: motif-scan 2.4s ease-in-out infinite; } @keyframes motif-scan { 0%, 100% { transform: translateX(-8px); } 50% { transform: translateX(6px); } } /* SEO: the rank arrow ticks up and fades. */ .motif-rankup { animation: motif-rankup 2.4s ease-in-out infinite; } @keyframes motif-rankup { 0%, 100% { transform: translateY(4px); opacity: 0; } 50% { transform: translateY(-2px); opacity: 1; } } /* Continuous rotation (radar sweep, electron orbit). */ .motif-spin { animation: motif-spin 3s linear infinite; } @keyframes motif-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* Data: a bar rises and falls, staggered into a travelling wave. */ .motif-bar { animation: motif-bar 1.6s ease-in-out infinite; } @keyframes motif-bar { 0%, 100% { transform: scaleY(0.25); } 50% { transform: scaleY(1); } } /* Stroke that draws itself along a normalised path (content frame, git branch). */ .motif-draw { stroke-dasharray: 1; animation: motif-draw 2.6s ease-in-out infinite; } @keyframes motif-draw { 0% { stroke-dashoffset: 1; } 60%, 100% { stroke-dashoffset: 0; } } /* Legal: the balance beam tips gently back and forth. */ .motif-tip { animation: motif-tip 2.6s ease-in-out infinite; } @keyframes motif-tip { 0%, 100% { transform: rotate(-9deg); } 50% { transform: rotate(9deg); } } /* Education: the tassel swings like a pendulum (pivot set in the gating block). */ .motif-swing { animation: motif-swing 2.2s ease-in-out infinite; } @keyframes motif-swing { 0%, 100% { transform: rotate(-16deg); } 50% { transform: rotate(16deg); } } /* Chat/network dot pulse (social typing dots, community nodes). */ .motif-typedot { animation: motif-typedot 1.4s ease-in-out infinite; } @keyframes motif-typedot { 0%, 70%, 100% { transform: scale(0.7); opacity: 0.4; } 35% { transform: scale(1.15); opacity: 1; } } /* Community: a connective link brightens and dims. */ .motif-link { animation: motif-link 2s ease-in-out infinite; } @keyframes motif-link { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } } /* Local: the pin drops in and settles with a short bounce. */ .motif-drop { animation: motif-drop 2.4s ease-in-out infinite; } @keyframes motif-drop { 0% { transform: translateY(-16px); opacity: 0; } 25% { opacity: 1; } 45% { transform: translateY(0); } 58% { transform: translateY(-4px); } 70%, 100% { transform: translateY(0); opacity: 1; } } /* General: sparkles twinkle in and out of phase. */ .motif-twinkle { animation: motif-twinkle 1.8s ease-in-out infinite; } @keyframes motif-twinkle { 0%, 100% { transform: scale(0.4); opacity: 0.2; } 50% { transform: scale(1); opacity: 1; } }