From 42780d82d793f0d0ce3d5f2c15ae4ae0440c4115 Mon Sep 17 00:00:00 2001 From: Jungwon Sohn Date: Sat, 22 Aug 2026 03:49:14 +0900 Subject: [PATCH 1/7] feat: publish Meta app-ads.txt for openscene.app (#175) Serve the Meta-supplied authorization record at /app-ads.txt so Meta can verify authorized in-app ad inventory for OpenScene. Co-authored-by: Claude Opus 5 --- apps/openscene/public/app-ads.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/openscene/public/app-ads.txt diff --git a/apps/openscene/public/app-ads.txt b/apps/openscene/public/app-ads.txt new file mode 100644 index 0000000..b792e34 --- /dev/null +++ b/apps/openscene/public/app-ads.txt @@ -0,0 +1 @@ +facebook.com, 1422347866415580, DIRECT, c3e20eee3f780d68 From 26a46778f0bc9bbecae4e28d68ecaf9be09e8cc9 Mon Sep 17 00:00:00 2001 From: Jungwon Sohn Date: Sun, 23 Aug 2026 13:50:42 +0900 Subject: [PATCH 2/7] feat: authorize Google ad inventory in app-ads.txt (#179) Add the Google publisher record alongside the existing Meta record so both partners are authorized to sell OpenScene in-app inventory. Co-authored-by: Claude Opus 5 --- apps/openscene/public/app-ads.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openscene/public/app-ads.txt b/apps/openscene/public/app-ads.txt index b792e34..7a9c136 100644 --- a/apps/openscene/public/app-ads.txt +++ b/apps/openscene/public/app-ads.txt @@ -1 +1,2 @@ facebook.com, 1422347866415580, DIRECT, c3e20eee3f780d68 +google.com, pub-7219451639389409, DIRECT, f08c47fec0942fa0 From b4883391f01e35b2d1c762f133990aab66369ae5 Mon Sep 17 00:00:00 2001 From: Jungwon Sohn Date: Mon, 24 Aug 2026 03:46:41 +0900 Subject: [PATCH 3/7] docs: correct OpenScene privacy banner location (#185) Co-authored-by: jungwon --- apps/openscene/lib/docs/content.en.ts | 2 ++ apps/openscene/test/docs.test.tsx | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/apps/openscene/lib/docs/content.en.ts b/apps/openscene/lib/docs/content.en.ts index f29843c..a9cde74 100644 --- a/apps/openscene/lib/docs/content.en.ts +++ b/apps/openscene/lib/docs/content.en.ts @@ -345,6 +345,8 @@ const dataAndPrivacy: DocPage = { "**No telemetry.** No analytics, crash reporting, or usage tracking", "**No background network calls.** The app talks to a provider only when you ask it to, using a provider you connected", ] }, + { kind: "heading", text: "Advertising disclosure" }, + { kind: "paragraph", text: "A banner ad may appear above the tab bar on project screens (the timeline, generation and library tabs), and an interstitial ad may appear only after a video export completes successfully." }, { kind: "heading", text: "Where your data lives" }, { kind: "paragraph", text: "Projects are folders you choose. Assets, chat history, and generated results are written inside them. App-managed projects and window recordings live under Electron user data, and recordings can be redirected with `VIDEO_TOOL_RECORDINGS_DIR`." }, { kind: "heading", text: "The process boundary" }, diff --git a/apps/openscene/test/docs.test.tsx b/apps/openscene/test/docs.test.tsx index e2d505b..6eef3e7 100644 --- a/apps/openscene/test/docs.test.tsx +++ b/apps/openscene/test/docs.test.tsx @@ -25,6 +25,13 @@ describe("OpenScene documentation routes", () => { expect(main.getByRole("link", { name: "Install and run" }).getAttribute("href")).toBe("/docs/install"); }); + it("renders the English data and privacy ad disclosure", async () => { + await renderRoute(["data-and-privacy"]); + const main = screen.getByRole("main"); + expect(main.textContent).toContain("A banner ad may appear above the tab bar on project screens (the timeline, generation and library tabs), and an interstitial ad may appear only after a video export completes successfully."); + expect(main.textContent).not.toContain("in Settings"); + }); + it("renders a Korean page from its own path", async () => { await renderRoute(["ko", "install"]); expect(screen.getByRole("heading", { level: 1, name: "설치와 실행" })).toBeDefined(); From 171900d34458a84cb4c5b60e488014cf60d2248d Mon Sep 17 00:00:00 2001 From: sjungwon03 Date: Fri, 28 Aug 2026 15:13:49 +0900 Subject: [PATCH 4/7] 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 5/7] 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 <> + +