diff --git a/landing/next-env.d.ts b/landing/next-env.d.ts
index ce4e94a..a419cbe 100644
--- a/landing/next-env.d.ts
+++ b/landing/next-env.d.ts
@@ -1,7 +1,7 @@
///
///
-import "./.next/types/routes.d.ts";
-import "./.next/types/root-params.d.ts";
+import "./.next/dev/types/routes.d.ts";
+import "./.next/dev/types/root-params.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/landing/package-lock.json b/landing/package-lock.json
index 0477d1d..d1b39fe 100644
--- a/landing/package-lock.json
+++ b/landing/package-lock.json
@@ -10,7 +10,9 @@
"dependencies": {
"next": "16.4.3",
"react": "19.2.8",
- "react-dom": "19.2.8"
+ "react-dom": "19.2.8",
+ "framer-motion": "^12.43.0",
+ "lucide-react": "^0.488.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.4",
@@ -6858,6 +6860,57 @@
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
+ },
+ "node_modules/framer-motion": {
+ "version": "12.43.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz",
+ "integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.43.0",
+ "motion-utils": "^12.39.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/motion-dom": {
+ "version": "12.43.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.43.0.tgz",
+ "integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.39.0"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.39.0",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz",
+ "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==",
+ "license": "MIT"
+ },
+ "node_modules/lucide-react": {
+ "version": "0.488.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.488.0.tgz",
+ "integrity": "sha512-ronlL0MyKut4CEzBY/ai2ZpKPxyWO4jUqdAkm2GNK5Zn3Rj+swDz+3lvyAUXN0PNqPKIX6XM9Xadwz/skLs/pQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
}
}
}
diff --git a/landing/package.json b/landing/package.json
index 94f96ee..39daaef 100644
--- a/landing/package.json
+++ b/landing/package.json
@@ -12,7 +12,9 @@
"dependencies": {
"next": "16.4.3",
"react": "19.2.8",
- "react-dom": "19.2.8"
+ "react-dom": "19.2.8",
+ "framer-motion": "^12.43.0",
+ "lucide-react": "^0.488.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.4",
diff --git a/landing/public/screenshots/app/dashboard-overview.png b/landing/public/screenshots/app/dashboard-overview.png
new file mode 100644
index 0000000..26489ce
Binary files /dev/null and b/landing/public/screenshots/app/dashboard-overview.png differ
diff --git a/landing/public/screenshots/app/dependency-analysis-detail.png b/landing/public/screenshots/app/dependency-analysis-detail.png
new file mode 100644
index 0000000..35fc347
Binary files /dev/null and b/landing/public/screenshots/app/dependency-analysis-detail.png differ
diff --git a/landing/public/screenshots/app/dependency-graph.png b/landing/public/screenshots/app/dependency-graph.png
new file mode 100644
index 0000000..1c00b7e
Binary files /dev/null and b/landing/public/screenshots/app/dependency-graph.png differ
diff --git a/landing/public/screenshots/app/migration-assistant.png b/landing/public/screenshots/app/migration-assistant.png
new file mode 100644
index 0000000..716e766
Binary files /dev/null and b/landing/public/screenshots/app/migration-assistant.png differ
diff --git a/landing/public/screenshots/app/pydantic-upgrade-simulation-in-progress.png b/landing/public/screenshots/app/pydantic-upgrade-simulation-in-progress.png
new file mode 100644
index 0000000..8335b59
Binary files /dev/null and b/landing/public/screenshots/app/pydantic-upgrade-simulation-in-progress.png differ
diff --git a/landing/public/screenshots/app/repository-inventory.png b/landing/public/screenshots/app/repository-inventory.png
new file mode 100644
index 0000000..f4bbee9
Binary files /dev/null and b/landing/public/screenshots/app/repository-inventory.png differ
diff --git a/landing/public/screenshots/app/security-findings.png b/landing/public/screenshots/app/security-findings.png
new file mode 100644
index 0000000..e7ed7a1
Binary files /dev/null and b/landing/public/screenshots/app/security-findings.png differ
diff --git a/landing/public/screenshots/app/upgrade-simulation-babel-in-progress.png b/landing/public/screenshots/app/upgrade-simulation-babel-in-progress.png
new file mode 100644
index 0000000..8dc2083
Binary files /dev/null and b/landing/public/screenshots/app/upgrade-simulation-babel-in-progress.png differ
diff --git a/landing/public/screenshots/app/upgrade-simulation-babel-results.png b/landing/public/screenshots/app/upgrade-simulation-babel-results.png
new file mode 100644
index 0000000..d173b8b
Binary files /dev/null and b/landing/public/screenshots/app/upgrade-simulation-babel-results.png differ
diff --git a/landing/src/app/globals.css b/landing/src/app/globals.css
index 3e445eb..4cf12a3 100644
--- a/landing/src/app/globals.css
+++ b/landing/src/app/globals.css
@@ -1,120 +1,208 @@
@import "tailwindcss";
+@custom-variant dark (&:is(.dark *));
+
:root {
- --ink: #070b12;
- --panel: #0a1018;
- --panel-raised: #0d151f;
- --line: rgba(220, 234, 245, .095);
- --text: #e8eef6;
- --muted: #91a0b2;
- --quiet: #657488;
- --teal: #3dd6c6;
- --teal-dim: #163b39;
- --mono: "IBM Plex Mono", ui-monospace, monospace;
- --sans: "DM Sans", ui-sans-serif, system-ui, sans-serif;
- --display: "Syne", "DM Sans", ui-sans-serif, sans-serif;
-}
-
-* { box-sizing: border-box; }
-html { scroll-behavior: smooth; scroll-padding-top: 88px; color-scheme: dark; }
-body { margin: 0; min-width: 320px; background: var(--ink); color: var(--text); font-family: var(--sans); -webkit-font-smoothing: antialiased; }
-body::before { position: fixed; z-index: -1; inset: 0; content: ""; pointer-events: none; background: radial-gradient(ellipse at 12% -10%, rgba(61,214,198,.09), transparent 36%), radial-gradient(ellipse at 88% 5%, rgba(48,111,122,.08), transparent 31%); }
-a { color: inherit; text-decoration: none; }
-button { font: inherit; }
-::selection { background: rgba(61,214,198,.28); color: white; }
-.container { width: min(1160px, calc(100% - 64px)); margin-inline: auto; }
-.skip-link { position: fixed; z-index: 100; top: 8px; left: 8px; transform: translateY(-150%); border-radius: 8px; background: var(--teal); padding: 12px 16px; color: #04201c; font-weight: 700; }
-.skip-link:focus { transform: translateY(0); }
-.site-header { position: sticky; z-index: 50; top: 0; border-bottom: 1px solid rgba(255,255,255,.08); background: rgba(7,11,18,.86); backdrop-filter: blur(16px); }
-.nav-inner { width: min(1320px, calc(100% - 64px)); height: 76px; margin: auto; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 28px; }
-.brand { display: inline-flex; width: fit-content; align-items: center; gap: 10px; color: #fff; font-family: var(--display); font-size: 17px; font-weight: 650; letter-spacing: -.04em; }
-.brand-mark { display: grid; width: 34px; height: 34px; place-items: center; border: 1px solid rgba(61,214,198,.3); border-radius: 10px; background: rgba(61,214,198,.11); color: var(--teal); }
-.brand-mark svg { width: 21px; height: 21px; }
-.desktop-nav { display: flex; align-items: center; gap: clamp(14px, 2vw, 29px); }
-.desktop-nav a, .nav-github { color: #aeb9c7; font-size: 13px; font-weight: 550; white-space: nowrap; transition: color .18s ease; }
-.desktop-nav a:hover, .nav-github:hover { color: #fff; }
-.nav-github { justify-self: end; display: inline-flex; align-items: center; gap: 9px; border: 1px solid rgba(61,214,198,.28); border-radius: 999px; background: rgba(61,214,198,.08); padding: 10px 16px; color: #c6f8f1; }
-.nav-github span { color: var(--teal); }
-.menu-toggle, .mobile-nav { display: none; }
-.menu-toggle:focus-visible, a:focus-visible, button:focus-visible { outline: 2px solid #7af2e4; outline-offset: 4px; }
-
-.hero { position: relative; overflow: hidden; padding: 94px 0 76px; }
-.section-grid { background-image: linear-gradient(rgba(255,255,255,.022) 1px, transparent 1px), linear-gradient(90deg,rgba(255,255,255,.022) 1px,transparent 1px); background-size: 68px 68px; background-position: center -1px; }
-.hero::after { position: absolute; inset: 0; content: ""; pointer-events: none; background: radial-gradient(ellipse at center 18%, transparent 4%, rgba(7,11,18,.28) 69%, var(--ink) 100%); }
-.hero-glow { position: absolute; top: 60px; left: 50%; width: 780px; height: 520px; transform: translateX(-50%); border-radius: 50%; background: rgba(27,159,148,.09); filter: blur(100px); }
-.hero-content { position: relative; z-index: 1; }
-.hero-copy { max-width: 910px; margin: auto; text-align: center; }
-.eyebrow { display: inline-flex; align-items: center; gap: 9px; color: #85d8d0; font-family: var(--mono); font-size: 10px; font-weight: 500; letter-spacing: .14em; line-height: 1.5; text-transform: uppercase; }
-.status-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--teal); box-shadow: 0 0 12px rgba(61,214,198,.65); }
-h1, h2, h3, p { margin-top: 0; }
-.hero h1 { margin: 24px 0 20px; font-family: var(--display); font-size: clamp(38px, 5.3vw, 67px); font-weight: 600; letter-spacing: -.065em; line-height: 1.1; }
-.hero h1 span, .hosting-copy h2 span, .final-cta h2 span { color: var(--teal); }
-.hero-lede { max-width: 740px; margin: 0 auto; color: #9eacbc; font-size: 16px; line-height: 1.78; }
-.hero-actions, .hosting-actions { display: flex; justify-content: center; flex-wrap: wrap; gap: 12px; margin-top: 30px; }
-.button { min-height: 47px; display: inline-flex; justify-content: center; align-items: center; gap: 11px; border: 1px solid transparent; border-radius: 999px; padding: 0 20px; font-size: 13px; font-weight: 700; transition: background .18s, border-color .18s, transform .18s; }
-.button:hover { transform: translateY(-1px); }
-.button-primary { background: var(--teal); color: #04201c; box-shadow: 0 7px 28px rgba(61,214,198,.14); }
-.button-primary:hover { background: #6de5d9; }
-.button-secondary { border-color: rgba(255,255,255,.14); background: rgba(255,255,255,.025); color: #e4eaf0; }
-.button-secondary:hover { border-color: rgba(61,214,198,.38); background: rgba(61,214,198,.06); }
-.product-attributes { display: flex; justify-content: center; flex-wrap: wrap; gap: 19px; margin: 25px 0 0; padding: 0; list-style: none; color: #8a98a9; font-family: var(--mono); font-size: 10px; }
-.product-attributes li { display: flex; align-items: center; gap: 6px; }
-.product-attributes li span { color: #4da99f; font-size: 9px; }
-.hero-preview { position: relative; min-height: 380px; margin: 61px auto 0; display: grid; grid-template-columns: 1.3fr .77fr; align-items: center; gap: 16px; }
-.preview-shadow { position: absolute; top: 15%; left: 14%; width: 72%; height: 70%; border-radius: 50%; background: rgba(25,117,111,.16); filter: blur(68px); }
-.window { position: relative; overflow: hidden; border: 1px solid rgba(214,230,241,.13); border-radius: 15px; background: rgba(10,16,24,.96); box-shadow: 0 24px 75px -42px rgba(0,0,0,.95), 0 4px 12px rgba(0,0,0,.18); }
-.window-title { min-height: 42px; display: flex; align-items: center; gap: 11px; border-bottom: 1px solid rgba(255,255,255,.07); background: rgba(255,255,255,.018); padding: 0 14px; color: #8190a1; font-family: var(--mono); font-size: 9px; }
-.window-title > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.window-title > b { margin-left: auto; flex-shrink: 0; border: 1px solid rgba(61,214,198,.2); border-radius: 4px; padding: 3px 6px; color: #79c9c0; font-size: 8px; font-weight: 500; letter-spacing: .05em; }
-.window-dots { display: flex; gap: 5px; flex-shrink: 0; }
-.window-dots i { width: 7px; height: 7px; border-radius: 50%; background: #f07178a6; }
-.window-dots i:nth-child(2) { background: #e6b450a6; }.window-dots i:nth-child(3) { background: #7fd962a6; }
-.terminal-window { z-index: 1; transform: perspective(1400px) rotateY(3deg) rotateX(1deg); }
-.terminal-body { min-height: 300px; display: grid; grid-template-columns: 168px 1fr; }
-.terminal-sidebar { position: relative; display: flex; flex-direction: column; gap: 12px; border-right: 1px solid rgba(255,255,255,.06); padding: 17px 12px; color: #768598; font-size: 9px; }
-.side-label { margin: 0 0 2px 5px; color: #586779; font-family: var(--mono); font-size: 8px; letter-spacing: .11em; }
-.side-active { border-radius: 5px; background: rgba(61,214,198,.1); padding: 7px 5px; color: #a6e8e1; }
-.side-user { position: absolute; right: 12px; bottom: 13px; left: 12px; display: flex; align-items: center; gap: 7px; border-top: 1px solid rgba(255,255,255,.06); padding-top: 11px; color: #647286; font-size: 8px; }
-.user-dot { display: grid; width: 19px; height: 19px; place-items: center; border-radius: 50%; background: #213b3b; color: #8edbd2; font-family: var(--mono); }
-.terminal-main { min-width: 0; padding: 17px 16px; font-family: var(--mono); }
-.terminal-command { overflow: hidden; color: #cbd5df; font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }.terminal-prompt { margin-right: 7px; color: var(--teal); }
-.terminal-divider { height: 1px; margin: 14px 0 11px; background: rgba(255,255,255,.07); }
-.terminal-step { display: grid; grid-template-columns: 15px minmax(0,1fr) auto; align-items: center; gap: 6px; min-height: 35px; border-bottom: 1px solid rgba(255,255,255,.035); color: #91a0af; font-size: 9px; }
-.step-check { color: #54d4c3; }.step-state { color: #5c6c7d; font-size: 8px; white-space: nowrap; }
-.scan-progress { position: relative; display: flex; justify-content: space-between; margin-top: 16px; color: #667587; font-size: 8px; letter-spacing: .06em; }.scan-progress i { position: absolute; top: 17px; left: 0; width: 100%; height: 3px; border-radius: 4px; background: linear-gradient(90deg,var(--teal) 0 100%,#17202b 100%); box-shadow: 0 0 8px rgba(61,214,198,.25); }
-.metrics-window { z-index: 2; margin-left: -47px; transform: translateY(27px); }
-.metrics-content { padding: 17px 16px 19px; }.metric-top { display: flex; align-items: center; justify-content: space-between; }.metric-top > div { display: grid; gap: 5px; }.metric-label, .metric-card span, .bar-row span { color: #6d7a8b; font-family: var(--mono); font-size: 8px; letter-spacing: .07em; }.metric-top strong { color: white; font-family: var(--display); font-size: 36px; letter-spacing: -.06em; }.metric-pill { border: 1px solid rgba(61,214,198,.16); border-radius: 999px; padding: 4px 7px; color: #73bdb5; font-family: var(--mono); font-size: 8px; }
-.metric-cards { display: grid; grid-template-columns: repeat(3,1fr); gap: 6px; margin: 14px 0 18px; }.metric-card { display: grid; gap: 6px; border: 1px solid rgba(255,255,255,.06); border-radius: 7px; background: rgba(255,255,255,.02); padding: 8px 7px; }.metric-card span { font-size: 7px; letter-spacing: 0; }.metric-card b { color: #dce7ee; font-family: var(--mono); font-size: 14px; font-weight: 500; }
-.metric-bars { display: grid; gap: 10px; }.bar-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 8px; align-items: center; }.bar-row b { color: #a2afbd; font-family: var(--mono); font-size: 8px; font-weight: 400; }.bar-row i { grid-column: 1 / -1; height: 3px; overflow: hidden; border-radius: 3px; background: rgba(255,255,255,.06); }.bar-row em { display: block; height: 100%; border-radius: inherit; background: var(--teal); }
-.illustrative-note { display: flex; justify-content: center; gap: 7px; margin: 22px auto 0; color: #6f7c8d; font-size: 10px; }.illustrative-note span { color: #69c9c0; }
-
-.section { border-top: 1px solid rgba(255,255,255,.06); padding: 104px 0; }
-.section-muted { background: linear-gradient(180deg, rgba(255,255,255,.014), rgba(255,255,255,.009)); }
-.section-heading { max-width: 690px; margin-bottom: 43px; }.section-heading h2, .activity-row h2 { margin: 12px 0 14px; color: #f0f4f7; font-family: var(--display); font-size: clamp(30px, 3.3vw, 43px); font-weight: 550; letter-spacing: -.055em; line-height: 1.16; }.section-heading p, .activity-row p { max-width: 620px; margin: 0; color: var(--muted); font-size: 15px; line-height: 1.75; }
-.capability-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 13px; }.capability-card { position: relative; min-height: 208px; border: 1px solid var(--line); border-radius: 13px; background: linear-gradient(145deg,rgba(255,255,255,.033),rgba(255,255,255,.012)); padding: 22px; transition: border-color .2s, transform .2s, background .2s; }.capability-card:hover { transform: translateY(-2px); border-color: rgba(61,214,198,.26); background: linear-gradient(145deg,rgba(61,214,198,.055),rgba(255,255,255,.012)); }.capability-icon { width: 34px; height: 34px; display: grid; place-items: center; border: 1px solid rgba(61,214,198,.2); border-radius: 9px; background: rgba(61,214,198,.07); color: #70d8cd; font-family: var(--mono); font-size: 17px; }.card-index { position: absolute; top: 26px; right: 22px; color: #4c5a6c; font-family: var(--mono); font-size: 9px; }.capability-card h3 { margin: 23px 0 8px; color: #e7edf2; font-size: 15px; font-weight: 600; }.capability-card p, .workflow-card p { margin: 0; color: #8997a8; font-size: 12px; line-height: 1.7; }
-.split-layout { display: grid; grid-template-columns: .86fr 1.14fr; align-items: center; gap: 70px; }.split-layout .section-heading { margin-bottom: 19px; }.split-layout .section-heading h2 { font-size: clamp(30px, 3vw, 39px); }.reverse-on-wide { grid-template-columns: 1.05fr .95fr; }.reverse-on-wide > :first-child { order: 1; }.reverse-on-wide > :last-child { order: 2; }.body-copy { color: #94a1b1; font-size: 13px; line-height: 1.8; }.check-list { display: grid; gap: 12px; margin: 25px 0 0; padding: 0; list-style: none; color: #b2bdc9; font-size: 12px; }.check-list li { display: flex; align-items: center; gap: 10px; }.check-list li::before { content: "✓"; color: var(--teal); font-family: var(--mono); }
-.scan-window, .upgrade-window, .migration-window, .insight-window { width: 100%; }.scan-timeline { padding: 18px 21px 10px; }.scan-line { min-height: 43px; display: grid; grid-template-columns: 28px 1fr auto; align-items: center; gap: 10px; border-bottom: 1px solid rgba(255,255,255,.045); }.scan-line > span { color: #59706f; font-family: var(--mono); font-size: 9px; }.scan-line b { color: #c4ced7; font-size: 11px; font-weight: 500; }.scan-line em { color: #76cfc5; font-family: var(--mono); font-size: 8px; font-style: normal; }.scan-footer { display: flex; align-items: center; gap: 8px; border-top: 1px solid rgba(255,255,255,.06); background: rgba(255,255,255,.015); padding: 13px 20px; color: #728192; font-size: 9px; }
-.upgrade-body { padding: 18px; }.package-select { display: grid; gap: 8px; }.package-select > span, .version-compare span, .security-summary span { color: #68778a; font-family: var(--mono); font-size: 8px; letter-spacing: .08em; }.package-select b { display: flex; justify-content: space-between; border: 1px solid rgba(255,255,255,.08); border-radius: 7px; background: rgba(255,255,255,.02); padding: 10px 12px; color: #e1eaf0; font-family: var(--mono); font-size: 11px; font-weight: 400; }.package-select i { color: #76879a; font-style: normal; }.version-compare { display: grid; grid-template-columns: 1fr 24px 1fr; align-items: center; gap: 10px; margin: 17px 0; border: 1px solid rgba(255,255,255,.06); border-radius: 8px; background: rgba(255,255,255,.015); padding: 12px; }.version-compare div { display: grid; gap: 7px; }.version-compare strong { color: #d7e1ea; font-family: var(--mono); font-size: 13px; font-weight: 500; }.compare-arrow { color: var(--teal); text-align: center; }.evidence-box { display: grid; gap: 8px; }.evidence-box > div { display: grid; grid-template-columns: 20px 1fr; align-items: center; gap: 2px 7px; border: 1px solid rgba(255,255,255,.055); border-radius: 7px; padding: 9px; }.evidence-icon { grid-row: span 2; color: #72d6cb; font-family: var(--mono); }.muted-icon { color: #a6ad74; }.evidence-box b { color: #c7d0da; font-size: 10px; font-weight: 550; }.evidence-box small { color: #718092; font-size: 9px; }.risk-note { display: flex; gap: 9px; margin-top: 12px; border: 1px solid rgba(230,180,80,.16); border-radius: 7px; background: rgba(230,180,80,.04); padding: 10px; }.risk-note > span { color: #e6b450; font-family: var(--mono); font-size: 11px; }.risk-note p { margin: 0; color: #a5a08a; font-size: 9px; line-height: 1.55; }.demo-tag { display: inline-flex; border: 1px solid rgba(61,214,198,.18); border-radius: 999px; padding: 7px 10px; color: #77c3bb; font-family: var(--mono); font-size: 8px; }
-.migration-content { padding: 17px; }.migration-context { display: grid; gap: 7px; }.migration-context > span { color: #68bfb6; font-family: var(--mono); font-size: 8px; letter-spacing: .06em; }.migration-context b { color: #d9e2e9; font-size: 11px; font-weight: 550; }.migration-context small { color: #718092; font-size: 9px; }.code-diff { overflow: auto; margin-top: 15px; border: 1px solid rgba(255,255,255,.06); border-radius: 7px; background: #080d13; padding: 9px 0; }.code-diff > div { min-width: max-content; display: flex; gap: 12px; padding: 7px 12px; color: #bcbfc3; font-family: var(--mono); font-size: 9px; }.code-diff > div span { color: #eb8589; }.code-diff .diff-add { background: rgba(61,214,198,.08); color: #b4e6df; }.code-diff .diff-add span { color: #64dacb; }.code-diff code { font: inherit; }.migration-explain { display: flex; gap: 10px; margin-top: 12px; border: 1px solid rgba(61,214,198,.12); border-radius: 8px; background: rgba(61,214,198,.035); padding: 11px; }.ai-spark { color: var(--teal); }.migration-explain p { margin: 0; color: #8897a7; font-size: 9px; line-height: 1.6; }.migration-explain b { color: #d0e2df; font-weight: 550; }.migration-foot { display: flex; justify-content: space-between; gap: 8px; margin-top: 14px; color: #68798a; font-family: var(--mono); font-size: 8px; }.migration-foot span:last-child { color: #72cfc5; }
-.insights-grid { display: grid; grid-template-columns: 1.07fr .93fr; align-items: stretch; gap: 15px; }.insight-window { display: flex; flex-direction: column; }.graph-canvas { padding: 15px 12px 6px; }.graph-canvas svg { width: 100%; overflow: visible; }.graph-edges path { fill: none; stroke: rgba(61,214,198,.27); stroke-width: 1.2; }.graph-node rect { fill: #121c27; stroke: rgba(255,255,255,.14); }.graph-node text { fill: #bec9d3; font: 10px var(--mono); }.graph-node.root rect { fill: #1b4743; stroke: rgba(61,214,198,.65); }.graph-node.root text { fill: #bdf6ee; }.graph-node.risk rect { fill: #321d23; stroke: rgba(240,113,120,.58); }.graph-node.risk text { fill: #f09ca0; }.graph-legend { display: flex; justify-content: center; flex-wrap: wrap; gap: 17px; color: #788697; font-family: var(--mono); font-size: 8px; }.graph-legend span { display: inline-flex; align-items: center; gap: 6px; }.graph-legend i { width: 7px; height: 7px; border: 1px solid #8392a2; border-radius: 50%; }.graph-legend .legend-root { border-color: var(--teal); background: #358b81; }.graph-legend .legend-risk { border-color: #e27c82; background: #8c4247; }.card-caption { margin: auto 0 0; border-top: 1px solid rgba(255,255,255,.06); padding: 13px 16px; color: #69788a; font-size: 9px; line-height: 1.5; }.security-body { display: grid; gap: 14px; padding: 17px; }.security-summary { display: grid; grid-template-columns: repeat(3,1fr); gap: 7px; }.security-summary > div { display: grid; gap: 7px; border: 1px solid rgba(255,255,255,.06); border-radius: 7px; background: rgba(255,255,255,.018); padding: 9px; }.security-summary b { color: #d5dfe7; font-family: var(--mono); font-size: 10px; font-weight: 500; }.security-summary .severity { color: #f18d93; }.finding-card { border: 1px solid rgba(240,113,120,.18); border-radius: 8px; background: rgba(240,113,120,.025); padding: 12px; }.finding-card > div:first-child { display: flex; align-items: center; gap: 7px; }.finding-dot { width: 6px; height: 6px; border-radius: 50%; background: #ed777f; box-shadow: 0 0 8px #ed777f88; }.finding-card b { color: #d8dfe5; font-size: 10px; font-weight: 550; }.finding-id { margin-left: auto; color: #8e7378; font-family: var(--mono); font-size: 8px; }.finding-card p { margin: 10px 0 14px; color: #9a8990; font-size: 9px; line-height: 1.5; }.range-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; color: #788596; font-size: 8px; }.range-row code { color: #dfadb0; font-family: var(--mono); font-size: 8px; }.range-line { width: 25px; height: 1px; background: #b86f76; }.security-foot { color: #718092; font-size: 9px; line-height: 1.5; }
-.ecosystem-layout { display: grid; grid-template-columns: 1.04fr .96fr; align-items: center; gap: 78px; }.ecosystem-layout .section-heading { margin-bottom: 17px; }.text-link { display: inline-flex; gap: 7px; margin-top: 10px; color: #7dd8cf; font-size: 12px; font-weight: 550; }.text-link:hover { color: #b3f3ec; }.ecosystem-card { border: 1px solid var(--line); border-radius: 12px; background: rgba(10,16,24,.72); padding: 20px 22px 17px; }.ecosystem-card-head { display: flex; justify-content: space-between; border-bottom: 1px solid rgba(255,255,255,.065); padding-bottom: 14px; color: #728193; font-family: var(--mono); font-size: 8px; letter-spacing: .1em; }.ecosystem-card-head .mono { color: #506171; }.ecosystem-card ul { display: grid; grid-template-columns: 1fr 1fr; gap: 0 20px; margin: 5px 0 0; padding: 0; list-style: none; }.ecosystem-card li { display: grid; grid-template-columns: 23px 1fr 12px; align-items: center; gap: 8px; min-height: 47px; border-bottom: 1px solid rgba(255,255,255,.045); color: #c4ced7; font-size: 11px; }.ecosystem-number { color: #526274; font-family: var(--mono); font-size: 8px; }.ecosystem-arrow { color: #5cb7ae; font-size: 10px; }.ecosystem-card > p { margin: 13px 0 0; color: #718092; font-size: 9px; line-height: 1.5; }
-.workflow-grid { display: grid; grid-template-columns: repeat(4,1fr); gap: 13px; }.workflow-card { position: relative; min-height: 190px; border: 1px solid var(--line); border-radius: 11px; background: rgba(255,255,255,.018); padding: 20px; }.workflow-num { color: #66cfc4; font-family: var(--mono); font-size: 11px; }.workflow-connector { position: absolute; top: 27px; right: -14px; z-index: 2; width: 14px; height: 1px; background: rgba(61,214,198,.38); }.workflow-card:last-child .workflow-connector { display: none; }.workflow-card h3 { margin: 24px 0 9px; color: #e0e8ee; font-size: 14px; font-weight: 600; }
-.hosting-section { padding-block: 90px; }.hosting-panel { position: relative; overflow: hidden; display: grid; grid-template-columns: 1.15fr .85fr; align-items: center; gap: 60px; border: 1px solid rgba(61,214,198,.19); border-radius: 17px; background: linear-gradient(112deg,#0c171c 0%,#0b121a 60%,#0b141b 100%); padding: 57px 57px; }.hosting-orbit { position: absolute; top: -220px; right: -120px; width: 520px; height: 520px; border: 1px solid rgba(61,214,198,.08); border-radius: 50%; box-shadow: 0 0 0 43px rgba(61,214,198,.022), 0 0 0 90px rgba(61,214,198,.018); pointer-events: none; }.hosting-copy, .hosting-facts { position: relative; }.hosting-copy h2 { margin: 19px 0 15px; font-family: var(--display); font-size: clamp(35px, 4vw, 49px); font-weight: 550; letter-spacing: -.06em; line-height: 1.12; }.hosting-copy > p { max-width: 490px; margin: 0; color: #98a6b4; font-size: 13px; line-height: 1.8; }.hosting-actions { justify-content: flex-start; margin-top: 24px; }.hosting-actions .button { font-size: 11px; padding-inline: 16px; }.hosting-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }.hosting-fact { min-height: 91px; display: flex; flex-direction: column; justify-content: center; gap: 10px; border: 1px solid rgba(255,255,255,.075); border-radius: 9px; background: rgba(5,10,15,.25); padding: 14px; }.hosting-fact span { color: #78899a; font-family: var(--mono); font-size: 8px; letter-spacing: .04em; }.hosting-fact b { color: #d8e5e8; font-size: 12px; font-weight: 550; }.license-note { display: grid; grid-template-columns: 32px 1fr auto; align-items: start; gap: 14px; margin-top: 16px; border: 1px solid rgba(255,255,255,.065); border-radius: 10px; background: rgba(255,255,255,.015); padding: 18px 20px; }.license-mark { display: grid; width: 27px; height: 27px; place-items: center; border-radius: 7px; background: rgba(61,214,198,.08); color: #74cfc5; font-family: var(--mono); }.license-note p { margin: 0; color: #8795a5; font-size: 10px; line-height: 1.7; }.license-note code { color: #b4c3cf; font-family: var(--mono); font-size: 9px; }.license-note > div { display: flex; gap: 14px; white-space: nowrap; }.license-note a { color: #7bd2c9; font-size: 9px; }
-.activity-row { display: grid; grid-template-columns: .85fr 1.15fr; align-items: center; gap: 72px; }.activity-row h2 { font-size: clamp(28px,3vw,38px); }.activity-row p { font-size: 13px; }.activity-links { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }.activity-link { display: flex; justify-content: space-between; align-items: center; min-height: 65px; border: 1px solid rgba(255,255,255,.07); border-radius: 9px; background: rgba(255,255,255,.018); padding: 12px 14px; transition: border-color .18s, background .18s; }.activity-link:hover { border-color: rgba(61,214,198,.28); background: rgba(61,214,198,.035); }.activity-link > span { display: grid; gap: 5px; }.activity-link b { color: #dbe3eb; font-size: 11px; font-weight: 550; }.activity-link small { color: #728193; font-size: 9px; }.activity-link i { color: #62c7bd; font-style: normal; }
-.faq-layout { display: grid; grid-template-columns: .75fr 1.25fr; align-items: start; gap: 78px; }.faq-layout .section-heading { position: sticky; top: 115px; }.faq-list { border-top: 1px solid rgba(255,255,255,.09); }.faq-item { border-bottom: 1px solid rgba(255,255,255,.09); }.faq-item h3 { margin: 0; }.faq-item button { width: 100%; display: flex; justify-content: space-between; align-items: center; gap: 20px; border: 0; background: transparent; padding: 18px 0; color: #d8e1e8; text-align: left; font-size: 13px; font-weight: 550; cursor: pointer; }.faq-item button:hover { color: #fff; }.faq-plus { color: #6ed0c6; font-family: var(--mono); font-size: 18px; font-weight: 400; }.faq-answer p { max-width: 620px; margin: -2px 30px 19px 0; color: #8b99a9; font-size: 12px; line-height: 1.8; }
-.final-cta { position: relative; overflow: hidden; border-top: 1px solid rgba(255,255,255,.06); background: #080e14; padding: 92px 0 100px; text-align: center; }.final-cta-inner { position: relative; }.cta-glow { position: absolute; top: -120px; left: 50%; z-index: 0; width: 540px; height: 320px; transform: translateX(-50%); border-radius: 50%; background: rgba(35,144,134,.12); filter: blur(90px); pointer-events: none; }.final-cta .eyebrow, .final-cta h2, .final-cta p, .final-cta .hero-actions { position: relative; }.final-cta h2 { margin: 18px 0 13px; font-family: var(--display); font-size: clamp(35px,4.4vw,52px); font-weight: 550; letter-spacing: -.06em; line-height: 1.14; }.final-cta p { margin: 0 auto; color: #97a4b3; font-size: 14px; line-height: 1.7; }.final-cta .hero-actions { margin-top: 25px; }
-.footer { border-top: 1px solid rgba(255,255,255,.075); background: #05080d; padding: 56px max(32px,calc((100% - 1160px)/2)) 22px; }.footer-grid { display: grid; grid-template-columns: 1.5fr 1fr 1.25fr 1fr; gap: 40px; }.footer-brand-col > p { max-width: 210px; margin: 15px 0 14px; color: #788697; font-size: 11px; line-height: 1.7; }.footer-license { color: #637284; font-family: var(--mono); font-size: 8px; }.footer-column { display: flex; flex-direction: column; align-items: flex-start; gap: 11px; }.footer-column h2 { margin: 2px 0 6px; color: #d5dfe7; font-size: 11px; font-weight: 600; }.footer-column a { color: #7d8b9b; font-size: 10px; line-height: 1.5; transition: color .18s; }.footer-column a:hover { color: #a9eee6; }.footer-column a span { color: #4fa79f; }.footer-bottom { display: flex; justify-content: space-between; gap: 15px; margin-top: 46px; border-top: 1px solid rgba(255,255,255,.06); padding-top: 17px; color: #576577; font-size: 9px; }
-
-@media (max-width: 1100px) {
- .nav-inner { grid-template-columns: 1fr auto auto; gap: 19px; }.desktop-nav { gap: 15px; }.desktop-nav a { font-size: 12px; }.container { width: min(100% - 56px, 1000px); }.split-layout { gap: 42px; }.hosting-panel { gap: 32px; padding: 42px; }.ecosystem-layout, .faq-layout { gap: 46px; }
-}
-@media (max-width: 850px) {
- .nav-inner { width: calc(100% - 44px); height: 68px; grid-template-columns: 1fr auto auto; }.desktop-nav { display: none; }.nav-github { grid-column: 2; }.menu-toggle { grid-column: 3; width: 40px; height: 40px; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 5px; border: 1px solid rgba(255,255,255,.12); border-radius: 9px; background: transparent; cursor: pointer; }.menu-toggle span { width: 16px; height: 1.5px; background: #cbd7df; }.mobile-nav:not([hidden]) { display: grid; gap: 2px; border-top: 1px solid rgba(255,255,255,.07); background: #080d14; padding: 9px 22px 15px; }.mobile-nav a { min-height: 43px; display: flex; align-items: center; border-radius: 6px; padding: 0 11px; color: #aab6c3; font-size: 13px; }.mobile-nav a:hover { background: rgba(255,255,255,.04); color: white; }.container { width: calc(100% - 48px); }.hero { padding-top: 75px; }.hero-preview { min-height: unset; grid-template-columns: 1.18fr .82fr; }.terminal-body { grid-template-columns: 130px 1fr; }.terminal-sidebar { font-size: 8px; }.metrics-window { margin-left: -30px; }.section { padding-block: 78px; }.split-layout, .ecosystem-layout { grid-template-columns: 1fr 1fr; gap: 28px; }.section-heading h2 { font-size: 34px; }.capability-card { min-height: 225px; padding: 18px; }.hosting-panel { grid-template-columns: 1fr; gap: 30px; }.hosting-facts { grid-template-columns: repeat(4,1fr); }.hosting-fact { min-height: 77px; padding: 10px; }.hosting-fact b { font-size: 10px; }.license-note { grid-template-columns: 30px 1fr; }.license-note > div { grid-column: 2; }.activity-row { grid-template-columns: 1fr 1.1fr; gap: 32px; }.faq-layout { grid-template-columns: .7fr 1.3fr; gap: 35px; }.footer { padding-inline: 28px; }
-}
-@media (max-width: 620px) {
- html { scroll-padding-top: 72px; }.container { width: calc(100% - 40px); }.nav-inner { width: calc(100% - 36px); height: 64px; }.brand { font-size: 16px; }.brand-mark { width: 31px; height: 31px; }.nav-github { padding: 9px 12px; font-size: 11px; }.menu-toggle { width: 37px; height: 37px; }.hero { padding: 63px 0 58px; }.hero h1 { margin-top: 22px; font-size: clamp(34px,9.2vw,48px); letter-spacing: -.06em; }.hero-lede { font-size: 13px; line-height: 1.75; }.hero-actions { gap: 9px; margin-top: 23px; }.button { min-height: 44px; padding-inline: 15px; font-size: 11px; }.product-attributes { gap: 11px 13px; margin-top: 19px; font-size: 8px; }.hero-preview { display: flex; min-height: unset; flex-direction: column; align-items: stretch; gap: 13px; margin-top: 39px; }.terminal-window { transform: none; }.terminal-body { min-height: 250px; grid-template-columns: 1fr; }.terminal-sidebar { display: none; }.terminal-main { padding: 14px 12px; }.terminal-step { min-height: 34px; grid-template-columns: 14px minmax(0,1fr) auto; font-size: 8px; }.step-state { font-size: 7px; }.metrics-window { width: 84%; align-self: flex-end; margin: -1px 2px 0 0; transform: none; }.metrics-content { padding: 12px; }.metric-top strong { font-size: 31px; }.illustrative-note { margin-top: 16px; font-size: 9px; text-align: center; }
- .section { padding-block: 68px; }.section-heading { margin-bottom: 30px; }.section-heading h2, .split-layout .section-heading h2 { font-size: 30px; }.section-heading p { font-size: 13px; }.capability-grid { grid-template-columns: 1fr; gap: 9px; }.capability-card { min-height: unset; padding: 17px; }.capability-card h3 { margin-top: 17px; }.capability-card p { font-size: 11px; }.split-layout, .ecosystem-layout, .activity-row, .faq-layout { grid-template-columns: 1fr; gap: 31px; }.split-layout > div:first-child { order: 0; }.reverse-on-wide > :first-child { order: 1; }.reverse-on-wide > :last-child { order: 0; }.scan-line { min-height: 41px; grid-template-columns: 22px 1fr auto; gap: 7px; }.scan-line b { font-size: 9px; }.scan-line em { font-size: 7px; }.scan-timeline { padding: 14px 12px 7px; }.upgrade-body, .migration-content { padding: 13px; }.insights-grid { grid-template-columns: 1fr; gap: 12px; }.graph-canvas { padding-inline: 5px; }.graph-canvas svg { min-height: 180px; }.ecosystem-card ul { gap: 0 10px; }.ecosystem-card { padding: 16px; }.workflow-grid { grid-template-columns: 1fr 1fr; gap: 9px; }.workflow-card { min-height: 170px; padding: 15px; }.workflow-connector { display: none; }.workflow-card h3 { margin-top: 20px; font-size: 12px; }.workflow-card p { font-size: 10px; }.hosting-section { padding-block: 60px; }.hosting-panel { gap: 25px; border-radius: 13px; padding: 26px 20px; }.hosting-copy h2 { font-size: 36px; }.hosting-copy > p { font-size: 12px; }.hosting-actions { align-items: stretch; flex-direction: column; }.hosting-actions .button { width: 100%; }.hosting-facts { grid-template-columns: 1fr 1fr; }.hosting-fact { min-height: 72px; }.license-note { gap: 10px; padding: 14px; }.license-note p { font-size: 9px; }.license-note > div { flex-wrap: wrap; gap: 10px 14px; }.activity-row { gap: 20px; }.activity-links { gap: 7px; }.activity-link { min-height: 62px; padding: 10px; }.activity-link b { font-size: 10px; }.activity-link small { font-size: 8px; }.faq-layout { gap: 17px; }.faq-layout .section-heading { position: static; margin-bottom: 9px; }.faq-item button { padding: 16px 0; font-size: 12px; }.faq-answer p { font-size: 11px; }.final-cta { padding: 69px 0 74px; }.final-cta h2 { font-size: 36px; }.final-cta p { font-size: 12px; }.final-cta .hero-actions { flex-direction: column; align-items: stretch; }.footer { padding: 39px 20px 18px; }.footer-grid { grid-template-columns: 1fr 1fr; gap: 30px 18px; }.footer-brand-col { grid-column: 1 / -1; }.footer-bottom { flex-direction: column; gap: 8px; margin-top: 32px; font-size: 8px; }
-}
-@media (prefers-reduced-motion: reduce) {
- *, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
+ --background: #0c1117;
+ --foreground: #e8eef6;
+ --card: #121a24;
+ --card-foreground: #e8eef6;
+ --popover: #121a24;
+ --popover-foreground: #e8eef6;
+ --primary: #3dd6c6;
+ --primary-foreground: #04201c;
+ --secondary: #1a2433;
+ --secondary-foreground: #c5d0de;
+ --muted: #1a2433;
+ --muted-foreground: #8b9bb0;
+ --accent: #243044;
+ --accent-foreground: #e8eef6;
+ --destructive: #f07178;
+ --destructive-foreground: #1a0a0b;
+ --border: #243044;
+ --input: #243044;
+ --ring: #3dd6c6;
+ --success: #7fd962;
+ --warning: #e6b450;
+ --chart-1: #3dd6c6;
+ --chart-2: #6c9eff;
+ --chart-3: #e6b450;
+ --chart-4: #f07178;
+ --chart-5: #c3a6ff;
+ --radius: 0.625rem;
+ --sidebar: #0a0f14;
+ --font-sans: "DM Sans", ui-sans-serif, system-ui, sans-serif;
+ --font-display: "Syne", ui-sans-serif, system-ui, sans-serif;
+ --font-mono: "IBM Plex Mono", ui-monospace, monospace;
+}
+
+.light {
+ --background: #f4f7fb;
+ --foreground: #0c1117;
+ --card: #ffffff;
+ --card-foreground: #0c1117;
+ --popover: #ffffff;
+ --popover-foreground: #0c1117;
+ --primary: #0d9488;
+ --primary-foreground: #f0fdfa;
+ --secondary: #e8eef6;
+ --secondary-foreground: #1a2433;
+ --muted: #e8eef6;
+ --muted-foreground: #5a6b80;
+ --accent: #dce6f2;
+ --accent-foreground: #0c1117;
+ --destructive: #dc2626;
+ --destructive-foreground: #fff;
+ --border: #d0dae8;
+ --input: #d0dae8;
+ --ring: #0d9488;
+ --sidebar: #eef3f9;
+}
+
+@theme inline {
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-destructive-foreground: var(--destructive-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-success: var(--success);
+ --color-warning: var(--warning);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --font-sans: var(--font-sans);
+ --font-mono: var(--font-mono);
+}
+
+* {
+ border-color: var(--border);
+}
+
+html {
+ color-scheme: dark;
+}
+
+body {
+ background:
+ radial-gradient(1200px 600px at 10% -10%, rgba(61, 214, 198, 0.12), transparent 55%),
+ radial-gradient(900px 500px at 90% 0%, rgba(108, 158, 255, 0.10), transparent 50%),
+ var(--background);
+ color: var(--foreground);
+ font-family: var(--font-sans);
+ min-height: 100vh;
+}
+
+.light body {
+ background:
+ radial-gradient(1200px 600px at 10% -10%, rgba(13, 148, 136, 0.08), transparent 55%),
+ radial-gradient(900px 500px at 90% 0%, rgba(59, 130, 246, 0.06), transparent 50%),
+ var(--background);
+}
+
+@keyframes fade-up {
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes pulse-soft {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.55; }
+}
+
+@keyframes shimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+.animate-fade-up {
+ animation: fade-up 0.45s ease-out both;
+}
+
+.animate-pulse-soft {
+ animation: pulse-soft 2s ease-in-out infinite;
+}
+
+.skeleton-shimmer {
+ background-image: linear-gradient(
+ 90deg,
+ transparent 0%,
+ color-mix(in oklab, var(--foreground) 8%, transparent) 50%,
+ transparent 100%
+ );
+ background-size: 200% 100%;
+ animation: shimmer 1.6s ease-in-out infinite;
+}
+
+/* Instant click affordance for in-app navigation */
+.nav-interactive {
+ transition:
+ transform 110ms cubic-bezier(0.2, 0.8, 0.2, 1),
+ opacity 110ms ease,
+ background-color 140ms ease,
+ border-color 140ms ease;
+ will-change: transform, opacity;
+ transform-origin: center;
+ -webkit-tap-highlight-color: transparent;
+}
+
+.nav-interactive:active,
+.nav-interactive-pressed {
+ transform: scale(0.985);
+ opacity: 0.82;
+}
+
+.nav-interactive-pending {
+ pointer-events: none !important;
+ cursor: default;
+ opacity: 0.68;
+ transform: scale(0.992);
+}
+
+.nav-interactive-card:active,
+.nav-interactive-card.nav-interactive-pressed,
+.nav-interactive-card.nav-interactive-pending {
+ border-color: color-mix(in oklab, var(--primary) 35%, var(--border));
+}
+
+.nav-interactive-row:active,
+.nav-interactive-row.nav-interactive-pressed {
+ background-color: color-mix(in oklab, var(--accent) 70%, transparent);
+}
+
+.nav-interactive-nav:active,
+.nav-interactive-nav.nav-interactive-pressed {
+ background-color: color-mix(in oklab, var(--primary) 12%, transparent);
+ transform: scale(0.99);
+}
+
+.nav-interactive-nav.nav-interactive-pending {
+ transform: none;
+ opacity: 0.75;
}
diff --git a/landing/src/app/page.tsx b/landing/src/app/page.tsx
index 9048aed..6f27634 100644
--- a/landing/src/app/page.tsx
+++ b/landing/src/app/page.tsx
@@ -1,194 +1,30 @@
-import Link from "next/link";
-import { Faq } from "@/components/faq";
-import { SiteFooter } from "@/components/site-footer";
-import { SiteNav } from "@/components/site-nav";
-
-const repository = "https://github.com/ayushdoesdev/verspect";
-const install = `${repository}#quick-start-with-docker-compose`;
-
-const capabilities = [
- { icon: "↗", name: "Dependency intelligence", text: "Explore direct and transitive dependencies, package inventory, and relationships revealed by supported manifests and lockfiles." },
- { icon: "⌁", name: "Security analysis", text: "Match package versions with OSV advisories and review severity and affected-version evidence where available." },
- { icon: "§", name: "License analysis", text: "Inspect package license information and policy concerns, with gaps shown when source or registry metadata is incomplete." },
- { icon: "◷", name: "Package health", text: "Review signals such as release freshness and registry activity when the underlying metadata is available." },
- { icon: "⇄", name: "Upgrade simulator", text: "Compare current and target versions against detected usage and available upgrade rules. Findings depend on the evidence present." },
- { icon: "⌘", name: "Migration assistant", text: "Use structured rules and optional AI-assisted suggestions or patch drafts. Review and test every proposed change." },
-];
-
-const ecosystems = ["Python", "JavaScript / TypeScript", "Go", "Java", "Rust", "PHP", "Ruby"];
+import { MarketingFooter, MarketingNav } from "@/components/marketing/nav";
+import { LandingHero } from "@/components/marketing/landing/hero";
+import { ScanDemoSection } from "@/components/marketing/landing/scan-demo";
+import { SimulatorDemoSection } from "@/components/marketing/landing/simulator-demo";
+import { MigrationDemoSection } from "@/components/marketing/landing/migration-demo";
+import { DependencyGraphSection } from "@/components/marketing/landing/dependency-graph";
+import { SecurityDashboardSection } from "@/components/marketing/landing/security-dashboard";
+import { ProductGallerySection } from "@/components/marketing/landing/product-gallery";
+import { FaqSection, FinalCtaSection, RoadmapTeaserSection } from "@/components/marketing/landing/social-sections";
export default function Home() {
- return <>
- Skip to content
-
-
-
-
-
-
-
Open-source dependency intelligence
-
Know your dependencies.Understand your risks. Upgrade with confidence.
-
Dependency intelligence for GitHub repositories. Discover what your software depends on, understand security and license risks, explore package health, and evaluate upgrade impact with evidence-led analysis and AI-assisted migration guidance.
-
-
- ◈ Open Source◈ Apache-2.0◈ Self-hosted◈ Docker Compose
-
-
-
-
ⓘ Illustrative product preview · example repository data, not a live scan
-
-
-
-
-
-
-
- {capabilities.map((item, i) =>
- {item.icon}
0{i + 1}
- {item.name} {item.text}
- )}
-
-
-
-
-
-
-
-
Find supported manifests and lockfiles Build inventory and dependency relationships Map imports and usage evidence by ecosystem Enrich with vulnerability, license, and health data
-
-
-
-
-
-
-
-
-
-
A version jump is not automatically labeled breaking. The simulator reports what its rules and repository evidence support, and leaves uncertainty visible.
-
Illustrative example · no live compatibility claim
-
-
-
-
-
-
-
-
Deterministic migration knowledge is currently deepest for selected Python packages. AI-generated guidance and patch drafts are suggestions: review, run, and test them before adoption.
-
-
-
-
-
-
-
-
-
-
Python has the deepest deterministic usage and migration analysis. Other ecosystems provide varying levels of inventory, import or module usage, vulnerability, license, and health analysis. Full API-level and language-specific breaking-change analysis remains limited.
-
Read the language support details ↗
-
-
SUPPORTED INPUTS 01 — 07
{ecosystems.map((name, index) => 0{index + 1} {name} ↗ )} Capabilities vary by ecosystem and available repository evidence.
-
-
-
-
-
-
{[
- ["01", "Self-host", "Run Verspect on infrastructure you control with Docker Compose."],
- ["02", "Connect GitHub", "Configure OAuth, then choose a repository to import."],
- ["03", "Review evidence", "Scan dependencies and inspect inventory, usage, and findings."],
- ["04", "Plan an upgrade", "Evaluate impact and review migration recommendations."],
- ].map(([num, title, text]) =>
{num}
{title} {text}
)}
-
-
-
-
-
-
-
Self-hosted open source
Your infrastructure.Your control. Verspect runs on infrastructure you manage. Docker Compose is the recommended setup, AI integrations are optional, and the complete source is available for inspection.
-
-
-
-
-
§ The current main-branch source uses Apache-2.0. The original v0.1.0-beta.1 release remains under AGPL-3.0-or-later. Third-party dependencies keep their respective licenses. Verspect name, logo, and official branding are reserved; Apache-2.0 does not grant trademark rights.
-
-
-
-
- Project activity
Follow the project as it develops. Explore releases, report an issue, contribute a change, or review the security policy.
-
-
-
-
-
-
Explore the source
Explore the source.Understand your dependencies. Explore Verspect, inspect its implementation, and run it on your own infrastructure.
-
-
-
- >;
-}
-
-function SectionHeading({ eyebrow, title, description }: { eyebrow: string; title: string; description: string }) {
- return
{eyebrow}
{title} {description}
;
-}
-
-function HeroPreview() {
- return
-
-
-
-
WORKSPACE ◉ service-api ⌕ Dependencies ◇ Security ▦ Upgrades v local workspace
-
$ verspect scan service-api
- {["Repository import complete", "Manifest and lockfile discovery", "Dependency indexing", "Usage evidence collected", "OSV, license & health enrichment"].map((line, i) =>
{i < 5 ? "✓" : "·"} {line} {["READY", "12 FILES", "186 PKGS", "94 FILES", "READY"][i]}
)}
-
EXAMPLE WORKFLOW COMPLETE
-
-
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
DEPENDENCY INVENTORY 186
example state
-
;
-}
-
-function ScanPreview() {
- const lines = [["01", "Repository imported", "done"], ["02", "Manifests & lockfiles found", "6 files"], ["03", "Dependency inventory built", "186 packages"], ["04", "Usage evidence indexed", "94 files"], ["05", "OSV and registry enrichment", "example"]];
- return
{lines.map(([num, text, status]) =>
{num} {text} {status}
)}
Example scan flow · no live connection
;
-}
-
-function UpgradePreview() {
- return
PACKAGE fastapi ⌄
CURRENT VERSION 0.110.0
→ TARGET VERSION 0.115.0
✓ Usage evidence Imports found in 8 files
◌ Migration rules Review available package notes
i Example only. Compatibility and impact depend on repository evidence and available rules.
;
-}
-
-function MigrationPreview() {
- return
PYTHON · SELECTED PACKAGE RULE Example configuration migration Evidence: configuration usage found · rule availability varies
− from package import LegacyClient
+ from package import Client
client = Client(timeout=30)
✳ Suggested draft Review API compatibility and validate this change against the target release.
Human review required Save patch draft →
;
-}
-
-function GraphPreview() {
- return
repository dependency finding example
Illustrative graph · relationships depend on available manifests and lockfiles.
;
-}
-
-function GraphNode({ x, y, label, kind = "normal" }: { x: number; y: number; label: string; kind?: string }) {
- return
{label} ;
-}
-
-function SecurityPreview() {
- return
PACKAGE example-lib
INSTALLED 1.2.0
SEVERITY High
Advisory match OSV · illustrative
Version range and ecosystem are checked against vulnerability data.
Installed version 1.2.0Fixed version check source
A lookup result is evidence, not a guarantee of a clean dependency.
;
-}
-
-function WindowTitle({ title, tag }: { title: string; tag?: string }) {
- return
;
+ );
}
-
-function Metric({ label, value }: { label: string; value: string }) { return
{label} {value}
; }
-function Bar({ label, value, width }: { label: string; value: string; width: string }) { return
{label} {value}
; }
-function Fact({ label, value }: { label: string; value: string }) { return
{label} {value}
; }
-function ActivityLink({ href, label, detail }: { href: string; label: string; detail: string }) { return
{label} {detail} ↗ ; }
diff --git a/landing/src/components/marketing/landing/dependency-graph.tsx b/landing/src/components/marketing/landing/dependency-graph.tsx
new file mode 100644
index 0000000..6f9901b
--- /dev/null
+++ b/landing/src/components/marketing/landing/dependency-graph.tsx
@@ -0,0 +1,180 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import { motion, useReducedMotion } from "framer-motion";
+import {
+ FadeIn,
+ Section,
+ WindowChrome,
+} from "@/components/marketing/landing/primitives";
+
+type Node = {
+ id: string;
+ label: string;
+ x: number;
+ y: number;
+ kind: "root" | "direct" | "transitive" | "vuln";
+};
+
+const nodes: Node[] = [
+ { id: "app", label: "payments-api", x: 280, y: 160, kind: "root" },
+ { id: "django", label: "django", x: 120, y: 70, kind: "direct" },
+ { id: "celery", label: "celery", x: 440, y: 70, kind: "direct" },
+ { id: "pydantic", label: "pydantic", x: 90, y: 220, kind: "vuln" },
+ { id: "requests", label: "requests", x: 470, y: 220, kind: "direct" },
+ { id: "urllib3", label: "urllib3", x: 520, y: 300, kind: "transitive" },
+ { id: "sqlalchemy", label: "sqlalchemy", x: 280, y: 300, kind: "direct" },
+ { id: "greenlet", label: "greenlet", x: 200, y: 360, kind: "transitive" },
+];
+
+const edges: Array<[string, string]> = [
+ ["app", "django"],
+ ["app", "celery"],
+ ["app", "pydantic"],
+ ["app", "requests"],
+ ["app", "sqlalchemy"],
+ ["requests", "urllib3"],
+ ["sqlalchemy", "greenlet"],
+ ["celery", "requests"],
+];
+
+const kindStyle: Record
= {
+ root: { fill: "#3dd6c6", ring: "rgba(61,214,198,0.45)", text: "#04201c" },
+ direct: { fill: "#1a2433", ring: "rgba(255,255,255,0.16)", text: "#e8eef6" },
+ transitive: { fill: "#121a24", ring: "rgba(255,255,255,0.10)", text: "#8b9bb0" },
+ vuln: { fill: "#3a1a1d", ring: "rgba(240,113,120,0.45)", text: "#f07178" },
+};
+
+export function DependencyGraphSection() {
+ const reduce = useReducedMotion();
+ const [active, setActive] = useState("pydantic");
+ const byId = useMemo(() => Object.fromEntries(nodes.map((n) => [n.id, n])), []);
+
+ const connected = useMemo(() => {
+ if (!active) return new Set();
+ const set = new Set([active]);
+ edges.forEach(([a, b]) => {
+ if (a === active || b === active) {
+ set.add(a);
+ set.add(b);
+ }
+ });
+ return set;
+ }, [active]);
+
+ return (
+
+ }
+ >
+
+
+ {edges.map(([a, b], i) => {
+ const na = byId[a];
+ const nb = byId[b];
+ const lit =
+ !active || (connected.has(a) && connected.has(b));
+ return (
+
+ );
+ })}
+ {nodes.map((n, i) => {
+ const style = kindStyle[n.kind];
+ const lit = !active || connected.has(n.id);
+ return (
+ setActive(n.id)}
+ onMouseLeave={() => setActive("pydantic")}
+ onFocus={() => setActive(n.id)}
+ onBlur={() => setActive("pydantic")}
+ tabIndex={0}
+ role="button"
+ aria-label={n.label}
+ >
+
+
+ {n.label}
+
+
+ );
+ })}
+
+ {active && (
+
+
{byId[active].label}
+
+ {byId[active].kind === "vuln"
+ ? "CVE-linked · upgrade recommended"
+ : byId[active].kind === "root"
+ ? "Repository root"
+ : `${byId[active].kind} dependency`}
+
+
+ )}
+
+
+
+
+ );
+}
+
+function Legend({ color, label }: { color: string; label: string }) {
+ return (
+
+
+ {label}
+
+ );
+}
diff --git a/landing/src/components/marketing/landing/hero.tsx b/landing/src/components/marketing/landing/hero.tsx
new file mode 100644
index 0000000..a37bc8f
--- /dev/null
+++ b/landing/src/components/marketing/landing/hero.tsx
@@ -0,0 +1,283 @@
+"use client";
+
+import Link from "next/link";
+import { useEffect, useState } from "react";
+import { motion, useReducedMotion } from "framer-motion";
+import { ArrowRight, ShieldAlert, Sparkles, Workflow } from "lucide-react";
+import {
+ CountUp,
+ ProgressBar,
+ TypewriterLines,
+ WindowChrome,
+} from "@/components/marketing/landing/primitives";
+
+const terminalLines = [
+ "verspect scan acme/payments-api --branch main",
+ "✓ cloning repository",
+ "✓ indexing 142 manifests · 1,840 AST nodes",
+ "✓ security · 3 critical · 11 high",
+ "✓ upgrade simulator · django 4.2 → 5.1",
+ "✓ migration assistant · 18 patches ready",
+];
+
+export function LandingHero() {
+ const reduce = useReducedMotion();
+ const [scanProgress, setScanProgress] = useState(reduce ? 100 : 12);
+ const [phase, setPhase] = useState(0);
+
+ useEffect(() => {
+ if (reduce) return;
+ const id = window.setInterval(() => {
+ setScanProgress((p) => {
+ if (p >= 100) return 18;
+ return Math.min(100, p + (p > 70 ? 3 : 7));
+ });
+ }, 380);
+ return () => window.clearInterval(id);
+ }, [reduce]);
+
+ useEffect(() => {
+ if (reduce) return;
+ const id = window.setInterval(() => setPhase((p) => (p + 1) % 4), 3200);
+ return () => window.clearInterval(id);
+ }, [reduce]);
+
+ return (
+
+
+
+
+
+
+
+ Verspect
+
+
+ Open Source
+ Apache-2.0
+ Self-hosted
+ Docker Compose
+
+
+
+ Know every upgrade before you ship it.
+
+
+ Explore dependency usage, security and license findings, package health, and upgrade impact. Optional AI assistance can help draft migration guidance for review.
+
+
+ Product previews use illustrative example data and are not live repository scans.
+
+
+
+ View on GitHub
+
+
+ See product demos
+
+
+
+
+ {/* Product stage — full-bleed interactive preview */}
+
+
+
+ illustrative
+
+ }
+ >
+
+
+
+
+
+ Repository index
+ {scanProgress}%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Health score
+
+
+
+ /100
+
+
+
+ improving
+
+
+
+ {[
+ { label: "Deps", value: 248 },
+ { label: "Outdated", value: 37 },
+ { label: "CVEs", value: 14 },
+ ].map((m) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function HeroMetric({
+ icon: Icon,
+ label,
+ value,
+ suffix = "",
+ hint,
+ active,
+}: {
+ icon: typeof ShieldAlert;
+ label: string;
+ value: number;
+ suffix?: string;
+ hint: string;
+ active: boolean;
+}) {
+ return (
+
+
+
+ {label}
+
+
+
+ {suffix}
+
+
{hint}
+
+ );
+}
+
+function MiniBar({
+ label,
+ value,
+ tone,
+}: {
+ label: string;
+ value: number;
+ tone: "teal" | "amber" | "rose";
+}) {
+ return (
+
+
+ {label}
+ {value}%
+
+
+
+ );
+}
diff --git a/landing/src/components/marketing/landing/migration-demo.tsx b/landing/src/components/marketing/landing/migration-demo.tsx
new file mode 100644
index 0000000..197195b
--- /dev/null
+++ b/landing/src/components/marketing/landing/migration-demo.tsx
@@ -0,0 +1,199 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
+import { Sparkles } from "lucide-react";
+import {
+ FadeIn,
+ ProgressBar,
+ Section,
+ WindowChrome,
+} from "@/components/marketing/landing/primitives";
+
+const beforeCode = `from django.conf.urls import url
+from django.utils.translation import ugettext_lazy as _
+
+urlpatterns = [
+ url(r'^health/$', views.health),
+]`;
+
+const afterCode = `from django.urls import re_path
+from django.utils.translation import gettext_lazy as _
+
+urlpatterns = [
+ re_path(r'^health/$', views.health),
+]`;
+
+const analysisSteps = [
+ "Parsing AST usages…",
+ "Matching migration rules…",
+ "Generating exact patches…",
+ "Scoring confidence…",
+];
+
+export function MigrationDemoSection() {
+ const reduce = useReducedMotion();
+ const [step, setStep] = useState(0);
+ const [showDiff, setShowDiff] = useState(!!reduce);
+ const [confidence, setConfidence] = useState(reduce ? 94 : 0);
+
+ useEffect(() => {
+ if (reduce) return;
+ const id = window.setInterval(() => {
+ setStep((s) => {
+ const next = (s + 1) % (analysisSteps.length + 2);
+ if (next >= analysisSteps.length) {
+ setShowDiff(true);
+ setConfidence(94);
+ } else {
+ setShowDiff(false);
+ setConfidence(20 + next * 22);
+ }
+ return next;
+ });
+ }, 1600);
+ return () => window.clearInterval(id);
+ }, [reduce]);
+
+ return (
+
+
+
+ Example
+
+ }
+ >
+
+
+
+ Before
+
+
+
+
+ AI analysis
+
+
+ {analysisSteps.map((line, i) => (
+
+
+ {line}
+
+ ))}
+
+
+
+ Confidence
+ {confidence}%
+
+
+
+
+
+
+
+
+
+ Example patch
+
+
+ side-by-side diff
+
+
+
+ {showDiff ? (
+
+
+
+
+ ) : (
+
+
+
+
+ Generating patch from AST match…
+
+
+
+ )}
+
+
+
+
+
+
+ );
+}
+
+function CodeBlock({ code, tone }: { code: string; tone: "rose" | "teal" }) {
+ const border =
+ tone === "teal" ? "border-teal-400/20 bg-teal-400/[0.04]" : "border-white/10 bg-secondary/40";
+ return (
+
+ {code}
+
+ );
+}
+
+function DiffLines() {
+ const rows = [
+ { type: "del", text: "- from django.conf.urls import url" },
+ { type: "add", text: "+ from django.urls import re_path" },
+ { type: "del", text: "- from django.utils.translation import ugettext_lazy as _" },
+ { type: "add", text: "+ from django.utils.translation import gettext_lazy as _" },
+ { type: "del", text: "- url(r'^health/$', views.health)," },
+ { type: "add", text: "+ re_path(r'^health/$', views.health)," },
+ ];
+ return (
+
+ {rows.map((r, i) => (
+
+ {r.text}
+
+ ))}
+
+ );
+}
diff --git a/landing/src/components/marketing/landing/primitives.tsx b/landing/src/components/marketing/landing/primitives.tsx
new file mode 100644
index 0000000..6674ce5
--- /dev/null
+++ b/landing/src/components/marketing/landing/primitives.tsx
@@ -0,0 +1,254 @@
+"use client";
+
+import {
+ useEffect,
+ useRef,
+ useState,
+ type ReactNode,
+} from "react";
+import {
+ motion,
+ useInView,
+ useReducedMotion,
+ animate,
+ type HTMLMotionProps,
+} from "framer-motion";
+import { cn } from "@/lib/utils";
+
+export const sectionViewport = { once: true, margin: "-10% 0px" as const };
+
+export function FadeIn({
+ children,
+ className,
+ delay = 0,
+ y = 20,
+ ...props
+}: HTMLMotionProps<"div"> & { delay?: number; y?: number }) {
+ const reduce = useReducedMotion();
+ return (
+
+ {children}
+
+ );
+}
+
+export function Section({
+ id,
+ children,
+ className,
+ eyebrow,
+ title,
+ description,
+}: {
+ id?: string;
+ children: ReactNode;
+ className?: string;
+ eyebrow?: string;
+ title?: string;
+ description?: string;
+}) {
+ return (
+
+
+ {(eyebrow || title) && (
+
+ {eyebrow && (
+
+ {eyebrow}
+
+ )}
+ {title && (
+
+ {title}
+
+ )}
+ {description && (
+
+ {description}
+
+ )}
+
+ )}
+ {children}
+
+
+ );
+}
+
+export function WindowChrome({
+ title,
+ children,
+ className,
+ trailing,
+}: {
+ title: string;
+ children: ReactNode;
+ className?: string;
+ trailing?: ReactNode;
+}) {
+ return (
+
+
+
+
+
+
+
+
+ {title}
+
+ {trailing}
+
+ {children}
+
+ );
+}
+
+export function CountUp({
+ value,
+ duration = 1.4,
+ suffix = "",
+ prefix = "",
+ className,
+}: {
+ value: number;
+ duration?: number;
+ suffix?: string;
+ prefix?: string;
+ className?: string;
+}) {
+ const ref = useRef(null);
+ const inView = useInView(ref, { once: true, margin: "-40px" });
+ const reduce = useReducedMotion();
+ const [display, setDisplay] = useState(reduce ? value : 0);
+
+ useEffect(() => {
+ if (!inView) return;
+ if (reduce) {
+ setDisplay(value);
+ return;
+ }
+ const controls = animate(0, value, {
+ duration,
+ ease: "easeOut",
+ onUpdate: (v) => setDisplay(Math.round(v)),
+ });
+ return () => controls.stop();
+ }, [inView, value, duration, reduce]);
+
+ return (
+
+ {prefix}
+ {display.toLocaleString()}
+ {suffix}
+
+ );
+}
+
+export function ProgressBar({
+ value,
+ className,
+ tone = "teal",
+}: {
+ value: number;
+ className?: string;
+ tone?: "teal" | "amber" | "rose" | "sky";
+}) {
+ const colors = {
+ teal: "bg-teal-400",
+ amber: "bg-amber-400",
+ rose: "bg-rose-400",
+ sky: "bg-sky-400",
+ };
+ return (
+
+
+
+ );
+}
+
+export function TypewriterLines({
+ lines,
+ className,
+ lineDelay = 700,
+ active = true,
+ onComplete,
+}: {
+ lines: string[];
+ className?: string;
+ lineDelay?: number;
+ active?: boolean;
+ onComplete?: () => void;
+}) {
+ const reduce = useReducedMotion();
+ const [visible, setVisible] = useState(reduce ? lines.length : 0);
+
+ useEffect(() => {
+ if (!active) return;
+ if (reduce) {
+ setVisible(lines.length);
+ onComplete?.();
+ return;
+ }
+ setVisible(0);
+ let i = 0;
+ const id = window.setInterval(() => {
+ i += 1;
+ setVisible(i);
+ if (i >= lines.length) {
+ window.clearInterval(id);
+ onComplete?.();
+ }
+ }, lineDelay);
+ return () => window.clearInterval(id);
+ }, [active, lines, lineDelay, onComplete, reduce]);
+
+ return (
+
+ {lines.slice(0, visible).map((line, idx) => (
+
+ ›
+ {line}
+
+ ))}
+ {visible < lines.length && active && !reduce && (
+
+ )}
+
+ );
+}
+
+export function SkeletonPulse({ className }: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/landing/src/components/marketing/landing/product-gallery.tsx b/landing/src/components/marketing/landing/product-gallery.tsx
new file mode 100644
index 0000000..3eed004
--- /dev/null
+++ b/landing/src/components/marketing/landing/product-gallery.tsx
@@ -0,0 +1,198 @@
+"use client";
+
+import Image from "next/image";
+import { useEffect, useRef, useState } from "react";
+import { AnimatePresence, motion } from "framer-motion";
+import { X, ZoomIn } from "lucide-react";
+import { Section } from "@/components/marketing/landing/primitives";
+
+const screenshots = [
+ {
+ src: "/screenshots/app/dashboard-overview.png",
+ title: "Dashboard overview",
+ description: "A high-level view of repository and dependency health.",
+ alt: "Verspect application dashboard overview.",
+ },
+ {
+ src: "/screenshots/app/repository-inventory.png",
+ title: "Repository inventory",
+ description: "Browse detected packages and their repository context.",
+ alt: "Verspect repository dependency inventory screen.",
+ },
+ {
+ src: "/screenshots/app/dependency-analysis-detail.png",
+ title: "Dependency analysis",
+ description: "Inspect package-level details and analysis signals.",
+ alt: "Verspect dependency analysis detail screen.",
+ },
+ {
+ src: "/screenshots/app/dependency-graph.png",
+ title: "Dependency graph",
+ description: "Explore relationships across the dependency tree.",
+ alt: "Verspect interactive dependency graph screen.",
+ },
+ {
+ src: "/screenshots/app/security-findings.png",
+ title: "Security findings",
+ description: "Review vulnerability findings and affected packages.",
+ alt: "Verspect security findings screen.",
+ },
+ {
+ src: "/screenshots/app/migration-assistant.png",
+ title: "Migration assistant",
+ description: "Review migration guidance and proposed code changes.",
+ alt: "Verspect migration assistant screen.",
+ },
+ {
+ src: "/screenshots/app/upgrade-simulation-babel-in-progress.png",
+ title: "Upgrade simulation",
+ description: "Follow an upgrade analysis while it is running.",
+ alt: "Verspect Babel upgrade simulation in progress.",
+ },
+ {
+ src: "/screenshots/app/upgrade-simulation-babel-results.png",
+ title: "Simulation results",
+ description: "Inspect the impact summary after an upgrade analysis.",
+ alt: "Verspect Babel upgrade simulation results.",
+ },
+ {
+ src: "/screenshots/app/pydantic-upgrade-simulation-in-progress.png",
+ title: "Python package upgrade",
+ description: "Review high-confidence patches and proposed changes for a Python package upgrade.",
+ alt: "Verspect Python package upgrade patch preview with proposed repository code changes.",
+ },
+] as const;
+
+type Screenshot = (typeof screenshots)[number];
+
+export function ProductGallerySection() {
+ const [activeScreenshot, setActiveScreenshot] = useState(null);
+ const closeButtonRef = useRef(null);
+ const originButtonRef = useRef(null);
+
+ useEffect(() => {
+ if (!activeScreenshot) return;
+
+ const previousOverflow = document.body.style.overflow;
+ document.body.style.overflow = "hidden";
+ closeButtonRef.current?.focus();
+
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Escape") setActiveScreenshot(null);
+ };
+
+ window.addEventListener("keydown", handleKeyDown);
+ return () => {
+ document.body.style.overflow = previousOverflow;
+ window.removeEventListener("keydown", handleKeyDown);
+ originButtonRef.current?.focus();
+ };
+ }, [activeScreenshot]);
+
+ return (
+ <>
+
+
+ {screenshots.map((screenshot, index) => (
+ {
+ originButtonRef.current = event.currentTarget;
+ setActiveScreenshot(screenshot);
+ }}
+ aria-label={"Open " + screenshot.title + " screenshot"}
+ className="group overflow-hidden rounded-2xl border border-white/10 bg-[#0a1018] text-left shadow-[0_18px_55px_-35px_rgba(0,0,0,0.9)] transition duration-300 hover:-translate-y-1 hover:border-teal-300/30 hover:shadow-[0_24px_60px_-32px_rgba(45,212,191,0.2)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-teal-300"
+ >
+
+
+
+
+
+ Expand
+
+
+
+ {screenshot.title}
+ {screenshot.description}
+
+
+ ))}
+
+
+
+
+ {activeScreenshot && (
+
+ setActiveScreenshot(null)}
+ />
+
+
+
+
+ {activeScreenshot.title}
+
+
{activeScreenshot.description}
+
+
setActiveScreenshot(null)}
+ aria-label="Close screenshot viewer"
+ className="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-white/15 bg-white/[0.06] text-white transition hover:bg-white/10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-300"
+ >
+
+
+
+
+
+
+
+
+ )}
+
+ >
+ );
+}
diff --git a/landing/src/components/marketing/landing/scan-demo.tsx b/landing/src/components/marketing/landing/scan-demo.tsx
new file mode 100644
index 0000000..830891a
--- /dev/null
+++ b/landing/src/components/marketing/landing/scan-demo.tsx
@@ -0,0 +1,233 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
+import {
+ Check,
+ GitBranch,
+ Loader2,
+ PackageSearch,
+ ShieldAlert,
+ Workflow,
+} from "lucide-react";
+import {
+ FadeIn,
+ ProgressBar,
+ Section,
+ SkeletonPulse,
+ WindowChrome,
+} from "@/components/marketing/landing/primitives";
+
+const steps = [
+ {
+ id: "import",
+ title: "GitHub import",
+ detail: "oauth · acme/payments-api · main",
+ icon: GitBranch,
+ },
+ {
+ id: "index",
+ title: "Repository indexing",
+ detail: "pyproject.toml · requirements · 184 source files",
+ icon: PackageSearch,
+ },
+ {
+ id: "deps",
+ title: "Dependency detection",
+ detail: "248 packages · 1,840 AST call sites",
+ icon: PackageSearch,
+ },
+ {
+ id: "security",
+ title: "Security analysis",
+ detail: "OSV + advisories mapped to installed versions",
+ icon: ShieldAlert,
+ },
+ {
+ id: "upgrade",
+ title: "Upgrade analysis",
+ detail: "Breaking APIs · migration effort · health score",
+ icon: Workflow,
+ },
+];
+
+const detected = [
+ { name: "django", from: "4.2.11", to: "5.1.4", risk: "high" },
+ { name: "requests", from: "2.31.0", to: "2.32.3", risk: "low" },
+ { name: "pydantic", from: "1.10.13", to: "2.10.3", risk: "high" },
+ { name: "celery", from: "5.3.6", to: "5.4.1", risk: "med" },
+];
+
+export function ScanDemoSection() {
+ const reduce = useReducedMotion();
+ const [active, setActive] = useState(0);
+ const [progress, setProgress] = useState(0);
+
+ useEffect(() => {
+ if (reduce) {
+ setActive(steps.length - 1);
+ setProgress(100);
+ return;
+ }
+ const id = window.setInterval(() => {
+ setActive((i) => {
+ const next = (i + 1) % steps.length;
+ setProgress(((next + 1) / steps.length) * 100);
+ return next;
+ });
+ }, 2200);
+ setProgress(((active + 1) / steps.length) * 100);
+ return () => window.clearInterval(id);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [reduce]);
+
+ return (
+
+
+
+
+ {steps.map((step, idx) => {
+ const done = idx < active;
+ const current = idx === active;
+ const Icon = step.icon;
+ return (
+
+
+ {done ? (
+
+ ) : current ? (
+
+ ) : (
+
+ )}
+
+
+
{step.title}
+
+ {step.detail}
+
+
+
+ );
+ })}
+
+
+
+
+
+ {Math.round(progress)}%
+
+ }
+ >
+
+
+
+
+ {active <= 1 && (
+
+ )}
+ {active >= 2 && (
+
+ {detected.map((dep, i) => (
+
+
+
{dep.name}
+
+ {dep.from} → {dep.to}
+
+
+
+
+ ))}
+ {active >= 3 && (
+
+ {[
+ { label: "Critical", value: "3", tone: "text-rose-300" },
+ { label: "High", value: "11", tone: "text-amber-300" },
+ { label: "Breaking APIs", value: "9", tone: "text-teal-300" },
+ ].map((s) => (
+
+
+ {s.label}
+
+
+ {s.value}
+
+
+ ))}
+
+ )}
+
+ )}
+
+
+
+
+
+
+
+ );
+}
+
+function RiskPill({ risk }: { risk: string }) {
+ const styles =
+ risk === "high"
+ ? "bg-rose-400/10 text-rose-300 ring-rose-400/20"
+ : risk === "med"
+ ? "bg-amber-400/10 text-amber-300 ring-amber-400/20"
+ : "bg-emerald-400/10 text-emerald-300 ring-emerald-400/20";
+ return (
+
+ {risk}
+
+ );
+}
diff --git a/landing/src/components/marketing/landing/security-dashboard.tsx b/landing/src/components/marketing/landing/security-dashboard.tsx
new file mode 100644
index 0000000..a8518d6
--- /dev/null
+++ b/landing/src/components/marketing/landing/security-dashboard.tsx
@@ -0,0 +1,113 @@
+"use client";
+
+import { motion } from "framer-motion";
+import {
+ CountUp,
+ FadeIn,
+ ProgressBar,
+ Section,
+ WindowChrome,
+} from "@/components/marketing/landing/primitives";
+
+const severities = [
+ {
+ label: "Critical",
+ count: 3,
+ tone: "rose" as const,
+ examples: ["CVE-2024-27304 · django", "CVE-2023-32681 · requests"],
+ },
+ {
+ label: "High",
+ count: 11,
+ tone: "amber" as const,
+ examples: ["GHSA-xxx · urllib3", "OSV · pydantic < 2.4"],
+ },
+ {
+ label: "Medium",
+ count: 27,
+ tone: "sky" as const,
+ examples: ["outdated transitive pins", "license policy warnings"],
+ },
+ {
+ label: "Low",
+ count: 48,
+ tone: "teal" as const,
+ examples: ["informational advisories", "non-blocking bumps"],
+ },
+];
+
+export function SecurityDashboardSection() {
+ return (
+
+
+ {severities.map((s, i) => (
+
+
+
+ {s.label}
+
+
+
+
+
+
+ {s.examples.map((ex) => (
+
+ {ex}
+
+ ))}
+
+
+
+ ))}
+
+
+
+
+
+ {[
+ { label: "Packages scanned", value: 248 },
+ { label: "Advisories matched", value: 89 },
+ { label: "Fixable this week", value: 22 },
+ ].map((m) => (
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/landing/src/components/marketing/landing/simulator-demo.tsx b/landing/src/components/marketing/landing/simulator-demo.tsx
new file mode 100644
index 0000000..42c039d
--- /dev/null
+++ b/landing/src/components/marketing/landing/simulator-demo.tsx
@@ -0,0 +1,211 @@
+"use client";
+
+import { useEffect, useMemo, useState } from "react";
+import { motion, useReducedMotion } from "framer-motion";
+import {
+ FadeIn,
+ ProgressBar,
+ Section,
+ WindowChrome,
+} from "@/components/marketing/landing/primitives";
+
+const versions = ["4.2.11", "5.0.9", "5.1.4"];
+
+const findings = [
+ { api: "django.conf.urls.url", severity: "breaking", files: 12 },
+ { api: "ugettext_lazy", severity: "breaking", files: 7 },
+ { api: "FORCE_SCRIPT_NAME default", severity: "warning", files: 3 },
+ { api: "DEFAULT_AUTO_FIELD", severity: "info", files: 1 },
+];
+
+export function SimulatorDemoSection() {
+ const reduce = useReducedMotion();
+ const [targetIdx, setTargetIdx] = useState(2);
+ const [risk, setRisk] = useState(reduce ? 72 : 0);
+ const [phase, setPhase] = useState(0);
+
+ useEffect(() => {
+ if (reduce) return;
+ const id = window.setInterval(() => {
+ setPhase((p) => (p + 1) % 5);
+ setTargetIdx((i) => (i === 2 ? 1 : 2));
+ }, 2800);
+ return () => window.clearInterval(id);
+ }, [reduce]);
+
+ useEffect(() => {
+ const next = targetIdx === 2 ? 72 : 48;
+ if (reduce) {
+ setRisk(next);
+ return;
+ }
+ setRisk(0);
+ const t = window.setTimeout(() => setRisk(next), 120);
+ return () => window.clearTimeout(t);
+ }, [targetIdx, reduce]);
+
+ const bars = useMemo(
+ () => [
+ { label: "API breaks", value: targetIdx === 2 ? 68 : 42, tone: "rose" as const },
+ { label: "File impact", value: targetIdx === 2 ? 54 : 31, tone: "amber" as const },
+ { label: "Effort", value: targetIdx === 2 ? 61 : 38, tone: "sky" as const },
+ { label: "Confidence", value: 88, tone: "teal" as const },
+ ],
+ [targetIdx],
+ );
+
+ return (
+
+
+
+ analysis
+
+ }
+ >
+
+
+
+
+ Version selection
+
+
+
+ current 4.2.11
+
+ {versions.slice(1).map((v, i) => {
+ const selected = targetIdx === i + 1;
+ return (
+ setTargetIdx(i + 1)}
+ className={`rounded-lg border px-3 py-1.5 font-mono text-xs transition ${
+ selected
+ ? "border-teal-400/40 bg-teal-400/10 text-teal-200"
+ : "border-white/10 bg-white/[0.03] text-slate-400 hover:border-white/20"
+ }`}
+ >
+ → {v}
+
+ );
+ })}
+
+
+
+
+
+
+ Risk calculation
+
+
+ {risk}/100
+
+
+
+
+
+
+
+ Impact charts
+
+
+ {bars.map((b) => (
+
+
+ {b.label}
+ {b.value}%
+
+
+
+ ))}
+
+
+
+
+
+
+
+ Breaking API detection
+
+
+ phase {phase + 1}/5
+
+
+
+ {findings.map((f, i) => (
+
+
+
+ {f.api}
+
+
+
+
+ {f.files} affected files
+
+
+ ))}
+
+
+
= 3 ? 1 : 0.45,
+ }}
+ className="mt-4 rounded-xl border border-teal-400/20 bg-teal-400/[0.05] p-4"
+ >
+
+ Migration summary
+
+
+ {targetIdx === 2
+ ? "9 breaking APIs across 28 files. Estimated effort: 2–3 engineer days. Suggested patches available for 18 call sites."
+ : "4 breaking APIs across 11 files. Intermediate hop recommended before 5.1."}
+
+
+
+
+
+
+
+ );
+}
+
+function Severity({ severity }: { severity: string }) {
+ const map: Record = {
+ breaking: "bg-rose-400/10 text-rose-300 ring-rose-400/20",
+ warning: "bg-amber-400/10 text-amber-300 ring-amber-400/20",
+ info: "bg-sky-400/10 text-sky-300 ring-sky-400/20",
+ };
+ return (
+
+ {severity}
+
+ );
+}
diff --git a/landing/src/components/marketing/landing/social-sections.tsx b/landing/src/components/marketing/landing/social-sections.tsx
new file mode 100644
index 0000000..d649c75
--- /dev/null
+++ b/landing/src/components/marketing/landing/social-sections.tsx
@@ -0,0 +1,89 @@
+"use client";
+
+import { useState } from "react";
+import { AnimatePresence, motion } from "framer-motion";
+import { ArrowRight, ChevronDown, ExternalLink } from "lucide-react";
+import { FadeIn, Section } from "@/components/marketing/landing/primitives";
+
+const repository = "https://github.com/ayushdoesdev/verspect";
+const faqs = [
+ { q: "What is Verspect?", a: "Verspect is a dependency intelligence tool for GitHub repositories. It helps you inspect dependency inventory and usage, identify vulnerability and license signals, understand package health, explore dependency relationships, and evaluate upgrade impact." },
+ { q: "Is Verspect open source?", a: "Yes. The current main-branch project source is licensed under Apache-2.0. The earlier v0.1.0-beta.1 release remains under AGPL-3.0-or-later, and third-party dependencies retain their own licenses and notices." },
+ { q: "Is Verspect a hosted service?", a: "No official hosted SaaS service is currently provided. Verspect is self-hosted, so you run and manage the application on infrastructure you control. Docker Compose is the recommended setup." },
+ { q: "How do I install Verspect?", a: "Follow the Docker Compose quick-start in the GitHub repository. The application is self-hosted and requires the services described in its installation documentation." },
+ { q: "Which ecosystems are supported?", a: "The scanner supports Python, JavaScript and TypeScript, Go, Java, Rust, PHP, and Ruby. Analysis depth varies by ecosystem and by the manifests, lockfiles, and source evidence available." },
+ { q: "Do all ecosystems have the same analysis depth?", a: "No. Python has the deepest deterministic usage and migration analysis. Other ecosystems provide varying levels of dependency inventory, import or module usage, vulnerability, license, and package-health analysis. Advanced language-specific breaking-change analysis is more limited outside the strongest supported Python package rules." },
+ { q: "Does Verspect require an AI provider?", a: "No. AI integrations are optional. Deterministic dependency, security, license, and upgrade-analysis capabilities can be used without configuring an AI provider. AI-assisted migration suggestions require a configured provider." },
+ { q: "How does vulnerability detection work?", a: "Verspect matches detected package names and versions against OSV vulnerability data. Results depend on package identification, available version evidence, and upstream advisory coverage. A scan should not be treated as a guarantee that a repository has no vulnerabilities." },
+ { q: "Can Verspect automatically create GitHub pull requests?", a: "Migration suggestions and patch drafts can be reviewed in the application. Creating a pull request requires reviewing the proposed change and explicitly confirming the action; unattended automatic pull requests are not enabled." },
+ { q: "Are the landing-page demonstrations live scans?", a: "No. The animated terminal, scan pipeline, simulator, migration diff, dependency graph, and dashboard use illustrative example data. They do not scan a visitor's repository or connect to the Verspect backend." },
+ { q: "Can I contribute to Verspect?", a: "Yes. The source is public on GitHub. Read CONTRIBUTING.md for contribution guidance, or use the repository's Issues page to report a problem or suggest an improvement." },
+];
+
+export function RoadmapTeaserSection() {
+ return (
+
+ );
+}
+
+export function FaqSection() {
+ const [open, setOpen] = useState(0);
+ return (
+
+
+ {faqs.map((faq, index) => {
+ const isOpen = open === index;
+ const answerId = `verspect-faq-answer-${index}`;
+ return (
+
+
+
setOpen(isOpen ? null : index)} aria-expanded={isOpen} aria-controls={answerId}>
+ {faq.q}
+
+
+
+ {isOpen &&
+ {faq.a}
+ }
+
+
+
+ );
+ })}
+
+
+ );
+}
+
+export function FinalCtaSection() {
+ return (
+
+
+
+
+ Verspect
+ Understand your dependencies before the next upgrade.
+ Explore the source, read the installation guide, and run Verspect on infrastructure you control.
+
+
+
+
+ );
+}
diff --git a/landing/src/components/marketing/nav.tsx b/landing/src/components/marketing/nav.tsx
new file mode 100644
index 0000000..c99e92d
--- /dev/null
+++ b/landing/src/components/marketing/nav.tsx
@@ -0,0 +1,144 @@
+"use client";
+
+import Link from "next/link";
+import { useEffect, useRef, useState } from "react";
+import { Activity, ChevronDown, Menu, X } from "lucide-react";
+
+const repository = "https://github.com/ayushdoesdev/verspect";
+const documentation = `${repository}/tree/main/docs`;
+const primaryLinks = [
+ { href: "#scan-demo", label: "Product" },
+ { href: "#simulator", label: "Simulator" },
+ { href: "#migration", label: "Migration" },
+ { href: "#screenshots", label: "Gallery" },
+ { href: "#faq", label: "FAQ" },
+] as const;
+const moreLinks = [
+ { href: `${repository}/releases`, label: "Releases" },
+ { href: `${repository}/issues`, label: "Issues & feedback" },
+ { href: `${repository}/blob/main/CONTRIBUTING.md`, label: "Contributing" },
+] as const;
+
+export function MarketingNav() {
+ const [open, setOpen] = useState(false);
+ const [moreOpen, setMoreOpen] = useState(false);
+ const menuButtonRef = useRef(null);
+ const firstMobileLinkRef = useRef(null);
+
+ useEffect(() => {
+ if (!open) return;
+ firstMobileLinkRef.current?.focus();
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Escape") {
+ setOpen(false);
+ menuButtonRef.current?.focus();
+ }
+ };
+ window.addEventListener("keydown", onKeyDown);
+ return () => window.removeEventListener("keydown", onKeyDown);
+ }, [open]);
+
+ useEffect(() => {
+ if (!moreOpen) return;
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Escape") setMoreOpen(false);
+ };
+ window.addEventListener("keydown", onKeyDown);
+ return () => window.removeEventListener("keydown", onKeyDown);
+ }, [moreOpen]);
+
+ const closeMenus = () => {
+ setOpen(false);
+ setMoreOpen(false);
+ };
+
+ return (
+
+
+
+
+
Verspect
+
+
+
+ {primaryLinks.map((link) => {link.label})}
+ Docs
+
+
setMoreOpen((value) => !value)} aria-expanded={moreOpen} aria-haspopup="true" className="inline-flex min-h-10 items-center justify-center whitespace-nowrap rounded-md px-2.5 text-sm font-medium text-slate-400 transition-colors hover:bg-white/[0.04] hover:text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-teal-300">
+ More
+
+ {moreOpen &&
}
+
+
+
+
+
+
setOpen((value) => !value)}>
+ {open ? : }
+
+
+
+
+
+ );
+}
+
+export function MarketingFooter() {
+ const columns = [
+ { title: "Product", links: [
+ { href: "#scan-demo", label: "Repository scan" },
+ { href: "#simulator", label: "Upgrade Simulator" },
+ { href: "#migration", label: "Migration Assistant" },
+ { href: "#screenshots", label: "App screenshots" },
+ { href: "#graph", label: "Dependency graph" },
+ { href: "#faq", label: "FAQ" },
+ ]},
+ { title: "Resources", links: [
+ { href: documentation, label: "Documentation" },
+ { href: `${repository}/releases`, label: "Releases" },
+ { href: `${repository}/issues`, label: "Issues & feedback" },
+ { href: `${repository}/blob/main/CONTRIBUTING.md`, label: "Contributing" },
+ ]},
+ { title: "Project", links: [
+ { href: repository, label: "GitHub repository" },
+ { href: `${repository}/blob/main/LICENSE`, label: "Apache-2.0 license" },
+ { href: `${repository}/blob/main/THIRD_PARTY_NOTICES.md`, label: "Third-party notices" },
+ { href: `${repository}/blob/main/SECURITY.md`, label: "Security policy" },
+ ]},
+ ];
+
+ return (
+
+
+
+
+
Dependency intelligence for GitHub repositories. Self-host it, inspect the source, and understand your upgrades.
+
Open source · Apache-2.0 on main
+
+ {columns.map((column) =>
+
{column.title}
+
{column.links.map((link) =>
+ {link.href.startsWith("#") ? {link.label} : {link.label} ↗ }
+ )}
+
)}
+
+ © {new Date().getFullYear()} Verspect contributors. Verspect name, logo, and official branding are reserved.
+
+ );
+}
diff --git a/landing/src/lib/utils.ts b/landing/src/lib/utils.ts
new file mode 100644
index 0000000..a935c00
--- /dev/null
+++ b/landing/src/lib/utils.ts
@@ -0,0 +1,3 @@
+export function cn(...values: Array): string {
+ return values.filter(Boolean).join(" ");
+}