diff --git a/CHANGELOG.md b/CHANGELOG.md index f9283be..365df20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to Abduction are documented here. This project follows ## [Unreleased] +### Fixed + +- Made the Text & UI scale control resize viewport-relative layout (modals, panes, splash) consistently with everything else, instead of leaving it measured against the real, unscaled window. +- Restored the Open editor executable-path fix that a prior merge silently dropped, and added a macOS fallback that launches the editor's application directly (via `open -a`) when its CLI shim was never added to PATH. + ## [0.1.16] - 2026-09-03 ### Added diff --git a/backend/repository_git.go b/backend/repository_git.go index 59f8bb2..b2ff3a9 100644 --- a/backend/repository_git.go +++ b/backend/repository_git.go @@ -328,6 +328,21 @@ func (service *RepositoryService) Fingerprint(repositoryPath string) (string, er return fmt.Sprintf("%x", fingerprint.Sum(nil)), nil } +// macEditorApplications maps common editor CLI commands to the macOS +// application `open -a` can launch when the CLI shim was never installed +// into PATH (for example, VS Code's "Shell Command: Install 'code' command" +// was never run). +var macEditorApplications = map[string]string{ + "code": "Visual Studio Code", + "code-insiders": "Visual Studio Code - Insiders", + "cursor": "Cursor", + "subl": "Sublime Text", + "zed": "Zed", + "webstorm": "WebStorm", + "idea": "IntelliJ IDEA", + "atom": "Atom", +} + // OpenInEditor starts the configured editor without blocking Abduction. func (service *RepositoryService) OpenInEditor(repositoryPath string, relativePath string) error { targetPath, pathError := SafeRepositoryPath(repositoryPath, relativePath) @@ -338,8 +353,31 @@ func (service *RepositoryService) OpenInEditor(repositoryPath string, relativePa if len(editorParts) == 0 { return errors.New("no editor configured") } - commandArguments := append(editorParts[1:], targetPath) - return exec.Command(editorParts[0], commandArguments...).Start() + if editorBinary, lookupError := ExecutablePath(editorParts[0]); lookupError == nil { + commandArguments := append(editorParts[1:], targetPath) + return exec.Command(editorBinary, commandArguments...).Start() + } + if runtime.GOOS == "darwin" { + if arguments, known := macEditorLaunchArguments(editorParts, targetPath); known { + return exec.Command("open", arguments...).Start() + } + } + return fmt.Errorf("editor %q not found", editorParts[0]) +} + +// macEditorLaunchArguments builds the `open` argument vector that launches a +// known editor application directly when its CLI shim is not on PATH. +func macEditorLaunchArguments(editorParts []string, targetPath string) ([]string, bool) { + applicationName, known := macEditorApplications[editorParts[0]] + if !known { + return nil, false + } + arguments := []string{"-a", applicationName, targetPath} + if len(editorParts) > 1 { + arguments = append(arguments, "--args") + arguments = append(arguments, editorParts[1:]...) + } + return arguments, true } // SafeRepositoryPath resolves a path and proves that it remains inside its repository. diff --git a/backend/repository_test.go b/backend/repository_test.go index 3ef0eaf..ddfa052 100644 --- a/backend/repository_test.go +++ b/backend/repository_test.go @@ -7,6 +7,7 @@ import ( "reflect" "strings" "testing" + "time" ) // TestSafeRepositoryPathRejectsTraversal protects the desktop filesystem boundary. @@ -93,6 +94,64 @@ func TestRunGitSurvivesAnEmptyPATH(testingContext *testing.T) { } } +// TestOpenInEditorUsesExecutablePathFallbacks protects macOS GUI launches from +// silently failing to open an editor installed outside the minimal launch PATH. +func TestOpenInEditorUsesExecutablePathFallbacks(testingContext *testing.T) { + home := testingContext.TempDir() + localBin := filepath.Join(home, ".local", "bin") + if makeError := os.MkdirAll(localBin, 0o755); makeError != nil { + testingContext.Fatal(makeError) + } + repositoryPath := testingContext.TempDir() + markerPath := filepath.Join(repositoryPath, "editor-opened") + editorStub := filepath.Join(localBin, "stub-editor") + script := "#!/bin/sh\necho \"$@\" > " + markerPath + "\n" + if writeError := os.WriteFile(editorStub, []byte(script), 0o755); writeError != nil { + testingContext.Fatal(writeError) + } + testingContext.Setenv("HOME", home) + testingContext.Setenv("PATH", "") + service := NewRepositoryService(Config{Editor: "stub-editor"}) + if openError := service.OpenInEditor(repositoryPath, ""); openError != nil { + testingContext.Fatalf("expected the editor to resolve via ~/.local/bin despite an empty PATH, received: %v", openError) + } + deadline := time.Now().Add(2 * time.Second) + for time.Now().Before(deadline) { + if _, statError := os.Stat(markerPath); statError == nil { + return + } + time.Sleep(20 * time.Millisecond) + } + testingContext.Fatal("expected the stub editor to run and record its arguments") +} + +// TestMacEditorLaunchArgumentsResolvesKnownApplications protects editors that +// were installed without their optional CLI shim on PATH. +func TestMacEditorLaunchArgumentsResolvesKnownApplications(testingContext *testing.T) { + arguments, known := macEditorLaunchArguments([]string{"code"}, "/tmp/example") + if !known { + testingContext.Fatal("expected \"code\" to resolve to a known macOS application") + } + if !reflect.DeepEqual(arguments, []string{"-a", "Visual Studio Code", "/tmp/example"}) { + testingContext.Fatalf("unexpected open arguments: %#v", arguments) + } + if _, known := macEditorLaunchArguments([]string{"some-unknown-editor"}, "/tmp/example"); known { + testingContext.Fatal("expected an unrecognised editor command to report unknown") + } +} + +// TestMacEditorLaunchArgumentsForwardsExtraFlags keeps configured editor +// flags working when Abduction has to fall back to launching the application. +func TestMacEditorLaunchArgumentsForwardsExtraFlags(testingContext *testing.T) { + arguments, known := macEditorLaunchArguments([]string{"code", "--new-window"}, "/tmp/example") + if !known { + testingContext.Fatal("expected \"code\" to resolve to a known macOS application") + } + if !reflect.DeepEqual(arguments, []string{"-a", "Visual Studio Code", "/tmp/example", "--args", "--new-window"}) { + testingContext.Fatalf("unexpected open arguments: %#v", arguments) + } +} + // TestRepositoryFingerprintChangesWithWorkingTree protects live UI refreshes. func TestRepositoryFingerprintChangesWithWorkingTree(testingContext *testing.T) { repositoryPath := testingContext.TempDir() diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0736acf..8a058c7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -9,6 +9,7 @@ import { HistoryView, ReviewsView, StatsView } from "./features/repository/Repos import { AnalysisView, SecurityView, ToolsView } from "./features/operations/OperationViews"; import { EmptyWorkspace, LogsView, SettingsView, ThemeSwitcher, themes, type LogEntry } from "./features/settings/SettingsViews"; import { Loading, Rail, RemoteRepositoryNotice, Titlebar, WorkspaceHeader, destinations, isEditingTarget } from "./features/shell/Shell"; +import { updateViewportUnits } from "./viewport"; type AppCommand = { id: string; label: string; detail: string; keys: string[]; icon: LucideIcon; run: () => void }; @@ -104,6 +105,7 @@ export default function App() { document.documentElement.style.setProperty("--glass", String(bootstrap.config.glass)); document.documentElement.style.setProperty("--glass-opacity", `${Math.round(bootstrap.config.glass * 100)}%`); document.documentElement.style.setProperty("--scale", String(bootstrap.config.scale)); + updateViewportUnits(); }, [bootstrap]); // updateTheme applies and persists a theme chosen from the desktop chrome. diff --git a/frontend/src/cockpit.css b/frontend/src/cockpit.css index c7e9027..7c0f130 100644 --- a/frontend/src/cockpit.css +++ b/frontend/src/cockpit.css @@ -20,7 +20,7 @@ :root[data-theme="hackerman-ghost"] { --page:#020807;--surface:#06100e;--surface-2:#091b17;--surface-3:#0e2b25;--border:rgba(47,255,193,.24);--text:#e5fff8;--muted:#5d8d80;--accent:#2fffc1;--accent-2:#00a8ff;--cyan:#b8fff1;--green:#2fffc1;--yellow:#adff7d;--red:#64ffda;--ambient-primary:#2fffc1;--ambient-secondary:#00a8ff;--ambient-cyan:#b8fff1;--code-canvas:#010504;--glass-cast:#091b17;--shadow-cast:rgba(47,255,193,.19);--selection:rgba(47,255,193,.24); } body { background: var(--depth-canvas); } -.shell { position:relative; isolation:isolate; grid-template:68px minmax(0,1fr) / 70px minmax(0,1fr); background:radial-gradient(900px 520px at 62% -30%,color-mix(in srgb,var(--ambient-primary) 28%,transparent),transparent 66%),radial-gradient(650px 520px at 105% 100%,color-mix(in srgb,var(--ambient-secondary) 18%,transparent),transparent 70%),var(--depth-canvas); width:calc(100% / var(--scale,1)); height:calc(100vh / var(--scale,1)); transform:scale(var(--scale,1)); transform-origin:top left; } +.shell { position:relative; isolation:isolate; grid-template:68px minmax(0,1fr) / 70px minmax(0,1fr); background:radial-gradient(900px 520px at 62% -30%,color-mix(in srgb,var(--ambient-primary) 28%,transparent),transparent 66%),radial-gradient(650px 520px at 105% 100%,color-mix(in srgb,var(--ambient-secondary) 18%,transparent),transparent 70%),var(--depth-canvas); width:calc(100% / var(--scale,1)); height:calc(calc(100 * var(--vh)) / var(--scale,1)); transform:scale(var(--scale,1)); transform-origin:top left; } .shell::before { content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;opacity:.22;background-image:linear-gradient(color-mix(in srgb,var(--accent) 10%,transparent) 1px,transparent 1px),linear-gradient(90deg,color-mix(in srgb,var(--accent) 10%,transparent) 1px,transparent 1px);background-size:54px 54px;mask-image:linear-gradient(to bottom,black,transparent 72%); } .titlebar { margin:0;padding:0 18px;border:0;border-bottom:1px solid color-mix(in srgb,var(--cyan) 24%,var(--border));border-radius:0;background:linear-gradient(100deg,color-mix(in srgb,var(--surface-2) 94%,var(--page)),color-mix(in srgb,var(--page) 92%,transparent))!important;box-shadow:0 14px 48px rgba(0,0,0,.38)!important; } @@ -109,11 +109,11 @@ body { background: var(--depth-canvas); } .picker__option--active { border-color:color-mix(in srgb,var(--cyan) 48%,transparent)!important;background:linear-gradient(90deg,color-mix(in srgb,var(--cyan) 18%,transparent),color-mix(in srgb,var(--accent-2) 9%,transparent))!important;box-shadow:inset 3px 0 var(--cyan); } .repo-action { padding:5px 8px;border:1px solid color-mix(in srgb,var(--cyan) 35%,var(--border));border-radius:7px;color:var(--cyan);background:color-mix(in srgb,var(--cyan) 8%,transparent);font:8px var(--mono);white-space:nowrap; } .repo-action--local { color:var(--muted);border-color:var(--border);background:transparent; } -.command-backdrop { position:fixed;inset:0;z-index:1000;display:grid;place-items:start center;padding-top:clamp(70px,12vh,130px);background:rgba(0,1,8,.68);backdrop-filter:blur(8px);animation:command-fade 130ms ease-out; } -.command-palette,.shortcut-help { width:min(680px,calc(100vw - 32px));overflow:hidden;border:1px solid color-mix(in srgb,var(--cyan) 38%,var(--border));border-radius:10px 24px 10px 24px;background:linear-gradient(145deg,color-mix(in srgb,var(--surface-2) 98%,var(--page)),color-mix(in srgb,var(--page) 97%,black));box-shadow:var(--shadow-float);animation:command-rise 160ms cubic-bezier(.2,.8,.2,1); } +.command-backdrop { position:fixed;inset:0;z-index:1000;display:grid;place-items:start center;padding-top:clamp(70px,calc(12 * var(--vh)),130px);background:rgba(0,1,8,.68);backdrop-filter:blur(8px);animation:command-fade 130ms ease-out; } +.command-palette,.shortcut-help { width:min(680px,calc(calc(100 * var(--vw)) - 32px));overflow:hidden;border:1px solid color-mix(in srgb,var(--cyan) 38%,var(--border));border-radius:10px 24px 10px 24px;background:linear-gradient(145deg,color-mix(in srgb,var(--surface-2) 98%,var(--page)),color-mix(in srgb,var(--page) 97%,black));box-shadow:var(--shadow-float);animation:command-rise 160ms cubic-bezier(.2,.8,.2,1); } .command-palette>header { display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:12px;padding:14px 16px;border-bottom:1px solid var(--border);color:var(--cyan); } .command-palette>header input { width:100%;padding:5px;border:0;outline:0;color:var(--text);background:transparent;font-size:16px; } -.command-list { max-height:min(520px,58vh);overflow:auto;padding:7px; } +.command-list { max-height:min(520px,calc(58 * var(--vh)));overflow:auto;padding:7px; } .command-item { display:grid;grid-template-columns:36px minmax(0,1fr) auto;align-items:center;gap:11px;width:100%;padding:10px;border:1px solid transparent;border-radius:7px 13px 7px 13px;color:var(--text);background:transparent;text-align:left; } .command-item__icon { display:grid;place-items:center;width:34px;height:34px;border-radius:7px;color:var(--muted);background:var(--depth-recessed); } .command-item strong,.command-item small { display:block; } @@ -148,8 +148,8 @@ body { background: var(--depth-canvas); } .header-actions{gap:5px} .header-actions select{min-width:88px;max-width:130px} .header-actions .editor-action,.header-actions .github-action,.pull-status{display:none} - .code-layout{grid-template-columns:minmax(180px,min(var(--tree-pane-width, 300px), 32vw)) minmax(0,1fr)} - .code-layout--chat{grid-template-columns:minmax(180px,28vw) minmax(0,1fr) minmax(260px,36vw)} + .code-layout{grid-template-columns:minmax(180px,min(var(--tree-pane-width, 300px), calc(32 * var(--vw)))) minmax(0,1fr)} + .code-layout--chat{grid-template-columns:minmax(180px,calc(28 * var(--vw))) minmax(0,1fr) minmax(260px,calc(36 * var(--vw)))} .document.markdown{padding:28px 30px 70px} } @media(max-height:650px){ @@ -170,13 +170,13 @@ body { background: var(--depth-canvas); } .splash__binary>div:nth-child(2){animation-delay:-.55s}.splash__binary>div:nth-child(3){animation-delay:-.3s}.splash__binary>div:nth-child(4){animation-delay:-.8s} .splash__binary--upper { animation:binary-harvest-upper 3.2s cubic-bezier(.18,.76,.2,1) both; } .splash__binary--lower { animation:binary-harvest-lower 3.2s cubic-bezier(.18,.76,.2,1) both; } -.splash__scythe { z-index:5;width:min(760px,78vw);transform-origin:76% 23%;animation:scythe-harvest 2.25s .55s cubic-bezier(.12,.74,.18,1) both; } +.splash__scythe { z-index:5;width:min(760px,calc(78 * var(--vw)));transform-origin:76% 23%;animation:scythe-harvest 2.25s .55s cubic-bezier(.12,.74,.18,1) both; } .splash__handle { stroke-width:8;stroke:rgba(225,233,255,.62); } .splash__blade { stroke-width:12;filter:drop-shadow(0 0 5px #38ddff); } .splash__cut { z-index:4;height:4px;animation:harvest-cut 1.05s 1.52s cubic-bezier(.15,.7,.2,1) both; } .splash__impact { position:absolute;z-index:6;width:18px;height:18px;border-radius:50%;background:white;box-shadow:0 0 12px 5px #fff,0 0 35px 14px var(--cyan),0 0 90px 34px var(--accent),0 0 150px 55px var(--accent-2);opacity:0;transform:translate(52px,-20px);animation:harvest-impact .75s 1.72s ease-out both; } .splash__shards { position:absolute;z-index:6;width:10px;height:10px;transform:translate(52px,-20px) rotate(-19deg); } -.splash__shards i { --shard-angle:0deg;position:absolute;width:clamp(28px,5vw,72px);height:2px;background:linear-gradient(90deg,#fff,var(--cyan),transparent);box-shadow:0 0 8px var(--cyan);opacity:0;transform-origin:left center;animation:harvest-shard .82s 1.8s cubic-bezier(.1,.7,.2,1) both; } +.splash__shards i { --shard-angle:0deg;position:absolute;width:clamp(28px,calc(5 * var(--vw)),72px);height:2px;background:linear-gradient(90deg,#fff,var(--cyan),transparent);box-shadow:0 0 8px var(--cyan);opacity:0;transform-origin:left center;animation:harvest-shard .82s 1.8s cubic-bezier(.1,.7,.2,1) both; } .splash__shards i:nth-child(1){--shard-angle:-78deg}.splash__shards i:nth-child(2){--shard-angle:-58deg;animation-delay:1.84s}.splash__shards i:nth-child(3){--shard-angle:-38deg}.splash__shards i:nth-child(4){--shard-angle:-18deg;animation-delay:1.88s}.splash__shards i:nth-child(5){--shard-angle:2deg}.splash__shards i:nth-child(6){--shard-angle:22deg;animation-delay:1.82s}.splash__shards i:nth-child(7){--shard-angle:42deg}.splash__shards i:nth-child(8){--shard-angle:62deg;animation-delay:1.9s}.splash__shards i:nth-child(9){--shard-angle:82deg}.splash__shards i:nth-child(10){--shard-angle:112deg;animation-delay:1.86s}.splash__shards i:nth-child(11){--shard-angle:148deg}.splash__shards i:nth-child(12){--shard-angle:188deg;animation-delay:1.92s} .splash__identity { z-index:7;animation:harvest-identity .92s 2.16s cubic-bezier(.16,.8,.2,1) both; } .splash__identity h1 { margin-top:20px;font-size:40px;letter-spacing:.08em; } @@ -198,8 +198,8 @@ body { background: var(--depth-canvas); } @media(prefers-reduced-motion:reduce){.splash__vignette,.splash__shards,.splash__impact{display:none}.splash__identity>span{animation:none;opacity:1}.splash__identity{animation:none;opacity:1;clip-path:none;filter:none}} .splash-encounter { position:absolute;inset:0;z-index:10;display:grid;place-content:center;justify-items:center;padding:24px;background:radial-gradient(ellipse at center,rgba(5,18,37,.88),rgba(1,3,9,.94) 68%);animation:encounter-reveal .5s ease-out both; } -.encounter-quote { width:min(680px,86vw);margin:0 auto 18px;padding:18px 22px 16px;border:1px solid rgba(111,199,211,.28);border-radius:10px 22px 10px 22px;background:rgba(1,5,13,.72);box-shadow:0 18px 55px rgba(0,0,0,.45),0 0 30px rgba(70,232,255,.1);backdrop-filter:blur(8px); } -.encounter-quote blockquote { margin:0;color:#d9ffff;font:clamp(11px,1.25vw,15px)/1.45 var(--mono);letter-spacing:.015em;text-wrap:balance; } +.encounter-quote { width:min(680px,calc(86 * var(--vw)));margin:0 auto 18px;padding:18px 22px 16px;border:1px solid rgba(111,199,211,.28);border-radius:10px 22px 10px 22px;background:rgba(1,5,13,.72);box-shadow:0 18px 55px rgba(0,0,0,.45),0 0 30px rgba(70,232,255,.1);backdrop-filter:blur(8px); } +.encounter-quote blockquote { margin:0;color:#d9ffff;font:clamp(11px,calc(1.25 * var(--vw)),15px)/1.45 var(--mono);letter-spacing:.015em;text-wrap:balance; } .encounter-quote figcaption { display:flex;flex-wrap:wrap;justify-content:center;gap:5px 12px;margin-top:8px;font:9px/1.35 var(--mono);letter-spacing:.08em;text-transform:uppercase; } .encounter-quote figcaption strong { color:#9ef8ff; } .encounter-quote figcaption span { color:#607d88; } @@ -207,7 +207,7 @@ body { background: var(--depth-canvas); } .splash-encounter>button:hover,.splash-encounter>button:focus-visible { border-color:#9ef8ff;background:rgba(25,69,82,.8);box-shadow:0 0 24px rgba(70,232,255,.25); } .splash-encounter>button span { color:#607d88;font-size:8px; } @keyframes encounter-reveal { from{opacity:0;filter:blur(7px)} to{opacity:1;filter:none} } -@media(max-height:720px){.splash--ascii .splash__identity h1{font-size:clamp(20px,2.5vw,32px)}.encounter-quote{padding:12px 16px}.encounter-quote blockquote{font-size:10px}.splash--ascii .splash__identity>span{display:none}} +@media(max-height:720px){.splash--ascii .splash__identity h1{font-size:clamp(20px,calc(2.5 * var(--vw)),32px)}.encounter-quote{padding:12px 16px}.encounter-quote blockquote{font-size:10px}.splash--ascii .splash__identity>span{display:none}} .picker { display:flex;flex-direction:column; } .picker__list { flex:1;min-height:120px; } @@ -221,7 +221,7 @@ body { background: var(--depth-canvas); } .picker-empty { padding:25px;color:var(--muted);text-align:center;font-size:10px; } .guide-backdrop { position:fixed;inset:0;z-index:100;display:grid;place-items:center;padding:24px;background:rgba(1,3,10,.8);backdrop-filter:blur(12px); } -.first-run-guide { width:min(720px,92vw);padding:28px;border:1px solid color-mix(in srgb,var(--cyan) 35%,var(--border));border-radius:14px 30px 14px 30px;background:linear-gradient(145deg,color-mix(in srgb,var(--surface-2) 96%,#07152b),var(--surface));box-shadow:0 34px 100px rgba(0,0,0,.68),0 0 60px color-mix(in srgb,var(--cyan) 10%,transparent); } +.first-run-guide { width:min(720px,calc(92 * var(--vw)));padding:28px;border:1px solid color-mix(in srgb,var(--cyan) 35%,var(--border));border-radius:14px 30px 14px 30px;background:linear-gradient(145deg,color-mix(in srgb,var(--surface-2) 96%,#07152b),var(--surface));box-shadow:0 34px 100px rgba(0,0,0,.68),0 0 60px color-mix(in srgb,var(--cyan) 10%,transparent); } .first-run-guide h2 { margin:5px 0 6px;font-size:25px; } .first-run-guide>p { margin:0;color:var(--muted); } .first-run-guide>div { display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:23px 0; } @@ -231,10 +231,10 @@ body { background: var(--depth-canvas); } .first-run-guide article span { color:var(--muted);font-size:10px;line-height:1.55; } .first-run-guide footer { display:flex;align-items:center;justify-content:space-between;gap:20px; } .first-run-guide footer small { color:var(--muted); } -.first-run-setup { width:min(780px,92vw); } +.first-run-setup { width:min(780px,calc(92 * var(--vw))); } .first-run-setup .workspace-setup-field { display:grid;grid-template-columns:1fr;gap:8px;margin:22px 0 0;color:var(--muted);font:10px var(--mono);letter-spacing:.08em;text-transform:uppercase; } .first-run-setup .workspace-setup-field>div { display:flex;gap:8px; } .first-run-setup .workspace-setup-field input { min-width:0;flex:1;padding:12px 14px;border:1px solid var(--border);border-radius:9px;background:var(--page);color:var(--text);font:12px var(--mono);text-transform:none;letter-spacing:0; } .first-run-setup .workspace-setup-message { margin:12px 0 0;padding:10px 12px;border:1px solid color-mix(in srgb,var(--cyan) 30%,var(--border));border-radius:8px;color:var(--text);background:color-mix(in srgb,var(--cyan) 7%,transparent);font-size:11px; } .first-run-setup footer>div,.empty-actions { display:flex;gap:8px; } -@media(max-width:700px){.first-run-guide>div{grid-template-columns:1fr}.first-run-guide{max-height:90vh;overflow:auto}} +@media(max-width:700px){.first-run-guide>div{grid-template-columns:1fr}.first-run-guide{max-height:calc(90 * var(--vh));overflow:auto}} diff --git a/frontend/src/features/settings/SettingsViews.tsx b/frontend/src/features/settings/SettingsViews.tsx index b2964b7..6a7ac2f 100644 --- a/frontend/src/features/settings/SettingsViews.tsx +++ b/frontend/src/features/settings/SettingsViews.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import { ScrollText } from "lucide-react"; import { api } from "../../api"; import type { Bootstrap, ThemeName } from "../../types"; +import { updateViewportUnits } from "../../viewport"; export const themes: { name: ThemeName; label: string; palette: string[] }[] = [ { name: "reaper-dark", label: "Abduction Night", palette: ["#050713", "#315cff", "#19d9ff", "#a449ff"] }, @@ -60,6 +61,7 @@ export function SettingsView({ bootstrap, onSaved, onError }: { bootstrap: Boots document.documentElement.style.setProperty("--glass", String(draft.glass)); document.documentElement.style.setProperty("--glass-opacity", `${Math.round(draft.glass * 100)}%`); document.documentElement.style.setProperty("--scale", String(draft.scale)); + updateViewportUnits(); }, [draft]); // saveSettings validates preferences in Go and refreshes repository discovery. diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 0b0edb5..c14b4e2 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,13 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; +import { updateViewportUnits } from "./viewport"; import "./styles.css"; import "./security.css"; import "./cockpit.css"; const applicationRoot = document.getElementById("root"); if (!applicationRoot) throw new Error("Abduction could not find its application root"); +updateViewportUnits(); +window.addEventListener("resize", updateViewportUnits); ReactDOM.createRoot(applicationRoot).render(); diff --git a/frontend/src/security.css b/frontend/src/security.css index 6462a51..a21bb41 100644 --- a/frontend/src/security.css +++ b/frontend/src/security.css @@ -1,5 +1,5 @@ .security-view { - height: calc(100vh - 116px); + height: calc(calc(100 * var(--vh)) - 116px); min-height: 0; display: grid; grid-template-columns: minmax(240px, 310px) minmax(0, 1fr); @@ -111,7 +111,7 @@ body { background:var(--page) } .search-results span { grid-column: 1 / -1; overflow: hidden; color: var(--muted); font: 9px var(--mono); text-overflow: ellipsis; white-space: nowrap; } .search-results p { padding: 16px 8px; color: var(--muted); text-align: center; } -.reviews-view { height: calc(100vh - 116px); overflow: auto; padding: 25px 30px 60px; } +.reviews-view { height: calc(calc(100 * var(--vh)) - 116px); overflow: auto; padding: 25px 30px 60px; } .reviews-view > header { display: flex; align-items: center; justify-content: space-between; max-width: 1100px; margin: 0 auto 15px; } .reviews-view h2 { margin: 0; } .review-list { display: grid; gap: 8px; max-width: 1100px; margin: auto; } @@ -123,7 +123,7 @@ body { background:var(--page) } .review-state { padding: 5px 7px; border: 1px solid var(--border); border-radius: 99px; color: var(--muted); font: 8px var(--mono); text-align: center; text-transform: uppercase; } .review-state--open { border-color: color-mix(in srgb, var(--green) 45%, transparent); color: var(--green); background: color-mix(in srgb, var(--green) 8%, transparent); } .review-toolbar { display:flex;gap:10px;max-width:1100px;margin:0 auto 14px }.review-toolbar input,.card-heading input{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:10px;color:var(--text);background:color-mix(in srgb,var(--surface-2) 78%,transparent);font:9px var(--mono);outline:none}.review-toolbar input:focus,.card-heading input:focus{border-color:color-mix(in srgb,var(--accent) 55%,var(--border));box-shadow:0 0 18px color-mix(in srgb,var(--accent) 12%,transparent)} -.reviews-layout{display:block;max-width:1100px;margin:auto}.reviews-view--detail .reviews-layout{display:grid;grid-template-columns:minmax(340px,clamp(340px,34vw,460px)) minmax(0,1fr);gap:12px;max-width:min(1500px,calc(100vw - 125px))}.reviews-layout>.review-list{min-width:0;margin:0}.review-item--active{border-color:color-mix(in srgb,var(--accent) 65%,var(--border))!important;background:color-mix(in srgb,var(--accent) 10%,var(--surface))!important}.pr-detail{min-width:0;overflow:hidden;border:1px solid var(--border);border-radius:14px;background:color-mix(in srgb,var(--surface-2) 76%,transparent);box-shadow:var(--edge)}.pr-detail>header{display:flex;align-items:start;justify-content:space-between;gap:14px;padding:18px;border-bottom:1px solid var(--border)}.pr-detail h3{margin:3px 0 0;font-size:14px}.pr-metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--border)}.pr-metrics span{padding:11px;background:var(--surface);color:var(--muted);font:8px var(--mono)}.pr-metrics b{display:block;margin-bottom:4px;color:var(--text);font-size:12px}.diff-add{color:var(--green)!important}.diff-delete{color:var(--danger,#ff5d73)!important}.pr-decision{display:flex;gap:8px;padding:12px 18px}.pr-decision span{padding:5px 8px;border:1px solid color-mix(in srgb,var(--accent-2) 35%,var(--border));border-radius:99px;color:var(--accent-2);font:8px var(--mono);text-transform:lowercase}.pr-body{max-height:120px;overflow:auto;margin:0;padding:0 18px 14px;color:var(--muted);font-size:10px;line-height:1.6;white-space:pre-wrap}.pr-files{padding:0 18px 14px}.pr-detail h4{margin:8px 0 10px;font-size:9px;text-transform:uppercase;letter-spacing:.12em}.pr-files>div{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:8px;padding:6px 0;border-top:1px solid var(--border);font-size:8px}.pr-files code{overflow:hidden;text-overflow:ellipsis}.pr-diff{border-top:1px solid var(--border)}.pr-diff h4{padding:0 18px}.pr-diff pre{max-height:55vh;overflow:auto;margin:0;padding:10px 0;background:color-mix(in srgb,var(--page) 82%,black);font:8px/1.55 var(--mono)}.diff-line{display:block;min-width:max-content;padding:0 14px;white-space:pre}.diff-line--add{color:color-mix(in srgb,var(--green) 82%,var(--text));background:color-mix(in srgb,var(--green) 9%,transparent)}.diff-line--delete{color:#ff8294;background:rgba(255,70,95,.08)}.diff-line--meta{color:var(--cyan);background:color-mix(in srgb,var(--cyan) 6%,transparent)} +.reviews-layout{display:block;max-width:1100px;margin:auto}.reviews-view--detail .reviews-layout{display:grid;grid-template-columns:minmax(340px,clamp(340px,calc(34 * var(--vw)),460px)) minmax(0,1fr);gap:12px;max-width:min(1500px,calc(calc(100 * var(--vw)) - 125px))}.reviews-layout>.review-list{min-width:0;margin:0}.review-item--active{border-color:color-mix(in srgb,var(--accent) 65%,var(--border))!important;background:color-mix(in srgb,var(--accent) 10%,var(--surface))!important}.pr-detail{min-width:0;overflow:hidden;border:1px solid var(--border);border-radius:14px;background:color-mix(in srgb,var(--surface-2) 76%,transparent);box-shadow:var(--edge)}.pr-detail>header{display:flex;align-items:start;justify-content:space-between;gap:14px;padding:18px;border-bottom:1px solid var(--border)}.pr-detail h3{margin:3px 0 0;font-size:14px}.pr-metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--border)}.pr-metrics span{padding:11px;background:var(--surface);color:var(--muted);font:8px var(--mono)}.pr-metrics b{display:block;margin-bottom:4px;color:var(--text);font-size:12px}.diff-add{color:var(--green)!important}.diff-delete{color:var(--danger,#ff5d73)!important}.pr-decision{display:flex;gap:8px;padding:12px 18px}.pr-decision span{padding:5px 8px;border:1px solid color-mix(in srgb,var(--accent-2) 35%,var(--border));border-radius:99px;color:var(--accent-2);font:8px var(--mono);text-transform:lowercase}.pr-body{max-height:120px;overflow:auto;margin:0;padding:0 18px 14px;color:var(--muted);font-size:10px;line-height:1.6;white-space:pre-wrap}.pr-files{padding:0 18px 14px}.pr-detail h4{margin:8px 0 10px;font-size:9px;text-transform:uppercase;letter-spacing:.12em}.pr-files>div{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:8px;padding:6px 0;border-top:1px solid var(--border);font-size:8px}.pr-files code{overflow:hidden;text-overflow:ellipsis}.pr-diff{border-top:1px solid var(--border)}.pr-diff h4{padding:0 18px}.pr-diff pre{max-height:calc(55 * var(--vh));overflow:auto;margin:0;padding:10px 0;background:color-mix(in srgb,var(--page) 82%,black);font:8px/1.55 var(--mono)}.diff-line{display:block;min-width:max-content;padding:0 14px;white-space:pre}.diff-line--add{color:color-mix(in srgb,var(--green) 82%,var(--text));background:color-mix(in srgb,var(--green) 9%,transparent)}.diff-line--delete{color:#ff8294;background:rgba(255,70,95,.08)}.diff-line--meta{color:var(--cyan);background:color-mix(in srgb,var(--cyan) 6%,transparent)} .pr-review{padding:14px 18px;border-top:1px solid var(--border)}.pr-review textarea{width:100%;box-sizing:border-box;padding:10px;border:1px solid var(--border);border-radius:10px;color:var(--text);background:var(--surface);font:9px/1.5 var(--mono);resize:vertical}.pr-review__actions{display:flex;flex-wrap:wrap;gap:7px;margin-top:8px}.pr-review__actions .danger{border-color:rgba(255,80,100,.35);color:#ff8294}.review-confirm{position:fixed;z-index:100;inset:0;display:grid;place-items:center;padding:24px;background:rgba(0,0,0,.68);backdrop-filter:blur(8px)}.review-confirm>section{width:min(480px,100%);padding:22px;border:1px solid var(--border);border-radius:16px;background:var(--surface);box-shadow:0 30px 80px rgba(0,0,0,.55)}.review-confirm h3{margin:5px 0 10px}.review-confirm p{max-height:160px;overflow:auto;color:var(--muted);font-size:10px;white-space:pre-wrap}.review-confirm footer{display:flex;justify-content:flex-end;gap:8px;margin-top:18px} @media(max-width:1050px){.reviews-layout{grid-template-columns:1fr}.pr-detail{min-height:400px}} @@ -232,9 +232,9 @@ body { background:var(--page) } .history, .tools, .reviews-view, .security-view, .settings-view { margin: 0 10px 10px; border: 1px solid color-mix(in srgb, var(--accent) 16%, var(--border)); border-radius: calc(var(--radius) + 2px); background: color-mix(in srgb, var(--surface) var(--glass-opacity), transparent); box-shadow: var(--edge), 0 26px 55px rgba(0, 0, 0, .24), 0 0 calc(38px * var(--glow)) color-mix(in srgb, var(--accent) 9%, transparent); } /* Settings is a real configuration workspace, not a generic form page. */ -.settings-view { min-height: 0; overflow: auto; padding: 32px clamp(24px, 5vw, 72px) 28px; } +.settings-view { min-height: 0; overflow: auto; padding: 32px clamp(24px, calc(5 * var(--vw)), 72px) 28px; } .settings-view > header { max-width: 820px; margin: 0 auto 26px; } -.settings-view > header h2 { margin: 0; font-size: clamp(25px, 3vw, 38px); letter-spacing: -.045em; } +.settings-view > header h2 { margin: 0; font-size: clamp(25px, calc(3 * var(--vw)), 38px); letter-spacing: -.045em; } .settings-view > header p { max-width: 630px; color: var(--muted); font-size: 12px; line-height: 1.65; } .settings-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; max-width: 1100px; margin: auto; } .settings-card { display: grid; align-content: start; gap: 17px; min-width: 0; padding: 22px; border: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border)); border-radius: var(--radius); background: radial-gradient(circle at 0 0, color-mix(in srgb, var(--accent) 10%, transparent), transparent 42%), linear-gradient(145deg, color-mix(in srgb, var(--surface-2) 88%, transparent), color-mix(in srgb, var(--surface) 78%, transparent)); box-shadow: var(--edge), 0 18px 42px rgba(0, 0, 0, .2); } @@ -260,7 +260,7 @@ body { background:var(--page) } .install-pill summary::-webkit-details-marker { display: none; } .install-pill[open] summary { border-color: color-mix(in srgb, var(--accent) 52%, transparent); color: var(--text); box-shadow: 0 0 calc(18px * var(--glow)) color-mix(in srgb, var(--accent) 18%, transparent); } .install-pill[open] summary span { transform: rotate(180deg); } -.install-menu { position: absolute; top: calc(100% + 8px); right: 0; z-index: 50; display: grid; width: min(420px, 70vw); padding: 6px; border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border)); border-radius: calc(var(--radius) - 3px); background: color-mix(in srgb, var(--surface-2) 96%, transparent); box-shadow: var(--edge), 0 24px 55px rgba(0, 0, 0, .55), 0 0 calc(32px * var(--glow)) color-mix(in srgb, var(--accent) 14%, transparent); backdrop-filter: blur(24px) saturate(150%); } +.install-menu { position: absolute; top: calc(100% + 8px); right: 0; z-index: 50; display: grid; width: min(420px, calc(70 * var(--vw))); padding: 6px; border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border)); border-radius: calc(var(--radius) - 3px); background: color-mix(in srgb, var(--surface-2) 96%, transparent); box-shadow: var(--edge), 0 24px 55px rgba(0, 0, 0, .55), 0 0 calc(32px * var(--glow)) color-mix(in srgb, var(--accent) 14%, transparent); backdrop-filter: blur(24px) saturate(150%); } .install-menu button { display: grid; grid-template-columns: 58px minmax(0, 1fr) 42px; align-items: center; gap: 9px; width: 100%; padding: 10px; border: 0; border-radius: 8px; color: var(--text); background: transparent; text-align: left; } .install-menu button:hover { background: color-mix(in srgb, var(--accent) 11%, transparent); } .install-menu strong { color: var(--accent-2); font-size: 10px; } @@ -285,9 +285,9 @@ body { background:var(--page) } .log-empty p { margin: 6px 0 0; font-size: 10px; } /* Visual theme gallery follows Omarchy's preview-first selection model. */ -.theme-switcher { min-height: 0; overflow: auto; margin: 0 10px 10px; padding: 28px clamp(22px, 4vw, 58px) 50px; border: 1px solid color-mix(in srgb, var(--ambient-primary) 18%, var(--border)); border-radius: calc(var(--radius) + 2px); background: color-mix(in srgb, var(--surface) var(--glass-opacity), transparent); box-shadow: var(--edge), 0 26px 55px rgba(0,0,0,.24), 0 0 calc(42px * var(--glow)) color-mix(in srgb,var(--ambient-primary) 10%,transparent); } +.theme-switcher { min-height: 0; overflow: auto; margin: 0 10px 10px; padding: 28px clamp(22px, calc(4 * var(--vw)), 58px) 50px; border: 1px solid color-mix(in srgb, var(--ambient-primary) 18%, var(--border)); border-radius: calc(var(--radius) + 2px); background: color-mix(in srgb, var(--surface) var(--glass-opacity), transparent); box-shadow: var(--edge), 0 26px 55px rgba(0,0,0,.24), 0 0 calc(42px * var(--glow)) color-mix(in srgb,var(--ambient-primary) 10%,transparent); } .theme-switcher > header { display: flex; align-items: end; justify-content: space-between; gap: 24px; max-width: 1180px; margin: 0 auto 25px; } -.theme-switcher h2 { margin: 0; font-size: clamp(25px,3vw,38px); letter-spacing: -.045em; } +.theme-switcher h2 { margin: 0; font-size: clamp(25px,calc(3 * var(--vw)),38px); letter-spacing: -.045em; } .theme-switcher header p { max-width: 690px; margin: 8px 0 0; color: var(--muted); font-size: 11px; line-height: 1.6; } .theme-count { padding: 7px 10px; border: 1px solid var(--border); border-radius: 99px; color: var(--muted); font: 9px var(--mono); white-space: nowrap; } .theme-gallery { display: grid; grid-template-columns: repeat(auto-fill,minmax(210px,1fr)); gap: 13px; max-width: 1180px; margin: auto; } @@ -360,8 +360,8 @@ body { background:var(--page) } .clone-box>div:first-child strong{display:block;font-size:11px;white-space:nowrap}.clone-box>div:last-child{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:7px}.clone-box input{min-width:0;width:100%;padding:9px 10px;border:1px solid var(--border);border-radius:9px;color:var(--text);background:var(--page);font:9px var(--mono)}.clone-box .primary{padding:8px 12px} /* Focus Reader overlays all chrome while keeping tree and chat independently optional. */ -.code-layout--focus { position:fixed;inset:10px;z-index:500;height:auto;margin:0;grid-template-columns:minmax(220px,min(var(--tree-pane-width, 300px), 42vw)) minmax(0,1fr);border-color:color-mix(in srgb,var(--ambient-primary) 40%,var(--border));box-shadow:0 35px 100px rgba(0,0,0,.65),0 0 calc(70px * var(--glow)) color-mix(in srgb,var(--ambient-primary) 18%,transparent);animation:focus-reader-in 180ms cubic-bezier(.2,.8,.2,1) } -.code-layout--focus.code-layout--chat{grid-template-columns:minmax(220px,min(var(--tree-pane-width, 300px), 42vw)) minmax(0,1fr) minmax(320px,390px)} +.code-layout--focus { position:fixed;inset:10px;z-index:500;height:auto;margin:0;grid-template-columns:minmax(220px,min(var(--tree-pane-width, 300px), calc(42 * var(--vw)))) minmax(0,1fr);border-color:color-mix(in srgb,var(--ambient-primary) 40%,var(--border));box-shadow:0 35px 100px rgba(0,0,0,.65),0 0 calc(70px * var(--glow)) color-mix(in srgb,var(--ambient-primary) 18%,transparent);animation:focus-reader-in 180ms cubic-bezier(.2,.8,.2,1) } +.code-layout--focus.code-layout--chat{grid-template-columns:minmax(220px,min(var(--tree-pane-width, 300px), calc(42 * var(--vw)))) minmax(0,1fr) minmax(320px,390px)} .code-layout--focus.code-layout--tree-hidden{grid-template-columns:minmax(0,1fr)} .code-layout--focus.code-layout--tree-hidden.code-layout--chat{grid-template-columns:minmax(0,1fr) minmax(320px,390px)} .code-layout--tree-hidden>.tree{display:none} @@ -417,7 +417,7 @@ body,button,input,select,textarea { font-family:"JetBrains Mono Nerd Font","JetB .ref-list { display:flex;flex-wrap:wrap;gap:4px;margin-top:7px }.ref-list span{padding:3px 6px;border:1px solid color-mix(in srgb,var(--green) 30%,var(--border));border-radius:5px;color:var(--green);background:color-mix(in srgb,var(--green) 7%,transparent);font:8px var(--mono)} /* Repository stats balances concise facts with code-oriented composition detail. */ -.stats-view { min-height:0;overflow:auto;margin:0 10px 10px;padding:26px clamp(22px,4vw,56px) 55px;border:1px solid var(--border);border-radius:calc(var(--radius) + 2px);background:color-mix(in srgb,var(--surface) var(--glass-opacity),transparent);box-shadow:var(--edge),0 26px 55px rgba(0,0,0,.26),0 0 calc(42px * var(--glow)) color-mix(in srgb,var(--accent) 10%,transparent) } +.stats-view { min-height:0;overflow:auto;margin:0 10px 10px;padding:26px clamp(22px,calc(4 * var(--vw)),56px) 55px;border:1px solid var(--border);border-radius:calc(var(--radius) + 2px);background:color-mix(in srgb,var(--surface) var(--glass-opacity),transparent);box-shadow:var(--edge),0 26px 55px rgba(0,0,0,.26),0 0 calc(42px * var(--glow)) color-mix(in srgb,var(--accent) 10%,transparent) } .stats-view>header { display:flex;align-items:end;justify-content:space-between;gap:22px;max-width:1180px;margin:0 auto 20px }.stats-view h2{margin:0;font-size:25px;letter-spacing:-.04em}.stats-view header p{margin:6px 0 0;color:var(--muted);font:9px var(--mono)}.stats-language{padding:7px 10px;border:1px solid color-mix(in srgb,var(--cyan) 35%,var(--border));border-radius:99px;color:var(--cyan);font:9px var(--mono)} .stats-facts { display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:8px;max-width:1180px;margin:auto }.stats-facts article{padding:15px;border:1px solid var(--border);border-radius:12px;background:linear-gradient(145deg,color-mix(in srgb,var(--surface-2) 88%,transparent),color-mix(in srgb,var(--surface) 72%,transparent));box-shadow:var(--edge)}.stats-facts span,.stats-facts strong{display:block}.stats-facts span{color:var(--muted);font-size:8px}.stats-facts strong{margin-top:8px;color:var(--text);font-size:17px;letter-spacing:-.04em} .stats-visuals{display:grid;grid-template-columns:minmax(240px,.55fr) minmax(0,1.45fr);gap:12px;max-width:1180px;margin:12px auto}.stats-visuals>article{border:1px solid var(--border);border-radius:var(--radius);background:linear-gradient(145deg,color-mix(in srgb,var(--surface-2) 84%,transparent),color-mix(in srgb,var(--surface) 70%,transparent));box-shadow:var(--edge)}.language-donut{display:flex;align-items:center;gap:16px;padding:16px}.language-donut>div{position:relative;width:86px;height:86px;flex:none}.language-donut svg{width:100%;height:100%;transform:rotate(-90deg)}.language-donut circle{fill:none;stroke-width:5}.donut-track{stroke:var(--surface-3)}.donut-segment{stroke-linecap:butt}.donut-segment--1{stroke:var(--accent)}.donut-segment--2{stroke:var(--cyan)}.donut-segment--3{stroke:var(--accent-2)}.donut-segment--4{stroke:var(--green)}.donut-segment--5{stroke:#ffb454}.donut-segment--6{stroke:#ff6f91}.language-donut>div>strong{position:absolute;inset:0;display:grid;place-items:center;font-size:16px}.language-donut>span{display:grid;gap:5px;color:var(--muted);font-size:8px}.language-donut>span b{color:var(--text);font-size:12px}.stats-signal{padding:20px}.stats-signal h3{margin:7px 0 18px}.stats-signal>div{height:9px;overflow:hidden;border-radius:99px;background:var(--surface-3)}.stats-signal i{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,var(--accent),var(--cyan),var(--green));box-shadow:0 0 18px color-mix(in srgb,var(--cyan) 35%,transparent)}.stats-signal p{margin:10px 0 0;color:var(--muted);font:8px var(--mono)} @@ -515,7 +515,7 @@ body { background: radial-gradient(900px 560px at 74% -18%, color-mix(in srgb, v .lint-option>.install-pill,.scanner-row>.install-pill,.provider-install .install-pill{display:none} /* Dependencies is the single home for optional host-tool installation. */ -.dependency-view{padding:28px clamp(20px,4vw,54px) 70px}.dependency-hero{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;max-width:1180px;margin:0 auto 18px}.dependency-hero h2{margin:0;font-size:34px;letter-spacing:-.045em}.dependency-hero p{max-width:680px;margin:8px 0 0;color:var(--muted);font-size:10px;line-height:1.6}.dependency-hero>div:last-child{display:grid;justify-items:end}.dependency-hero>div:last-child strong{color:var(--cyan);font-size:25px}.dependency-hero>div:last-child span{color:var(--muted);font-size:8px;text-transform:uppercase;letter-spacing:.14em}.dependency-search{display:block;max-width:1180px;margin:0 auto 22px;padding:12px 14px;border:1px solid var(--border);border-radius:11px;color:var(--text);background:var(--page)} +.dependency-view{padding:28px clamp(20px,calc(4 * var(--vw)),54px) 70px}.dependency-hero{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;max-width:1180px;margin:0 auto 18px}.dependency-hero h2{margin:0;font-size:34px;letter-spacing:-.045em}.dependency-hero p{max-width:680px;margin:8px 0 0;color:var(--muted);font-size:10px;line-height:1.6}.dependency-hero>div:last-child{display:grid;justify-items:end}.dependency-hero>div:last-child strong{color:var(--cyan);font-size:25px}.dependency-hero>div:last-child span{color:var(--muted);font-size:8px;text-transform:uppercase;letter-spacing:.14em}.dependency-search{display:block;max-width:1180px;margin:0 auto 22px;padding:12px 14px;border:1px solid var(--border);border-radius:11px;color:var(--text);background:var(--page)} .dependency-group{max-width:1180px;margin:0 auto 24px}.dependency-group>header{display:flex;align-items:center;justify-content:space-between;margin-bottom:9px;padding:0 3px}.dependency-group>header h3{margin:0;font-size:11px;text-transform:uppercase;letter-spacing:.1em}.dependency-group>header span{color:var(--muted);font:8px var(--mono)}.dependency-group>div{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:10px}.dependency-card{min-width:0;overflow:hidden;border:1px solid var(--border);border-radius:13px;background:linear-gradient(145deg,color-mix(in srgb,var(--surface-2) 92%,transparent),color-mix(in srgb,var(--surface) 82%,transparent));box-shadow:var(--edge),0 14px 30px rgba(0,0,0,.18)}.dependency-card--ready{border-color:color-mix(in srgb,var(--green) 25%,var(--border))}.dependency-card>header{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:start;gap:10px;padding:14px}.dependency-card>header h4{margin:0;font:12px var(--mono)}.dependency-card>header p{overflow:hidden;margin:4px 0 0;color:var(--muted);font-size:8px;text-overflow:ellipsis;white-space:nowrap}.dependency-card>header small{display:block;margin-top:5px;color:var(--accent-2);font:7px var(--mono)}.dependency-card>header>b{padding:4px 6px;border:1px solid var(--border);border-radius:5px;color:var(--muted);font:7px var(--mono)}.dependency-card--ready>header>b{color:var(--green);border-color:color-mix(in srgb,var(--green) 35%,var(--border))}.dependency-commands{display:grid;border-top:1px solid var(--border)}.dependency-commands button{display:grid;grid-template-columns:92px minmax(0,1fr) 42px;align-items:center;gap:8px;width:100%;padding:8px 11px;border:0;border-bottom:1px solid color-mix(in srgb,var(--border) 52%,transparent);color:var(--text);background:color-mix(in srgb,var(--page) 28%,transparent);text-align:left}.dependency-commands button:last-child{border-bottom:0}.dependency-commands button:hover{background:color-mix(in srgb,var(--accent) 9%,transparent)}.dependency-commands span{color:var(--accent-2);font-size:7px}.dependency-commands code{min-width:0;overflow:hidden;color:var(--muted);font:8px var(--mono);text-overflow:ellipsis;white-space:nowrap}.dependency-commands b{color:var(--cyan);font:7px var(--mono);text-align:right} @media(max-width:800px){.dependency-group>div{grid-template-columns:1fr}.dependency-hero{align-items:start}.dependency-commands button{grid-template-columns:72px minmax(0,1fr) 38px}} @@ -535,8 +535,8 @@ body { background: radial-gradient(900px 560px at 74% -18%, color-mix(in srgb, v /* File-scoped linting keeps tool selection beside normalized diagnostics. */ .lint-panel{position:relative;z-index:1;width:100%;min-width:0;max-width:100%;overflow:hidden;border-bottom:1px solid var(--border);background:color-mix(in srgb,var(--surface-2) 82%,transparent);contain:inline-size} .lint-panel__toggle{width:100%;height:38px;display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:10px;padding:0 18px;border:0;color:var(--text);background:transparent;text-align:left}.lint-panel__toggle>span{color:var(--cyan);font-size:10px;font-weight:700}.lint-panel__toggle small{color:var(--muted);font:8px var(--mono)}.lint-panel__toggle b{color:var(--accent-2);font-size:15px} -.lint-panel__body{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(150px,1fr);height:min(480px,58vh);min-height:300px;min-width:0;max-width:100%;overflow:hidden;border-top:1px solid var(--border);box-shadow:0 16px 32px rgba(0,0,0,.2)}.lint-picker{min-width:0;max-height:230px;overflow-y:auto;overflow-x:hidden;padding:14px;border-right:0;border-bottom:1px solid var(--border)}.lint-picker>header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.lint-picker>header strong{font-size:11px}.lint-picker>header .primary{flex:0 0 auto;padding:7px 10px;font-size:9px}.lint-picker>p,.lint-results p,.lint-empty{color:var(--muted);font-size:9px;line-height:1.55} +.lint-panel__body{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(150px,1fr);height:min(480px,calc(58 * var(--vh)));min-height:300px;min-width:0;max-width:100%;overflow:hidden;border-top:1px solid var(--border);box-shadow:0 16px 32px rgba(0,0,0,.2)}.lint-picker{min-width:0;max-height:230px;overflow-y:auto;overflow-x:hidden;padding:14px;border-right:0;border-bottom:1px solid var(--border)}.lint-picker>header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px}.lint-picker>header strong{font-size:11px}.lint-picker>header .primary{flex:0 0 auto;padding:7px 10px;font-size:9px}.lint-picker>p,.lint-results p,.lint-empty{color:var(--muted);font-size:9px;line-height:1.55} .lint-option{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px;padding:8px 0;border-top:1px solid color-mix(in srgb,var(--border) 65%,transparent)}.lint-option label{display:flex;align-items:center;gap:9px;min-width:0;cursor:pointer}.lint-option label>span{min-width:0}.lint-option input{width:14px;height:14px;flex:0 0 14px;accent-color:var(--cyan)}.lint-option strong,.lint-option small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lint-option strong{font-size:9px}.lint-option small{margin-top:3px;color:var(--muted);font-size:7px}.lint-option>.install-pill{grid-column:2;min-width:max-content}.lint-option:has(>.install-pill[open]){grid-template-columns:minmax(0,1fr)}.lint-option:has(>.install-pill[open])>.install-pill{grid-column:1;width:100%}.lint-option>.install-pill[open]>summary{width:max-content;margin-left:auto}.lint-option .install-menu{position:static;width:100%;margin-top:8px;max-width:none}.lint-option .install-menu button{grid-template-columns:76px minmax(0,1fr) 38px}.lint-option .install-menu code{white-space:normal;overflow-wrap:anywhere;line-height:1.4} .lint-results{min-width:0;max-width:100%;overflow:auto;padding:12px 14px;background:var(--code-canvas)}.lint-results>article{max-width:100%;margin-bottom:12px;border:1px solid var(--border);border-radius:10px;overflow:hidden;background:color-mix(in srgb,var(--surface) 78%,transparent)}.lint-results>article>header{display:flex;justify-content:space-between;padding:8px 10px;border-bottom:1px solid var(--border)}.lint-results>article>header strong{font-size:9px}.lint-results>article>header span{color:var(--muted);font:8px var(--mono)}.lint-results article>button{width:100%;min-width:0;display:grid;grid-template-columns:auto auto minmax(0,1fr);align-items:start;gap:8px;padding:8px 10px;border:0;border-bottom:1px solid color-mix(in srgb,var(--border) 55%,transparent);color:var(--text);background:transparent;text-align:left}.lint-results article>button:hover{background:color-mix(in srgb,var(--accent) 9%,transparent)}.lint-results article>button code{max-width:180px;overflow:hidden;color:var(--accent-2);font:8px var(--mono);text-overflow:ellipsis;white-space:nowrap}.lint-results article>button span{min-width:0;overflow-wrap:anywhere;font-size:9px;line-height:1.45}.lint-results article>pre{max-width:100%;max-height:150px;overflow:auto;margin:0;padding:10px;color:var(--muted);font:8px/1.55 var(--mono);white-space:pre-wrap;overflow-wrap:anywhere} .lint-severity{padding:2px 5px;border-radius:4px;color:var(--yellow);background:color-mix(in srgb,var(--yellow) 10%,transparent);font:7px var(--mono)}.lint-severity--error{color:var(--red);background:color-mix(in srgb,var(--red) 10%,transparent)} -@media(max-width:900px){.lint-panel__body{height:min(420px,55vh)}.lint-results article>button{grid-template-columns:auto minmax(0,1fr)}.lint-results article>button span{grid-column:1/-1}} +@media(max-width:900px){.lint-panel__body{height:min(420px,calc(55 * var(--vh)))}.lint-results article>button{grid-template-columns:auto minmax(0,1fr)}.lint-results article>button span{grid-column:1/-1}} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 811acb0..8ded96c 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -16,7 +16,7 @@ html, body, #root { margin:0; min-width:100%; min-height:100%; overflow:hidden; body { color:var(--text); background:radial-gradient(900px 620px at 76% -14%,color-mix(in srgb,var(--accent) 29%,transparent),transparent 62%),radial-gradient(760px 520px at -8% 108%,color-mix(in srgb,var(--accent-2) 23%,transparent),transparent 64%),linear-gradient(155deg,var(--page),color-mix(in srgb,var(--surface) 72%,var(--page))); } button,input,select { font:inherit; } button,select { cursor:pointer; } button:focus-visible,input:focus-visible,select:focus-visible { outline:0; border-color:var(--accent-2)!important; box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 20%,transparent),0 0 calc(20px * var(--glow-size)) color-mix(in srgb,var(--accent) 28%,transparent)!important; } -.shell { height:100vh; display:grid; grid-template:42px 1fr / 68px 292px 1fr; } +.shell { height:calc(100 * var(--vh)); display:grid; grid-template:42px 1fr / 68px 292px 1fr; } .titlebar { grid-column:1/-1; display:grid; grid-template-columns:1fr auto 1fr; align-items:center; padding:0 20px; border-bottom:1px solid var(--border); background:color-mix(in srgb,var(--page) 80%,transparent); backdrop-filter:blur(22px) saturate(150%); box-shadow:0 8px 36px rgba(0,0,0,.22); --wails-draggable:drag; user-select:none; z-index:10; } .titlebar span { font-weight:750; letter-spacing:.04em; background:linear-gradient(90deg,var(--text),var(--accent),var(--accent-2)); background-clip:text; color:transparent; }.titlebar small{justify-self:end;color:var(--muted);font:11px var(--mono)} .rail { grid-row:2; display:flex; flex-direction:column; align-items:center; gap:10px; padding:15px 10px; border-right:1px solid var(--border); background:linear-gradient(180deg,color-mix(in srgb,var(--surface) 88%,transparent),color-mix(in srgb,var(--page) 90%,transparent)); box-shadow:5px 0 calc(30px * var(--glow-size)) color-mix(in srgb,var(--accent) 9%,transparent); } @@ -29,14 +29,14 @@ button:focus-visible,input:focus-visible,select:focus-visible { outline:0; borde .workspace { grid-row:2;min-width:0;overflow:auto;padding:0 24px 38px }.workspace__header{position:sticky;top:0;z-index:5;display:flex;align-items:center;justify-content:space-between;gap:20px;padding:18px 0 14px;background:linear-gradient(var(--page) 72%,transparent);backdrop-filter:blur(10px)}.workspace__header h1{margin:0;font-size:24px;letter-spacing:-.025em}.header-actions{display:flex;align-items:center;gap:8px}.header-actions select{width:auto;padding:8px 30px 8px 10px}.pill,.chip{white-space:nowrap;padding:6px 10px;border:1px solid var(--border);border-radius:99px;color:var(--muted);font:10px var(--mono);background:color-mix(in srgb,var(--surface-2) 70%,transparent)} .ghost,.primary,.crumb { border-radius:9px;padding:8px 12px;color:var(--text);border:1px solid var(--border);background:transparent;font-size:12px}.ghost:hover{background:color-mix(in srgb,var(--accent) 12%,transparent);box-shadow:0 0 calc(17px * var(--glow-size)) color-mix(in srgb,var(--accent) 18%,transparent)}.primary{border-color:color-mix(in srgb,var(--accent-2) 55%,transparent);background:linear-gradient(135deg,var(--accent),var(--accent-2));box-shadow:var(--edge),0 8px calc(22px * var(--glow-size)) color-mix(in srgb,var(--accent) 30%,transparent)} .panel { min-width:0;border:1px solid var(--border);border-radius:17px;background:radial-gradient(circle at 8% 0,color-mix(in srgb,var(--accent) 8%,transparent),transparent 34%),linear-gradient(145deg,color-mix(in srgb,var(--surface-2) 94%,transparent),color-mix(in srgb,var(--surface) 88%,transparent));box-shadow:var(--edge),var(--elevation),var(--halo);backdrop-filter:blur(16px) saturate(140%) } -.code-layout{height:calc(100vh - 126px);min-height:500px;display:grid;grid-template-columns:clamp(260px,30vw,380px) minmax(0,1fr);gap:14px}.tree,.reader{min-width:0;height:100%;overflow:auto}.tree{padding:12px}.tree__head{display:flex;gap:5px;align-items:center;min-width:0;padding:0 2px 10px;color:var(--muted);font:10px var(--mono);overflow:hidden}.tree__head>span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb{padding:5px 7px;font:10px var(--mono)}.tree__list{display:grid}.tree__entry{display:grid;grid-template-columns:20px minmax(0,1fr) auto;align-items:center;gap:6px;width:100%;padding:7px 6px;border:0;border-radius:8px;color:var(--muted);background:transparent;text-align:left;font-size:12px}.tree__entry:hover{color:var(--text);background:linear-gradient(90deg,color-mix(in srgb,var(--accent) 15%,transparent),color-mix(in srgb,var(--accent-2) 7%,transparent))}.tree__entry small{font:9px var(--mono);opacity:.7}.tree__entry b{color:var(--accent-2)}.entry-icon{color:var(--muted)}.entry-icon--folder{color:var(--accent)}.tree__up{color:var(--accent-2);margin-bottom:3px} +.code-layout{height:calc(calc(100 * var(--vh)) - 126px);min-height:500px;display:grid;grid-template-columns:clamp(260px,calc(30 * var(--vw)),380px) minmax(0,1fr);gap:14px}.tree,.reader{min-width:0;height:100%;overflow:auto}.tree{padding:12px}.tree__head{display:flex;gap:5px;align-items:center;min-width:0;padding:0 2px 10px;color:var(--muted);font:10px var(--mono);overflow:hidden}.tree__head>span:last-child{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb{padding:5px 7px;font:10px var(--mono)}.tree__list{display:grid}.tree__entry{display:grid;grid-template-columns:20px minmax(0,1fr) auto;align-items:center;gap:6px;width:100%;padding:7px 6px;border:0;border-radius:8px;color:var(--muted);background:transparent;text-align:left;font-size:12px}.tree__entry:hover{color:var(--text);background:linear-gradient(90deg,color-mix(in srgb,var(--accent) 15%,transparent),color-mix(in srgb,var(--accent-2) 7%,transparent))}.tree__entry small{font:9px var(--mono);opacity:.7}.tree__entry b{color:var(--accent-2)}.entry-icon{color:var(--muted)}.entry-icon--folder{color:var(--accent)}.tree__up{color:var(--accent-2);margin-bottom:3px} .reader{background:color-mix(in srgb,var(--surface) 92%,transparent)}.reader__head{position:sticky;top:0;z-index:2;display:flex;align-items:center;justify-content:space-between;gap:20px;padding:16px 20px;border-bottom:1px solid var(--border);background:color-mix(in srgb,var(--surface) 88%,transparent);backdrop-filter:blur(18px)}.reader__meta{display:flex;align-items:center;gap:10px;color:var(--muted);font:10px var(--mono)}.reader__loading{display:grid;place-items:center;height:70%;color:var(--muted)}.document{min-height:calc(100% - 70px)}.document.code{overflow:auto;padding:12px 0;background:#11111b}.document.code pre{margin:0!important;padding:0 18px!important;font:12px/1.65 var(--mono)!important}.document.code td:first-child{user-select:none;opacity:.45}.document.markdown{max-width:920px;margin:0 auto;padding:34px 44px 70px;line-height:1.72}.markdown h1,.markdown h2,.markdown h3{line-height:1.25;margin:1.6em 0 .6em}.markdown h1{font-size:30px}.markdown h2{font-size:22px;border-bottom:1px solid var(--border);padding-bottom:9px}.markdown p,.markdown li{color:color-mix(in srgb,var(--text) 90%,var(--muted))}.markdown a{color:var(--cyan)}.markdown code{font-family:var(--mono);font-size:.88em;background:color-mix(in srgb,var(--surface-3) 65%,transparent);border:1px solid var(--border);border-radius:5px;padding:2px 5px}.markdown pre{overflow:auto;padding:16px;border:1px solid var(--border);border-radius:12px;background:var(--page)}.markdown pre code{border:0;background:transparent;padding:0}.markdown blockquote{margin-left:0;padding-left:16px;border-left:3px solid var(--accent);color:var(--muted)} .markdown-code-frame{position:relative;margin:1em 0}.markdown-code-frame pre{margin:0;padding:42px 18px 18px}.markdown-copy{position:absolute;z-index:4;top:8px;right:8px;padding:6px 10px;border:1px solid color-mix(in srgb,var(--border) 82%,var(--accent));border-radius:7px;color:var(--muted);background:color-mix(in srgb,var(--surface-2) 98%,var(--page));box-shadow:var(--edge),0 5px 14px rgba(0,0,0,.32);font:9px var(--mono);line-height:1;cursor:pointer;transition:120ms ease}.markdown-copy:hover,.markdown-copy:focus-visible{color:var(--text);border-color:color-mix(in srgb,var(--cyan) 58%,var(--border));background:var(--surface-3);box-shadow:0 0 13px color-mix(in srgb,var(--cyan) 22%,transparent)}.markdown-copy--done{color:var(--green);border-color:color-mix(in srgb,var(--green) 55%,var(--border))} .history{padding:20px;max-width:1100px}.timeline{position:relative;display:grid;padding-left:10px}.timeline::before{content:"";position:absolute;left:18px;top:12px;bottom:12px;width:1px;background:linear-gradient(var(--accent),color-mix(in srgb,var(--accent-2) 50%,transparent));box-shadow:0 0 8px color-mix(in srgb,var(--accent) 55%,transparent)}.commit{position:relative;display:grid;grid-template-columns:18px 64px minmax(0,1fr);gap:12px;padding:10px 0;align-items:start}.commit__node{position:relative;z-index:1;width:9px;height:9px;margin-top:4px;border:2px solid var(--accent);border-radius:50%;background:var(--surface);box-shadow:0 0 9px color-mix(in srgb,var(--accent) 65%,transparent)}.commit code{color:var(--accent-2);font:11px var(--mono)}.commit strong,.commit small{display:block}.commit strong{font-size:13px}.commit small{margin-top:5px;color:var(--muted);font-size:10px}.tools{max-width:1100px}.tool-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.tool{display:grid;grid-template-columns:12px 1fr auto;align-items:center;gap:12px;padding:17px}.tool h3{margin:0 0 4px;font:13px var(--mono)}.tool p{margin:0;color:var(--muted);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tool__light{width:8px;height:8px;border-radius:50%;background:var(--muted)}.tool__light--ready{background:var(--green);box-shadow:0 0 9px var(--green)}.chip--ready{color:var(--green);border-color:color-mix(in srgb,var(--green) 42%,transparent);background:color-mix(in srgb,var(--green) 9%,transparent)} -.empty{max-width:620px;margin:10vh auto;padding:44px;text-align:center}.empty .reaper-mark{margin:0 auto 20px}.empty p{color:var(--muted)}.empty code{font-family:var(--mono);color:var(--accent-2)}.loading{height:100vh;display:grid;place-content:center;justify-items:center;gap:8px;text-align:center}.loading .reaper-mark{width:68px;height:68px;border-radius:20px;font-size:24px;animation:pulse 2s ease-in-out infinite}.loading h1{margin:10px 0 0}.loading p{margin:0;color:var(--muted)}.notice{margin-bottom:12px;padding:10px 12px;border:1px solid color-mix(in srgb,var(--red) 45%,transparent);border-radius:10px;color:var(--red);background:color-mix(in srgb,var(--red) 9%,transparent);font-size:12px}@keyframes pulse{50%{transform:scale(1.06);filter:brightness(1.18)}} +.empty{max-width:620px;margin:calc(10 * var(--vh)) auto;padding:44px;text-align:center}.empty .reaper-mark{margin:0 auto 20px}.empty p{color:var(--muted)}.empty code{font-family:var(--mono);color:var(--accent-2)}.loading{height:calc(100 * var(--vh));display:grid;place-content:center;justify-items:center;gap:8px;text-align:center}.loading .reaper-mark{width:68px;height:68px;border-radius:20px;font-size:24px;animation:pulse 2s ease-in-out infinite}.loading h1{margin:10px 0 0}.loading p{margin:0;color:var(--muted)}.notice{margin-bottom:12px;padding:10px 12px;border:1px solid color-mix(in srgb,var(--red) 45%,transparent);border-radius:10px;color:var(--red);background:color-mix(in srgb,var(--red) 9%,transparent);font-size:12px}@keyframes pulse{50%{transform:scale(1.06);filter:brightness(1.18)}} @media(max-width:1150px){.shell{grid-template-columns:62px 250px 1fr}.header-actions .pill{display:none}.code-layout{grid-template-columns:220px minmax(0,1fr)}} @media(prefers-reduced-motion:reduce){*{animation-duration:.001ms!important;transition-duration:.001ms!important}} -.splash{position:fixed;inset:0;display:grid;place-items:center;overflow:hidden;background:radial-gradient(750px 420px at 50% 48%,rgba(92,67,255,.17),transparent 66%),#02030b;color:#f7f7ff}.splash__aura{position:absolute;width:560px;height:360px;border-radius:50%;background:radial-gradient(circle,rgba(91,91,255,.23),rgba(56,221,255,.08) 44%,transparent 72%);filter:blur(28px);animation:splash-aura 2.2s ease both}.splash__binary{position:absolute;inset:0;display:grid;align-content:center;gap:20px;transform:rotate(-8deg) scale(1.22);color:rgba(148,163,255,.22);font:20px/1 var(--mono);letter-spacing:.4em;white-space:nowrap;animation:binary-arrive 1.45s cubic-bezier(.2,.8,.2,1) both}.splash__binary>div:nth-child(odd){transform:translateX(-7%)}.splash__binary>div:nth-child(even){transform:translateX(8%)}.splash__scythe{position:absolute;width:min(620px,72vw);overflow:visible;filter:drop-shadow(0 0 8px rgba(56,221,255,.8)) drop-shadow(0 0 26px rgba(102,90,255,.65));transform:translate(48%,-34%) rotate(-13deg);animation:scythe-swing 1.05s .35s cubic-bezier(.16,.76,.28,1) both}.splash__handle{fill:none;stroke:rgba(205,214,244,.52);stroke-width:9;stroke-linecap:round}.splash__blade{fill:none;stroke:url(#scythe-edge);stroke-width:13;stroke-linecap:round;stroke-linejoin:round}.splash__cut{position:absolute;width:130vw;height:3px;background:linear-gradient(90deg,transparent,var(--cyan) 38%,var(--accent-2) 55%,transparent);box-shadow:0 0 9px var(--cyan),0 0 34px var(--accent);transform:rotate(-19deg) scaleX(0);animation:cut-flash .72s .95s ease-out both}.splash__identity{position:relative;display:grid;justify-items:center;opacity:0;clip-path:inset(50% 0 50%);animation:identity-reveal .8s 1.18s cubic-bezier(.2,.8,.2,1) forwards}.splash__mark{display:grid;place-items:center;width:76px;height:76px;border-radius:22px;color:white;font-size:27px;font-weight:900;background:linear-gradient(145deg,#557dff,#9b4dff);border:1px solid rgba(196,181,253,.62);box-shadow:inset 0 1px rgba(255,255,255,.22),0 0 26px rgba(91,91,255,.72),0 0 70px rgba(56,145,255,.28)}.splash__identity h1{margin:18px 0 2px;font-size:34px;letter-spacing:.06em;background:linear-gradient(90deg,#fff,#b7c7ff 52%,#c5a4ff);background-clip:text;color:transparent}.splash__identity p{margin:0;color:#9ba2c8;font:10px var(--mono);letter-spacing:.18em;text-transform:uppercase}@keyframes binary-arrive{0%{opacity:0;transform:rotate(-8deg) scale(1.36)}55%{opacity:1}100%{opacity:.78;transform:rotate(-8deg) scale(1.22)}}@keyframes scythe-swing{0%{opacity:0;transform:translate(80%,-65%) rotate(-48deg)}18%{opacity:1}78%{opacity:1}100%{opacity:0;transform:translate(-60%,48%) rotate(22deg)}}@keyframes cut-flash{0%{opacity:0;transform:rotate(-19deg) scaleX(0)}34%{opacity:1;transform:rotate(-19deg) scaleX(1)}100%{opacity:0;transform:rotate(-19deg) scaleX(1)}}@keyframes identity-reveal{to{opacity:1;clip-path:inset(0 0 0)}}@keyframes splash-aura{0%{opacity:0;transform:scale(.6)}70%{opacity:1;transform:scale(1)}100%{opacity:.72;transform:scale(1.12)}} +.splash{position:fixed;inset:0;display:grid;place-items:center;overflow:hidden;background:radial-gradient(750px 420px at 50% 48%,rgba(92,67,255,.17),transparent 66%),#02030b;color:#f7f7ff}.splash__aura{position:absolute;width:560px;height:360px;border-radius:50%;background:radial-gradient(circle,rgba(91,91,255,.23),rgba(56,221,255,.08) 44%,transparent 72%);filter:blur(28px);animation:splash-aura 2.2s ease both}.splash__binary{position:absolute;inset:0;display:grid;align-content:center;gap:20px;transform:rotate(-8deg) scale(1.22);color:rgba(148,163,255,.22);font:20px/1 var(--mono);letter-spacing:.4em;white-space:nowrap;animation:binary-arrive 1.45s cubic-bezier(.2,.8,.2,1) both}.splash__binary>div:nth-child(odd){transform:translateX(-7%)}.splash__binary>div:nth-child(even){transform:translateX(8%)}.splash__scythe{position:absolute;width:min(620px,calc(72 * var(--vw)));overflow:visible;filter:drop-shadow(0 0 8px rgba(56,221,255,.8)) drop-shadow(0 0 26px rgba(102,90,255,.65));transform:translate(48%,-34%) rotate(-13deg);animation:scythe-swing 1.05s .35s cubic-bezier(.16,.76,.28,1) both}.splash__handle{fill:none;stroke:rgba(205,214,244,.52);stroke-width:9;stroke-linecap:round}.splash__blade{fill:none;stroke:url(#scythe-edge);stroke-width:13;stroke-linecap:round;stroke-linejoin:round}.splash__cut{position:absolute;width:calc(130 * var(--vw));height:3px;background:linear-gradient(90deg,transparent,var(--cyan) 38%,var(--accent-2) 55%,transparent);box-shadow:0 0 9px var(--cyan),0 0 34px var(--accent);transform:rotate(-19deg) scaleX(0);animation:cut-flash .72s .95s ease-out both}.splash__identity{position:relative;display:grid;justify-items:center;opacity:0;clip-path:inset(50% 0 50%);animation:identity-reveal .8s 1.18s cubic-bezier(.2,.8,.2,1) forwards}.splash__mark{display:grid;place-items:center;width:76px;height:76px;border-radius:22px;color:white;font-size:27px;font-weight:900;background:linear-gradient(145deg,#557dff,#9b4dff);border:1px solid rgba(196,181,253,.62);box-shadow:inset 0 1px rgba(255,255,255,.22),0 0 26px rgba(91,91,255,.72),0 0 70px rgba(56,145,255,.28)}.splash__identity h1{margin:18px 0 2px;font-size:34px;letter-spacing:.06em;background:linear-gradient(90deg,#fff,#b7c7ff 52%,#c5a4ff);background-clip:text;color:transparent}.splash__identity p{margin:0;color:#9ba2c8;font:10px var(--mono);letter-spacing:.18em;text-transform:uppercase}@keyframes binary-arrive{0%{opacity:0;transform:rotate(-8deg) scale(1.36)}55%{opacity:1}100%{opacity:.78;transform:rotate(-8deg) scale(1.22)}}@keyframes scythe-swing{0%{opacity:0;transform:translate(80%,-65%) rotate(-48deg)}18%{opacity:1}78%{opacity:1}100%{opacity:0;transform:translate(-60%,48%) rotate(22deg)}}@keyframes cut-flash{0%{opacity:0;transform:rotate(-19deg) scaleX(0)}34%{opacity:1;transform:rotate(-19deg) scaleX(1)}100%{opacity:0;transform:rotate(-19deg) scaleX(1)}}@keyframes identity-reveal{to{opacity:1;clip-path:inset(0 0 0)}}@keyframes splash-aura{0%{opacity:0;transform:scale(.6)}70%{opacity:1;transform:scale(1)}100%{opacity:.72;transform:scale(1.12)}} @media(prefers-reduced-motion:reduce){.splash__binary,.splash__scythe,.splash__cut{display:none}.splash__identity{opacity:1;clip-path:none}.splash__aura{opacity:.7}} .splash__binary--upper { clip-path:polygon(0 0,100% 0,100% 38%,0 72%);animation:binary-split-upper 1.85s cubic-bezier(.2,.8,.2,1) both } .splash__binary--lower { clip-path:polygon(0 72%,100% 38%,100% 100%,0 100%);animation:binary-split-lower 1.85s cubic-bezier(.2,.8,.2,1) both } @@ -51,16 +51,16 @@ button:focus-visible,input:focus-visible,select:focus-visible { outline:0; borde .titlebar{grid-template-columns:1fr minmax(300px,520px) 1fr;padding:0 16px;gap:16px} .titlebar__brand{display:flex;align-items:center;gap:9px;justify-self:start}.titlebar__brand strong{font-size:14px;letter-spacing:.035em}.titlebar__mark{display:grid;place-items:center;width:30px;height:30px;border-radius:9px;color:white!important;background:linear-gradient(145deg,var(--accent),var(--accent-2));box-shadow:var(--edge),0 0 calc(18px * var(--glow-size)) color-mix(in srgb,var(--accent) 38%,transparent);background-clip:border-box!important} .top-context{justify-self:center;width:min(660px,100%);display:grid;grid-template-columns:minmax(0,1fr) 150px;gap:7px;--wails-draggable:no-drag}.repo-picker{width:100%;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:10px;padding:7px 8px 7px 12px;color:var(--text);border:1px solid var(--border);border-radius:12px;background:linear-gradient(145deg,color-mix(in srgb,var(--surface-3) 65%,transparent),color-mix(in srgb,var(--surface) 88%,transparent));box-shadow:var(--edge),0 0 calc(20px * var(--glow-size)) color-mix(in srgb,var(--accent) 10%,transparent)}.repo-picker>span{color:var(--muted);font-size:9px;text-transform:uppercase;letter-spacing:.12em}.repo-picker>strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left;font-size:12px}.branch-picker{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;padding-left:9px;border:1px solid var(--border);border-radius:12px;color:var(--accent-2);background:color-mix(in srgb,var(--surface-2) 80%,transparent)}.branch-picker select{min-width:0;border:0;padding:7px 22px 7px 7px;background:transparent;font:10px var(--mono)}kbd{padding:3px 6px;border:1px solid var(--border);border-radius:5px;color:var(--muted);background:color-mix(in srgb,var(--surface-3) 62%,transparent);font:9px var(--mono)}.titlebar__platform{justify-self:end;color:var(--muted);font:10px var(--mono)} -.picker-backdrop{position:fixed;inset:58px 0 0;z-index:50;display:grid;place-items:start center;padding-top:7vh;background:rgba(2,3,11,.62);backdrop-filter:blur(8px)}.picker{width:min(640px,calc(100vw - 48px));max-height:70vh;display:grid;grid-template-rows:auto auto minmax(0,1fr) auto;overflow:hidden;border:1px solid color-mix(in srgb,var(--accent) 48%,transparent);border-radius:18px;background:linear-gradient(145deg,color-mix(in srgb,var(--surface-2) 98%,transparent),color-mix(in srgb,var(--surface) 96%,transparent));box-shadow:var(--edge),0 32px 90px rgba(0,0,0,.62),0 0 calc(50px * var(--glow-size)) color-mix(in srgb,var(--accent) 20%,transparent);animation:picker-in 150ms ease}.picker>header{padding:18px 20px 10px}.picker h2{margin:0;font-size:18px}.picker>input{margin:4px 14px 10px;width:calc(100% - 28px);padding:12px 14px;color:var(--text);border:1px solid var(--border);border-radius:11px;background:var(--page)}.picker__list{overflow:auto;padding:4px 8px 12px}.picker__list>button{width:100%;display:grid;grid-template-columns:32px minmax(0,1fr) auto;align-items:center;gap:10px;padding:10px;border:1px solid transparent;border-radius:11px;color:var(--text);background:transparent;text-align:left}.picker__list>button:hover,.picker__list>button:focus{border-color:color-mix(in srgb,var(--accent) 36%,transparent);background:linear-gradient(90deg,color-mix(in srgb,var(--accent) 18%,transparent),color-mix(in srgb,var(--accent-2) 10%,transparent))}.picker__list strong,.picker__list small{display:block}.picker__list small{margin-top:3px;color:var(--muted);font-size:10px}.picker>footer{display:flex;gap:16px;padding:10px 15px;border-top:1px solid var(--border);color:var(--muted);font-size:9px}@keyframes picker-in{from{opacity:0;transform:translateY(-8px) scale(.985)}} +.picker-backdrop{position:fixed;inset:58px 0 0;z-index:50;display:grid;place-items:start center;padding-top:calc(7 * var(--vh));background:rgba(2,3,11,.62);backdrop-filter:blur(8px)}.picker{width:min(640px,calc(calc(100 * var(--vw)) - 48px));max-height:calc(70 * var(--vh));display:grid;grid-template-rows:auto auto minmax(0,1fr) auto;overflow:hidden;border:1px solid color-mix(in srgb,var(--accent) 48%,transparent);border-radius:18px;background:linear-gradient(145deg,color-mix(in srgb,var(--surface-2) 98%,transparent),color-mix(in srgb,var(--surface) 96%,transparent));box-shadow:var(--edge),0 32px 90px rgba(0,0,0,.62),0 0 calc(50px * var(--glow-size)) color-mix(in srgb,var(--accent) 20%,transparent);animation:picker-in 150ms ease}.picker>header{padding:18px 20px 10px}.picker h2{margin:0;font-size:18px}.picker>input{margin:4px 14px 10px;width:calc(100% - 28px);padding:12px 14px;color:var(--text);border:1px solid var(--border);border-radius:11px;background:var(--page)}.picker__list{overflow:auto;padding:4px 8px 12px}.picker__list>button{width:100%;display:grid;grid-template-columns:32px minmax(0,1fr) auto;align-items:center;gap:10px;padding:10px;border:1px solid transparent;border-radius:11px;color:var(--text);background:transparent;text-align:left}.picker__list>button:hover,.picker__list>button:focus{border-color:color-mix(in srgb,var(--accent) 36%,transparent);background:linear-gradient(90deg,color-mix(in srgb,var(--accent) 18%,transparent),color-mix(in srgb,var(--accent-2) 10%,transparent))}.picker__list strong,.picker__list small{display:block}.picker__list small{margin-top:3px;color:var(--muted);font-size:10px}.picker>footer{display:flex;gap:16px;padding:10px 15px;border-top:1px solid var(--border);color:var(--muted);font-size:9px}@keyframes picker-in{from{opacity:0;transform:translateY(-8px) scale(.985)}} .rail{grid-row:2;grid-column:1;padding:12px 7px}.rail__brand{display:none}.rail__item{width:42px;height:42px} .workspace{grid-row:2;grid-column:2;padding:0;overflow:hidden;display:grid;grid-template-rows:58px minmax(0,1fr)} .workspace__header{position:relative;min-height:58px;padding:8px 18px;border-bottom:1px solid var(--border);background:color-mix(in srgb,var(--surface) 58%,transparent)}.workspace__header h1{font-size:17px}.workspace__header .eyebrow{font-size:8px;margin-bottom:2px}.header-actions select{min-width:105px} -.code-layout{height:auto;min-height:0;display:grid;grid-template-columns:minmax(220px,min(var(--tree-pane-width, 300px), 42vw)) minmax(0,1fr);gap:0} +.code-layout{height:auto;min-height:0;display:grid;grid-template-columns:minmax(220px,min(var(--tree-pane-width, 300px), calc(42 * var(--vw)))) minmax(0,1fr);gap:0} .tree{height:100%;min-width:0;overflow:auto;padding:0 10px 16px;border-right:1px solid var(--border);border-radius:0;background:linear-gradient(180deg,color-mix(in srgb,var(--surface-2) 74%,transparent),color-mix(in srgb,var(--surface) 70%,transparent));box-shadow:5px 0 calc(26px * var(--glow-size)) color-mix(in srgb,var(--accent) 6%,transparent)} .tree__title{position:sticky;top:0;z-index:2;display:flex;align-items:center;justify-content:space-between;margin:0 -10px 8px;padding:12px 14px 10px;border-bottom:1px solid var(--border);color:var(--muted);background:color-mix(in srgb,var(--surface) 90%,transparent);backdrop-filter:blur(15px);font-size:10px;text-transform:uppercase;letter-spacing:.12em}.tree__title small{font:9px var(--mono)} .reader{height:100%;overflow:auto;border:0;border-radius:0;background:color-mix(in srgb,var(--page) 76%,var(--surface));box-shadow:none}.reader__head{padding:13px 22px}.document.markdown{max-width:1040px;padding:40px 54px 90px}.document.code{min-height:100%} -.history,.tools{height:calc(100vh - 116px);max-width:none;margin:0;border:0;border-radius:0;overflow:auto;box-shadow:none;background:transparent}.history{padding:24px 34px}.tools{padding:24px 30px}.empty{margin:auto;align-self:center} -@media(max-width:900px){.titlebar{grid-template-columns:auto 1fr}.titlebar__platform{display:none}.repo-picker{justify-self:stretch}.code-layout{grid-template-columns:minmax(220px,min(var(--tree-pane-width, 300px), 32vw)) minmax(0,1fr)}} +.history,.tools{height:calc(calc(100 * var(--vh)) - 116px);max-width:none;margin:0;border:0;border-radius:0;overflow:auto;box-shadow:none;background:transparent}.history{padding:24px 34px}.tools{padding:24px 30px}.empty{margin:auto;align-self:center} +@media(max-width:900px){.titlebar{grid-template-columns:auto 1fr}.titlebar__platform{display:none}.repo-picker{justify-self:stretch}.code-layout{grid-template-columns:minmax(220px,min(var(--tree-pane-width, 300px), calc(32 * var(--vw)))) minmax(0,1fr)}} .code-layout--chat{grid-template-columns:270px minmax(360px,1fr) minmax(300px,380px)}.chat-button{color:var(--accent-2);border-color:color-mix(in srgb,var(--accent-2) 38%,transparent)}.chat-drawer{height:100%;min-width:0;display:grid;grid-template-rows:auto auto minmax(0,1fr) auto;border-left:1px solid var(--border);background:linear-gradient(160deg,color-mix(in srgb,var(--surface-2) 94%,transparent),color-mix(in srgb,var(--surface) 96%,transparent));box-shadow:-8px 0 calc(30px * var(--glow-size)) rgba(0,0,0,.24),-3px 0 calc(24px * var(--glow-size)) color-mix(in srgb,var(--accent-2) 8%,transparent)}.chat-drawer>header{display:flex;align-items:center;justify-content:space-between;padding:15px 16px;border-bottom:1px solid var(--border)}.chat-drawer h2{margin:0;font-size:15px}.chat-drawer>header button{width:28px;height:28px;border:1px solid var(--border);border-radius:8px;color:var(--muted);background:transparent}.chat-context{display:flex;gap:6px;padding:9px 12px;border-bottom:1px solid var(--border);overflow:hidden}.chat-context span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:4px 7px;border-radius:6px;color:var(--muted);background:color-mix(in srgb,var(--accent) 8%,transparent);font:9px var(--mono)}.chat-messages{overflow:auto;padding:14px;display:grid;align-content:start;gap:12px}.chat-message{padding:11px 12px;border:1px solid var(--border);border-radius:12px;background:color-mix(in srgb,var(--surface-3) 36%,transparent)}.chat-message--user{margin-left:28px;border-color:color-mix(in srgb,var(--accent) 35%,transparent);background:color-mix(in srgb,var(--accent) 10%,transparent)}.chat-message small{color:var(--accent-2);font-size:9px;text-transform:uppercase;letter-spacing:.1em}.chat-message p{margin:6px 0 0;white-space:pre-wrap;overflow-wrap:anywhere;color:color-mix(in srgb,var(--text) 91%,var(--muted));font-size:11px;line-height:1.6}.chat-empty{align-self:center;padding:25px;text-align:center;color:var(--muted);font-size:11px;line-height:1.55}.chat-empty span{color:var(--accent-2);font-size:28px;filter:drop-shadow(0 0 10px var(--accent))}.chat-drawer>footer{display:grid;gap:8px;padding:11px;border-top:1px solid var(--border);background:color-mix(in srgb,var(--page) 50%,transparent)}.chat-drawer>footer>div{display:flex;gap:6px}.chat-drawer select{width:auto;padding:6px 24px 6px 8px;font-size:10px}.chat-drawer textarea{resize:none;width:100%;padding:10px;color:var(--text);border:1px solid var(--border);border-radius:10px;background:var(--page);font:11px/1.5 var(--mono)} /* Abduction identity and animated encounter system. */ @@ -71,17 +71,17 @@ button:focus-visible,input:focus-visible,select:focus-visible { outline:0; borde .splash-craft{position:absolute;z-index:4;left:50%;top:16%;width:230px;height:80px;transform:translateX(-50%);animation:craft-arrive 1s cubic-bezier(.18,.8,.2,1) both}.splash-craft>span{position:absolute;left:65px;top:5px;width:100px;height:48px;border:2px solid #77e9ff;border-radius:70px 70px 18px 18px;background:radial-gradient(circle at 50% 85%,rgba(94,236,255,.5),rgba(89,77,191,.35) 50%,#101738);box-shadow:inset 0 0 18px rgba(79,222,255,.35)}.splash-craft>i{position:absolute;left:12px;top:39px;width:206px;height:29px;border-radius:50%;background:linear-gradient(#8499c4,#182343 43%,#080d22 70%);border:2px solid #63dffc;box-shadow:0 4px 20px rgba(50,214,255,.65)}.splash-craft>b{position:absolute;left:47px;top:56px;width:136px;height:17px;border-radius:0 0 70% 70%;background:#090f2b}.splash-craft>em{position:absolute;left:52px;top:53px;width:9px;height:9px;border-radius:50%;background:#b4ff5e;box-shadow:0 0 12px #75ff6e}.splash-craft>em:last-child{left:169px} .tractor-beam{position:absolute;z-index:2;left:50%;top:23%;width:290px;height:61%;transform:translateX(-50%) scaleY(0);transform-origin:top;clip-path:polygon(40% 0,60% 0,100% 100%,0 100%);background:linear-gradient(90deg,transparent,rgba(109,246,255,.12) 22%,rgba(174,255,154,.35) 50%,rgba(109,246,255,.12) 78%,transparent);filter:drop-shadow(0 0 18px rgba(104,241,255,.4));animation:beam-on .5s .75s ease-out forwards} .abductee{position:absolute;z-index:3;left:50%;bottom:17%;transform:translateX(-50%);animation:abduct 1.5s 1.1s cubic-bezier(.5,0,.65,1) forwards}.abductee--code{display:flex;gap:14px;color:#baff8e;font:700 18px var(--mono);text-shadow:0 0 9px #48ffb5}.abductee--code code:nth-child(2){transform:translateY(8px)}.abductee--cow{width:94px;height:56px}.cow__body{position:absolute;left:6px;top:10px;width:61px;height:29px;border-radius:45% 45% 35% 30%;background:#d8d8da;box-shadow:inset 15px 3px #283044}.cow__head{position:absolute;right:5px;top:16px;width:27px;height:23px;border-radius:42%;background:#d8d8da}.abductee--cow i{position:absolute;left:18px;top:35px;width:5px;height:22px;background:#c7cad1}.abductee--cow i:nth-of-type(2){left:53px}.abductee--cow b{position:absolute;right:3px;top:13px;width:12px;height:3px;background:#c7cad1;transform:rotate(25deg)}.abductee--cow b:last-child{top:33px}.abductee--person{width:54px;height:86px}.person__head{position:absolute;left:18px;width:19px;height:19px;border-radius:50%;background:#10172a}.person__body{position:absolute;left:15px;top:17px;width:25px;height:42px;border-radius:12px 12px 5px 5px;background:#111a31}.abductee--person i,.abductee--person b{position:absolute;top:24px;width:6px;height:38px;border-radius:6px;background:#111a31;transform-origin:top}.abductee--person i{left:11px;transform:rotate(24deg)}.abductee--person i:nth-of-type(2){right:10px;transform:rotate(-24deg)}.abductee--person b{top:54px;left:17px;transform:rotate(9deg)}.abductee--person b:last-child{left:31px;transform:rotate(-9deg)} -.splash--abduction .splash__identity{z-index:6;align-self:end;margin-bottom:5vh;animation:identity-reveal .8s 2.05s cubic-bezier(.2,.8,.2,1) forwards}.splash--abduction .splash__identity>.alien-glyph{width:58px;height:58px}.splash--abduction .splash__identity h1{margin:8px 0 4px;font-size:38px;letter-spacing:.16em;text-transform:uppercase}.splash--abduction .splash__identity span{margin-top:7px;color:#657397;font:8px var(--mono);letter-spacing:.14em;text-transform:uppercase} +.splash--abduction .splash__identity{z-index:6;align-self:end;margin-bottom:calc(5 * var(--vh));animation:identity-reveal .8s 2.05s cubic-bezier(.2,.8,.2,1) forwards}.splash--abduction .splash__identity>.alien-glyph{width:58px;height:58px}.splash--abduction .splash__identity h1{margin:8px 0 4px;font-size:38px;letter-spacing:.16em;text-transform:uppercase}.splash--abduction .splash__identity span{margin-top:7px;color:#657397;font:8px var(--mono);letter-spacing:.14em;text-transform:uppercase} .ufo-loader{display:grid;place-items:center;gap:10px;color:var(--muted);font:10px var(--mono);letter-spacing:.08em}.ufo-loader__craft{position:relative;display:block;width:52px;height:34px;animation:ufo-spin 1.15s linear infinite;filter:drop-shadow(0 0 8px var(--cyan))}.ufo-loader__craft:before{content:"";position:absolute;left:15px;top:1px;width:22px;height:16px;border:1px solid var(--cyan);border-radius:20px 20px 4px 4px;background:color-mix(in srgb,var(--accent) 28%,var(--surface))}.ufo-loader__craft:after{content:"";position:absolute;left:3px;top:14px;width:46px;height:13px;border-radius:50%;background:linear-gradient(var(--cyan),var(--accent));box-shadow:0 7px 12px color-mix(in srgb,var(--cyan) 36%,transparent)}.ufo-loader__craft i,.ufo-loader__craft b{position:absolute;z-index:2;top:18px;width:4px;height:4px;border-radius:50%;background:#c5ff80}.ufo-loader__craft i{left:13px}.ufo-loader__craft b{right:13px}.reader__loading{gap:13px;text-transform:lowercase}.reader__loading:before{content:"";width:52px;height:34px;background:linear-gradient(transparent 0 38%,var(--cyan) 40% 55%,var(--accent) 57% 76%,transparent 78%);clip-path:polygon(38% 0,62% 0,72% 38%,94% 55%,78% 78%,58% 82%,68% 100%,32% 100%,42% 82%,22% 78%,6% 55%,28% 38%);filter:drop-shadow(0 0 7px var(--cyan));animation:ufo-spin 1.15s linear infinite} @keyframes craft-arrive{from{opacity:0;transform:translate(-140%,-80%) rotate(-9deg)}to{opacity:1;transform:translateX(-50%)}}@keyframes beam-on{to{transform:translateX(-50%) scaleY(1)}}@keyframes abduct{0%{transform:translateX(-50%) translateY(0) rotate(0)}65%{opacity:1}100%{opacity:0;transform:translateX(-50%) translateY(-310px) rotate(12deg) scale(.55)}}@keyframes star-pulse{to{opacity:.25}}@keyframes ufo-spin{0%{transform:perspective(90px) rotateY(0) rotate(-4deg)}50%{transform:perspective(90px) rotateY(180deg) rotate(4deg)}100%{transform:perspective(90px) rotateY(360deg) rotate(-4deg)}} @media(prefers-reduced-motion:reduce){.abductee{opacity:0}.tractor-beam{transform:translateX(-50%) scaleY(1)}.splash-craft{transform:translateX(-50%)}.ufo-loader__craft,.reader__loading:before{animation:none}} /* ASCII encounter: crisp terminal art with restrained pixel stepping. */ -.splash--ascii{display:block;background:#02050d;color:#9ef8ff;font-family:var(--mono);image-rendering:pixelated;text-shadow:0 0 7px rgba(70,232,255,.55);isolation:isolate}.splash--ascii:before{content:"";position:absolute;inset:0;z-index:8;pointer-events:none;background:repeating-linear-gradient(0deg,rgba(255,255,255,.022) 0,rgba(255,255,255,.022) 1px,transparent 1px,transparent 4px);mix-blend-mode:screen}.splash--ascii:after{content:"";position:absolute;inset:0;box-shadow:inset 0 0 120px #000;pointer-events:none;z-index:7}.ascii-stars{position:absolute;inset:7% 6% auto;white-space:pre;font:clamp(12px,1.4vw,19px)/4 var(--mono);letter-spacing:clamp(8px,1.5vw,22px);color:#6fc7d3;opacity:.72;animation:ascii-flicker 1.2s steps(2,end) infinite}.ascii-moon{position:absolute;right:10%;top:10%;margin:0;color:#c7edee;font:clamp(13px,1.7vw,24px)/.8 var(--mono);letter-spacing:-.12em;filter:drop-shadow(0 0 13px rgba(169,238,255,.45))}.ascii-craft{position:absolute;z-index:4;left:50%;top:11%;margin:0;transform:translateX(-50%);color:#b8fbff;font:700 clamp(10px,1.35vw,18px)/1.05 var(--mono);letter-spacing:0;white-space:pre;text-align:center;filter:drop-shadow(0 0 8px #2ad9ff);animation:ascii-craft-in .8s steps(8,end) both}.ascii-beam{position:absolute;z-index:2;left:50%;top:29%;width:min(32vw,330px);height:51%;display:grid;grid-template-columns:repeat(4,1fr);justify-items:center;overflow:hidden;transform:translateX(-50%);clip-path:polygon(34% 0,66% 0,100% 100%,0 100%);color:rgba(137,255,209,.24);background:repeating-linear-gradient(90deg,transparent 0 12px,rgba(69,236,221,.025) 12px 15px);font:clamp(8px,1vw,13px)/1 var(--mono);animation:ascii-beam-in .45s .65s steps(5,end) both}.ascii-beam span{writing-mode:vertical-rl;letter-spacing:12px;animation:ascii-stream .65s steps(4,end) infinite}.ascii-beam span:nth-child(even){animation-delay:-.3s;color:rgba(102,222,255,.3)}.ascii-target{position:absolute;z-index:3;left:50%;bottom:19%;margin:0;transform:translateX(-50%);white-space:pre;text-align:center;color:#ceff8d;font:700 clamp(12px,1.5vw,20px)/1.05 var(--mono);filter:drop-shadow(0 0 5px #7aff96);animation:ascii-abduct 1.45s 1.05s steps(15,end) forwards}.ascii-target--cow{color:#e8f2df}.ascii-target--person{color:#a8e9ff}.ascii-woods{position:absolute;z-index:1;left:50%;bottom:0;width:110%;margin:0;transform:translateX(-50%) scaleX(1.25);overflow:hidden;white-space:pre;text-align:center;color:#183e43;font:700 clamp(13px,2vw,27px)/.88 var(--mono);letter-spacing:.08em;text-shadow:0 -4px 12px rgba(44,135,127,.28)}.splash--ascii .splash__identity{position:absolute;z-index:6;left:50%;bottom:4.5%;transform:translateX(-50%);width:100%;animation:ascii-title-in .55s 2.08s steps(7,end) forwards}.splash--ascii .splash__identity h1{margin:0 0 7px;color:#d9ffff;background:none;-webkit-text-fill-color:initial;font:800 clamp(23px,3vw,42px)/1 var(--mono);letter-spacing:.14em;text-shadow:3px 0 #5c46aa,-3px 0 #1bcad3,0 0 17px rgba(80,237,255,.58)}.splash--ascii .splash__identity p{color:#8cc9c6;font:10px var(--mono);letter-spacing:.22em}.splash--ascii .splash__identity span{margin-top:8px;color:#607d88;font:9px var(--mono);letter-spacing:.08em;text-transform:none;animation:cursor-blink .65s steps(1,end) infinite} +.splash--ascii{display:block;background:#02050d;color:#9ef8ff;font-family:var(--mono);image-rendering:pixelated;text-shadow:0 0 7px rgba(70,232,255,.55);isolation:isolate}.splash--ascii:before{content:"";position:absolute;inset:0;z-index:8;pointer-events:none;background:repeating-linear-gradient(0deg,rgba(255,255,255,.022) 0,rgba(255,255,255,.022) 1px,transparent 1px,transparent 4px);mix-blend-mode:screen}.splash--ascii:after{content:"";position:absolute;inset:0;box-shadow:inset 0 0 120px #000;pointer-events:none;z-index:7}.ascii-stars{position:absolute;inset:7% 6% auto;white-space:pre;font:clamp(12px,calc(1.4 * var(--vw)),19px)/4 var(--mono);letter-spacing:clamp(8px,calc(1.5 * var(--vw)),22px);color:#6fc7d3;opacity:.72;animation:ascii-flicker 1.2s steps(2,end) infinite}.ascii-moon{position:absolute;right:10%;top:10%;margin:0;color:#c7edee;font:clamp(13px,calc(1.7 * var(--vw)),24px)/.8 var(--mono);letter-spacing:-.12em;filter:drop-shadow(0 0 13px rgba(169,238,255,.45))}.ascii-craft{position:absolute;z-index:4;left:50%;top:11%;margin:0;transform:translateX(-50%);color:#b8fbff;font:700 clamp(10px,calc(1.35 * var(--vw)),18px)/1.05 var(--mono);letter-spacing:0;white-space:pre;text-align:center;filter:drop-shadow(0 0 8px #2ad9ff);animation:ascii-craft-in .8s steps(8,end) both}.ascii-beam{position:absolute;z-index:2;left:50%;top:29%;width:min(calc(32 * var(--vw)),330px);height:51%;display:grid;grid-template-columns:repeat(4,1fr);justify-items:center;overflow:hidden;transform:translateX(-50%);clip-path:polygon(34% 0,66% 0,100% 100%,0 100%);color:rgba(137,255,209,.24);background:repeating-linear-gradient(90deg,transparent 0 12px,rgba(69,236,221,.025) 12px 15px);font:clamp(8px,calc(1 * var(--vw)),13px)/1 var(--mono);animation:ascii-beam-in .45s .65s steps(5,end) both}.ascii-beam span{writing-mode:vertical-rl;letter-spacing:12px;animation:ascii-stream .65s steps(4,end) infinite}.ascii-beam span:nth-child(even){animation-delay:-.3s;color:rgba(102,222,255,.3)}.ascii-target{position:absolute;z-index:3;left:50%;bottom:19%;margin:0;transform:translateX(-50%);white-space:pre;text-align:center;color:#ceff8d;font:700 clamp(12px,calc(1.5 * var(--vw)),20px)/1.05 var(--mono);filter:drop-shadow(0 0 5px #7aff96);animation:ascii-abduct 1.45s 1.05s steps(15,end) forwards}.ascii-target--cow{color:#e8f2df}.ascii-target--person{color:#a8e9ff}.ascii-woods{position:absolute;z-index:1;left:50%;bottom:0;width:110%;margin:0;transform:translateX(-50%) scaleX(1.25);overflow:hidden;white-space:pre;text-align:center;color:#183e43;font:700 clamp(13px,calc(2 * var(--vw)),27px)/.88 var(--mono);letter-spacing:.08em;text-shadow:0 -4px 12px rgba(44,135,127,.28)}.splash--ascii .splash__identity{position:absolute;z-index:6;left:50%;bottom:4.5%;transform:translateX(-50%);width:100%;animation:ascii-title-in .55s 2.08s steps(7,end) forwards}.splash--ascii .splash__identity h1{margin:0 0 7px;color:#d9ffff;background:none;-webkit-text-fill-color:initial;font:800 clamp(23px,calc(3 * var(--vw)),42px)/1 var(--mono);letter-spacing:.14em;text-shadow:3px 0 #5c46aa,-3px 0 #1bcad3,0 0 17px rgba(80,237,255,.58)}.splash--ascii .splash__identity p{color:#8cc9c6;font:10px var(--mono);letter-spacing:.22em}.splash--ascii .splash__identity span{margin-top:8px;color:#607d88;font:9px var(--mono);letter-spacing:.08em;text-transform:none;animation:cursor-blink .65s steps(1,end) infinite} @keyframes ascii-craft-in{from{opacity:0;transform:translate(-80%,-80px)}to{opacity:1;transform:translateX(-50%)}}@keyframes ascii-beam-in{from{opacity:0;height:0}to{opacity:1;height:51%}}@keyframes ascii-stream{to{transform:translateY(24px)}}@keyframes ascii-abduct{0%{transform:translateX(-50%)}70%{opacity:1}100%{opacity:0;transform:translate(-50%,-285px) scale(.62)}}@keyframes ascii-title-in{from{opacity:0;clip-path:inset(0 50%)}to{opacity:1;clip-path:inset(0)}}@keyframes ascii-flicker{50%{opacity:.42}}@keyframes cursor-blink{50%{opacity:.45}} @media(prefers-reduced-motion:reduce){.ascii-craft{animation:none}.ascii-target{opacity:0}.ascii-beam{height:51%;animation:none}.ascii-stars,.ascii-beam span,.splash--ascii .splash__identity span{animation:none}.splash--ascii .splash__identity{opacity:1;clip-path:none}} /* Richer classic-saucer pass: soft atmosphere around deliberately simple ASCII. */ -.splash--ascii{background:radial-gradient(ellipse at 50% 35%,#101b38 0,#050a19 45%,#010309 82%);text-shadow:0 0 7px rgba(70,232,255,.48)}.ascii-aurora{position:absolute;inset:-18% -10% 35%;opacity:.28;background:radial-gradient(ellipse at 34% 40%,rgba(64,255,177,.24),transparent 38%),radial-gradient(ellipse at 68% 22%,rgba(91,89,255,.3),transparent 42%);filter:blur(42px);transform:skewY(-5deg);animation:aurora-drift 5s ease-in-out infinite alternate}.ascii-stars{inset:4% 4% auto;font-size:clamp(11px,1.25vw,17px);line-height:3.35;letter-spacing:clamp(7px,1.25vw,18px);color:#8ddbe1;opacity:.8}.ascii-moon{right:8%;top:8%;color:#d4ecdf;font-size:clamp(10px,1.25vw,18px);line-height:.92;letter-spacing:0;text-shadow:0 0 6px #a9d9d3,0 0 28px rgba(181,227,218,.48)}.ascii-craft{top:9%;width:max-content;max-width:90vw;color:#d8feff;font-size:clamp(8px,1.18vw,16px);line-height:1.03;text-shadow:0 0 4px #83ecff,0 0 15px rgba(65,205,255,.6);filter:none;animation:classic-saucer-in .9s steps(10,end) both,classic-saucer-hover 3.2s .9s ease-in-out infinite}.ascii-craft:after{content:"";position:absolute;left:12%;right:12%;bottom:-10px;height:8px;border-radius:50%;background:rgba(117,242,255,.42);filter:blur(7px);box-shadow:0 0 25px rgba(133,255,192,.55)}.ascii-beam{top:28%;width:min(38vw,390px);height:56%;grid-template-columns:repeat(5,1fr);color:rgba(164,255,196,.2);background:repeating-linear-gradient(90deg,transparent 0 15px,rgba(94,251,213,.035) 15px 18px);filter:drop-shadow(0 0 16px rgba(102,255,213,.18))}.ascii-beam:after{content:"";position:absolute;inset:auto 9% 0;height:10px;border:1px dashed rgba(138,255,203,.32);border-radius:50%;box-shadow:0 0 22px rgba(107,255,194,.3);animation:landing-ring 1.1s steps(4,end) infinite}.ascii-target{bottom:17%;text-shadow:0 0 5px currentColor,0 0 14px rgba(130,255,175,.5)}.ascii-hills{position:absolute;z-index:0;left:50%;bottom:14%;width:120%;margin:0;transform:translateX(-50%) scaleX(1.15);overflow:hidden;color:#183549;font:clamp(9px,1.25vw,17px)/1 var(--mono);text-align:center;white-space:pre;text-shadow:0 -3px 12px rgba(42,107,153,.25)}.ascii-woods{z-index:1;width:115%;color:#204b42;font-size:clamp(10px,1.45vw,20px);line-height:.9;letter-spacing:.04em;text-shadow:0 -3px 9px rgba(67,158,113,.25)}.splash--ascii .splash__identity{bottom:3%;padding-top:8px;background:linear-gradient(transparent,rgba(1,3,9,.72) 30%)} +.splash--ascii{background:radial-gradient(ellipse at 50% 35%,#101b38 0,#050a19 45%,#010309 82%);text-shadow:0 0 7px rgba(70,232,255,.48)}.ascii-aurora{position:absolute;inset:-18% -10% 35%;opacity:.28;background:radial-gradient(ellipse at 34% 40%,rgba(64,255,177,.24),transparent 38%),radial-gradient(ellipse at 68% 22%,rgba(91,89,255,.3),transparent 42%);filter:blur(42px);transform:skewY(-5deg);animation:aurora-drift 5s ease-in-out infinite alternate}.ascii-stars{inset:4% 4% auto;font-size:clamp(11px,calc(1.25 * var(--vw)),17px);line-height:3.35;letter-spacing:clamp(7px,calc(1.25 * var(--vw)),18px);color:#8ddbe1;opacity:.8}.ascii-moon{right:8%;top:8%;color:#d4ecdf;font-size:clamp(10px,calc(1.25 * var(--vw)),18px);line-height:.92;letter-spacing:0;text-shadow:0 0 6px #a9d9d3,0 0 28px rgba(181,227,218,.48)}.ascii-craft{top:9%;width:max-content;max-width:calc(90 * var(--vw));color:#d8feff;font-size:clamp(8px,calc(1.18 * var(--vw)),16px);line-height:1.03;text-shadow:0 0 4px #83ecff,0 0 15px rgba(65,205,255,.6);filter:none;animation:classic-saucer-in .9s steps(10,end) both,classic-saucer-hover 3.2s .9s ease-in-out infinite}.ascii-craft:after{content:"";position:absolute;left:12%;right:12%;bottom:-10px;height:8px;border-radius:50%;background:rgba(117,242,255,.42);filter:blur(7px);box-shadow:0 0 25px rgba(133,255,192,.55)}.ascii-beam{top:28%;width:min(calc(38 * var(--vw)),390px);height:56%;grid-template-columns:repeat(5,1fr);color:rgba(164,255,196,.2);background:repeating-linear-gradient(90deg,transparent 0 15px,rgba(94,251,213,.035) 15px 18px);filter:drop-shadow(0 0 16px rgba(102,255,213,.18))}.ascii-beam:after{content:"";position:absolute;inset:auto 9% 0;height:10px;border:1px dashed rgba(138,255,203,.32);border-radius:50%;box-shadow:0 0 22px rgba(107,255,194,.3);animation:landing-ring 1.1s steps(4,end) infinite}.ascii-target{bottom:17%;text-shadow:0 0 5px currentColor,0 0 14px rgba(130,255,175,.5)}.ascii-hills{position:absolute;z-index:0;left:50%;bottom:14%;width:120%;margin:0;transform:translateX(-50%) scaleX(1.15);overflow:hidden;color:#183549;font:clamp(9px,calc(1.25 * var(--vw)),17px)/1 var(--mono);text-align:center;white-space:pre;text-shadow:0 -3px 12px rgba(42,107,153,.25)}.ascii-woods{z-index:1;width:115%;color:#204b42;font-size:clamp(10px,calc(1.45 * var(--vw)),20px);line-height:.9;letter-spacing:.04em;text-shadow:0 -3px 9px rgba(67,158,113,.25)}.splash--ascii .splash__identity{bottom:3%;padding-top:8px;background:linear-gradient(transparent,rgba(1,3,9,.72) 30%)} @keyframes classic-saucer-in{from{opacity:0;transform:translate(-85%,-90px) scale(.78)}to{opacity:1;transform:translateX(-50%) scale(1)}}@keyframes classic-saucer-hover{0%,100%{transform:translate(-50%,0)}50%{transform:translate(-50%,5px)}}@keyframes aurora-drift{to{opacity:.42;transform:translateX(4%) skewY(-2deg)}}@keyframes landing-ring{50%{opacity:.45;transform:scaleX(.88)}} @media(prefers-reduced-motion:reduce){.ascii-craft,.ascii-aurora,.ascii-beam:after{animation:none}.ascii-craft{transform:translateX(-50%)}} diff --git a/frontend/src/viewport.ts b/frontend/src/viewport.ts new file mode 100644 index 0000000..905f5c4 --- /dev/null +++ b/frontend/src/viewport.ts @@ -0,0 +1,10 @@ +// updateViewportUnits recomputes --vh/--vw as 1% of the true viewport divided +// by the active --scale, so viewport-unit CSS keeps its real-world proportions +// under the whole-app scale transform on .shell instead of measuring against +// the unscaled window. +export function updateViewportUnits() { + const root = document.documentElement; + const scale = Number(getComputedStyle(root).getPropertyValue("--scale")) || 1; + root.style.setProperty("--vh", `${window.innerHeight / 100 / scale}px`); + root.style.setProperty("--vw", `${window.innerWidth / 100 / scale}px`); +}