From d155ac73ce6fe3e999735da95ca147d47177fe66 Mon Sep 17 00:00:00 2001
From: Nyk <0xnykcd@googlemail.com>
Date: Tue, 3 Mar 2026 21:14:16 +0700
Subject: [PATCH] feat: align landing branding and isolate vercel root in
landing
---
docs/commercial/checkout-migration.md | 11 +-
landing/README.md | 19 ++
landing/index.html | 144 +++++++++++
landing/styles.css | 331 ++++++++++++++++++++++++++
vercel.json => landing/vercel.json | 8 +-
site/index.html | 152 ------------
site/styles.css | 311 ------------------------
7 files changed, 504 insertions(+), 472 deletions(-)
create mode 100644 landing/README.md
create mode 100644 landing/index.html
create mode 100644 landing/styles.css
rename vercel.json => landing/vercel.json (69%)
delete mode 100644 site/index.html
delete mode 100644 site/styles.css
diff --git a/docs/commercial/checkout-migration.md b/docs/commercial/checkout-migration.md
index a88588d..79b38ea 100644
--- a/docs/commercial/checkout-migration.md
+++ b/docs/commercial/checkout-migration.md
@@ -11,7 +11,14 @@ This runbook moves Dictx Pro sales to `https://dictx.splitlabs.io/buy` while kee
## 1) Domain + Checkout
1. Create `dictx.splitlabs.io` as your Vercel custom domain and serve the landing site there.
-2. Configure branded checkout:
+2. Deploy the dedicated Vercel landing project from `landing/`:
+
+- Root Directory: `landing`
+- Framework Preset: `Other`
+- Build Command: empty
+- Output Directory: empty
+
+3. Configure branded checkout:
- Product name: `Dictx Pro`
- Offer copy: `Signed binaries, auto-updates, and direct support`
@@ -63,7 +70,7 @@ Completed in this repo:
2. Publish FAQ with key points:
- Existing licenses remain honored
-- New purchases go through `https://dictx.splitlabs.io/buy`
+- New purchases go through `https://dictx.splitlabs.io/buy` (redirect target managed in `landing/vercel.json`)
- Support contact stays unchanged
3. Use template:
diff --git a/landing/README.md b/landing/README.md
new file mode 100644
index 0000000..b0175cc
--- /dev/null
+++ b/landing/README.md
@@ -0,0 +1,19 @@
+# Dictx Landing (Vercel)
+
+Deploy this folder as a separate Vercel project.
+
+## Vercel Settings
+
+- Root Directory: `landing`
+- Framework Preset: `Other`
+- Build Command: _(empty)_
+- Output Directory: _(empty)_
+
+## Domain
+
+Attach `dictx.splitlabs.io` to this Vercel project.
+
+## Routes
+
+- `/` serves `landing/index.html`
+- `/buy` redirects to `https://buy.splitlabs.io`
diff --git a/landing/index.html b/landing/index.html
new file mode 100644
index 0000000..e65658f
--- /dev/null
+++ b/landing/index.html
@@ -0,0 +1,144 @@
+
+
+
+
+
+ Dictx | Privacy-first desktop speech-to-text
+
+
+
+
+
+
+
+
+
+ Split Labs • Dictx
+ Voice capture for serious writing workflows.
+
+ Transcribe locally, keep control of your data, and ship faster with a
+ desktop app designed for daily use.
+
+
+
+ $29 one-time • signed binaries • auto-updates • macOS / Windows /
+ Linux
+
+
+
+
+
+ 100% local transcription
+
+ No cloud dependency for core speech-to-text. Your audio stays
+ on-device.
+
+
+
+ Built for daily flow
+
+ Hotkeys, quick model switching, and output controls that fit real
+ writing sessions.
+
+
+
+ Obsidian-ready output
+
+ Export notes with metadata and keep your knowledge workflow tidy.
+
+
+
+
+
+ Free or Pro
+
+
+ Free
+ $0
+
+ - GPL source code
+ - Build from source
+ - All core functionality
+ - Community support
+
+ View source
+
+
+ Pro
+ $29 one-time
+
+ - Signed desktop binaries
+ - In-app auto-updater
+ - Professional checkout
+ - Directly supports development
+
+ Get Dictx Pro
+
+
+
+
+
+ FAQ
+
+ Is Dictx still open source?
+ Yes. Dictx remains GPL-licensed and free to build from source.
+
+
+ What does Pro actually add?
+
+ Pro is the distribution layer: signed installers and updater
+ convenience.
+
+
+
+ Where does /buy go?
+
+ `/buy` redirects to the hosted checkout target configured in
+ `landing/vercel.json`.
+
+
+
+
+
+
+
+
+
+
diff --git a/landing/styles.css b/landing/styles.css
new file mode 100644
index 0000000..d35e506
--- /dev/null
+++ b/landing/styles.css
@@ -0,0 +1,331 @@
+:root {
+ --color-text: #e4e4e7;
+ --color-background: #09090b;
+ --color-background-ui: #6d28d9;
+ --color-logo-primary: #a78bfa;
+ --color-logo-stroke: #c4b5fd;
+ --color-mid-gray: #71717a;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ padding: 0;
+ background:
+ radial-gradient(
+ circle at 15% -10%,
+ rgba(109, 40, 217, 0.3),
+ transparent 40%
+ ),
+ radial-gradient(
+ circle at 85% -20%,
+ rgba(167, 139, 250, 0.22),
+ transparent 42%
+ ),
+ var(--color-background);
+ color: var(--color-text);
+ font-family:
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+}
+
+.noise {
+ position: fixed;
+ inset: 0;
+ pointer-events: none;
+ background-image: radial-gradient(
+ rgba(255, 255, 255, 0.18) 0.28px,
+ transparent 0.28px
+ );
+ background-size: 3px 3px;
+ opacity: 0.05;
+}
+
+.shell {
+ max-width: 1060px;
+ margin: 0 auto;
+ padding: 0 20px;
+}
+
+.nav {
+ padding-top: 22px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 20px;
+}
+
+.wordmark {
+ color: var(--color-logo-primary);
+ text-decoration: none;
+ font-size: 34px;
+ font-weight: 700;
+ letter-spacing: -0.04em;
+}
+
+.menu {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+.menu a {
+ color: color-mix(in srgb, var(--color-text) 86%, transparent);
+ text-decoration: none;
+ font-size: 14px;
+}
+
+.mini-cta {
+ padding: 8px 12px;
+ border-radius: 10px;
+ border: 1px solid color-mix(in srgb, var(--color-mid-gray) 40%, transparent);
+ background: color-mix(in srgb, var(--color-logo-primary) 18%, transparent);
+}
+
+main {
+ padding-top: 30px;
+ padding-bottom: 66px;
+}
+
+.panel {
+ border: 1px solid color-mix(in srgb, var(--color-mid-gray) 28%, transparent);
+ border-radius: 16px;
+ background: linear-gradient(
+ 180deg,
+ color-mix(in srgb, var(--color-logo-primary) 9%, transparent) 0%,
+ color-mix(in srgb, var(--color-background) 96%, transparent) 70%
+ );
+ backdrop-filter: blur(2px);
+}
+
+.hero {
+ padding: 34px;
+}
+
+.kicker {
+ margin: 0;
+ color: color-mix(in srgb, var(--color-logo-primary) 75%, white 25%);
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 0.11em;
+ text-transform: uppercase;
+}
+
+h1 {
+ margin: 10px 0 0;
+ font-size: clamp(34px, 5.8vw, 64px);
+ line-height: 0.98;
+ letter-spacing: -0.03em;
+}
+
+.lede {
+ margin: 18px 0 0;
+ max-width: 720px;
+ color: color-mix(in srgb, var(--color-text) 80%, transparent);
+ font-size: clamp(18px, 2.2vw, 23px);
+}
+
+.actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ margin-top: 26px;
+}
+
+.btn {
+ font-weight: 500;
+ border-radius: 10px;
+ border: 1px solid transparent;
+ text-decoration: none;
+ padding: 10px 16px;
+ font-size: 14px;
+ transition:
+ background-color 0.15s ease,
+ border-color 0.15s ease,
+ transform 0.12s ease;
+}
+
+.btn:hover {
+ transform: translateY(-1px);
+}
+
+.btn.primary {
+ color: #fff;
+ background: var(--color-background-ui);
+ border-color: var(--color-background-ui);
+}
+
+.btn.primary:hover {
+ background: color-mix(in srgb, var(--color-background-ui) 84%, black 16%);
+ border-color: color-mix(in srgb, var(--color-background-ui) 84%, black 16%);
+}
+
+.btn.soft {
+ color: var(--color-text);
+ background: color-mix(in srgb, var(--color-logo-primary) 18%, transparent);
+ border-color: transparent;
+}
+
+.btn.soft:hover,
+.btn.ghost:hover {
+ border-color: var(--color-logo-primary);
+}
+
+.btn.ghost {
+ color: var(--color-text);
+ background: color-mix(in srgb, var(--color-mid-gray) 10%, transparent);
+ border-color: color-mix(in srgb, var(--color-mid-gray) 20%, transparent);
+}
+
+.meta {
+ margin: 14px 0 0;
+ color: color-mix(in srgb, var(--color-text) 58%, transparent);
+ font-size: 13px;
+}
+
+.grid3 {
+ margin-top: 16px;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.card {
+ border: 1px solid color-mix(in srgb, var(--color-mid-gray) 24%, transparent);
+ border-radius: 14px;
+ background: color-mix(in srgb, var(--color-background) 92%, #13131a 8%);
+ padding: 18px;
+}
+
+.card h2 {
+ margin: 0;
+ font-size: 20px;
+}
+
+.card p {
+ margin: 10px 0 0;
+ color: color-mix(in srgb, var(--color-text) 72%, transparent);
+}
+
+.pricing,
+.faq {
+ margin-top: 16px;
+ padding: 22px;
+}
+
+.pricing h2,
+.faq h2 {
+ margin: 0;
+ font-size: 28px;
+ letter-spacing: -0.02em;
+}
+
+.price-grid {
+ margin-top: 14px;
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.plan {
+ border: 1px solid color-mix(in srgb, var(--color-mid-gray) 24%, transparent);
+ border-radius: 14px;
+ padding: 16px;
+ background: color-mix(in srgb, var(--color-background) 88%, #15151f 12%);
+}
+
+.plan.pro {
+ border-color: color-mix(in srgb, var(--color-logo-primary) 48%, transparent);
+ box-shadow: inset 0 0 0 1px
+ color-mix(in srgb, var(--color-logo-primary) 18%, transparent);
+}
+
+.plan-name {
+ margin: 0;
+ font-size: 12px;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: color-mix(in srgb, var(--color-text) 58%, transparent);
+}
+
+.plan-price {
+ margin: 9px 0;
+ font-size: 34px;
+ font-weight: 700;
+}
+
+.plan-price span {
+ font-size: 14px;
+ color: color-mix(in srgb, var(--color-text) 62%, transparent);
+}
+
+.plan ul {
+ list-style: none;
+ padding: 0;
+ margin: 0 0 14px;
+ display: grid;
+ gap: 8px;
+}
+
+.plan li {
+ font-size: 14px;
+ color: color-mix(in srgb, var(--color-text) 78%, transparent);
+ padding-left: 18px;
+ position: relative;
+}
+
+.plan li::before {
+ content: "";
+ position: absolute;
+ width: 7px;
+ height: 7px;
+ border-radius: 999px;
+ left: 0;
+ top: 7px;
+ background: var(--color-logo-primary);
+}
+
+details {
+ border-top: 1px solid
+ color-mix(in srgb, var(--color-mid-gray) 24%, transparent);
+ padding: 12px 0;
+}
+
+summary {
+ cursor: pointer;
+ font-weight: 600;
+}
+
+details p {
+ margin: 9px 0 0;
+ color: color-mix(in srgb, var(--color-text) 72%, transparent);
+}
+
+.foot {
+ padding-bottom: 30px;
+ color: color-mix(in srgb, var(--color-text) 48%, transparent);
+ font-size: 12px;
+}
+
+@media (max-width: 860px) {
+ .nav {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .menu {
+ flex-wrap: wrap;
+ }
+
+ .hero {
+ padding: 24px;
+ }
+
+ .grid3,
+ .price-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/vercel.json b/landing/vercel.json
similarity index 69%
rename from vercel.json
rename to landing/vercel.json
index 570dd66..3ea9a5a 100644
--- a/vercel.json
+++ b/landing/vercel.json
@@ -6,15 +6,9 @@
"statusCode": 307
}
],
- "rewrites": [
- {
- "source": "/",
- "destination": "/site/index.html"
- }
- ],
"headers": [
{
- "source": "/site/(.*)",
+ "source": "/(.*)",
"headers": [
{
"key": "Cache-Control",
diff --git a/site/index.html b/site/index.html
deleted file mode 100644
index 72552b6..0000000
--- a/site/index.html
+++ /dev/null
@@ -1,152 +0,0 @@
-
-
-
-
-
- Dictx | Private speech-to-text for serious writing
-
-
-
-
-
-
-
-
-
-
-
-
- Split Labs
-
- Talk. Edit. Ship.
- Without sending your voice to the cloud.
-
-
- Dictx is desktop speech-to-text for people who write all day. Local
- transcription, polished text output, and pro-grade distribution.
-
-
-
- $29 one-time • signed binary • auto-updates • macOS / Windows / Linux
-
-
-
-
-
- Private by default
-
- All core transcription runs locally on your machine. No voice data
- routed through Dictx servers.
-
-
-
- Fast enough for flow state
-
- Built for real writing sessions, with quick trigger-to-text loops
- and model options for speed or precision.
-
-
-
- Built for Obsidian workflows
-
- Capture, clean, and export voice notes into your vault with
- metadata-rich output.
-
-
-
-
-
- Choose your path
-
-
- Free
- $0
-
- - Full GPL source code
- - Build from source
- - All core transcription features
- - Community support
-
- View Source
-
-
- Pro
- $29 one-time
-
- - Signed installers
- - Built-in auto-updates
- - Professional distribution
- - Funds active development
-
- Get Dictx Pro
-
-
-
-
-
- FAQ
-
- Is Dictx still open source?
- Yes. Dictx remains GPL-licensed and free to build from source.
-
-
- What does Pro include?
-
- Pro is the convenience layer: signed binaries and updater flow so
- you do not have to compile manually.
-
-
-
- How do updates work?
-
- The app checks signed release metadata and applies updates in-app
- when available.
-
-
-
-
-
-
-
-
-
-
diff --git a/site/styles.css b/site/styles.css
deleted file mode 100644
index 2ac35d5..0000000
--- a/site/styles.css
+++ /dev/null
@@ -1,311 +0,0 @@
-:root {
- --bg: #f4efe6;
- --surface: #fdf9f1;
- --text: #191611;
- --muted: #5d5347;
- --line: #d5cab9;
- --brand: #0f7d5b;
- --brand-deep: #0a4f3a;
- --accent: #d96a2d;
-}
-
-* {
- box-sizing: border-box;
-}
-
-body {
- margin: 0;
- color: var(--text);
- background:
- radial-gradient(circle at 15% 10%, #fff5da 0%, transparent 28%),
- radial-gradient(circle at 88% 15%, #dceee8 0%, transparent 35%), var(--bg);
- font-family: "Bricolage Grotesque", sans-serif;
- line-height: 1.4;
-}
-
-.grain {
- pointer-events: none;
- position: fixed;
- inset: 0;
- opacity: 0.08;
- background-image: radial-gradient(#000 0.3px, transparent 0.3px);
- background-size: 3px 3px;
-}
-
-.topbar {
- max-width: 1080px;
- margin: 0 auto;
- padding: 20px 24px;
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.brand {
- color: var(--brand-deep);
- text-decoration: none;
- font-family: "Fraunces", serif;
- font-size: 28px;
-}
-
-.topbar nav {
- display: flex;
- align-items: center;
- gap: 18px;
-}
-
-.topbar nav a {
- color: var(--text);
- text-decoration: none;
- font-size: 14px;
-}
-
-.cta-mini {
- padding: 8px 12px;
- border-radius: 999px;
- background: var(--text);
- color: #fff !important;
-}
-
-main {
- max-width: 1080px;
- margin: 0 auto;
- padding: 18px 24px 80px;
-}
-
-.hero {
- background: linear-gradient(145deg, #fefbf4 0%, #f7efdf 55%, #f4e6d2 100%);
- border: 1px solid var(--line);
- border-radius: 28px;
- padding: 44px;
- box-shadow: 0 20px 45px rgba(48, 34, 10, 0.08);
- animation: rise 500ms ease-out both;
-}
-
-.eyebrow {
- margin: 0;
- color: var(--brand);
- text-transform: uppercase;
- letter-spacing: 0.09em;
- font-size: 12px;
- font-weight: 700;
-}
-
-h1 {
- margin: 10px 0 0;
- font-size: clamp(36px, 6vw, 68px);
- line-height: 0.98;
- letter-spacing: -0.025em;
-}
-
-h1 span {
- display: block;
- font-family: "Fraunces", serif;
- color: var(--brand-deep);
- font-size: 0.7em;
- margin-top: 12px;
-}
-
-.lead {
- max-width: 720px;
- margin: 22px 0 0;
- font-size: clamp(18px, 2.4vw, 24px);
- color: var(--muted);
-}
-
-.hero-actions {
- margin-top: 30px;
- display: flex;
- gap: 14px;
- flex-wrap: wrap;
-}
-
-.btn {
- border: 1px solid var(--text);
- border-radius: 12px;
- padding: 13px 18px;
- text-decoration: none;
- font-weight: 700;
- transition:
- transform 120ms ease,
- box-shadow 120ms ease;
-}
-
-.btn:hover {
- transform: translateY(-2px);
-}
-
-.btn-primary {
- background: var(--brand);
- color: #fff;
- border-color: var(--brand-deep);
- box-shadow: 0 10px 20px rgba(15, 125, 91, 0.25);
-}
-
-.btn-ghost {
- background: transparent;
- color: var(--text);
-}
-
-.subline {
- margin-top: 14px;
- font-size: 13px;
- color: var(--muted);
-}
-
-.pillars {
- margin-top: 26px;
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 14px;
-}
-
-.pillars article,
-.pricing,
-.faq {
- border: 1px solid var(--line);
- border-radius: 18px;
- background: var(--surface);
-}
-
-.pillars article {
- padding: 18px;
-}
-
-.pillars h2 {
- margin: 0;
- font-size: 20px;
-}
-
-.pillars p {
- margin: 10px 0 0;
- color: var(--muted);
-}
-
-.pricing {
- margin-top: 22px;
- padding: 24px;
-}
-
-.pricing > h2,
-.faq h2 {
- margin: 0;
- font-family: "Fraunces", serif;
- font-size: 34px;
-}
-
-.cards {
- margin-top: 16px;
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 14px;
-}
-
-.card {
- border: 1px solid var(--line);
- border-radius: 14px;
- padding: 18px;
- background: #fff;
-}
-
-.card.pro {
- background: linear-gradient(170deg, #f1fff7 0%, #ebf5ff 100%);
-}
-
-.card-label {
- margin: 0;
- text-transform: uppercase;
- letter-spacing: 0.07em;
- font-size: 11px;
- color: var(--muted);
-}
-
-.price {
- margin: 10px 0;
- font-size: 34px;
- font-weight: 800;
-}
-
-.price span {
- font-size: 14px;
- font-weight: 600;
- color: var(--muted);
-}
-
-.card ul {
- margin: 0 0 18px;
- padding: 0;
- list-style: none;
- display: grid;
- gap: 8px;
-}
-
-.card li {
- padding-left: 20px;
- position: relative;
-}
-
-.card li::before {
- content: "◆";
- color: var(--accent);
- position: absolute;
- left: 0;
- top: 0;
- font-size: 11px;
-}
-
-.faq {
- margin-top: 22px;
- padding: 24px;
-}
-
-details {
- border-top: 1px solid var(--line);
- padding: 14px 0;
-}
-
-summary {
- cursor: pointer;
- font-weight: 700;
-}
-
-details p {
- margin: 10px 0 0;
- color: var(--muted);
-}
-
-footer {
- max-width: 1080px;
- margin: 0 auto;
- padding: 0 24px 36px;
- color: var(--muted);
- font-size: 13px;
-}
-
-@keyframes rise {
- from {
- opacity: 0;
- transform: translateY(10px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-@media (max-width: 840px) {
- .hero {
- padding: 28px;
- }
-
- .pillars,
- .cards {
- grid-template-columns: 1fr;
- }
-
- .topbar {
- flex-direction: column;
- gap: 10px;
- align-items: flex-start;
- }
-}