From 171900d34458a84cb4c5b60e488014cf60d2248d Mon Sep 17 00:00:00 2001 From: sjungwon03 Date: Fri, 28 Aug 2026 15:13:49 +0900 Subject: [PATCH 1/3] feat(theorvane): add LabFox, PlanGuard, and Proxmox MCP to the product lineup Expand the studio hub from three to six products, surfacing the newer Theorvane org projects. Each new product links to its GitHub repository (no dedicated site yet), gets an official product mark, an editorial art tint, and an orbit node. Also fix a rename leftover: the hero orbit's OpenScene node used the `--scene` modifier while the CSS still positioned `--video`, leaving the node unplaced. Rebalanced all six nodes into an even hexagon. Co-Authored-By: Claude Opus 4.8 --- apps/theorvane/app/globals.css | 12 ++++-- apps/theorvane/app/page.tsx | 37 ++++++++++++++++++- apps/theorvane/public/products/labfox.svg | 13 +++++++ apps/theorvane/public/products/planguard.svg | 10 +++++ .../theorvane/public/products/proxmox-mcp.svg | 13 +++++++ apps/theorvane/test/home.test.tsx | 16 ++++++++ 6 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 apps/theorvane/public/products/labfox.svg create mode 100644 apps/theorvane/public/products/planguard.svg create mode 100644 apps/theorvane/public/products/proxmox-mcp.svg diff --git a/apps/theorvane/app/globals.css b/apps/theorvane/app/globals.css index fa99233..281d46f 100644 --- a/apps/theorvane/app/globals.css +++ b/apps/theorvane/app/globals.css @@ -49,9 +49,12 @@ h1 em, h2 em { font-family: Georgia, serif; font-weight: 400; } .studio-orbit__core span { color: var(--signal); font: 700 2rem Georgia, serif; } .studio-orbit__node { position: absolute; z-index: 3; display: grid; place-items: center; width: 74px; aspect-ratio: 1; border: 1px solid var(--line); border-radius: 16px; background: #101426; box-shadow: 11px 11px 0 rgb(0 0 0 / 20%); } .studio-orbit__node img { width: 55%; height: 55%; object-fit: contain; } -.studio-orbit__node--mcp { top: 9%; left: 15%; } -.studio-orbit__node--chain { right: 7%; bottom: 20%; } -.studio-orbit__node--video { bottom: 5%; left: 24%; } +.studio-orbit__node--mcp { top: 24.8%; left: 12%; } +.studio-orbit__node--proxmox { top: 24.8%; right: 12%; } +.studio-orbit__node--chain { top: 59.8%; right: 12%; } +.studio-orbit__node--guard { bottom: 7.3%; left: 42.3%; } +.studio-orbit__node--scene { top: 59.8%; left: 12%; } +.studio-orbit__node--fox { top: 7.3%; left: 42.3%; } .studio-orbit p { position: absolute; right: -4%; bottom: -1.5rem; margin: 0; color: var(--mist); font: 800 .62rem var(--font-mono); letter-spacing: .13em; } .studio-system { padding: clamp(5rem, 10vw, 10rem) 0; background: var(--porcelain); color: var(--ink); } @@ -84,6 +87,9 @@ h2 { margin: 0; font-size: clamp(2.7rem, 5.5vw, 5.6rem); line-height: .92; lette .product-editorial__mark img { display: block; width: 62%; height: 62%; object-fit: contain; } .product-editorial__feature:nth-child(2) .product-editorial__art { background: radial-gradient(circle at 36% 30%, rgb(98 213 157 / 22%), transparent 44%), #0a1215; } .product-editorial__feature:nth-child(3) .product-editorial__art { background: radial-gradient(circle at 36% 30%, rgb(169 141 255 / 22%), transparent 42%), radial-gradient(circle at 72% 78%, rgb(98 213 157 / 17%), transparent 34%), #10101a; } +.product-editorial__feature:nth-child(4) .product-editorial__art { background: radial-gradient(circle at 36% 30%, rgb(251 146 60 / 22%), transparent 44%), #171009; } +.product-editorial__feature:nth-child(5) .product-editorial__art { background: radial-gradient(circle at 36% 30%, rgb(244 63 94 / 22%), transparent 44%), #170a10; } +.product-editorial__feature:nth-child(6) .product-editorial__art { background: radial-gradient(circle at 36% 30%, rgb(34 211 238 / 22%), transparent 44%), #081416; } .principles { padding: clamp(5.5rem, 10vw, 10rem) 0; border-top: 1px solid var(--line); } .principles__heading { max-width: 54rem; } diff --git a/apps/theorvane/app/page.tsx b/apps/theorvane/app/page.tsx index ec69240..cf23f5c 100644 --- a/apps/theorvane/app/page.tsx +++ b/apps/theorvane/app/page.tsx @@ -7,6 +7,9 @@ const github = "https://github.com/Theorvane"; const typeMcp = "https://typemcp.theorvane.tech/"; const typeChain = "https://typechain.theorvane.tech/"; const openScene = "https://openscene.app/"; +const labFox = "https://github.com/Theorvane/labfox"; +const planGuard = "https://github.com/Theorvane/planguard"; +const proxmoxMcp = "https://github.com/Theorvane/proxmox-mcp"; const products = [ { @@ -36,6 +39,33 @@ const products = [ number: "03", signal: "Local workflow", }, + { + description: "A cross-platform GitLab workflow client for merge requests, code review, and CI/CD—on Android, iOS, Windows, and macOS, with self-hosted support.", + href: labFox, + icon: "/products/labfox.svg", + iconAlt: "LabFox official product mark", + name: "LabFox", + number: "04", + signal: "GitLab client", + }, + { + description: "An AI-assisted review platform for Terraform pull requests. It surfaces the real risk in an infrastructure change before it merges, and leaves the apply in your hands.", + href: planGuard, + icon: "/products/planguard.svg", + iconAlt: "PlanGuard official product mark", + name: "PlanGuard", + number: "05", + signal: "Infra review", + }, + { + description: "A safety-gated TypeMCP server for the Proxmox VE API. It exposes virtualization operations behind explicit, auditable boundaries instead of raw access.", + href: proxmoxMcp, + icon: "/products/proxmox-mcp.svg", + iconAlt: "Proxmox MCP official product mark", + name: "Proxmox MCP", + number: "06", + signal: "Safety-gated MCP", + }, ] as const; const schema = { @@ -69,6 +99,9 @@ export default function HomePage() {
+
+
+

THE SYSTEM STAYS YOURS

@@ -82,7 +115,7 @@ export default function HomePage() {
-

Product index / 02

Choose a focused tool.

Three products. Three concrete boundaries. No platform-shaped abstraction around them.

+

Product index / 02

Choose a focused tool.

Six products. Six concrete boundaries. No platform-shaped abstraction around them.

@@ -93,6 +126,6 @@ export default function HomePage() {

Theorvane / Open by default

Explore the products.
Keep the system yours.

Explore products

TypeScript-first · Protocol-aware · Open-source

-
THEORVANE®

Independent software studio building small, durable developer products.

© {new Date().getFullYear()} Theorvane.Built with intent.
+
THEORVANE®

Independent software studio building small, durable developer products.

© {new Date().getFullYear()} Theorvane.Built with intent.
; } diff --git a/apps/theorvane/public/products/labfox.svg b/apps/theorvane/public/products/labfox.svg new file mode 100644 index 0000000..61120da --- /dev/null +++ b/apps/theorvane/public/products/labfox.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/apps/theorvane/public/products/planguard.svg b/apps/theorvane/public/products/planguard.svg new file mode 100644 index 0000000..082202f --- /dev/null +++ b/apps/theorvane/public/products/planguard.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/theorvane/public/products/proxmox-mcp.svg b/apps/theorvane/public/products/proxmox-mcp.svg new file mode 100644 index 0000000..50d8a54 --- /dev/null +++ b/apps/theorvane/public/products/proxmox-mcp.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/apps/theorvane/test/home.test.tsx b/apps/theorvane/test/home.test.tsx index 9e9160f..2c599f8 100644 --- a/apps/theorvane/test/home.test.tsx +++ b/apps/theorvane/test/home.test.tsx @@ -33,6 +33,9 @@ describe("Theorvane homepage", () => { expect(footer.querySelector('a[href="https://typemcp.theorvane.tech/"]')).toBeTruthy(); expect(footer.querySelector('a[href="https://typechain.theorvane.tech/"]')).toBeTruthy(); expect(footer.querySelector('a[href="https://openscene.app/"]')).toBeTruthy(); + expect(footer.querySelector('a[href="https://github.com/Theorvane/labfox"]')).toBeTruthy(); + expect(footer.querySelector('a[href="https://github.com/Theorvane/planguard"]')).toBeTruthy(); + expect(footer.querySelector('a[href="https://github.com/Theorvane/proxmox-mcp"]')).toBeTruthy(); }); it("offers a product index with all canonical destinations", () => { @@ -78,4 +81,17 @@ describe("Theorvane homepage", () => { expect(screen.getByText(/local-first, open-source video editor/i)).toBeTruthy(); expect(screen.getByText(/no cloud uploads, accounts, or analytics/i)).toBeTruthy(); }); + + it("features the expanded product lineup with GitHub destinations", () => { + render(createElement(HomePage)); + + for (const [name, href] of [ + ["LabFox", "https://github.com/Theorvane/labfox"], + ["PlanGuard", "https://github.com/Theorvane/planguard"], + ["Proxmox MCP", "https://github.com/Theorvane/proxmox-mcp"], + ] as const) { + expect(screen.getByRole("heading", { name })).toBeTruthy(); + expect(screen.getByRole("link", { name: `Explore ${name} ↗` }).getAttribute("href")).toBe(href); + } + }); }); From be1f62925505b58377af7e87631328d079c78e33 Mon Sep 17 00:00:00 2001 From: sjungwon03 Date: Fri, 28 Aug 2026 21:53:13 +0900 Subject: [PATCH 2/3] feat(sites): add dedicated LabFox, PlanGuard, and Proxmox MCP product sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scaffold three Next.js product landing sites, mirroring the existing site apps (@theorvane/ui + design-tokens, per-app SEO: layout metadata, sitemap, robots, OG image, generated icon, home + seo tests): - apps/labfox → labfox.theorvane.tech (amber; GitLab client) - apps/planguard → planguard.theorvane.tech (rose; Terraform PR review) - apps/proxmox → proxmox.theorvane.tech (cyan; safety-gated MCP) Each is a single-page landing with a product-specific palette, hero, feature grid, and a primary CTA to the product's GitHub repo. Analytics (Rybbit) is left out pending a per-site id. The hub still links these three products to GitHub; flip to the subdomains once DNS is live. Co-Authored-By: Claude Opus 4.8 --- apps/labfox/app/globals.css | 106 +++++++++++++++++++++++ apps/labfox/app/icon.svg | 13 +++ apps/labfox/app/layout.tsx | 32 +++++++ apps/labfox/app/opengraph-image.tsx | 55 ++++++++++++ apps/labfox/app/page.tsx | 92 ++++++++++++++++++++ apps/labfox/app/robots.ts | 11 +++ apps/labfox/app/sitemap.ts | 9 ++ apps/labfox/next-env.d.ts | 6 ++ apps/labfox/next.config.ts | 5 ++ apps/labfox/package.json | 8 ++ apps/labfox/public/logo.svg | 13 +++ apps/labfox/test/home.test.tsx | 44 ++++++++++ apps/labfox/test/seo.test.tsx | 42 ++++++++++ apps/labfox/tsconfig.json | 6 ++ apps/labfox/vitest.config.ts | 8 ++ apps/planguard/app/globals.css | 109 ++++++++++++++++++++++++ apps/planguard/app/icon.svg | 10 +++ apps/planguard/app/layout.tsx | 32 +++++++ apps/planguard/app/opengraph-image.tsx | 55 ++++++++++++ apps/planguard/app/page.tsx | 92 ++++++++++++++++++++ apps/planguard/app/robots.ts | 11 +++ apps/planguard/app/sitemap.ts | 9 ++ apps/planguard/next-env.d.ts | 6 ++ apps/planguard/next.config.ts | 5 ++ apps/planguard/package.json | 8 ++ apps/planguard/public/logo.svg | 10 +++ apps/planguard/test/home.test.tsx | 44 ++++++++++ apps/planguard/test/seo.test.tsx | 42 ++++++++++ apps/planguard/tsconfig.json | 6 ++ apps/planguard/vitest.config.ts | 8 ++ apps/proxmox/app/globals.css | 111 +++++++++++++++++++++++++ apps/proxmox/app/icon.svg | 14 ++++ apps/proxmox/app/layout.tsx | 32 +++++++ apps/proxmox/app/opengraph-image.tsx | 55 ++++++++++++ apps/proxmox/app/page.tsx | 98 ++++++++++++++++++++++ apps/proxmox/app/robots.ts | 11 +++ apps/proxmox/app/sitemap.ts | 9 ++ apps/proxmox/next-env.d.ts | 6 ++ apps/proxmox/next.config.ts | 5 ++ apps/proxmox/package.json | 8 ++ apps/proxmox/public/logo.svg | 13 +++ apps/proxmox/test/home.test.tsx | 44 ++++++++++ apps/proxmox/test/seo.test.tsx | 42 ++++++++++ apps/proxmox/tsconfig.json | 6 ++ apps/proxmox/vitest.config.ts | 8 ++ package-lock.json | 92 ++++++++++++++------ 46 files changed, 1425 insertions(+), 26 deletions(-) create mode 100644 apps/labfox/app/globals.css create mode 100644 apps/labfox/app/icon.svg create mode 100644 apps/labfox/app/layout.tsx create mode 100644 apps/labfox/app/opengraph-image.tsx create mode 100644 apps/labfox/app/page.tsx create mode 100644 apps/labfox/app/robots.ts create mode 100644 apps/labfox/app/sitemap.ts create mode 100644 apps/labfox/next-env.d.ts create mode 100644 apps/labfox/next.config.ts create mode 100644 apps/labfox/package.json create mode 100644 apps/labfox/public/logo.svg create mode 100644 apps/labfox/test/home.test.tsx create mode 100644 apps/labfox/test/seo.test.tsx create mode 100644 apps/labfox/tsconfig.json create mode 100644 apps/labfox/vitest.config.ts create mode 100644 apps/planguard/app/globals.css create mode 100644 apps/planguard/app/icon.svg create mode 100644 apps/planguard/app/layout.tsx create mode 100644 apps/planguard/app/opengraph-image.tsx create mode 100644 apps/planguard/app/page.tsx create mode 100644 apps/planguard/app/robots.ts create mode 100644 apps/planguard/app/sitemap.ts create mode 100644 apps/planguard/next-env.d.ts create mode 100644 apps/planguard/next.config.ts create mode 100644 apps/planguard/package.json create mode 100644 apps/planguard/public/logo.svg create mode 100644 apps/planguard/test/home.test.tsx create mode 100644 apps/planguard/test/seo.test.tsx create mode 100644 apps/planguard/tsconfig.json create mode 100644 apps/planguard/vitest.config.ts create mode 100644 apps/proxmox/app/globals.css create mode 100644 apps/proxmox/app/icon.svg create mode 100644 apps/proxmox/app/layout.tsx create mode 100644 apps/proxmox/app/opengraph-image.tsx create mode 100644 apps/proxmox/app/page.tsx create mode 100644 apps/proxmox/app/robots.ts create mode 100644 apps/proxmox/app/sitemap.ts create mode 100644 apps/proxmox/next-env.d.ts create mode 100644 apps/proxmox/next.config.ts create mode 100644 apps/proxmox/package.json create mode 100644 apps/proxmox/public/logo.svg create mode 100644 apps/proxmox/test/home.test.tsx create mode 100644 apps/proxmox/test/seo.test.tsx create mode 100644 apps/proxmox/tsconfig.json create mode 100644 apps/proxmox/vitest.config.ts diff --git a/apps/labfox/app/globals.css b/apps/labfox/app/globals.css new file mode 100644 index 0000000..6350c7b --- /dev/null +++ b/apps/labfox/app/globals.css @@ -0,0 +1,106 @@ +:root { + color-scheme: dark; + --ground: #100b06; + --raised: #191009; + --raised-2: #22160c; + --porcelain: #f7f0e6; + --mist: #b8a892; + --line: rgb(247 240 230 / 14%); + --accent: #f97316; + --accent-2: #fbbf24; + --accent-soft: rgb(249 115 22 / 16%); + --ok: #62d59d; + --run: #fbbf24; +} + +* { box-sizing: border-box; } +html { scroll-behavior: smooth; background: var(--ground); } +body { margin: 0; overflow-x: hidden; background: var(--ground); color: var(--porcelain); font-family: var(--font-sans); } +a { color: inherit; text-underline-offset: 4px; } +a:focus-visible, button:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; } +@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } } + +.shell { width: min(1140px, calc(100% - 3rem)); margin: auto; } +.skip-link { position: fixed; left: 1rem; top: -5rem; z-index: 20; padding: .7rem 1rem; background: var(--accent); color: #1a1006; font-weight: 700; text-decoration: none; } +.skip-link:focus { top: 1rem; } + +.eyebrow { margin: 0; color: var(--accent); font: 800 .68rem var(--font-mono); letter-spacing: .14em; text-transform: uppercase; } +.wordmark { font: 800 1rem var(--font-mono); letter-spacing: .12em; text-decoration: none; } +.wordmark span { color: var(--accent); } + +.button { display: inline-flex; align-items: center; justify-content: center; min-height: 46px; border: 1px solid var(--line); padding: .7rem 1.15rem; color: var(--porcelain); font: 800 .72rem var(--font-mono); letter-spacing: .04em; text-decoration: none; border-radius: 10px; } +.button:hover { border-color: rgb(247 240 230 / 42%); } +.button.primary { border-color: var(--accent); background: linear-gradient(135deg, var(--accent-2), var(--accent)); color: #1a1006; } +.button.ghost { min-height: 40px; padding: .5rem .8rem; } + +.site-header { position: relative; z-index: 4; min-height: 74px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); } +.site-header nav { display: flex; align-items: center; gap: .5rem; } +.site-header nav a:not(.button) { min-height: 44px; display: grid; place-items: center; padding: 0 .6rem; font: 700 .72rem var(--font-mono); letter-spacing: .04em; text-decoration: none; color: var(--mist); } +.site-header nav a:not(.button):hover { color: var(--porcelain); } + +.hero { position: relative; overflow: hidden; border-bottom: 1px solid var(--line); background: radial-gradient(circle at 82% 12%, rgb(249 115 22 / 22%), transparent 42%), radial-gradient(circle at 96% 84%, rgb(251 191 36 / 12%), transparent 40%), var(--ground); } +.hero__grid { display: grid; grid-template-columns: minmax(0, 1.02fr) minmax(20rem, .82fr); gap: clamp(2rem, 6vw, 5rem); align-items: center; min-height: min(680px, calc(100vh - 74px)); } +.hero__copy { padding: clamp(4.5rem, 9vw, 8rem) 0; } +h1 { max-width: 12ch; margin: 1.2rem 0 1.5rem; font-size: clamp(3.4rem, 7vw, 6.4rem); line-height: .9; letter-spacing: -.06em; } +h1 em, h2 em { font-family: Georgia, serif; font-weight: 400; font-style: italic; color: var(--accent-2); } +.lede { max-width: 40rem; margin: 0; color: var(--mist); font-size: clamp(1.05rem, 1.5vw, 1.22rem); line-height: 1.65; } +.actions { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 2.2rem; } +.hero__meta { margin: 1.75rem 0 0; color: var(--mist); font: 700 .68rem var(--font-mono); letter-spacing: .1em; } + +.hero__panel { position: relative; justify-self: end; width: min(100%, 30rem); padding: 1.25rem; border: 1px solid var(--line); border-radius: 18px; background: linear-gradient(180deg, var(--raised-2), var(--raised)); box-shadow: 0 30px 90px rgb(0 0 0 / 45%); } +.hero__panel-bar { display: flex; gap: .5rem; margin-bottom: 1.25rem; } +.hero__panel-bar span { width: 11px; height: 11px; border-radius: 50%; background: rgb(247 240 230 / 20%); } +.hero__panel-bar span:first-child { background: var(--accent); } +.hero__mr-label { margin: 0 0 .4rem; color: var(--accent); font: 700 .66rem var(--font-mono); letter-spacing: .08em; } +.hero__mr h2 { margin: 0; font-size: 1.5rem; line-height: 1.1; letter-spacing: -.02em; } +.hero__mr-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-top: 1rem; color: var(--mist); font: 700 .66rem var(--font-mono); } +.pill { padding: .28rem .55rem; border-radius: 999px; font: 800 .62rem var(--font-mono); letter-spacing: .04em; } +.pill--open { background: var(--accent-soft); color: var(--accent-2); } +.pill--pipe { background: rgb(98 213 157 / 16%); color: var(--ok); } +.hero__pipeline { display: flex; align-items: center; gap: .5rem; margin-top: 1.25rem; padding-top: 1.1rem; border-top: 1px solid var(--line); color: var(--mist); font: 700 .68rem var(--font-mono); letter-spacing: .06em; } +.dot { width: 9px; height: 9px; border-radius: 50%; } +.dot--ok { background: var(--ok); } +.dot--run { background: var(--run); box-shadow: 0 0 0 4px rgb(251 191 36 / 18%); } + +.section-intro { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: 1.5rem 3rem; align-items: end; } +.section-intro .eyebrow { grid-column: 1 / -1; } +h2 { margin: 0; font-size: clamp(2.4rem, 5vw, 4.6rem); line-height: .94; letter-spacing: -.06em; } +.section-intro > p:last-child { margin: 0; color: var(--mist); line-height: 1.65; align-self: end; } + +.features { padding: clamp(5rem, 10vw, 9rem) 0; } +.feature-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; margin-top: 3.5rem; border: 1px solid var(--line); background: var(--line); } +.feature { position: relative; padding: 2rem 1.75rem 2.25rem; background: var(--raised); transition: background .25s ease; } +.feature:hover { background: var(--raised-2); } +.feature__num { color: var(--accent); font: 800 .8rem var(--font-mono); letter-spacing: .08em; } +.feature h3 { margin: 1.5rem 0 .8rem; font-size: 1.5rem; line-height: 1.05; letter-spacing: -.02em; } +.feature p { margin: 0; color: var(--mist); font-size: .98rem; line-height: 1.6; } + +.platforms { padding: 0 0 clamp(5rem, 10vw, 9rem); } +.platform-rail { display: flex; flex-wrap: wrap; gap: 1px; margin: 3rem 0 0; padding: 0; list-style: none; border: 1px solid var(--line); background: var(--line); } +.platform-rail li { flex: 1 1 8rem; padding: 1.6rem 1.25rem; background: var(--raised); font: 800 1.1rem var(--font-sans); letter-spacing: -.01em; } +.platform-rail li::before { content: "→ "; color: var(--accent); } + +.close { padding: clamp(5rem, 10vw, 8rem) 0; border-top: 1px solid var(--line); background: radial-gradient(circle at 12% 20%, rgb(249 115 22 / 14%), transparent 42%), var(--ground); } +.close__grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 2rem; align-items: center; } +.close h2 { max-width: 12ch; } +.close__grid > div { display: flex; flex-direction: column; align-items: flex-start; gap: 1rem; } +.close__grid p { margin: 0; color: var(--mist); font: 700 .7rem var(--font-mono); letter-spacing: .06em; } + +.site-footer { border-top: 1px solid var(--line); padding: clamp(3rem, 6vw, 5rem) 0 2rem; } +.site-footer__grid { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 2.5rem; } +.site-footer__grid > div:first-child p { max-width: 26rem; color: var(--mist); line-height: 1.6; } +.site-footer nav { display: flex; gap: 3rem; } +.site-footer nav div { display: flex; flex-direction: column; gap: .55rem; } +.site-footer nav strong { margin-bottom: .3rem; font: 800 .7rem var(--font-mono); letter-spacing: .1em; text-transform: uppercase; color: var(--porcelain); } +.site-footer nav a { color: var(--mist); font-size: .92rem; text-decoration: none; } +.site-footer nav a:hover { color: var(--porcelain); } +.site-footer__legal { display: flex; justify-content: space-between; margin-top: 3rem; padding-top: 1.5rem; border-top: 1px solid var(--line); color: var(--mist); font: 700 .66rem var(--font-mono); letter-spacing: .06em; } + +@media (max-width: 900px) { + .hero__grid { grid-template-columns: 1fr; } + .hero__panel { justify-self: stretch; margin-bottom: 4rem; } + .section-intro { grid-template-columns: 1fr; } + .feature-grid, .close__grid, .site-footer__grid { grid-template-columns: 1fr; } + .site-footer nav { gap: 2rem; } + .site-footer__legal { flex-direction: column; gap: .5rem; } +} diff --git a/apps/labfox/app/icon.svg b/apps/labfox/app/icon.svg new file mode 100644 index 0000000..cbbfccc --- /dev/null +++ b/apps/labfox/app/icon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/apps/labfox/app/layout.tsx b/apps/labfox/app/layout.tsx new file mode 100644 index 0000000..a8fdcf0 --- /dev/null +++ b/apps/labfox/app/layout.tsx @@ -0,0 +1,32 @@ +import type { Metadata } from "next"; +import "@theorvane/design-tokens/tokens.css"; +import "./globals.css"; + +const siteUrl = new URL("https://labfox.theorvane.tech"); +const title = "LabFox — the GitLab workflow client for every platform"; +const description = "LabFox is a cross-platform GitLab client for merge requests, code review, and CI/CD — on Android, iOS, Windows, and macOS, with first-class self-hosted support."; + +export const metadata: Metadata = { + metadataBase: siteUrl, + title, + description, + applicationName: "LabFox", + keywords: ["LabFox", "GitLab", "GitLab client", "merge requests", "code review", "CI/CD", "self-hosted", "Flutter", "cross-platform"], + authors: [{ name: "Theorvane", url: "https://theorvane.tech" }], + creator: "Theorvane", + publisher: "Theorvane", + category: "technology", + alternates: { canonical: "/" }, + robots: { index: true, follow: true, googleBot: { index: true, follow: true, "max-image-preview": "large", "max-snippet": -1, "max-video-preview": -1 } }, + openGraph: { type: "website", url: siteUrl, title, description, siteName: "LabFox", locale: "en_US" }, + twitter: { card: "summary_large_image", title, description }, +}; + +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + + {/* Analytics: add the Rybbit script here once a LabFox site id is provisioned. */} + {children} + + ); +} diff --git a/apps/labfox/app/opengraph-image.tsx b/apps/labfox/app/opengraph-image.tsx new file mode 100644 index 0000000..ebdef30 --- /dev/null +++ b/apps/labfox/app/opengraph-image.tsx @@ -0,0 +1,55 @@ +import { ImageResponse } from "next/og"; + +export const alt = "LabFox — the GitLab workflow client for every platform"; +export const size = { width: 1200, height: 630 }; +export const contentType = "image/png"; + +// The site's own warm dark palette, echoing the amber accent on the landing page. +const ink = "#f7f0e6"; +const ground = "#100b06"; +const glow = "#2a1608"; +const accent = "#f97316"; +const muted = "#b8a892"; + +export default function OpenGraphImage() { + return new ImageResponse( + ( +
+
+
+
LABFOX
+
+ +
+
+ GitLab workflow client +
+
+
Your GitLab,
+
in hand.
+
+
+ Merge requests, code review, and pipelines — native on every platform, self-hosted included. +
+
+ +
+
labfox.theorvane.tech
+
Android · iOS · Windows · macOS
+
+
+ ), + size, + ); +} diff --git a/apps/labfox/app/page.tsx b/apps/labfox/app/page.tsx new file mode 100644 index 0000000..82fcecb --- /dev/null +++ b/apps/labfox/app/page.tsx @@ -0,0 +1,92 @@ +import { ExternalLink, SkipLink } from "@theorvane/ui"; + +const github = "https://github.com/Theorvane/labfox"; +const studio = "https://theorvane.tech/"; +const siteUrl = "https://labfox.theorvane.tech/"; + +const features = [ + { + number: "01", + title: "Merge requests, reviewed in full", + body: "Open, filter, and act on merge requests with threaded discussion, diffs, and approvals — the whole review loop, not a read-only mirror.", + }, + { + number: "02", + title: "Pipelines you can actually watch", + body: "Follow CI/CD jobs as they run, read logs inline, and retry or cancel a stage without leaving the request you were reviewing.", + }, + { + number: "03", + title: "Self-hosted is first-class", + body: "Point LabFox at gitlab.com or your own instance. Personal and project tokens stay on the device; nothing is proxied through us.", + }, + { + number: "04", + title: "One client, every platform", + body: "A single Flutter codebase ships a native-feeling app for Android, iOS, Windows, and macOS — the same workflow wherever you work.", + }, +] as const; + +const platforms = ["Android", "iOS", "Windows", "macOS"] as const; + +const schema = { + "@context": "https://schema.org", + "@type": "SoftwareApplication", + name: "LabFox", + url: siteUrl, + applicationCategory: "DeveloperApplication", + operatingSystem: "Android, iOS, Windows, macOS", + description: "A cross-platform GitLab workflow client for merge requests, code review, and CI/CD, with first-class self-hosted support.", + offers: { "@type": "Offer", price: "0", priceCurrency: "USD" }, + publisher: { "@type": "Organization", name: "Theorvane", url: studio }, +}; + +export default function HomePage() { + return <> + +