Files
Netcatty/index.css
T
2026-05-01 13:48:42 +08:00

538 lines
12 KiB
CSS

@import "tailwindcss";
/* Bundled icon-only fallback so terminals show Nerd Font glyphs (powerline,
devicons, etc.) regardless of which base font the user picks. The font is
referenced last in the fontFamily fallback chain (see withCjkFallback in
infrastructure/config/fonts.ts) — base text comes from the user's chosen
font, missing PUA glyphs fall through to this face.
Source: https://github.com/ryanoasis/nerd-fonts (NerdFontsSymbolsOnly,
v3.4.0). License: MIT — see public/fonts/SymbolsNerdFont-LICENSE.txt. */
@font-face {
font-family: "Symbols Nerd Font Mono";
/* Absolute path resolves against the document origin in both dev
(http://localhost:5173) and packaged (app://netcatty), regardless of
where the bundled CSS file ends up. A relative ./fonts/... would be
resolved against dist/assets/index-*.css in production and 404. */
src: url("/fonts/SymbolsNerdFontMono-Regular.ttf") format("truetype");
font-weight: normal;
font-style: normal;
font-display: block;
}
/* ============================================
Tailwind CSS v4 Theme Configuration
============================================ */
@theme {
/* Default border color - ensures 'border' class uses theme color instead of currentColor */
--default-border-color: var(--color-border);
/* Colors - mapped from CSS variables */
--color-border: hsl(var(--border));
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
--color-primary: hsl(var(--primary));
--color-primary-foreground: hsl(var(--primary-foreground));
--color-secondary: hsl(var(--secondary));
--color-secondary-foreground: hsl(var(--secondary-foreground));
--color-destructive: hsl(var(--destructive));
--color-destructive-foreground: hsl(var(--destructive-foreground));
--color-muted: hsl(var(--muted));
--color-muted-foreground: hsl(var(--muted-foreground));
--color-accent: hsl(var(--accent));
--color-accent-foreground: hsl(var(--accent-foreground));
--color-popover: hsl(var(--popover));
--color-popover-foreground: hsl(var(--popover-foreground));
--color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground));
/* Border Radius */
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
/* Fonts */
--font-sans: "Space Grotesk", system-ui, sans-serif;
--font-mono: "JetBrains Mono", monospace;
/* Animations */
--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-fade-in: fade-in 0.2s ease-out;
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(--radix-collapsible-content-height);
}
}
@keyframes accordion-up {
from {
height: var(--radix-collapsible-content-height);
}
to {
height: 0;
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}
@keyframes pop-in {
0% {
opacity: 0;
transform: scale(0.82) translateY(6px);
}
45% {
opacity: 1;
transform: scale(1.06) translateY(-2px);
}
72% {
transform: scale(0.97) translateY(1px);
}
88% {
transform: scale(1.01);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes ripple {
0% {
transform: scale(0);
opacity: 0.35;
}
100% {
transform: scale(1);
opacity: 0;
}
}
@keyframes split-panel-enter {
0% {
width: 0;
min-width: 0;
opacity: 0;
transform: translateX(22px);
}
55% {
opacity: 0.88;
}
100% {
width: var(--aside-inline-width);
min-width: var(--aside-inline-width);
opacity: 1;
transform: translateX(0);
}
}
.split-panel-enter {
animation: split-panel-enter 220ms cubic-bezier(0.24, 0.84, 0.32, 1) both;
will-change: width, opacity, transform;
}
:root {
color-scheme: light;
}
/* Global default border color - ensures 'border' class always uses theme color */
*,
*::before,
*::after {
border-color: hsl(var(--border));
}
body {
min-height: 100vh;
font-family: var(--font-sans);
background-color: hsl(var(--background));
color: hsl(var(--foreground));
}
[data-instant-theme-switch="true"],
[data-instant-theme-switch="true"] *,
[data-instant-theme-switch="true"] *::before,
[data-instant-theme-switch="true"] *::after {
transition: none !important;
}
.dark {
color-scheme: dark;
}
.dark body {
background-color: hsl(var(--background));
color: hsl(var(--foreground));
}
/* Immersive mode: fade-out overlay for smooth theme restoration */
.immersive-fade-overlay {
position: fixed;
inset: 0;
z-index: 9999;
pointer-events: none;
opacity: 1;
transition: opacity 200ms ease-out;
}
.immersive-fade-overlay.fade-out {
opacity: 0;
}
/* Immersive mode: override agent icon badge colors to follow the theme */
.immersive-transition [data-agent-badge] {
border-color: hsl(var(--primary) / 0.2) !important;
background-color: hsl(var(--primary) / 0.1) !important;
}
.netcatty-shell {
position: relative;
background: linear-gradient(180deg, hsl(var(--background)) 0%, hsl(var(--background)) 60%, hsl(var(--background) / 0.9) 100%);
}
/* Slim down xterm 6.0 VS Code scrollbar — wide hit area, thin visual slider */
.xterm .xterm-scrollable-element > .scrollbar.vertical {
width: 12px !important;
}
.xterm .xterm-scrollable-element > .scrollbar.vertical > .slider {
width: 6px !important;
border-radius: 3px;
left: 3px !important;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.thinking-shimmer {
background: linear-gradient(
90deg,
currentColor 0%,
currentColor 40%,
rgba(255, 255, 255, 0.6) 50%,
currentColor 60%,
currentColor 100%
);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: thinking-shimmer 1.5s ease-in-out infinite;
}
@keyframes thinking-shimmer {
0% {
background-position: 100% center;
}
100% {
background-position: -100% center;
}
}
@keyframes progress-shimmer {
0% {
transform: translateX(-200%);
}
100% {
transform: translateX(200%);
}
}
.glass-panel {
background: hsl(var(--secondary) / 0.95);
border: 1px solid hsl(var(--border) / 0.8);
box-shadow: 0 14px 40px hsl(var(--foreground) / 0.12);
}
.soft-card {
background: hsl(var(--card));
border: 1px solid hsl(var(--border) / 0.8);
box-shadow: 0 4px 12px hsl(var(--foreground) / 0.06);
}
@keyframes session-activity-breathe {
0%,
100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.18;
}
}
.session-activity-dot {
animation: session-activity-breathe 1.4s ease-in-out infinite;
}
.card-highlight {
position: relative;
overflow: hidden;
}
.card-highlight::before {
content: "";
position: absolute;
inset: -40%;
background: radial-gradient(500px circle at 20% 20%, hsl(var(--primary) / 0.18), transparent 55%);
opacity: 0;
transition: opacity 180ms ease;
}
.card-highlight:hover::before {
opacity: 1;
}
.elevate {
transition: transform 140ms ease, border-color 140ms ease;
}
.elevate:hover {
transform: translateY(-2px);
}
::selection {
background: hsl(var(--primary) / 0.22);
color: hsl(var(--foreground));
}
/* Port Forwarding Traffic Animation */
@keyframes traffic-flow {
0% {
stroke-dashoffset: 24;
}
100% {
stroke-dashoffset: 0;
}
}
@keyframes traffic-flow-reverse {
0% {
stroke-dashoffset: 0;
}
100% {
stroke-dashoffset: 24;
}
}
@keyframes pulse-glow {
0%,
100% {
opacity: 0.6;
}
50% {
opacity: 1;
}
}
.traffic-line {
stroke-dasharray: 8 4;
animation: traffic-flow 0.8s linear infinite;
}
.traffic-line-reverse {
stroke-dasharray: 8 4;
animation: traffic-flow-reverse 0.8s linear infinite;
}
.traffic-glow {
animation: pulse-glow 1.5s ease-in-out infinite;
}
/* Custom titlebar drag regions for Electron */
.app-drag {
-webkit-app-region: drag;
app-region: drag;
user-select: none;
-webkit-user-select: none;
}
.app-no-drag {
-webkit-app-region: no-drag;
app-region: no-drag;
user-select: auto;
-webkit-user-select: auto;
}
.app-no-drag * {
-webkit-app-region: no-drag;
app-region: no-drag;
user-select: auto;
-webkit-user-select: auto;
}
.no-scrollbar {
scrollbar-width: none;
-ms-overflow-style: none;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.workspace-pane {
outline: none;
overflow: visible;
}
/* Dim terminal text in unfocused workspace panes (default) */
.workspace-pane:not(:focus-within) .xterm-screen {
opacity: 0.82;
}
/* Keep current pane fully visible while its context menu / popover is open;
focus moves to the menu portal and would otherwise drop :focus-within. */
.workspace-pane[data-menu-open] .xterm-screen {
opacity: 1;
}
/* Border-style focus indicator (opt-in via data attribute) */
[data-workspace-focus="border"] .workspace-pane:not(:focus-within) .xterm-screen {
opacity: 1;
}
[data-workspace-focus="border"] .workspace-pane::after {
content: "";
position: absolute;
inset: 0;
border: 2px solid transparent;
pointer-events: none;
transition: border-color 120ms ease;
z-index: 40;
}
[data-workspace-focus="border"] .workspace-pane:focus-within::after {
border-color: hsl(var(--primary));
}
/* ── Streamdown code block overrides ── */
[data-streamdown="code-block"] {
position: relative !important;
border-radius: 10px !important;
background: hsl(var(--muted) / 0.5) !important;
overflow: hidden !important;
margin: 6px 0 !important;
padding: 0 !important;
border: none !important;
gap: 0 !important;
}
[data-streamdown="code-block-header"] {
height: auto !important;
padding: 4px 12px 0 !important;
font-size: 11px !important;
}
[data-streamdown="code-block-header"] span {
margin-left: 0 !important;
}
[data-streamdown="code-block"] > div:has(> [data-streamdown="code-block-actions"]) {
position: absolute !important;
top: 4px !important;
right: 4px !important;
z-index: 10 !important;
display: flex !important;
width: auto !important;
height: auto !important;
margin: 0 !important;
pointer-events: none !important;
}
[data-streamdown="code-block-actions"] {
position: static !important;
border: none !important;
background: none !important;
backdrop-filter: none !important;
padding: 0 !important;
gap: 2px !important;
opacity: 0;
transition: opacity 0.15s;
}
[data-streamdown="code-block"]:hover [data-streamdown="code-block-actions"] {
opacity: 1;
}
[data-streamdown="code-block-actions"] button {
padding: 4px !important;
border-radius: 4px;
}
[data-streamdown="code-block-body"] {
border: none !important;
border-radius: 0 !important;
background: transparent !important;
padding: 0 !important;
margin: 0 !important;
overflow-x: auto !important;
overflow-y: hidden !important;
overscroll-behavior-x: contain;
-webkit-overflow-scrolling: touch;
font-size: 0 !important; /* collapse whitespace text nodes */
}
[data-streamdown="code-block-body"] pre {
font-size: 12px !important; /* restore in pre */
}
[data-streamdown="code-block"] pre {
display: block !important;
margin: 0 !important;
background: transparent !important;
border: none !important;
border-radius: 0 !important;
padding: 6px 12px 10px !important;
width: max-content !important;
min-width: 100% !important;
font-size: 12px !important;
line-height: 1.5 !important;
white-space: pre !important;
}
/* Streamdown table overrides */
.ai-chat-message-content[data-role="assistant"] [data-streamdown="table-wrapper"] {
gap: 4px !important;
padding: 4px 8px 8px !important;
}
.ai-chat-message-content[data-role="assistant"] [data-streamdown="table-wrapper"] > div:first-child > button,
.ai-chat-message-content[data-role="assistant"] [data-streamdown="table-wrapper"] > div:first-child > div > button {
padding: 3px !important;
}