diff --git a/.env.example b/.env.example
index efdf7b3..738916e 100644
--- a/.env.example
+++ b/.env.example
@@ -39,3 +39,6 @@ SMTP_FROM=noreply@corrosionmgmt.com
# Server
API_PORT=3000
FRONTEND_URL=http://localhost:5174
+
+# Frontend (Vite — must be prefixed with VITE_)
+VITE_PANEL_URL=https://panel.corrosionmgmt.com
diff --git a/docker/nginx.conf b/docker/nginx.conf
index 6c6f19d..18c8895 100644
--- a/docker/nginx.conf
+++ b/docker/nginx.conf
@@ -67,6 +67,28 @@ http {
}
}
+ # Marketing site — corrosionmgmt.com (bare domain)
+ server {
+ listen 80;
+ server_name corrosionmgmt.com;
+
+ # Early access signup API
+ location /api/early-access/ {
+ limit_req zone=api burst=10 nodelay;
+ proxy_pass http://api;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
+ # SPA
+ location / {
+ root /usr/share/nginx/html;
+ try_files $uri $uri/ /index.html;
+ }
+ }
+
# Wildcard server — *.corrosionmgmt.com (public server sites)
server {
listen 80;
diff --git a/frontend/src/components/layout/MarketingLayout.vue b/frontend/src/components/layout/MarketingLayout.vue
index 966c9fb..3f19ec2 100644
--- a/frontend/src/components/layout/MarketingLayout.vue
+++ b/frontend/src/components/layout/MarketingLayout.vue
@@ -1,5 +1,7 @@
@@ -7,19 +9,19 @@ import { RouterView, RouterLink } from 'vue-router'
@@ -36,22 +38,22 @@ import { RouterView, RouterLink } from 'vue-router'
Product
- How It Works
- Pricing
- Roadmap
+ How It Works
+ Pricing
+ Roadmap
Company
- About
+ About
Status
diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts
index 8fad6b5..b9ff5a1 100644
--- a/frontend/src/router/index.ts
+++ b/frontend/src/router/index.ts
@@ -1,7 +1,53 @@
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
-const routes: RouteRecordRaw[] = [
+// ---------------------------------------------------------------------------
+// Domain detection — runs once at module load
+// ---------------------------------------------------------------------------
+const hostname = typeof window !== 'undefined' ? window.location.hostname : ''
+const isMarketingDomain = hostname === 'corrosionmgmt.com'
+
+// ---------------------------------------------------------------------------
+// Marketing page children — shared between both domain route sets
+// ---------------------------------------------------------------------------
+const marketingChildren: RouteRecordRaw[] = [
+ {
+ path: '',
+ name: 'landing',
+ component: () => import('@/views/marketing/LandingView.vue'),
+ },
+ {
+ path: 'pricing',
+ name: 'pricing',
+ component: () => import('@/views/marketing/PricingView.vue'),
+ },
+ {
+ path: 'how-it-works',
+ name: 'how-it-works',
+ component: () => import('@/views/marketing/HowItWorksView.vue'),
+ },
+ {
+ path: 'faq',
+ name: 'faq',
+ component: () => import('@/views/marketing/FaqView.vue'),
+ },
+ {
+ path: 'roadmap',
+ name: 'roadmap',
+ component: () => import('@/views/marketing/RoadmapView.vue'),
+ },
+ {
+ path: 'early-access',
+ name: 'early-access',
+ component: () => import('@/views/marketing/EarlyAccessView.vue'),
+ },
+]
+
+// ---------------------------------------------------------------------------
+// Panel domain routes — panel.corrosionmgmt.com, localhost, etc.
+// Existing behavior, unchanged.
+// ---------------------------------------------------------------------------
+const panelRoutes: RouteRecordRaw[] = [
// Auth routes (no layout)
{
path: '/login',
@@ -113,7 +159,7 @@ const routes: RouteRecordRaw[] = [
name: 'settings',
component: () => import('@/views/admin/SettingsView.vue'),
},
- // Platform Admin views (super-admin only, guarded in components)
+ // Platform Admin views (super-admin only)
{
path: 'admin',
name: 'platform-admin',
@@ -165,42 +211,11 @@ const routes: RouteRecordRaw[] = [
],
},
- // Marketing site (public, no auth)
+ // Marketing site (accessible on panel domain at /site/*)
{
path: '/site',
component: () => import('@/components/layout/MarketingLayout.vue'),
- children: [
- {
- path: '',
- name: 'landing',
- component: () => import('@/views/marketing/LandingView.vue'),
- },
- {
- path: 'pricing',
- name: 'pricing',
- component: () => import('@/views/marketing/PricingView.vue'),
- },
- {
- path: 'how-it-works',
- name: 'how-it-works',
- component: () => import('@/views/marketing/HowItWorksView.vue'),
- },
- {
- path: 'faq',
- name: 'faq',
- component: () => import('@/views/marketing/FaqView.vue'),
- },
- {
- path: 'roadmap',
- name: 'roadmap',
- component: () => import('@/views/marketing/RoadmapView.vue'),
- },
- {
- path: 'early-access',
- name: 'early-access',
- component: () => import('@/views/marketing/EarlyAccessView.vue'),
- },
- ],
+ children: marketingChildren,
},
// Status page
@@ -211,12 +226,52 @@ const routes: RouteRecordRaw[] = [
},
]
+// ---------------------------------------------------------------------------
+// Marketing domain routes — corrosionmgmt.com (bare domain)
+// Marketing pages at root. No admin/auth routes.
+// ---------------------------------------------------------------------------
+const marketingRoutes: RouteRecordRaw[] = [
+ // Marketing layout at /
+ {
+ path: '/',
+ component: () => import('@/components/layout/MarketingLayout.vue'),
+ children: marketingChildren,
+ },
+
+ // Backward compat: /site/* → root-level equivalents
+ {
+ path: '/site/:pathMatch(.*)*',
+ redirect: (to) => {
+ const sub = to.params.pathMatch
+ if (!sub || (Array.isArray(sub) && sub.length === 0)) return '/'
+ const subPath = Array.isArray(sub) ? sub.join('/') : sub
+ return subPath ? `/${subPath}` : '/'
+ },
+ },
+
+ // Status page
+ {
+ path: '/status',
+ name: 'status',
+ component: () => import('@/views/public/StatusPageView.vue'),
+ },
+
+ // Catch-all: unknown routes → landing page
+ {
+ path: '/:pathMatch(.*)*',
+ redirect: '/',
+ },
+]
+
+// ---------------------------------------------------------------------------
+// Router instance
+// ---------------------------------------------------------------------------
const router = createRouter({
history: createWebHistory(),
- routes,
+ routes: isMarketingDomain ? marketingRoutes : panelRoutes,
})
-// Auth guard
+// Auth guard — only meaningful on panel domain (marketing has no requiresAuth routes)
router.beforeEach((to, _from, next) => {
const auth = useAuthStore()
diff --git a/frontend/src/views/marketing/LandingView.vue b/frontend/src/views/marketing/LandingView.vue
index 45ffb3b..6d1d2d0 100644
--- a/frontend/src/views/marketing/LandingView.vue
+++ b/frontend/src/views/marketing/LandingView.vue
@@ -1,6 +1,7 @@
@@ -16,9 +17,9 @@ import { Shield, Zap, RefreshCw, Terminal, Users, Wifi, Server, ChevronRight } f
Deploy once. Automate everything. Never SSH again.
-
+
Buy License
-
+
View Live Demo
@@ -278,9 +279,9 @@ import { Shield, Zap, RefreshCw, Terminal, Users, Wifi, Server, ChevronRight } f
Stop babysitting your server.
Start orchestrating it.
-
+
Get Corrosion
-
+