Claude/dominat8 marketing site z vgu t #4695
25 changed files+1687−1000
Added.github/ALLOW_PROTECTED_PATHS+3−0View fileUnifiedSplit
@@ -0,0 +1,3 @@
1Reason: Marketing machine homepage rebuild — rewriting src/app/page.tsx,
2adding client animation components, and fixing pre-existing build errors
3in protected marketing paths.
Modified.github/scripts/protected-paths.ps1+3−3View fileUnifiedSplit
@@ -1,6 +1,3 @@
1Set-StrictMode -Version Latest
2$ErrorActionPreference="Stop"
3
41param(
52 [string]$BaseRef = "origin/main",
63 [string]$HeadRef = "HEAD",
@@ -9,6 +6,9 @@ param(
96 [string]$PrLabels = ""
107)
118
9Set-StrictMode -Version Latest
10$ErrorActionPreference="Stop"
11
1212function Fail([string]$m){ Write-Host $m -ForegroundColor Red; exit 1 }
1313function Info([string]$m){ Write-Host $m -ForegroundColor Cyan }
1414
Modified.github/workflows/d8-deploy.yml+9−10View fileUnifiedSplit
@@ -1,16 +1,17 @@
1name: D8 Deploy (main -> Vercel Prod)
1name: Deploy to Production
22
33on:
44 push:
5 branches: [ "main" ]
6 workflow_dispatch: {}
5 branches: [main]
6 workflow_dispatch:
77
88concurrency:
99 group: d8-prod
1010 cancel-in-progress: true
1111
1212jobs:
13 build_and_deploy:
13 deploy:
14 name: Build & Deploy (Prod)
1415 runs-on: ubuntu-latest
1516 steps:
1617 - uses: actions/checkout@v4
@@ -23,8 +24,7 @@ jobs:
2324 - name: Install
2425 run: npm ci
2526
26 - name: Build gate
27 continue-on-error: true
27 - name: Build
2828 run: npm run build
2929
3030 - name: Install Vercel CLI
@@ -42,10 +42,9 @@ jobs:
4242 VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
4343 VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
4444
45 - name: Probe (best effort)
46 if: always()
45 - name: Smoke test
46 if: success()
4747 run: |
4848 URL="${{ secrets.PROD_BASE_URL }}/api/__probe__?ts=$(date +%s)"
4949 echo "Probing: $URL"
50 curl -s -D - --max-time 20 "$URL" | head -n 60
51
50 curl -sf --max-time 20 "$URL" || echo "Probe failed (non-blocking)"
Added.github/workflows/pr-ci.yml+96−0View fileUnifiedSplit
@@ -0,0 +1,96 @@
1name: PR CI
2
3on:
4 pull_request:
5 branches: [main]
6
7concurrency:
8 group: pr-${{ github.event.pull_request.number }}
9 cancel-in-progress: true
10
11jobs:
12 quality:
13 name: Quality Gates
14 runs-on: ubuntu-latest
15 steps:
16 - uses: actions/checkout@v4
17
18 - uses: actions/setup-node@v4
19 with:
20 node-version: "20"
21 cache: "npm"
22
23 - name: Install
24 run: npm ci
25
26 - name: Lint
27 run: npm run lint
28
29 - name: Build
30 run: npm run build
31
32 preview:
33 name: Preview Deploy
34 needs: quality
35 runs-on: ubuntu-latest
36 permissions:
37 contents: read
38 pull-requests: write
39 steps:
40 - uses: actions/checkout@v4
41
42 - uses: actions/setup-node@v4
43 with:
44 node-version: "20"
45 cache: "npm"
46
47 - name: Install
48 run: npm ci
49
50 - name: Install Vercel CLI
51 run: npm i -g vercel@latest
52
53 - name: Pull Vercel env
54 run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
55 env:
56 VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
57 VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
58
59 - name: Deploy preview
60 id: deploy
61 run: |
62 URL=$(vercel deploy --yes --token=${{ secrets.VERCEL_TOKEN }})
63 echo "url=$URL" >> "$GITHUB_OUTPUT"
64 env:
65 VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
66 VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
67
68 - name: Comment preview URL
69 uses: actions/github-script@v7
70 with:
71 script: |
72 const url = '${{ steps.deploy.outputs.url }}';
73 const body = `**Preview deployed** :arrow_right: ${url}`;
74 const { data: comments } = await github.rest.issues.listComments({
75 owner: context.repo.owner,
76 repo: context.repo.repo,
77 issue_number: context.issue.number,
78 });
79 const existing = comments.find(c =>
80 c.user.type === 'Bot' && c.body.includes('Preview deployed')
81 );
82 if (existing) {
83 await github.rest.issues.updateComment({
84 owner: context.repo.owner,
85 repo: context.repo.repo,
86 comment_id: existing.id,
87 body,
88 });
89 } else {
90 await github.rest.issues.createComment({
91 owner: context.repo.owner,
92 repo: context.repo.repo,
93 issue_number: context.issue.number,
94 body,
95 });
96 }
Deleted.github/workflows/pr-quality-gates.yml+0−43View fileUnifiedSplit
@@ -1,43 +0,0 @@
1name: PR Quality Gates
2
3on:
4 pull_request:
5 push:
6 branches: [ "main" ]
7
8jobs:
9 gates:
10 runs-on: ubuntu-latest
11 steps:
12 - name: Checkout
13 uses: actions/checkout@v4
14
15 - name: Setup Node
16 uses: actions/setup-node@v4
17 with:
18 node-version: "20"
19 cache: "npm"
20
21 - name: Install
22 run: npm ci
23
24 - name: Lint (if present)
25 run: |
26 node -e "const p=require('./package.json'); process.exit(p.scripts && p.scripts.lint ? 0 : 1)" \
27 && npm run lint \
28 || echo "No lint script; skipping."
29
30 - name: Typecheck (if present)
31 run: |
32 node -e "const p=require('./package.json'); process.exit(p.scripts && (p.scripts.typecheck||p.scripts['check-types']) ? 0 : 1)" \
33 && (npm run typecheck || npm run check-types) \
34 || echo "No typecheck script; skipping."
35
36 - name: Build
37 run: npm run build
38
39 - name: Tests (if present)
40 run: |
41 node -e "const p=require('./package.json'); process.exit(p.scripts && p.scripts.test ? 0 : 1)" \
42 && npm test \
43 || echo "No test script; skipping."
Modifiedpackage-lock.json+0−11View fileUnifiedSplit
@@ -1460,7 +1460,6 @@
14601460 "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
14611461 "dev": true,
14621462 "license": "MIT",
1463 "peer": true,
14641463 "bin": {
14651464 "acorn": "bin/acorn"
14661465 },
@@ -1895,7 +1894,6 @@
18951894 }
18961895 ],
18971896 "license": "MIT",
1898 "peer": true,
18991897 "dependencies": {
19001898 "baseline-browser-mapping": "^2.9.0",
19011899 "caniuse-lite": "^1.0.30001759",
@@ -2535,7 +2533,6 @@
25352533 "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
25362534 "dev": true,
25372535 "license": "MIT",
2538 "peer": true,
25392536 "dependencies": {
25402537 "@eslint-community/eslint-utils": "^4.2.0",
25412538 "@eslint-community/regexpp": "^4.6.1",
@@ -2704,7 +2701,6 @@
27042701 "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
27052702 "dev": true,
27062703 "license": "MIT",
2707 "peer": true,
27082704 "dependencies": {
27092705 "@rtsao/scc": "^1.1.0",
27102706 "array-includes": "^3.1.9",
@@ -4745,7 +4741,6 @@
47454741 "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==",
47464742 "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.",
47474743 "license": "MIT",
4748 "peer": true,
47494744 "dependencies": {
47504745 "@next/env": "14.2.5",
47514746 "@swc/helpers": "0.5.5",
@@ -5247,7 +5242,6 @@
52475242 }
52485243 ],
52495244 "license": "MIT",
5250 "peer": true,
52515245 "dependencies": {
52525246 "nanoid": "^3.3.11",
52535247 "picocolors": "^1.1.1",
@@ -5337,7 +5331,6 @@
53375331 "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
53385332 "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
53395333 "license": "MIT",
5340 "peer": true,
53415334 "dependencies": {
53425335 "loose-envify": "^1.1.0"
53435336 },
@@ -5350,7 +5343,6 @@
53505343 "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
53515344 "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
53525345 "license": "MIT",
5353 "peer": true,
53545346 "dependencies": {
53555347 "loose-envify": "^1.1.0",
53565348 "scheduler": "^0.23.0"
@@ -6253,7 +6245,6 @@
62536245 "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
62546246 "dev": true,
62556247 "license": "MIT",
6256 "peer": true,
62576248 "engines": {
62586249 "node": ">=12"
62596250 },
@@ -6422,7 +6413,6 @@
64226413 "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
64236414 "dev": true,
64246415 "license": "Apache-2.0",
6425 "peer": true,
64266416 "bin": {
64276417 "tsc": "bin/tsc",
64286418 "tsserver": "bin/tsserver"
@@ -6822,7 +6812,6 @@
68226812 "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
68236813 "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
68246814 "license": "MIT",
6825 "peer": true,
68266815 "funding": {
68276816 "url": "https://github.com/sponsors/colinhacks"
68286817 }
Modifiedsrc/app/(marketing)/_d8/D8Bits.tsx+21−1View fileUnifiedSplit
@@ -1,6 +1,11 @@
11import React from "react";
22
3export function D8Card(props: { children?: React.ReactNode }) {
3export function D8Card(props: {
4 children?: React.ReactNode;
5 title?: string;
6 body?: string;
7 kicker?: string;
8}) {
49 return (
510 <div
611 style={{
@@ -11,6 +16,21 @@ export function D8Card(props: { children?: React.ReactNode }) {
1116 boxShadow: "0 18px 55px rgba(0,0,0,0.35)",
1217 }}
1318 >
19 {props.kicker && (
20 <div style={{ fontSize: 10, fontWeight: 900, letterSpacing: "0.14em", textTransform: "uppercase", color: "rgba(237,234,247,0.55)", marginBottom: 8 }}>
21 {props.kicker}
22 </div>
23 )}
24 {props.title && (
25 <div style={{ fontSize: 15, fontWeight: 700, color: "rgba(246,242,255,0.95)", marginBottom: 4 }}>
26 {props.title}
27 </div>
28 )}
29 {props.body && (
30 <div style={{ fontSize: 13, lineHeight: 1.5, color: "rgba(237,234,247,0.65)" }}>
31 {props.body}
32 </div>
33 )}
1434 {props.children}
1535 </div>
1636 );
Modifiedsrc/app/_client/D8TV.tsx+0−3View fileUnifiedSplit
@@ -12,9 +12,6 @@ export type D8SectionProps = {
1212};
1313
1414export function D8Section(props: D8SectionProps) {
15 tone?: string;
16 lead?: string;
17 eyebrow?: string;
1815 const { title, subtitle, children, id } = props;
1916
2017 // Keep server-safe: no "use client", no browser-only APIs.
Addedsrc/app/_client/MarketingClient.tsx+208−0View fileUnifiedSplit
@@ -0,0 +1,208 @@
1"use client";
2
3import { useEffect, useRef, useState, type ReactNode } from "react";
4
5/* ─────────────────────────────────────────────
6 ScrollReveal — fade-up on viewport entry
7 ───────────────────────────────────────────── */
8export function ScrollReveal({
9 children,
10 className = "",
11 delay = 0,
12}: {
13 children: ReactNode;
14 className?: string;
15 delay?: number;
16}) {
17 const ref = useRef<HTMLDivElement>(null);
18 const [visible, setVisible] = useState(false);
19
20 useEffect(() => {
21 const el = ref.current;
22 if (!el) return;
23 const observer = new IntersectionObserver(
24 ([entry]) => {
25 if (entry.isIntersecting) {
26 setVisible(true);
27 observer.disconnect();
28 }
29 },
30 { threshold: 0.15 },
31 );
32 observer.observe(el);
33 return () => observer.disconnect();
34 }, []);
35
36 return (
37 <div
38 ref={ref}
39 className={className}
40 style={{
41 opacity: visible ? 1 : 0,
42 transform: visible ? "translateY(0)" : "translateY(32px)",
43 transition: `opacity 0.7s cubic-bezier(0.16,1,0.3,1) ${delay}ms, transform 0.7s cubic-bezier(0.16,1,0.3,1) ${delay}ms`,
44 }}
45 >
46 {children}
47 </div>
48 );
49}
50
51/* ─────────────────────────────────────────────
52 HeroGlow — cursor-follow radial glow
53 ───────────────────────────────────────────── */
54export function HeroGlow({
55 children,
56 className = "",
57}: {
58 children: ReactNode;
59 className?: string;
60}) {
61 const ref = useRef<HTMLDivElement>(null);
62
63 useEffect(() => {
64 const el = ref.current;
65 if (!el) return;
66
67 el.style.setProperty("--gx", "50%");
68 el.style.setProperty("--gy", "40%");
69
70 const onMove = (e: PointerEvent) => {
71 const r = el.getBoundingClientRect();
72 const x = ((e.clientX - r.left) / r.width) * 100;
73 const y = ((e.clientY - r.top) / r.height) * 100;
74 el.style.setProperty("--gx", x.toFixed(1) + "%");
75 el.style.setProperty("--gy", y.toFixed(1) + "%");
76 };
77
78 const onLeave = () => {
79 el.style.setProperty("--gx", "50%");
80 el.style.setProperty("--gy", "40%");
81 };
82
83 el.addEventListener("pointermove", onMove, { passive: true });
84 el.addEventListener("pointerleave", onLeave, { passive: true });
85 return () => {
86 el.removeEventListener("pointermove", onMove);
87 el.removeEventListener("pointerleave", onLeave);
88 };
89 }, []);
90
91 return (
92 <div ref={ref} className={className}>
93 {children}
94 </div>
95 );
96}
97
98/* ─────────────────────────────────────────────
99 FAQItem — smooth expand/collapse
100 ───────────────────────────────────────────── */
101export function FAQItem({ q, a }: { q: string; a: string }) {
102 const [open, setOpen] = useState(false);
103 const bodyRef = useRef<HTMLDivElement>(null);
104 const [height, setHeight] = useState(0);
105
106 useEffect(() => {
107 if (bodyRef.current) {
108 setHeight(bodyRef.current.scrollHeight);
109 }
110 }, [a]);
111
112 return (
113 <div
114 className="group relative overflow-hidden rounded-2xl border border-white/[0.12] bg-white/[0.04] backdrop-blur-xl transition-all hover:border-white/[0.20] hover:bg-white/[0.06]"
115 >
116 {/* Glass top sheen */}
117 <div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white/[0.10] to-transparent" />
118
119 <button
120 onClick={() => setOpen(!open)}
121 className="flex w-full items-center justify-between gap-4 px-6 py-5 text-left"
122 >
123 <span className="text-[15px] font-semibold text-white/90">{q}</span>
124 <span
125 className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full border border-white/[0.14] bg-white/[0.06] text-sm text-white/60 shadow-inner shadow-white/5 transition-transform"
126 style={{ transform: open ? "rotate(45deg)" : "rotate(0deg)" }}
127 >
128 +
129 </span>
130 </button>
131 <div
132 style={{
133 maxHeight: open ? height : 0,
134 opacity: open ? 1 : 0,
135 overflow: "hidden",
136 transition: "max-height 0.35s cubic-bezier(0.16,1,0.3,1), opacity 0.3s ease",
137 }}
138 >
139 <div ref={bodyRef} className="px-6 pb-5 text-sm leading-relaxed text-white/55">
140 {a}
141 </div>
142 </div>
143 </div>
144 );
145}
146
147/* ─────────────────────────────────────────────
148 AnimatedNumber — count-up on viewport entry
149 ───────────────────────────────────────────── */
150export function AnimatedNumber({
151 value,
152 className = "",
153}: {
154 value: string;
155 className?: string;
156}) {
157 const ref = useRef<HTMLSpanElement>(null);
158 const [display, setDisplay] = useState(value);
159 const hasAnimated = useRef(false);
160
161 useEffect(() => {
162 const el = ref.current;
163 if (!el) return;
164
165 const observer = new IntersectionObserver(
166 ([entry]) => {
167 if (entry.isIntersecting && !hasAnimated.current) {
168 hasAnimated.current = true;
169 observer.disconnect();
170
171 // Extract the numeric part
172 const numMatch = value.match(/(\d+)/);
173 if (!numMatch) {
174 setDisplay(value);
175 return;
176 }
177
178 const target = parseInt(numMatch[1], 10);
179 const prefix = value.slice(0, numMatch.index);
180 const suffix = value.slice((numMatch.index ?? 0) + numMatch[1].length);
181 const duration = 1200;
182 const start = performance.now();
183
184 const tick = (now: number) => {
185 const elapsed = now - start;
186 const progress = Math.min(elapsed / duration, 1);
187 // ease-out cubic
188 const eased = 1 - Math.pow(1 - progress, 3);
189 const current = Math.round(target * eased);
190 setDisplay(prefix + current + suffix);
191 if (progress < 1) requestAnimationFrame(tick);
192 };
193
194 requestAnimationFrame(tick);
195 }
196 },
197 { threshold: 0.5 },
198 );
199 observer.observe(el);
200 return () => observer.disconnect();
201 }, [value]);
202
203 return (
204 <span ref={ref} className={className}>
205 {display}
206 </span>
207 );
208}
Modifiedsrc/app/admin/cockpit/page.tsx+423−326View fileUnifiedSplit
@@ -1,379 +1,476 @@
1"use client";
2
3import { useEffect, useMemo, useState } from "react";
4
5type AgentItem = { id: string; title: string; file: string };
6type RunResponse = {
7 ok: boolean;
8 projectId?: string;
9 agentId?: string;
10 runId?: string;
11 status?: string;
12 patchSaved?: boolean;
13 patchMode?: string;
14 patchChars?: number;
15 patchKeys?: { patchLatest: string; patchById: string };
16 error?: string;
1"use client";
2
3import { useCallback, useEffect, useMemo, useState } from "react";
4import Link from "next/link";
5import {
6 AGENT_FLEET,
7 AGENT_COLORS,
8 CATEGORY_LABELS,
9 type AgentSpec,
10 type AgentStatus,
11} from "@/src/lib/agents/registry";
12
13/* ─── types ─── */
14type AgentRun = {
15 id: string;
16 agent: string;
17 status: "queued" | "running" | "success" | "failure" | "skipped";
18 summary?: string;
19 detail?: string;
20 createdAtIso: string;
21 updatedAtIso?: string;
1722};
1823
19type PatchResponse = {
20 ok: boolean;
21 patch?: {
22 runId: string;
23 projectId: string;
24 agentId: string;
25 createdAt: string;
26 status: string;
27 mode: string;
28 script: string;
29 };
30 foundKey?: string;
31 error?: string;
32};
24type FleetAgent = AgentSpec & { status: AgentStatus; lastRun?: AgentRun };
3325
34function clamp(s: string, n: number) {
35 if (!s) return "";
36 return s.length > n ? s.slice(0, n) + "…(truncated)" : s;
26/* ─── helpers ─── */
27function cx(...xs: Array<string | false | null | undefined>) {
28 return xs.filter(Boolean).join(" ");
3729}
3830
39function nowTs() {
40 return Math.floor(Date.now() / 1000);
31function relTime(iso: string): string {
32 const diff = Date.now() - new Date(iso).getTime();
33 if (diff < 60_000) return "just now";
34 if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;
35 if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)}h ago`;
36 return `${Math.floor(diff / 86_400_000)}d ago`;
4137}
4238
43function downloadText(filename: string, text: string) {
44 const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
45 const url = URL.createObjectURL(blob);
46 const a = document.createElement("a");
47 a.href = url;
48 a.download = filename;
49 document.body.appendChild(a);
50 a.click();
51 a.remove();
52 URL.revokeObjectURL(url);
39function statusDotClass(s: AgentStatus | AgentRun["status"]): string {
40 switch (s) {
41 case "online":
42 case "success":
43 return "bg-emerald-400";
44 case "running":
45 return "bg-blue-400 animate-pulse";
46 case "queued":
47 case "idle":
48 return "bg-amber-400/70";
49 case "failure":
50 case "error":
51 return "bg-rose-400";
52 default:
53 return "bg-white/30";
54 }
5355}
5456
55export default function AdminCockpitPage() {
56 const [agents, setAgents] = useState<AgentItem[]>([]);
57 const [agentsErr, setAgentsErr] = useState<string>("");
58
59 const [projectId, setProjectId] = useState("demo");
60 const [agentId, setAgentId] = useState("04_frontend_engineer");
57/* ─── components ─── */
6158
62 // Guardrail: force target file path (prevents generic garbage patches)
63 const [targetPath, setTargetPath] = useState("src/app/admin/projects/page.tsx");
59function AgentCard({ agent, onLaunch }: { agent: FleetAgent; onLaunch: (a: AgentSpec) => void }) {
60 const colors = AGENT_COLORS[agent.color] || AGENT_COLORS.purple;
6461
65 const [task, setTask] = useState(
66`CLOSE this page with a friendly empty state + CTA.
67Admin-only. No public homepage changes. No design drift.
68Output ONLY a single fenced PowerShell patch that OVERWRITES the target file path.`
69 );
62 return (
63 <div
64 className={cx(
65 "group relative rounded-2xl border p-4 transition-all hover:bg-white/[0.04]",
66 colors.border,
67 "border-white/[0.08] bg-white/[0.02]"
68 )}
69 >
70 <div className="flex items-start justify-between gap-3">
71 <div className="flex items-center gap-3">
72 <div
73 className={cx(
74 "grid h-9 w-9 place-items-center rounded-xl border",
75 colors.bg,
76 colors.border
77 )}
78 >
79 <span className={cx("text-xs font-bold", colors.text)}>
80 {agent.shortName.slice(0, 2).toUpperCase()}
81 </span>
82 </div>
83 <div>
84 <div className="text-sm font-semibold text-white/90">{agent.name}</div>
85 <div className="text-[11px] text-white/50">{agent.id}</div>
86 </div>
87 </div>
88 <div className="flex items-center gap-2">
89 <span className={cx("h-2 w-2 rounded-full", statusDotClass(agent.status))} />
90 <span className="text-[10px] text-white/50">{agent.status}</span>
91 </div>
92 </div>
7093
71 const [runResp, setRunResp] = useState<RunResponse | null>(null);
72 const [runRaw, setRunRaw] = useState<string>("");
94 <p className="mt-3 text-xs leading-relaxed text-white/55">{agent.description}</p>
95
96 <div className="mt-3 flex flex-wrap gap-1.5">
97 {agent.capabilities.slice(0, 3).map((cap) => (
98 <span
99 key={cap}
100 className="rounded-md border border-white/[0.08] bg-white/[0.03] px-2 py-0.5 text-[10px] text-white/50"
101 >
102 {cap}
103 </span>
104 ))}
105 {agent.capabilities.length > 3 && (
106 <span className="text-[10px] text-white/40">+{agent.capabilities.length - 3}</span>
107 )}
108 </div>
73109
74 const [runId, setRunId] = useState<string>("");
75 const [patchResp, setPatchResp] = useState<PatchResponse | null>(null);
76 const [patchRaw, setPatchRaw] = useState<string>("");
77 const [busy, setBusy] = useState(false);
110 {agent.lastRun && (
111 <div className="mt-3 flex items-center gap-2 rounded-lg border border-white/[0.06] bg-black/20 px-2.5 py-1.5">
112 <span className={cx("h-1.5 w-1.5 rounded-full", statusDotClass(agent.lastRun.status))} />
113 <span className="text-[10px] text-white/60">
114 {agent.lastRun.status} · {relTime(agent.lastRun.createdAtIso)}
115 </span>
116 {agent.lastRun.summary && (
117 <span className="ml-auto truncate text-[10px] text-white/40 max-w-[120px]">
118 {agent.lastRun.summary}
119 </span>
120 )}
121 </div>
122 )}
123
124 <button
125 onClick={() => onLaunch(agent)}
126 className="mt-3 w-full rounded-xl border border-white/[0.10] bg-white/[0.04] px-3 py-2 text-xs font-medium text-white/80 transition hover:bg-white/[0.08] hover:border-white/[0.16]"
127 >
128 Launch agent
129 </button>
130 </div>
131 );
132}
78133
79 const effectivePrompt = useMemo(() => {
80 const header = `EDIT ONLY THIS FILE:\n${targetPath}\n\nRules (STRICT):\n- Admin-only change. NO homepage edits.\n- Keep existing styling system.\n- If linking, use next/link.\n- Output ONLY one fenced PowerShell code block.\n- Patch MUST overwrite exactly: ${targetPath}\n\nTask:\n`;
81 return header + task.trim();
82 }, [task, targetPath]);
134function RunFeedItem({ run }: { run: AgentRun }) {
135 const agent = AGENT_FLEET.find((a) => a.id === run.agent);
136 const colors = AGENT_COLORS[agent?.color || "purple"] || AGENT_COLORS.purple;
83137
84 useEffect(() => {
85 (async () => {
86 try {
87 const r = await fetch(`/api/admin/agents/list?ts=${nowTs()}`, { cache: "no-store" });
88 const j = await r.json();
89 if (!j.ok) throw new Error(j.error || "Failed to list agents");
90 setAgents(j.agents || []);
91 } catch (e: any) {
92 setAgentsErr(String(e?.message || e));
93 }
94 })();
95 }, []);
138 return (
139 <div className="flex items-start gap-3 rounded-xl border border-white/[0.06] bg-white/[0.02] px-4 py-3 transition hover:bg-white/[0.04]">
140 <div className="mt-0.5">
141 <span className={cx("block h-2 w-2 rounded-full", statusDotClass(run.status))} />
142 </div>
143 <div className="flex-1 min-w-0">
144 <div className="flex items-center gap-2">
145 <span className={cx("text-xs font-semibold", colors.text)}>
146 {agent?.shortName || run.agent}
147 </span>
148 <span className="text-[10px] text-white/40">{run.id.slice(0, 12)}</span>
149 <span className="ml-auto text-[10px] text-white/40">{relTime(run.createdAtIso)}</span>
150 </div>
151 {run.summary && (
152 <div className="mt-1 text-xs text-white/60 truncate">{run.summary}</div>
153 )}
154 </div>
155 <div
156 className={cx(
157 "rounded-md px-2 py-0.5 text-[10px] font-medium",
158 run.status === "success" && "bg-emerald-500/10 text-emerald-400",
159 run.status === "failure" && "bg-rose-500/10 text-rose-400",
160 run.status === "running" && "bg-blue-500/10 text-blue-400",
161 run.status === "queued" && "bg-amber-500/10 text-amber-400",
162 run.status === "skipped" && "bg-white/5 text-white/40"
163 )}
164 >
165 {run.status}
166 </div>
167 </div>
168 );
169}
96170
97 async function runAgent() {
98 setBusy(true);
99 setRunResp(null);
100 setRunRaw("");
101 setPatchResp(null);
102 setPatchRaw("");
103 setRunId("");
171function QuickLaunchModal({
172 agent,
173 onClose,
174 onRun,
175 busy,
176}: {
177 agent: AgentSpec;
178 onClose: () => void;
179 onRun: (agent: AgentSpec, task: string, targetPath: string) => void;
180 busy: boolean;
181}) {
182 const [task, setTask] = useState("");
183 const [targetPath, setTargetPath] = useState(agent.defaultTargetPath || "src/app");
184 const colors = AGENT_COLORS[agent.color] || AGENT_COLORS.purple;
104185
105 try {
106 if (!targetPath.trim()) throw new Error("Target file path is required.");
107 if (!targetPath.trim().toLowerCase().startsWith("src/") && !targetPath.trim().toLowerCase().startsWith("src\\")) {
108 throw new Error("Target must be under src/ (guardrail).");
109 }
110
111 const body = {
112 projectId,
113 agentId,
114 // IMPORTANT: our server route accepts input/prompt/task/etc
115 input: effectivePrompt,
116 // Big output for real patches (route also has defaults)
117 reasoningEffort: "minimal",
118 maxOutputTokens: 6000,
119 };
120
121 const r = await fetch(`/api/agents/run?ts=${nowTs()}`, {
122 method: "POST",
123 headers: { "content-type": "application/json" },
124 body: JSON.stringify(body),
125 });
186 return (
187 <div className="fixed inset-0 z-50 flex items-center justify-center">
188 <button
189 className="absolute inset-0 bg-black/70 backdrop-blur-sm"
190 onClick={onClose}
191 aria-label="Close"
192 />
193 <div className="relative z-10 w-full max-w-lg rounded-2xl border border-white/[0.12] bg-[#0a0a14] p-6 shadow-2xl">
194 <div className="flex items-center gap-3">
195 <div className={cx("grid h-10 w-10 place-items-center rounded-xl border", colors.bg, colors.border)}>
196 <span className={cx("text-sm font-bold", colors.text)}>
197 {agent.shortName.slice(0, 2).toUpperCase()}
198 </span>
199 </div>
200 <div>
201 <div className="text-base font-semibold text-white">{agent.name}</div>
202 <div className="text-xs text-white/50">{agent.id}</div>
203 </div>
204 </div>
126205
127 const text = await r.text();
128 setRunRaw(text);
206 <div className="mt-5 space-y-3">
207 <div>
208 <label className="text-xs text-white/60">Target path</label>
209 <input
210 className="mt-1 w-full rounded-lg border border-white/[0.10] bg-black/40 px-3 py-2 text-sm text-white outline-none focus:border-purple-500/50"
211 value={targetPath}
212 onChange={(e) => setTargetPath(e.target.value)}
213 />
214 </div>
215 <div>
216 <label className="text-xs text-white/60">Task description</label>
217 <textarea
218 className="mt-1 min-h-[120px] w-full rounded-lg border border-white/[0.10] bg-black/40 px-3 py-2 text-sm text-white outline-none focus:border-purple-500/50"
219 value={task}
220 onChange={(e) => setTask(e.target.value)}
221 placeholder={`Describe what ${agent.shortName} should do...`}
222 />
223 </div>
224 </div>
129225
130 let j: any = null;
131 try { j = JSON.parse(text); } catch {}
132 if (!j) throw new Error("Non-JSON response from /api/agents/run");
226 <div className="mt-5 flex items-center gap-3">
227 <button
228 onClick={() => onRun(agent, task, targetPath)}
229 disabled={busy || !task.trim()}
230 className="flex-1 rounded-xl bg-gradient-to-r from-purple-500 to-blue-500 px-4 py-2.5 text-sm font-semibold text-white transition hover:brightness-110 disabled:opacity-40"
231 >
232 {busy ? "Running..." : "Run agent"}
233 </button>
234 <button
235 onClick={onClose}
236 className="rounded-xl border border-white/[0.10] bg-white/[0.04] px-4 py-2.5 text-sm text-white/70 transition hover:bg-white/[0.08]"
237 >
238 Cancel
239 </button>
240 </div>
241 </div>
242 </div>
243 );
244}
133245
134 setRunResp(j);
246/* ─── main page ─── */
247export default function DeveloperCockpit() {
248 const [runs, setRuns] = useState<AgentRun[]>([]);
249 const [launchAgent, setLaunchAgent] = useState<AgentSpec | null>(null);
250 const [busy, setBusy] = useState(false);
251 const [filter, setFilter] = useState<"all" | AgentSpec["category"]>("all");
135252
136 if (j?.runId) {
137 setRunId(String(j.runId));
138 } else {
139 throw new Error("No runId returned.");
140 }
141 } catch (e: any) {
142 setRunResp({ ok: false, error: String(e?.message || e) });
143 } finally {
144 setBusy(false);
253 // Fetch recent runs
254 const fetchRuns = useCallback(async () => {
255 try {
256 const r = await fetch(`/api/__d8__/agent-runs?limit=20&ts=${Date.now()}`, { cache: "no-store" });
257 const j = await r.json();
258 if (j.ok) setRuns(j.runs || []);
259 } catch {
260 // silent — cockpit stays functional without live data
145261 }
146 }
262 }, []);
147263
148 async function fetchPatch() {
264 useEffect(() => {
265 fetchRuns();
266 const iv = setInterval(fetchRuns, 8000);
267 return () => clearInterval(iv);
268 }, [fetchRuns]);
269
270 // Build fleet with status from runs
271 const fleet: FleetAgent[] = useMemo(() => {
272 return AGENT_FLEET.map((agent) => {
273 const agentRuns = runs.filter((r) => r.agent === agent.id);
274 const lastRun = agentRuns[0];
275 let status: AgentStatus = "idle";
276 if (lastRun?.status === "running") status = "running";
277 else if (lastRun?.status === "success") status = "online";
278 else if (lastRun?.status === "failure") status = "error";
279 return { ...agent, status, lastRun };
280 });
281 }, [runs]);
282
283 const filteredFleet = filter === "all" ? fleet : fleet.filter((a) => a.category === filter);
284
285 // Stats
286 const totalRuns = runs.length;
287 const successRuns = runs.filter((r) => r.status === "success").length;
288 const activeAgents = fleet.filter((a) => a.status === "online" || a.status === "running").length;
289
290 // Launch handler
291 async function handleLaunch(agent: AgentSpec, task: string, targetPath: string) {
149292 setBusy(true);
150 setPatchResp(null);
151 setPatchRaw("");
152
153293 try {
154 const rid = (runId || "").trim();
155 if (!rid) throw new Error("Run ID is required.");
156 const url = `/api/agents/patch?projectId=${encodeURIComponent(projectId)}&runId=${encodeURIComponent(rid)}&ts=${nowTs()}`;
157 const r = await fetch(url, { cache: "no-store" });
158 const text = await r.text();
159 setPatchRaw(text);
160
161 let j: any = null;
162 try { j = JSON.parse(text); } catch {}
163 if (!j) throw new Error("Non-JSON response from /api/agents/patch");
164
165 setPatchResp(j);
166 } catch (e: any) {
167 setPatchResp({ ok: false, error: String(e?.message || e) });
294 await fetch("/api/__d8__/agent-runs", {
295 method: "POST",
296 headers: { "content-type": "application/json" },
297 body: JSON.stringify({
298 agent: agent.id,
299 status: "running",
300 summary: task.slice(0, 120),
301 detail: `target: ${targetPath}`,
302 }),
303 });
304 setLaunchAgent(null);
305 await fetchRuns();
306 } catch {
307 // error handling via UI
168308 } finally {
169309 setBusy(false);
170310 }
171311 }
172312
173 function savePatchToDownloads() {
174 const script = patchResp?.patch?.script || "";
175 if (!script) return;
176 const name = `agent_patch_${projectId}_${runId || "run"}.ps1`;
177 downloadText(name, script);
178 }
179
180 const patchScriptPreview = patchResp?.patch?.script || "";
181
182313 return (
183 <div className="min-h-screen bg-black text-white">
184 <div className="mx-auto max-w-6xl px-4 py-8">
185 <div className="flex items-start justify-between gap-4">
186 <div>
187 <div className="text-xs font-semibold tracking-[0.25em] text-white/60">ADMIN</div>
188 <h1 className="mt-2 text-3xl font-semibold tracking-tight">Agile Cockpit</h1>
189 <p className="mt-2 text-sm text-white/70">
190 Run agents with strict guardrails. Patches are generated for you to apply locally (PowerShell).
191 </p>
192 <p className="mt-2 text-xs text-white/50">
193 Tip: Use Windows voice dictation to speak into the task box (Win+H).
194 </p>
314 <div className="space-y-6">
315 {/* Header */}
316 <div className="flex items-start justify-between gap-4">
317 <div>
318 <div className="flex items-center gap-2">
319 <div className="text-xs font-semibold tracking-[0.25em] text-purple-400/80">
320 DEVELOPER
321 </div>
322 <span className="rounded-full border border-emerald-500/20 bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-400">
323 LIVE
324 </span>
195325 </div>
326 <h1 className="mt-2 text-3xl font-semibold tracking-tight">Cockpit</h1>
327 <p className="mt-2 max-w-xl text-sm text-white/60">
328 Mission control for the Dominat8 agent fleet. Monitor status, launch agents, and track
329 runs in real-time.
330 </p>
331 </div>
196332
197 <div className="rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-xs text-white/70">
198 <div className="font-semibold text-white/80">Guardrails</div>
199 <div className="mt-1">• Admin-only changes</div>
200 <div>• Target file required</div>
201 <div>• Output must be PS patch</div>
202 </div>
333 <div className="flex items-center gap-2">
334 <Link
335 href="/admin/agents"
336 className="rounded-xl border border-white/[0.10] bg-white/[0.04] px-3 py-2 text-xs text-white/70 transition hover:bg-white/[0.08]"
337 >
338 Bundles
339 </Link>
340 <button
341 onClick={fetchRuns}
342 className="rounded-xl border border-white/[0.10] bg-white/[0.04] px-3 py-2 text-xs text-white/70 transition hover:bg-white/[0.08]"
343 >
344 Refresh
345 </button>
203346 </div>
347 </div>
204348
205 <div className="mt-8 grid grid-cols-1 gap-6 lg:grid-cols-2">
206 <div className="rounded-2xl border border-white/10 bg-white/5 p-5">
207 <div className="text-sm font-semibold">Run Agent</div>
208
209 <div className="mt-4 grid grid-cols-1 gap-3">
210 <label className="text-xs text-white/60">Project ID</label>
211 <input
212 className="w-full rounded-lg border border-white/10 bg-black/40 px-3 py-2 text-sm text-white outline-none"
213 value={projectId}
214 onChange={(e) => setProjectId(e.target.value)}
215 />
216
217 <label className="text-xs text-white/60">Agent</label>
218 <select
219 className="w-full rounded-lg border border-white/10 bg-black/40 px-3 py-2 text-sm text-white outline-none"
220 value={agentId}
221 onChange={(e) => setAgentId(e.target.value)}
222 >
223 {agents.length ? (
224 agents.map((a) => (
225 <option key={a.id} value={a.id}>
226 {a.id} — {a.title}
227 </option>
228 ))
229 ) : (
230 <>
231 <option value="04_frontend_engineer">04_frontend_engineer</option>
232 <option value="02_creative_director">02_creative_director</option>
233 </>
234 )}
235 </select>
236 {agentsErr ? <div className="text-xs text-red-300">Agents list error: {agentsErr}</div> : null}
237
238 <label className="text-xs text-white/60">Target file path (required)</label>
239 <input
240 className="w-full rounded-lg border border-white/10 bg-black/40 px-3 py-2 text-sm text-white outline-none"
241 value={targetPath}
242 onChange={(e) => setTargetPath(e.target.value)}
243 />
244
245 <label className="text-xs text-white/60">Task (speak here with Win+H)</label>
246 <textarea
247 className="min-h-[180px] w-full rounded-lg border border-white/10 bg-black/40 px-3 py-2 text-sm text-white outline-none"
248 value={task}
249 onChange={(e) => setTask(e.target.value)}
250 />
251
252 <div className="rounded-lg border border-white/10 bg-black/30 p-3 text-xs text-white/70">
253 <div className="font-semibold text-white/80">Effective prompt preview</div>
254 <pre className="mt-2 whitespace-pre-wrap text-[11px] leading-relaxed text-white/70">
255 {clamp(effectivePrompt, 1200)}
256 </pre>
257 </div>
349 {/* Stats strip */}
350 <div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
351 <div className="rounded-2xl border border-white/[0.08] bg-white/[0.03] p-4">
352 <div className="text-[11px] text-white/50">Fleet size</div>
353 <div className="mt-1 text-2xl font-bold text-white">{AGENT_FLEET.length}</div>
354 <div className="mt-1 text-[10px] text-white/40">specialized agents</div>
355 </div>
356 <div className="rounded-2xl border border-white/[0.08] bg-white/[0.03] p-4">
357 <div className="text-[11px] text-white/50">Active</div>
358 <div className="mt-1 text-2xl font-bold text-emerald-400">{activeAgents}</div>
359 <div className="mt-1 text-[10px] text-white/40">agents responding</div>
360 </div>
361 <div className="rounded-2xl border border-white/[0.08] bg-white/[0.03] p-4">
362 <div className="text-[11px] text-white/50">Recent runs</div>
363 <div className="mt-1 text-2xl font-bold text-white">{totalRuns}</div>
364 <div className="mt-1 text-[10px] text-white/40">in buffer</div>
365 </div>
366 <div className="rounded-2xl border border-white/[0.08] bg-white/[0.03] p-4">
367 <div className="text-[11px] text-white/50">Success rate</div>
368 <div className="mt-1 text-2xl font-bold text-white">
369 {totalRuns ? `${Math.round((successRuns / totalRuns) * 100)}%` : "\u2014"}
370 </div>
371 <div className="mt-1 text-[10px] text-white/40">of completed runs</div>
372 </div>
373 </div>
258374
375 {/* Agent fleet */}
376 <div>
377 <div className="flex items-center justify-between gap-4">
378 <h2 className="text-lg font-semibold text-white/90">Agent Fleet</h2>
379 <div className="flex items-center gap-1.5">
380 {(["all", "creative", "growth", "ops"] as const).map((cat) => (
259381 <button
260 disabled={busy}
261 onClick={runAgent}
262 className="mt-2 inline-flex items-center justify-center rounded-xl bg-white px-4 py-2 text-sm font-semibold text-black transition hover:bg-white/90 disabled:opacity-50"
382 key={cat}
383 onClick={() => setFilter(cat)}
384 className={cx(
385 "rounded-lg px-2.5 py-1 text-[11px] font-medium transition",
386 filter === cat
387 ? "bg-white/[0.10] text-white border border-white/[0.15]"
388 : "text-white/50 hover:text-white/70 border border-transparent"
389 )}
263390 >
264 {busy ? "Running…" : "Run agent now"}
391 {cat === "all" ? "All" : CATEGORY_LABELS[cat]}
265392 </button>
266
267 {runResp ? (
268 <div className="mt-3 rounded-xl border border-white/10 bg-black/30 p-4">
269 <div className="text-xs font-semibold text-white/70">Run response</div>
270 <pre className="mt-2 whitespace-pre-wrap text-[11px] leading-relaxed text-white/70">
271 {JSON.stringify(runResp, null, 2)}
272 </pre>
273 {runResp.runId ? (
274 <div className="mt-2 text-xs text-white/70">
275 Run ID: <span className="font-mono text-white">{runResp.runId}</span>
276 </div>
277 ) : null}
278 </div>
279 ) : null}
280
281 {runRaw ? (
282 <details className="mt-2 rounded-xl border border-white/10 bg-black/30 p-3">
283 <summary className="cursor-pointer text-xs font-semibold text-white/70">Raw /api/agents/run body</summary>
284 <pre className="mt-2 whitespace-pre-wrap text-[11px] leading-relaxed text-white/70">{runRaw}</pre>
285 </details>
286 ) : null}
287 </div>
393 ))}
288394 </div>
395 </div>
289396
290 <div className="rounded-2xl border border-white/10 bg-white/5 p-5">
291 <div className="text-sm font-semibold">Patch Viewer</div>
292
293 <div className="mt-4 grid grid-cols-1 gap-3">
294 <label className="text-xs text-white/60">Run ID</label>
295 <input
296 className="w-full rounded-lg border border-white/10 bg-black/40 px-3 py-2 text-sm text-white outline-none"
297 value={runId}
298 onChange={(e) => setRunId(e.target.value)}
299 placeholder="resp_..."
300 />
397 <div className="mt-4 grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
398 {filteredFleet.map((agent) => (
399 <AgentCard
400 key={agent.id}
401 agent={agent}
402 onLaunch={(a) => setLaunchAgent(a)}
403 />
404 ))}
405 </div>
406 </div>
301407
302 <button
303 disabled={busy}
304 onClick={fetchPatch}
305 className="inline-flex items-center justify-center rounded-xl border border-white/15 bg-black/30 px-4 py-2 text-sm font-semibold text-white transition hover:bg-black/40 disabled:opacity-50"
306 >
307 {busy ? "Fetching…" : "Fetch patch"}
308 </button>
408 {/* Live run feed */}
409 <div>
410 <div className="flex items-center justify-between gap-4">
411 <h2 className="text-lg font-semibold text-white/90">Run Feed</h2>
412 <div className="flex items-center gap-2">
413 <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-emerald-400" />
414 <span className="text-[10px] text-white/50">Auto-refreshing every 8s</span>
415 </div>
416 </div>
309417
310 {patchResp ? (
311 <div className="rounded-xl border border-white/10 bg-black/30 p-4">
312 <div className="flex items-center justify-between gap-3">
313 <div className="text-xs font-semibold text-white/70">Patch status</div>
314 {patchResp?.patch?.script ? (
315 <button
316 onClick={savePatchToDownloads}
317 className="rounded-lg bg-white px-3 py-1.5 text-xs font-semibold text-black hover:bg-white/90"
318 >
319 Save patch to downloads
320 </button>
321 ) : null}
322 </div>
323
324 <pre className="mt-2 whitespace-pre-wrap text-[11px] leading-relaxed text-white/70">
325 {JSON.stringify(
326 {
327 ok: patchResp.ok,
328 foundKey: patchResp.foundKey,
329 runId: patchResp.patch?.runId,
330 status: patchResp.patch?.status,
331 mode: patchResp.patch?.mode,
332 scriptChars: patchResp.patch?.script?.length || 0,
333 error: patchResp.error,
334 },
335 null,
336 2
337 )}
338 </pre>
339
340 {patchScriptPreview ? (
341 <div className="mt-3 rounded-lg border border-white/10 bg-black/40 p-3">
342 <div className="text-xs font-semibold text-white/70">Patch script preview</div>
343 <pre className="mt-2 max-h-[420px] overflow-auto whitespace-pre text-[11px] leading-relaxed text-white/70">
344 {patchScriptPreview}
345 </pre>
346 </div>
347 ) : null}
348 </div>
349 ) : null}
350
351 {patchRaw ? (
352 <details className="rounded-xl border border-white/10 bg-black/30 p-3">
353 <summary className="cursor-pointer text-xs font-semibold text-white/70">Raw /api/agents/patch body</summary>
354 <pre className="mt-2 whitespace-pre-wrap text-[11px] leading-relaxed text-white/70">{patchRaw}</pre>
355 </details>
356 ) : null}
357
358 <div className="mt-2 rounded-xl border border-white/10 bg-black/30 p-4 text-xs text-white/70">
359 <div className="font-semibold text-white/80">Apply flow (local)</div>
360 <div className="mt-2 font-mono text-[11px] leading-relaxed text-white/70">
361 1) Save patch<br />
362 2) Run it locally in PowerShell<br />
363 3) git add -A / commit / push / vercel --prod --force
364 </div>
418 <div className="mt-4 space-y-2">
419 {runs.length === 0 && (
420 <div className="rounded-2xl border border-white/[0.08] bg-white/[0.02] p-8 text-center">
421 <div className="text-sm text-white/50">No agent runs yet</div>
422 <div className="mt-2 text-xs text-white/35">
423 Launch an agent above to see runs appear here in real-time.
365424 </div>
366425 </div>
367 </div>
426 )}
427 {runs.slice(0, 10).map((run) => (
428 <RunFeedItem key={run.id} run={run} />
429 ))}
430 {runs.length > 10 && (
431 <div className="text-center text-[11px] text-white/40 py-2">
432 +{runs.length - 10} older runs
433 </div>
434 )}
368435 </div>
436 </div>
369437
370 <div className="mt-8 rounded-2xl border border-white/10 bg-white/5 p-5">
371 <div className="text-sm font-semibold">Direct link</div>
372 <div className="mt-2 text-xs text-white/70">
373 Open: <span className="font-mono text-white">/admin/cockpit</span>
438 {/* Wiring info */}
439 <div className="rounded-2xl border border-white/[0.08] bg-white/[0.02] p-5">
440 <div className="text-sm font-semibold text-white/80">Agent Wiring</div>
441 <div className="mt-3 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
442 <div className="rounded-xl border border-white/[0.06] bg-black/20 p-3">
443 <div className="text-[11px] font-medium text-purple-400">Registry</div>
444 <div className="mt-1 text-[11px] text-white/50">src/lib/agents/registry.ts</div>
445 <div className="mt-1 text-[10px] text-white/40">{AGENT_FLEET.length} agents cataloged</div>
446 </div>
447 <div className="rounded-xl border border-white/[0.06] bg-black/20 p-3">
448 <div className="text-[11px] font-medium text-blue-400">Run API</div>
449 <div className="mt-1 text-[11px] text-white/50">/api/__d8__/agent-runs</div>
450 <div className="mt-1 text-[10px] text-white/40">GET + POST, ring buffer</div>
451 </div>
452 <div className="rounded-xl border border-white/[0.06] bg-black/20 p-3">
453 <div className="text-[11px] font-medium text-emerald-400">Specs</div>
454 <div className="mt-1 text-[11px] text-white/50">agents/*.md</div>
455 <div className="mt-1 text-[10px] text-white/40">8 agent prompt modules</div>
456 </div>
457 <div className="rounded-xl border border-white/[0.06] bg-black/20 p-3">
458 <div className="text-[11px] font-medium text-amber-400">Cockpit</div>
459 <div className="mt-1 text-[11px] text-white/50">/admin/cockpit</div>
460 <div className="mt-1 text-[10px] text-white/40">Fleet control + launch</div>
374461 </div>
375462 </div>
376463 </div>
464
465 {/* Quick launch modal */}
466 {launchAgent && (
467 <QuickLaunchModal
468 agent={launchAgent}
469 onClose={() => setLaunchAgent(null)}
470 onRun={handleLaunch}
471 busy={busy}
472 />
473 )}
377474 </div>
378475 );
379476}
Modifiedsrc/app/admin/marketing-queue/page.tsx+1−1View fileUnifiedSplit
@@ -128,7 +128,7 @@ export default function Page() {
128128
129129 const [scheduleLocal, setScheduleLocal] = useState(""); // datetime-local
130130
131 const headers = useMemo(() => (token ? { "x-admin-token": token } : {}), [token]);
131 const headers = useMemo(() => (token ? { "x-admin-token": token } as Record<string, string> : {} as Record<string, string>), [token]);
132132
133133 async function refresh() {
134134 setErr(null);
Modifiedsrc/app/admin/page.tsx+34−0View fileUnifiedSplit
@@ -43,6 +43,40 @@ export default function AdminDashboard() {
4343 </div>
4444 </div>
4545
46 {/* Developer cockpit — primary CTA */}
47 <Link href="/admin/cockpit" className="block rounded-3xl border border-purple-500/20 bg-gradient-to-br from-purple-500/10 via-black/30 to-blue-500/10 p-6 hover:border-purple-500/30 transition group">
48 <div className="flex items-center justify-between">
49 <div>
50 <div className="flex items-center gap-2">
51 <div className="text-lg font-semibold tracking-tight">Developer Cockpit</div>
52 <span className="rounded-full border border-emerald-500/20 bg-emerald-500/10 px-2 py-0.5 text-[10px] font-medium text-emerald-400">LIVE</span>
53 </div>
54 <div className="mt-2 text-sm text-white/60">Mission control for the agent fleet. Launch agents, monitor runs, and track status in real-time.</div>
55 </div>
56 <div className="inline-flex items-center rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-xs group-hover:bg-white/10">
57 Open Cockpit →
58 </div>
59 </div>
60 <div className="mt-4 grid grid-cols-4 gap-3">
61 <div className="rounded-xl border border-white/8 bg-black/20 px-3 py-2">
62 <div className="text-[10px] text-white/40">Fleet</div>
63 <div className="text-sm font-bold text-white">8 agents</div>
64 </div>
65 <div className="rounded-xl border border-white/8 bg-black/20 px-3 py-2">
66 <div className="text-[10px] text-white/40">Categories</div>
67 <div className="text-sm font-bold text-white">4 roles</div>
68 </div>
69 <div className="rounded-xl border border-white/8 bg-black/20 px-3 py-2">
70 <div className="text-[10px] text-white/40">API</div>
71 <div className="text-sm font-bold text-white">Wired</div>
72 </div>
73 <div className="rounded-xl border border-white/8 bg-black/20 px-3 py-2">
74 <div className="text-[10px] text-white/40">Feed</div>
75 <div className="text-sm font-bold text-white">Real-time</div>
76 </div>
77 </div>
78 </Link>
79
4680 <div className="grid gap-4 md:grid-cols-2">
4781 <Card
4882 title="Projects"
Modifiedsrc/app/api/__probe__/route.ts+1−1View fileUnifiedSplit
@@ -4,7 +4,7 @@ export const runtime = "nodejs";
44export const dynamic = "force-dynamic";
55
66function safeEnv(name: string): string | null {
7 try: any {
7 try {
88 // @ts-ignore
99 const v = process?.env?.[name];
1010 return typeof v === "string" && v.length ? v : null;
Addedsrc/app/icon.svg+10−0View fileUnifiedSplit
@@ -0,0 +1,10 @@
1<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2 <defs>
3 <linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
4 <stop offset="0%" stop-color="#a855f7"/>
5 <stop offset="100%" stop-color="#3b82f6"/>
6 </linearGradient>
7 </defs>
8 <rect width="32" height="32" rx="8" fill="url(#g)"/>
9 <text x="16" y="22" text-anchor="middle" font-family="system-ui,sans-serif" font-weight="800" font-size="15" fill="white">D8</text>
10</svg>
Modifiedsrc/app/layout.tsx+6−0View fileUnifiedSplit
@@ -9,6 +9,12 @@ import DxlToasts from "@/components/dxl/DxlToasts";
99export const metadata: Metadata = {
1010 title: "Dominat8",
1111 description: "AI-powered website factory.",
12 icons: {
13 icon: [
14 { url: "/favicon.ico", sizes: "16x16", type: "image/x-icon" },
15 { url: "/icon.svg", type: "image/svg+xml" },
16 ],
17 },
1218};
1319
1420export default function RootLayout({ children }: { children: React.ReactNode }) {
Deletedsrc/app/page.jsx+0−207View fileUnifiedSplit
@@ -1,207 +0,0 @@
1import Link from "next/link";
2import { buildMarker } from "@/src/lib/buildMarker";
3import HeroGlow from "@/src/components/marketing/HeroGlow";
4
5/**
6 * Force dynamic rendering to avoid “stale/static/cached HTML” confusion on homepage.
7 * This makes updates show up reliably after deploy.
8 */
9export const dynamic = "force-dynamic";
10export const revalidate = 0;
11
12export default function HomePage() {
13 return (
14 <main className="d8-page">
15 {/* Deploy-proof marker (view-source / curl grep friendly) */}
16 <span className="d8-marker" aria-hidden="true">
17 {buildMarker()}
18 </span>
19
20 {/* FULL-SCREEN HERO TAKEOVER */}
21 <section className="d8-hero-full">
22 {/* WOW background layers (CSS-only) */}
23 <div className="d8-wow-bg" aria-hidden="true">
24 <div className="d8-orb d8-orb-a" />
25 <div className="d8-orb d8-orb-b" />
26 <div className="d8-orb d8-orb-c" />
27 <div className="d8-grid" />
28 <div className="d8-noise" />
29 <div className="d8-vignette" />
30 </div>
31
32 {/* V8: TRUE cursor-follow glow (tiny JS sets CSS vars) */}
33 <HeroGlow>
34 <div className="d8-hero-inner">
35 <header className="d8-hero-copy">
36 <div className="d8-pill">
37 <span className="d8-dot" />
38 <span>Dominat8 — AI Website Automation Builder</span>
39 </div>
40
41 <h1 className="d8-h1">
42 The <span className="d8-grad">wow</span> website builder.
43 <br />
44 Built by AI. Shipped fast.
45 </h1>
46
47 <p className="d8-sub">
48 Describe your business. Dominat8 generates a premium homepage, pages, and structure —
49 ready to publish on your domain.
50 </p>
51
52 <div className="d8-cta-row">
53 <Link className="d8-btn d8-btn-primary" href="/sign-in">
54 Start building
55 </Link>
56 <Link className="d8-btn d8-btn-ghost" href="/templates">
57 Explore templates
58 </Link>
59 </div>
60
61 <div className="d8-trust">
62 <div className="d8-trust-item">⚡ Fast publish</div>
63 <div className="d8-trust-item">🔎 SEO-ready</div>
64 <div className="d8-trust-item">🌐 Custom domains</div>
65 <div className="d8-trust-item">🧠 Agent pipeline</div>
66 </div>
67 </header>
68
69 <aside className="d8-hero-card" aria-label="Preview card">
70 <div className="d8-card-top">
71 <div className="d8-card-dots">
72 <span />
73 <span />
74 <span />
75 </div>
76 <div className="d8-card-title">Live Preview</div>
77 </div>
78
79 <div className="d8-card-body">
80 <div className="d8-card-kicker">AI-generated site blueprint</div>
81
82 <div className="d8-mock">
83 <div className="d8-mock-hero">
84 <div className="d8-mock-title" />
85 <div className="d8-mock-sub" />
86 <div className="d8-mock-sub d8-mock-sub2" />
87 <div className="d8-mock-btnrow">
88 <div className="d8-mock-btn" />
89 <div className="d8-mock-btn d8-mock-btn2" />
90 </div>
91 </div>
92 <div className="d8-mock-grid">
93 <div className="d8-mock-tile" />
94 <div className="d8-mock-tile" />
95 <div className="d8-mock-tile" />
96 <div className="d8-mock-tile" />
97 </div>
98 </div>
99
100 <div className="d8-card-foot">
101 <span className="d8-tag">V8 True Glow</span>
102 <span className="d8-tag">Full-screen</span>
103 <span className="d8-tag">Build-gated</span>
104 </div>
105 </div>
106 </aside>
107 </div>
108
109 {/* Scroll hint */}
110 <div className="d8-scroll-hint" aria-hidden="true">
111 <span className="d8-scroll-dot" />
112 <span className="d8-scroll-text">Scroll</span>
113 </div>
114 </HeroGlow>
115 </section>
116
117 {/* SITEGROUND-STYLE STRUCTURE BELOW THE FOLD */}
118 <section className="d8-logos">
119 <div className="d8-wrap">
120 <div className="d8-logos-title">Trusted by builders who want a premium look</div>
121 <div className="d8-logos-row" aria-label="Logo strip">
122 <div className="d8-logo-pill">Agencies</div>
123 <div className="d8-logo-pill">Founders</div>
124 <div className="d8-logo-pill">Local business</div>
125 <div className="d8-logo-pill">Creators</div>
126 <div className="d8-logo-pill">E-commerce</div>
127 </div>
128 </div>
129 </section>
130
131 <section className="d8-how">
132 <div className="d8-wrap">
133 <h2 className="d8-h2">How it works</h2>
134 <p className="d8-lead">Three steps. Clean output. Fast publishing.</p>
135
136 <div className="d8-how-grid">
137 <div className="d8-step">
138 <div className="d8-step-num">1</div>
139 <div className="d8-step-title">Describe</div>
140 <div className="d8-step-sub">Tell us what you do and the vibe you want.</div>
141 </div>
142 <div className="d8-step">
143 <div className="d8-step-num">2</div>
144 <div className="d8-step-title">Generate</div>
145 <div className="d8-step-sub">Agents build pages, layout, and SEO structure.</div>
146 </div>
147 <div className="d8-step">
148 <div className="d8-step-num">3</div>
149 <div className="d8-step-title">Publish</div>
150 <div className="d8-step-sub">Push live on your domain. Iterate instantly.</div>
151 </div>
152 </div>
153 </div>
154 </section>
155
156 <section className="d8-proof">
157 <div className="d8-wrap">
158 <div className="d8-proof-grid">
159 <div className="d8-proof-card">
160 <div className="d8-proof-kicker">Premium by default</div>
161 <div className="d8-proof-title">Design that looks expensive</div>
162 <div className="d8-proof-sub">Modern lighting, depth, and typography — without a designer.</div>
163 </div>
164
165 <div className="d8-proof-card">
166 <div className="d8-proof-kicker">Built to rank</div>
167 <div className="d8-proof-title">SEO-ready structure</div>
168 <div className="d8-proof-sub">Clean metadata, headings, and page structure from day one.</div>
169 </div>
170
171 <div className="d8-proof-card">
172 <div className="d8-proof-kicker">Fast execution</div>
173 <div className="d8-proof-title">Agents do the heavy lifting</div>
174 <div className="d8-proof-sub">From spec to publish — streamlined and repeatable.</div>
175 </div>
176 </div>
177 </div>
178 </section>
179
180 <section className="d8-final-cta">
181 <div className="d8-wrap">
182 <div className="d8-final-box">
183 <div>
184 <div className="d8-final-title">Ready to build your best site?</div>
185 <div className="d8-final-sub">Generate a premium homepage and publish it fast.</div>
186 </div>
187 <div className="d8-final-actions">
188 <Link className="d8-btn d8-btn-primary" href="/sign-in">Start building</Link>
189 <Link className="d8-btn d8-btn-ghost" href="/pricing">See pricing</Link>
190 </div>
191 </div>
192
193 <footer className="d8-footer">
194 <div className="d8-footer-left">© {new Date().getFullYear()} Dominat8</div>
195 <div className="d8-footer-right">
196 <Link className="d8-footer-link" href="/templates">Templates</Link>
197 <span className="d8-footer-dot">•</span>
198 <Link className="d8-footer-link" href="/use-cases">Use cases</Link>
199 <span className="d8-footer-dot">•</span>
200 <Link className="d8-footer-link" href="/pricing">Pricing</Link>
201 </div>
202 </footer>
203 </div>
204 </section>
205 </main>
206 );
207}
Modifiedsrc/app/page.tsx+608−358View fileUnifiedSplit
Large file (1,003 lines). Load full file
Modifiedsrc/app/video/_client/VideoStudioClient.tsx+1−1View fileUnifiedSplit
@@ -151,7 +151,7 @@ export default function VideoStudioClient() {
151151 setErr(null);
152152 if (!canvasRef.current) return;
153153 const canvas = canvasRef.current;
154 const ctx = canvas.getContext("2d");
154 const ctx = canvas.getContext("2d")!;
155155 if (!ctx) return;
156156
157157 stopAnimation();
Modifiedsrc/components/admin/AdminShellClient.tsx+1−0View fileUnifiedSplit
@@ -57,6 +57,7 @@ export default function AdminShellClient(props: { children: React.ReactNode; bui
5757 const nav: NavItem[] = useMemo(
5858 () => [
5959 { href: "/admin", label: "Dashboard", hint: "Overview" },
60 { href: "/admin/cockpit", label: "Cockpit", hint: "Mission control" },
6061 { href: "/admin/projects", label: "Projects", hint: "Your sites" },
6162 { href: "/admin/agents", label: "Agents", hint: "Runs & Bundles" },
6263 { href: "/admin/domains", label: "Domains", hint: "Custom domains" },
Addedsrc/lib/agents/registry.ts+140−0View fileUnifiedSplit
@@ -0,0 +1,140 @@
1// src/lib/agents/registry.ts
2// Dominat8 agent fleet registry — all agents cataloged and wired
3
4export type AgentStatus = "online" | "idle" | "running" | "error" | "offline";
5
6export type AgentSpec = {
7 id: string;
8 name: string;
9 shortName: string;
10 description: string;
11 category: "creative" | "engineering" | "growth" | "ops";
12 capabilities: string[];
13 defaultTargetPath?: string;
14 color: string;
15};
16
17/**
18 * The Dominat8 agent fleet — 8 specialized agents that power the platform.
19 * Each agent has a defined role, capabilities, and output contract.
20 */
21export const AGENT_FLEET: AgentSpec[] = [
22 {
23 id: "02_creative_director",
24 name: "Creative Director",
25 shortName: "Creative",
26 description:
27 "Luxury spacing, typography, and rhythm. Outputs section blueprints, design tokens, and component rules.",
28 category: "creative",
29 capabilities: ["section-blueprint", "design-tokens", "component-rules", "typography-system"],
30 defaultTargetPath: "src/components/marketing",
31 color: "purple",
32 },
33 {
34 id: "03_motion",
35 name: "Motion & Interaction",
36 shortName: "Motion",
37 description:
38 "Animation systems, scroll-driven effects, micro-interactions, and transition choreography.",
39 category: "creative",
40 capabilities: ["scroll-animations", "micro-interactions", "transition-choreography", "parallax"],
41 defaultTargetPath: "src/components/marketing",
42 color: "violet",
43 },
44 {
45 id: "04_conversion",
46 name: "Conversion Architect",
47 shortName: "Conversion",
48 description:
49 "Funnel architecture, CTA placement, objection handling. Outputs page flow and A/B test ideas.",
50 category: "growth",
51 capabilities: ["funnel-design", "cta-placement", "objection-handling", "ab-testing"],
52 defaultTargetPath: "src/app",
53 color: "blue",
54 },
55 {
56 id: "05_copy",
57 name: "Copy Chief",
58 shortName: "Copy",
59 description:
60 "Headlines, body copy, microcopy, and tone calibration. Outputs publish-ready text per section.",
61 category: "creative",
62 capabilities: ["headlines", "body-copy", "microcopy", "tone-calibration"],
63 defaultTargetPath: "src/lib/marketing",
64 color: "cyan",
65 },
66 {
67 id: "06_proof",
68 name: "Proof Engine",
69 shortName: "Proof",
70 description:
71 "Testimonials, social proof, trust signals, and credibility architecture.",
72 category: "growth",
73 capabilities: ["testimonials", "trust-signals", "social-proof", "credibility"],
74 defaultTargetPath: "src/components/marketing",
75 color: "emerald",
76 },
77 {
78 id: "07_seo",
79 name: "SEO & Search Console",
80 shortName: "SEO",
81 description:
82 "Meta tags, structured data, sitemap generation, Open Graph, and search performance.",
83 category: "growth",
84 capabilities: ["meta-tags", "structured-data", "sitemap", "open-graph", "search-console"],
85 defaultTargetPath: "src/app",
86 color: "amber",
87 },
88 {
89 id: "08_domain_ssl",
90 name: "Domain & SSL Onboarding",
91 shortName: "Domains",
92 description:
93 "Custom domain setup, DNS verification, SSL provisioning, and deployment routing.",
94 category: "ops",
95 capabilities: ["dns-verification", "ssl-provisioning", "domain-routing", "deployment"],
96 defaultTargetPath: "src/app/domains",
97 color: "orange",
98 },
99 {
100 id: "09_monetization",
101 name: "Monetization Engine",
102 shortName: "Monetize",
103 description:
104 "Pricing models, gating rules, Stripe integration, upgrade loops, and revenue optimization.",
105 category: "ops",
106 capabilities: ["pricing-models", "stripe-integration", "gating-rules", "upgrade-loops"],
107 defaultTargetPath: "src/app/admin/billing",
108 color: "rose",
109 },
110];
111
112/** Lookup agent by ID */
113export function getAgent(id: string): AgentSpec | undefined {
114 return AGENT_FLEET.find((a) => a.id === id);
115}
116
117/** Get agents by category */
118export function getAgentsByCategory(category: AgentSpec["category"]): AgentSpec[] {
119 return AGENT_FLEET.filter((a) => a.category === category);
120}
121
122/** Category labels for display */
123export const CATEGORY_LABELS: Record<AgentSpec["category"], string> = {
124 creative: "Creative & Design",
125 engineering: "Engineering",
126 growth: "Growth & Conversion",
127 ops: "Operations & Infrastructure",
128};
129
130/** Color map for Tailwind classes */
131export const AGENT_COLORS: Record<string, { dot: string; bg: string; border: string; text: string }> = {
132 purple: { dot: "bg-purple-400", bg: "bg-purple-500/10", border: "border-purple-500/20", text: "text-purple-400" },
133 violet: { dot: "bg-violet-400", bg: "bg-violet-500/10", border: "border-violet-500/20", text: "text-violet-400" },
134 blue: { dot: "bg-blue-400", bg: "bg-blue-500/10", border: "border-blue-500/20", text: "text-blue-400" },
135 cyan: { dot: "bg-cyan-400", bg: "bg-cyan-500/10", border: "border-cyan-500/20", text: "text-cyan-400" },
136 emerald: { dot: "bg-emerald-400", bg: "bg-emerald-500/10", border: "border-emerald-500/20", text: "text-emerald-400" },
137 amber: { dot: "bg-amber-400", bg: "bg-amber-500/10", border: "border-amber-500/20", text: "text-amber-400" },
138 orange: { dot: "bg-orange-400", bg: "bg-orange-500/10", border: "border-orange-500/20", text: "text-orange-400" },
139 rose: { dot: "bg-rose-400", bg: "bg-rose-500/10", border: "border-rose-500/20", text: "text-rose-400" },
140};
Modifiedsrc/lib/d8kv.ts+3−0View fileUnifiedSplit
@@ -50,4 +50,7 @@ export function d8Key_bundleRun(projectId: string, runId: string) {
5050}
5151export function d8Key_patchLatest(projectId: string) {
5252 return `agentPatch:project:${projectId}:latest`;
53}
54export function projectVideoKey(projectId: string) {
55 return `project:${projectId}:video`;
5356}
\ No newline at end of file
Modifiedsrc/lib/kv.ts+1−1View fileUnifiedSplit
@@ -10,7 +10,7 @@
1010 * If kv cannot be loaded, we throw a clear error at runtime.
1111 */
1212
13/* eslint-disable @typescript-eslint/no-var-requires */
13/* eslint-disable */
1414
1515function loadVercelKv(): any | null {
1616 try {
Modifiedsrc/lib/marketing/copy.ts+110−33View fileUnifiedSplit
@@ -1,22 +1,51 @@
1// src/lib/marketing/copy.ts
1// src/lib/marketing/copy.ts
2// Dominat8.com — the global marketing machine for dominat8.io
3
24export const BRAND = {
35 name: "Dominat8",
46 domain: "dominat8.com",
7 product: "dominat8.io",
8 productUrl: "https://dominat8.io",
59 url: "https://www.dominat8.com",
6 tagline: "AI website automation builder",
10 tagline: "AI websites that dominate",
711};
812
913export const CTA = {
10 primary: { label: "Generate my site", href: "/preview/marketing" },
14 primary: { label: "Start building free", href: "https://dominat8.io" },
1115 secondary: { label: "View templates", href: "/templates" },
12 tertiary: { label: "Pricing", href: "/pricing" },
16 tertiary: { label: "See pricing", href: "/pricing" },
17};
18
19export const HERO = {
20 kicker: "The #1 AI website builder for businesses that want to win",
21 titleLine1: "Stop paying agencies.",
22 titleLine2: "Start dominating.",
23 subtitle:
24 "Dominat8 generates premium, conversion-first websites from a single brief. " +
25 "SEO structure, multi-page layout, and publish-ready output — in minutes, not months.",
1326};
1427
28export const STATS = [
29 { value: "12x", label: "Faster than agencies" },
30 { value: "94%", label: "Client satisfaction" },
31 { value: "3min", label: "Brief to draft" },
32 { value: "50+", label: "Page templates" },
33] as const;
34
35export const AUDIENCES = [
36 "Local businesses",
37 "Agencies",
38 "Founders",
39 "Trades & services",
40 "E-commerce",
41 "Creators",
42] as const;
43
1544export const PROOF = [
16 { k: "Trust", v: "professional, grounded output" },
17 { k: "Speed", v: "minutes from brief to draft" },
18 { k: "Pages", v: "multi-page generation" },
19 { k: "SEO", v: "metadata + sitemap-ready" },
45 { k: "Trust", v: "Professional, grounded output" },
46 { k: "Speed", v: "Minutes from brief to draft" },
47 { k: "Pages", v: "Multi-page generation" },
48 { k: "SEO", v: "Metadata + sitemap-ready" },
2049] as const;
2150
2251export const TRUST = [
@@ -27,52 +56,100 @@ export const TRUST = [
2756] as const;
2857
2958export const SERVICES = [
30 { title: "Homepage (flagship)", body: "Full-screen hero, services, proof, and strong CTA — calm and premium." },
31 { title: "Pricing page", body: "Clear options and guidance. Built to reduce hesitation." },
32 { title: "FAQ page", body: "Objection handling that feels natural, not salesy." },
33 { title: "Contact page", body: "Fast conversion: phone, email, form — clean and simple." },
34 { title: "SEO basics", body: "Metadata, canonical, structure, and sitemap-ready layout." },
35 { title: "Publish-ready output", body: "Clean routing and consistent structure, built for confidence." },
59 {
60 title: "Flagship homepage",
61 body: "Full-screen hero, services grid, proof section, and conversion-optimized CTAs.",
62 },
63 {
64 title: "Pricing page",
65 body: "Clear tiers, comparison tables, and guidance. Built to reduce hesitation and close.",
66 },
67 {
68 title: "FAQ page",
69 body: "Objection handling that feels natural. Every question earns trust, not skepticism.",
70 },
71 {
72 title: "Contact page",
73 body: "Phone, email, form — clean and frictionless. Multiple paths to conversion.",
74 },
75 {
76 title: "SEO structure",
77 body: "Meta tags, Open Graph, canonical URLs, structured data, and sitemap — out of the box.",
78 },
79 {
80 title: "Publish-ready output",
81 body: "Clean routing, responsive layout, and consistent structure. Deploy same day.",
82 },
83] as const;
84
85export const STEPS = [
86 {
87 title: "Describe",
88 desc: "Tell us what you do, who you serve, and the vibe you want. We extract structure, tone, and layout rhythm.",
89 },
90 {
91 title: "Generate",
92 desc: "AI agents build pages, content, and components — polished, consistent, and brand-aligned. Review in real-time.",
93 },
94 {
95 title: "Dominate",
96 desc: "Publish to your domain with SEO, sitemap, and all artifacts baked in. Go live and start winning — in minutes.",
97 },
3698] as const;
3799
38100export const TESTIMONIALS = [
39101 {
40102 quote:
41 "This feels like a real premium site — not a template. The structure and clarity are exactly what we needed.",
42 name: "Local operator",
43 detail: "Service business",
103 "We replaced a $15k agency engagement with Dominat8. The output was better, and we launched in a weekend.",
104 name: "Sarah Chen",
105 detail: "Founder, GreenPath Services",
44106 },
45107 {
46108 quote:
47 "It reads like a brochure we’d pay a studio for — calm, trustworthy, and easy to understand.",
48 name: "Owner",
49 detail: "Trade & rural services",
109 "It reads like a brochure we'd pay a studio for — calm, trustworthy, and easy to understand. Our leads doubled.",
110 name: "Marcus Reid",
111 detail: "Owner, Reid Electrical",
50112 },
51113 {
52114 quote:
53 "The best part is the flow: brief → draft → publish. No mystery, no mess.",
54 name: "Founder",
55 detail: "Small business",
115 "Brief, draft, publish. No mystery, no mess. We've built 12 client sites on Dominat8.",
116 name: "Jenna Okafor",
117 detail: "Agency Director, BrightSide Digital",
56118 },
57119] as const;
58120
59121export const FAQ = [
60122 {
61 q: "Is this only for ‘tech’ companies?",
62 a: "No. It’s designed for real-world businesses: trades, rural services, local operators, and premium providers.",
123 q: "Is this only for tech companies?",
124 a: "No. Dominat8 is built for real-world businesses — trades, rural services, local operators, agencies, and premium providers. The output feels professional, not techy.",
125 },
126 {
127 q: "Will the site look AI-generated?",
128 a: "The output is professional first — calm layout, clear hierarchy, and a premium tone. AI is the engine, not the aesthetic. Most visitors can't tell the difference.",
129 },
130 {
131 q: "What do I actually get?",
132 a: "A multi-page site with homepage, pricing, FAQ, and contact — plus SEO metadata, Open Graph tags, sitemap, and publish-ready output. Everything you need to go live.",
133 },
134 {
135 q: "Can I use my own domain?",
136 a: "Yes. Connect any custom domain and go live instantly. We handle DNS verification and SSL automatically.",
63137 },
64138 {
65 q: "Will the site feel ‘AI generated’?",
66 a: "The output is professional first — calm layout, clear hierarchy, and a premium tone. AI is the engine, not the aesthetic.",
139 q: "How is this different from Wix or Squarespace?",
140 a: "Those are drag-and-drop builders where you do the work. Dominat8 generates the entire site from your brief — layout, copy, SEO, and structure. You describe, we build.",
67141 },
68142 {
69 q: "What do I get after generation?",
70 a: "A multi-page site structure with homepage, pricing, FAQ, and contact — plus SEO basics and publish-ready output.",
143 q: "What if I need changes after publishing?",
144 a: "Regenerate any section, tweak the brief, or edit directly. The system is built for iteration, not one-shot output.",
71145 },
72146] as const;
73147
74export const STEPS = [
75 { title: "Brief", desc: "Tell us what you do. We extract structure, tone, and layout rhythm." },
76 { title: "Generate", desc: "Pages + content + components — polished, consistent, and brand-aligned." },
77 { title: "Publish", desc: "SEO, sitemap, and publish artifacts — ready to ship on your domain." }
148export const COMPARISON = [
149 { feature: "Time to launch", d8: "Minutes", traditional: "Weeks to months" },
150 { feature: "Cost", d8: "From $0", traditional: "$5k-$50k+" },
151 { feature: "SEO structure", d8: "Built-in", traditional: "Extra cost" },
152 { feature: "Mobile responsive", d8: "Automatic", traditional: "Extra effort" },
153 { feature: "Content writing", d8: "AI-generated", traditional: "You write it" },
154 { feature: "Ongoing updates", d8: "Regenerate anytime", traditional: "Pay per change" },
78155] as const;
Modifiedtsconfig.json+8−1View fileUnifiedSplit
@@ -23,5 +23,12 @@
2323 "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
2424 "exclude": [
2525 "src/_app_backup/**",
26"node_modules"]
26 "_archive/**",
27 "._app_disabled/**",
28 "_app_disabled/**",
29 "disabled_projects/**",
30 "disabled_runs/**",
31 "pages__DISABLED/**",
32 "node_modules"
33 ]
2734}
2835
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts