vibecarbon 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +110 -663
  2. package/README.md +4 -4
  3. package/carbon/.env.example +11 -0
  4. package/carbon/backup/compose-backup.sh +8 -0
  5. package/carbon/docker-compose.yml +11 -0
  6. package/carbon/ha/primary-init.sql +10 -2
  7. package/carbon/k8s/base/backup/cronjob.yaml +9 -0
  8. package/carbon/k8s/base/repl-gateway/kustomization.yaml +11 -0
  9. package/carbon/k8s/base/repl-gateway/repl-gateway.yaml +92 -0
  10. package/carbon/k8s/values/supabase.values.yaml +6 -1
  11. package/carbon/scripts/dev-init.js +6 -2
  12. package/carbon/src/client/assets/vibecarbon-icon.svg +6 -6
  13. package/carbon/src/client/assets/vibecarbon-wordmark-text-dark.svg +12 -0
  14. package/carbon/src/client/assets/vibecarbon-wordmark-text-light.svg +12 -0
  15. package/carbon/src/client/components/AIIntegrationSection.tsx +1 -3
  16. package/carbon/src/client/components/AppSidebar.tsx +35 -28
  17. package/carbon/src/client/components/CTAFooter.tsx +1 -1
  18. package/carbon/src/client/components/ComparisonSection.tsx +1 -3
  19. package/carbon/src/client/components/ContentPanel.tsx +1 -1
  20. package/carbon/src/client/components/DeploymentScenariosSection.tsx +1 -3
  21. package/carbon/src/client/components/FAQSection.tsx +1 -1
  22. package/carbon/src/client/components/Hero.tsx +13 -1
  23. package/carbon/src/client/components/Logo.tsx +66 -49
  24. package/carbon/src/client/components/Nav.tsx +1 -1
  25. package/carbon/src/client/components/NewsletterSignup.tsx +1 -1
  26. package/carbon/src/client/components/NotificationDrawer.tsx +3 -3
  27. package/carbon/src/client/components/PageHeader.tsx +1 -1
  28. package/carbon/src/client/components/PricingSection.tsx +2 -6
  29. package/carbon/src/client/components/TechStackSection.tsx +1 -3
  30. package/carbon/src/client/components/effects/FilmGrainOverlay.tsx +5 -0
  31. package/carbon/src/client/components/ui/button.tsx +1 -1
  32. package/carbon/src/client/components/ui/card.tsx +1 -1
  33. package/carbon/src/client/components/ui/dialog.tsx +1 -1
  34. package/carbon/src/client/components/ui/dropdown-menu.tsx +2 -2
  35. package/carbon/src/client/components/ui/popover.tsx +1 -1
  36. package/carbon/src/client/components/ui/select.tsx +1 -1
  37. package/carbon/src/client/components/ui/sidebar.tsx +1 -1
  38. package/carbon/src/client/index.css +186 -104
  39. package/carbon/src/client/lib/supabase.ts +41 -0
  40. package/carbon/src/client/lib/theme.ts +14 -14
  41. package/carbon/src/client/locales/de.json +27 -12
  42. package/carbon/src/client/locales/en.json +33 -18
  43. package/carbon/src/client/locales/es.json +27 -12
  44. package/carbon/src/client/locales/fr.json +27 -12
  45. package/carbon/src/client/locales/pt.json +27 -12
  46. package/carbon/src/client/pages/Blog.tsx +1 -1
  47. package/carbon/src/client/pages/Changelog.tsx +1 -1
  48. package/carbon/src/client/pages/Checkout.tsx +1 -1
  49. package/carbon/src/client/pages/Dashboard.tsx +33 -40
  50. package/carbon/src/client/pages/Docs.tsx +1 -1
  51. package/carbon/src/client/pages/Home.tsx +1 -1
  52. package/carbon/src/client/pages/Legal.tsx +1 -1
  53. package/carbon/src/server/billing/providers/paddle.ts +21 -2
  54. package/carbon/src/server/billing/providers/polar.ts +14 -0
  55. package/carbon/src/server/index.ts +12 -9
  56. package/carbon/src/server/lib/client-ip.ts +68 -0
  57. package/carbon/src/server/lib/env.ts +20 -6
  58. package/carbon/src/server/lib/rate-limiter.ts +11 -65
  59. package/carbon/src/server/lib/supabase.ts +10 -4
  60. package/carbon/src/server/middleware/requireOrgRole.ts +76 -0
  61. package/carbon/src/server/middleware/requirePlan.ts +64 -34
  62. package/carbon/src/server/middleware/requireSuperAdmin.ts +26 -0
  63. package/carbon/src/server/routes/v1/admin/contact.ts +5 -20
  64. package/carbon/src/server/routes/v1/admin/jobs.ts +6 -20
  65. package/carbon/src/server/routes/v1/admin/newsletter.ts +26 -23
  66. package/carbon/src/server/routes/v1/auth.ts +5 -2
  67. package/carbon/src/server/routes/v1/billing.ts +24 -64
  68. package/carbon/src/server/routes/v1/index.ts +8 -1
  69. package/carbon/src/server/routes/v1/newsletter.ts +18 -12
  70. package/carbon/src/server/routes/v1/theme.ts +36 -15
  71. package/carbon/supabase/migrations/00003_pg_cron.sql +2 -2
  72. package/carbon/supabase/migrations/00004_contact_newsletter.sql +24 -14
  73. package/carbon/tests/component/supabase-env-guard.test.ts +54 -0
  74. package/carbon/tests/integration/server/middleware/rate-limit-ip-spoof.test.ts +48 -0
  75. package/carbon/tests/integration/server/middleware/require-plan.test.ts +101 -0
  76. package/carbon/tests/integration/server/routes/admin-newsletter.test.ts +93 -0
  77. package/carbon/tests/integration/server/routes/billing-subscription-idor.test.ts +92 -0
  78. package/carbon/tests/integration/server/routes/newsletter-unsubscribe.test.ts +65 -0
  79. package/carbon/tests/integration/server/routes/theme-validation.test.ts +66 -0
  80. package/carbon/tests/unit/server/client-ip.test.ts +76 -0
  81. package/carbon/tests/unit/server/env-bool.test.ts +63 -0
  82. package/carbon/tests/unit/server/paddle-webhook.test.ts +57 -0
  83. package/carbon/tests/unit/server/polar-webhook.test.ts +63 -0
  84. package/carbon/tests/unit/server/supabase-client.test.ts +30 -0
  85. package/carbon/volumes/db/wal-archive.sh +36 -0
  86. package/package.json +2 -2
  87. package/src/backup.js +0 -2
  88. package/src/cli.js +16 -0
  89. package/src/deploy.js +16 -5
  90. package/src/destroy.js +78 -0
  91. package/src/failover.js +118 -237
  92. package/src/lib/command.js +19 -1
  93. package/src/lib/deploy/compose/ha.js +180 -102
  94. package/src/lib/deploy/compose/index.js +63 -20
  95. package/src/lib/deploy/image.js +19 -23
  96. package/src/lib/deploy/k8s/ha/index.js +397 -150
  97. package/src/lib/deploy/k8s/k3s.js +65 -14
  98. package/src/lib/deploy/orchestrator.js +176 -6
  99. package/src/lib/deploy/remote-build.js +8 -1
  100. package/src/lib/deploy/replication.js +540 -0
  101. package/src/lib/deploy/state.js +62 -5
  102. package/src/lib/deploy/utils.js +30 -2
  103. package/src/lib/deploy/wireguard.js +146 -0
  104. package/src/lib/host-keys.js +120 -5
  105. package/src/lib/iac/index.js +57 -23
  106. package/src/lib/licensing/gate.js +59 -0
  107. package/src/lib/licensing/index.js +1 -1
  108. package/src/lib/licensing/tiers.js +6 -5
  109. package/src/lib/prod-confirm.js +65 -0
  110. package/src/lib/providers/hetzner-s3.js +85 -0
  111. package/src/lib/ssh.js +35 -35
  112. package/src/restore.js +102 -14
  113. package/src/scale.js +4 -2
  114. package/src/status.js +122 -0
  115. package/src/upgrade.js +0 -4
  116. package/carbon/ha/activate-standby.sh +0 -52
  117. package/carbon/ha/standby-init.sh +0 -74
  118. package/carbon/src/client/assets/vibecarbon-logo-dark.svg +0 -29
  119. package/carbon/src/client/assets/vibecarbon-logo-light.svg +0 -29
  120. package/carbon/src/server/lib/request.ts +0 -34
  121. package/src/lib/pod-backups.js +0 -53
@@ -32,146 +32,169 @@ body {
32
32
 
33
33
  @custom-variant dark (&:is(.dark *));
34
34
 
35
- /* Theme: Teal with Glassmorphism */
36
- /* Primary: Bright Teal, Secondary Accent: Magenta */
37
- /* Typography: Noto Sans, JetBrains Mono (code) */
35
+ /* Theme: Refined Depth (dark) / Light Glass (light) */
36
+ /* Primary: Teal, Secondary Accent: Magenta — single interactive accent,
37
+ semantic colors reserved for status, glass applied selectively (.glass) */
38
+ /* Typography: Noto Sans (body + display), JetBrains Mono (code/data) */
38
39
 
39
40
  :root {
40
41
  /* ============================================
41
42
  CUSTOMIZE YOUR BRAND - Edit these values
42
43
  ============================================ */
43
- --primary: oklch(0.82 0.14 192); /* Brand primary (teal) */
44
- --primary-dim: oklch(0.55 0.12 192); /* Dimmed variant */
45
- --primary-foreground: oklch(0.98 0.01 181);
46
- --secondary-accent: oklch(0.45 0.2 350); /* Brand accent (magenta) */
44
+ --primary: oklch(0.52 0.124 192); /* Brand primary (deep teal, AA on light surfaces) */
45
+ --primary-dim: oklch(0.42 0.1 192); /* Dimmed variant */
46
+ --primary-foreground: oklch(0.99 0.005 192);
47
+ --secondary-accent: oklch(0.47 0.2 350); /* Brand accent (magenta) */
47
48
  --secondary-accent-foreground: oklch(0.99 0 0);
48
- --destructive: oklch(0.525 0.223 3.958); /* pink-700 */
49
+ --destructive: oklch(0.5 0.21 3.958);
49
50
  --info: var(--primary);
50
- --warning: oklch(0.769 0.188 70.08);
51
- --success: oklch(0.704 0.14 181);
51
+ --warning: oklch(0.62 0.13 75);
52
+ --success: oklch(0.5 0.12 181);
52
53
  --radius: 0.625rem; /* Border radius base */
53
54
  --radius-card: var(--radius-xl); /* Cards, dialogs, panels */
54
55
 
55
56
  /* Glow effects (derived from brand colors) */
56
- --glow-primary: oklch(0.82 0.14 192 / 0.25);
57
- --glow-accent: oklch(0.58 0.24 350 / 0.25);
57
+ --glow-primary: oklch(0.52 0.124 192 / 0.12);
58
+ --glow-accent: oklch(0.47 0.2 350 / 0.1);
58
59
 
59
- /* Light mode gradient (not visible by default) */
60
- --gradient-start: oklch(1 0 0);
61
- --gradient-end: oklch(0.98 0 0);
60
+ /* Canvas - pale cool gradient + faint ambient wash */
61
+ --gradient-start: oklch(0.985 0.005 220);
62
+ --gradient-end: oklch(0.945 0.014 215);
63
+ --canvas-wash: oklch(0.52 0.124 192 / 0.05);
62
64
 
63
65
  /* ============================================
64
66
  SURFACE COLORS - Rarely need customization
65
67
  ============================================ */
66
- --background: oklch(1 0 0);
67
- --foreground: oklch(0.13 0.028 261.692);
68
+ /* Transparent background to show canvas (both modes) */
69
+ --background: transparent;
70
+ --foreground: oklch(0.17 0.025 250);
71
+
72
+ /* Standard cards - quiet near-opaque surfaces (glass is opt-in via .glass) */
73
+ --card: oklch(0.995 0.004 210 / 0.92);
74
+ --card-foreground: oklch(0.17 0.025 250);
68
75
 
69
- --card: oklch(1 0 0);
70
- --card-foreground: oklch(0.13 0.028 261.692);
76
+ /* Glass surfaces - alpha gradient, light source top-left */
77
+ --glass-from: oklch(1 0 0 / 0.78);
78
+ --glass-to: oklch(0.97 0.01 215 / 0.58);
79
+ --glass-edge: oklch(1 0 0 / 0.65);
71
80
 
72
- --popover: oklch(1 0 0);
73
- --popover-foreground: oklch(0.13 0.028 261.692);
81
+ /* Popovers/dropdowns - SOLID (float over arbitrary content) */
82
+ --popover: oklch(0.99 0.004 210);
83
+ --popover-foreground: oklch(0.17 0.025 250);
74
84
 
75
- --secondary: oklch(0.967 0.001 286.375);
76
- --secondary-foreground: oklch(0.21 0.006 285.885);
85
+ --secondary: oklch(0.93 0.012 220 / 0.8);
86
+ --secondary-foreground: oklch(0.25 0.025 245);
77
87
 
78
- --muted: oklch(0.967 0.003 264.542);
79
- --muted-foreground: oklch(0.551 0.027 264.364);
88
+ --muted: oklch(0.94 0.01 220 / 0.7);
89
+ --muted-foreground: oklch(0.44 0.025 240);
80
90
 
81
- /* Accent matches primary for bold menu accent */
82
- --accent: var(--primary);
83
- --accent-foreground: var(--primary-foreground);
91
+ /* Accent - subtle primary tint for hover states */
92
+ --accent: oklch(0.52 0.124 192 / 0.1);
93
+ --accent-foreground: oklch(0.3 0.08 192);
94
+
95
+ /* Hairline borders */
96
+ --border: oklch(0.45 0.03 230 / 0.16);
97
+ --input: oklch(0.5 0.03 230 / 0.3);
98
+ --ring: var(--primary);
84
99
 
85
- --border: oklch(0.928 0.006 264.531);
86
- --input: oklch(0.928 0.006 264.531);
87
- --ring: oklch(0.707 0.022 261.325);
100
+ /* Elevation shadows - soft diffuse, cool-tinted */
101
+ --shadow-card: 0 1px 2px oklch(0.3 0.04 240 / 0.05), 0 10px 30px -10px oklch(0.3 0.05 240 / 0.12);
102
+ --shadow-pop: 0 4px 12px oklch(0.3 0.04 240 / 0.08), 0 16px 40px -12px oklch(0.3 0.05 240 / 0.18);
88
103
 
89
- /* Chart colors - vibrant multi-hue palette */
104
+ /* Chart colors - brand pair first, then supporting hues */
90
105
  --chart-1: var(--primary);
91
- --chart-2: var(--warning);
92
- --chart-3: var(--destructive);
93
- --chart-4: var(--success);
94
- --chart-5: oklch(0.65 0.22 290);
95
-
96
- /* Sidebar - Maia defaults with brand primary */
97
- --sidebar: oklch(0.985 0.002 247.839);
98
- --sidebar-foreground: oklch(0.13 0.028 261.692);
106
+ --chart-2: var(--secondary-accent);
107
+ --chart-3: oklch(0.5 0.17 275);
108
+ --chart-4: oklch(0.62 0.13 75);
109
+ --chart-5: oklch(0.54 0.11 155);
110
+
111
+ /* Sidebar - light glass rail */
112
+ --sidebar: oklch(0.975 0.007 212 / 0.55);
113
+ --sidebar-foreground: oklch(0.22 0.025 248);
99
114
  --sidebar-primary: var(--primary);
100
115
  --sidebar-primary-foreground: var(--primary-foreground);
101
- --sidebar-accent: var(--primary);
102
- --sidebar-accent-foreground: var(--primary-foreground);
103
- --sidebar-border: oklch(0.928 0.006 264.531);
104
- --sidebar-ring: oklch(0.707 0.022 261.325);
116
+ --sidebar-accent: oklch(0.52 0.124 192 / 0.12);
117
+ --sidebar-accent-foreground: oklch(0.3 0.08 192);
118
+ --sidebar-border: oklch(0.45 0.03 230 / 0.12);
119
+ --sidebar-ring: var(--primary);
105
120
  }
106
121
 
107
122
  .dark {
108
- /* Primary - brighter dim variant in dark mode */
123
+ /* Primary - bright teal on dark, brighter dim variant */
109
124
  --primary: oklch(0.82 0.14 192);
110
125
  --primary-dim: oklch(0.65 0.12 192);
111
- --primary-foreground: oklch(0.08 0 0);
126
+ --primary-foreground: oklch(0.1 0.02 220);
112
127
 
113
128
  /* Secondary accent (magenta) - brighter in dark mode */
114
129
  --secondary-accent: oklch(0.65 0.26 350);
115
130
  --secondary-accent-foreground: oklch(0.99 0 0);
116
131
 
117
- /* Glow colors */
118
- --glow-primary: oklch(0.82 0.14 192 / 0.5);
119
- --glow-accent: oklch(0.65 0.26 350 / 0.5);
132
+ /* Glow colors - restrained, focal points only */
133
+ --glow-primary: oklch(0.82 0.14 192 / 0.28);
134
+ --glow-accent: oklch(0.65 0.26 350 / 0.25);
120
135
 
121
- /* Body gradient colors */
122
- --gradient-start: oklch(0.12 0.02 265);
123
- --gradient-end: oklch(0.17 0.03 260);
136
+ /* Canvas - deep teal-tinted near-black + faint ambient wash */
137
+ --gradient-start: oklch(0.105 0.018 252);
138
+ --gradient-end: oklch(0.145 0.028 228);
139
+ --canvas-wash: oklch(0.82 0.14 192 / 0.09);
124
140
 
125
- /* Transparent background to show gradient */
141
+ /* Transparent background to show canvas */
126
142
  --background: transparent;
127
- --foreground: oklch(0.98 0 0);
143
+ --foreground: oklch(0.96 0.005 210);
144
+
145
+ /* Standard cards - quiet near-opaque surfaces */
146
+ --card: oklch(0.19 0.024 242 / 0.78);
147
+ --card-foreground: oklch(0.96 0.005 210);
128
148
 
129
- /* Card surfaces - with transparency */
130
- --card: oklch(0.28 0.04 255 / 0.5);
131
- --card-foreground: oklch(0.98 0 0);
149
+ /* Glass surfaces - alpha gradient, light source top-left */
150
+ --glass-from: oklch(0.25 0.028 238 / 0.6);
151
+ --glass-to: oklch(0.16 0.022 246 / 0.45);
152
+ --glass-edge: oklch(1 0 0 / 0.1);
132
153
 
133
- /* Popovers/dropdowns - cool-tinted to match body gradient */
134
- --popover: oklch(0.18 0.02 260);
135
- --popover-foreground: oklch(0.985 0 0);
154
+ /* Popovers/dropdowns - SOLID, cool-tinted to match canvas */
155
+ --popover: oklch(0.175 0.022 242);
156
+ --popover-foreground: oklch(0.97 0 0);
136
157
 
137
- /* Secondary - muted gray */
138
- --secondary: oklch(0.18 0 0);
139
- --secondary-foreground: oklch(0.9 0 0);
158
+ --secondary: oklch(0.25 0.02 240);
159
+ --secondary-foreground: oklch(0.92 0.008 220);
140
160
 
141
- /* Muted surfaces */
142
- --muted: oklch(0.15 0 0);
143
- --muted-foreground: oklch(0.707 0.022 261.325);
161
+ --muted: oklch(0.2 0.02 240 / 0.7);
162
+ --muted-foreground: oklch(0.71 0.02 228);
144
163
 
145
164
  /* Accent - subtle primary tint for hover states */
146
- --accent: oklch(0.30 0.04 200 / 0.5);
147
- --accent-foreground: oklch(0.98 0 0);
165
+ --accent: oklch(0.82 0.14 192 / 0.13);
166
+ --accent-foreground: oklch(0.96 0.005 210);
148
167
 
149
- /* Destructive - vibrant red */
150
- --destructive: oklch(0.525 0.223 3.958);
168
+ /* Status - lifted for dark-bg contrast */
169
+ --destructive: oklch(0.62 0.21 3.958);
151
170
  --info: var(--primary);
152
- --warning: oklch(0.769 0.188 70.08);
171
+ --warning: oklch(0.77 0.188 70.08);
153
172
  --success: oklch(0.704 0.14 181);
154
173
 
155
- /* Borders */
156
- --border: oklch(0.33 0.025 195);
157
- --input: oklch(1 0 0 / 15%);
174
+ /* Hairline borders - faint teal */
175
+ --border: oklch(0.9 0.03 200 / 0.12);
176
+ --input: oklch(1 0 0 / 0.12);
158
177
  --ring: var(--primary);
159
178
 
160
- /* Chart colors - vibrant multi-hue palette */
179
+ /* Elevation shadows - inner top edge-light + deep soft drop */
180
+ --shadow-card: inset 0 1px 0 oklch(1 0 0 / 0.06), 0 8px 24px -12px oklch(0 0 0 / 0.5);
181
+ --shadow-pop: inset 0 1px 0 oklch(1 0 0 / 0.08), 0 16px 48px -16px oklch(0 0 0 / 0.65);
182
+
183
+ /* Chart colors - brand pair first, then supporting hues */
161
184
  --chart-1: var(--primary);
162
- --chart-2: var(--warning);
163
- --chart-3: var(--destructive);
164
- --chart-4: var(--success);
165
- --chart-5: oklch(0.65 0.22 290);
185
+ --chart-2: var(--secondary-accent);
186
+ --chart-3: oklch(0.72 0.15 275);
187
+ --chart-4: oklch(0.8 0.15 80);
188
+ --chart-5: oklch(0.76 0.12 155);
166
189
 
167
- /* Sidebar */
190
+ /* Sidebar - transparent rail, canvas shows through */
168
191
  --sidebar: transparent;
169
- --sidebar-foreground: oklch(0.98 0 0);
192
+ --sidebar-foreground: oklch(0.96 0.005 210);
170
193
  --sidebar-primary: var(--primary);
171
- --sidebar-primary-foreground: oklch(0.08 0 0);
172
- --sidebar-accent: var(--primary);
173
- --sidebar-accent-foreground: oklch(0.08 0 0);
174
- --sidebar-border: transparent;
194
+ --sidebar-primary-foreground: oklch(0.1 0.02 220);
195
+ --sidebar-accent: oklch(0.82 0.14 192 / 0.13);
196
+ --sidebar-accent-foreground: oklch(0.96 0.005 210);
197
+ --sidebar-border: oklch(0.9 0.03 200 / 0.08);
175
198
  --sidebar-ring: var(--primary);
176
199
  }
177
200
 
@@ -179,6 +202,12 @@ body {
179
202
  /* Typography */
180
203
  --font-sans: "Noto Sans Variable", "Noto Sans", system-ui, sans-serif;
181
204
  --font-mono: "JetBrains Mono", ui-monospace, monospace;
205
+ /* Display slot kept as an extension point; defaults to the body sans face. */
206
+ --font-display: var(--font-sans);
207
+
208
+ /* Elevation shadows (values cascade from :root/.dark) */
209
+ --shadow-card: var(--shadow-card);
210
+ --shadow-pop: var(--shadow-pop);
182
211
 
183
212
  /* Extended primary colors */
184
213
  --color-primary-dim: var(--primary-dim);
@@ -242,21 +271,31 @@ body {
242
271
  body {
243
272
  @apply font-sans bg-background text-foreground antialiased cursor-default;
244
273
  }
245
- .dark body {
246
- background: linear-gradient(180deg, var(--gradient-start) 0%, var(--gradient-end) 100%);
274
+ /* Canvas: gradient + faint ambient wash (the "light" glass surfaces catch).
275
+ Both modes --background is transparent so this shows through. */
276
+ body {
277
+ background:
278
+ radial-gradient(120% 80% at 75% -20%, var(--canvas-wash), transparent 55%),
279
+ linear-gradient(180deg, var(--gradient-start) 0%, var(--gradient-end) 100%);
247
280
  background-attachment: fixed;
248
281
  }
249
282
  html {
250
283
  @apply font-sans;
251
284
  }
252
285
 
253
- /* Bold headlines */
286
+ /* Headlines: refined weights + balanced wrapping (display slot = body sans) */
254
287
  h1,
255
- h2,
288
+ h2 {
289
+ font-family: var(--font-display);
290
+ font-weight: 700;
291
+ letter-spacing: -0.015em;
292
+ text-wrap: balance;
293
+ }
256
294
  h3,
257
295
  h4 {
258
- font-weight: 900;
259
- letter-spacing: -0.02em;
296
+ font-weight: 700;
297
+ letter-spacing: -0.01em;
298
+ text-wrap: balance;
260
299
  }
261
300
  }
262
301
 
@@ -274,8 +313,19 @@ body {
274
313
  EFFECTS SYSTEM
275
314
  ============================================ */
276
315
 
277
- /* Film Grain Overlay */
278
- .film-grain::after {
316
+ /* Glass surface the ONLY glass treatment; applied selectively
317
+ (KPI strip, sidebar rail, marketing hero panels). Alpha gradient
318
+ mimics a top-left light source; border-top catches the light. */
319
+ .glass {
320
+ background: linear-gradient(135deg, var(--glass-from), var(--glass-to));
321
+ backdrop-filter: blur(14px);
322
+ border: 1px solid var(--border);
323
+ border-top-color: var(--glass-edge);
324
+ box-shadow: var(--shadow-card);
325
+ }
326
+
327
+ /* Film Grain Overlay (dark mode only — reads as dirt on light surfaces) */
328
+ .dark .film-grain::after {
279
329
  content: "";
280
330
  position: fixed;
281
331
  inset: 0;
@@ -287,30 +337,37 @@ body {
287
337
  background-repeat: repeat;
288
338
  }
289
339
 
290
- /* Glow utilities */
340
+ /* Active nav — teal icon, the sidebar's single focal accent */
341
+ [data-sidebar="menu-button"][data-active] svg {
342
+ color: var(--primary);
343
+ filter: drop-shadow(0 0 6px var(--glow-primary));
344
+ }
345
+
346
+ /* Glow utilities — restrained radii; reserved for focal points
347
+ (active nav, primary CTA, brand marks). Do not add new consumers. */
291
348
  .glow-teal {
292
- box-shadow: 0 0 20px var(--glow-primary), 0 0 40px var(--glow-primary);
349
+ box-shadow: 0 0 12px var(--glow-primary), 0 0 28px var(--glow-primary);
293
350
  }
294
351
 
295
352
  .glow-magenta {
296
- box-shadow: 0 0 20px var(--glow-accent), 0 0 40px var(--glow-accent);
353
+ box-shadow: 0 0 12px var(--glow-accent), 0 0 28px var(--glow-accent);
297
354
  }
298
355
 
299
356
  .glow-teal-subtle {
300
- box-shadow: 0 0 15px var(--glow-primary);
357
+ box-shadow: 0 0 10px var(--glow-primary);
301
358
  }
302
359
 
303
360
  .glow-magenta-subtle {
304
- box-shadow: 0 0 15px var(--glow-accent);
361
+ box-shadow: 0 0 10px var(--glow-accent);
305
362
  }
306
363
 
307
364
  /* Text glow */
308
365
  .text-glow-teal {
309
- text-shadow: 0 0 20px var(--glow-primary), 0 0 40px var(--glow-primary);
366
+ text-shadow: 0 0 12px var(--glow-primary), 0 0 28px var(--glow-primary);
310
367
  }
311
368
 
312
369
  .text-glow-magenta {
313
- text-shadow: 0 0 20px var(--glow-accent), 0 0 40px var(--glow-accent);
370
+ text-shadow: 0 0 12px var(--glow-accent), 0 0 28px var(--glow-accent);
314
371
  }
315
372
 
316
373
  /* Border glow on hover */
@@ -319,11 +376,11 @@ body {
319
376
  }
320
377
 
321
378
  .border-glow-teal:hover {
322
- box-shadow: 0 0 0 1px var(--primary), 0 0 15px var(--glow-primary);
379
+ box-shadow: 0 0 0 1px var(--primary), 0 0 10px var(--glow-primary);
323
380
  }
324
381
 
325
382
  .border-glow-magenta:hover {
326
- box-shadow: 0 0 0 1px var(--secondary-accent), 0 0 15px var(--glow-accent);
383
+ box-shadow: 0 0 0 1px var(--secondary-accent), 0 0 10px var(--glow-accent);
327
384
  }
328
385
 
329
386
  /* Sparkle shimmer for premium badges */
@@ -336,6 +393,7 @@ body {
336
393
  background-size: 200% 100%;
337
394
  animation: shimmer 3s ease-in-out infinite;
338
395
  }
396
+ @media (prefers-reduced-motion: reduce) { .badge-shimmer { animation: none; } }
339
397
 
340
398
  /* Marquee for LogoStrip */
341
399
  @keyframes marquee-scroll {
@@ -357,8 +415,9 @@ body {
357
415
  .hero-orb { animation: orb-pulse 6s ease-in-out infinite; }
358
416
  @media (prefers-reduced-motion: reduce) { .hero-orb { animation: none; } }
359
417
 
360
- /* Spotlight particle dots for glass containers */
361
- .spotlight-particles {
418
+ /* Spotlight particle dots for glass containers (dark mode only — white dots
419
+ read as noise on light surfaces) */
420
+ .dark .spotlight-particles {
362
421
  background-image: radial-gradient(circle, oklch(1 0 0 / 0.5) 1px, transparent 1px);
363
422
  background-size: 24px 24px;
364
423
  mask-image: radial-gradient(ellipse 80% 50% at 50% 0%, black 0%, transparent 80%);
@@ -458,4 +517,27 @@ body {
458
517
  :where(tr):not(:where([class~="not-prose"], [class~="not-prose"] *)):hover {
459
518
  background-color: oklch(0.20 0.02 260 / 0.3);
460
519
  }
520
+ }
521
+
522
+ /* ============================================
523
+ REDUCED TRANSPARENCY - solidify glass surfaces
524
+ ============================================ */
525
+
526
+ @media (prefers-reduced-transparency: reduce) {
527
+ :root {
528
+ --card: oklch(0.99 0.004 210);
529
+ --sidebar: oklch(0.975 0.007 212);
530
+ --glass-from: oklch(0.99 0.004 210);
531
+ --glass-to: oklch(0.97 0.008 212);
532
+ }
533
+ .dark {
534
+ --card: oklch(0.19 0.024 242);
535
+ --sidebar: oklch(0.13 0.02 245);
536
+ --glass-from: oklch(0.21 0.024 242);
537
+ --glass-to: oklch(0.17 0.022 244);
538
+ }
539
+ .glass,
540
+ [data-slot="sidebar-inner"] {
541
+ backdrop-filter: none;
542
+ }
461
543
  }
@@ -5,9 +5,50 @@ const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
5
5
  const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
6
6
 
7
7
  if (!supabaseUrl || !supabaseAnonKey) {
8
+ const missing = [
9
+ !supabaseUrl ? 'VITE_SUPABASE_URL' : null,
10
+ !supabaseAnonKey ? 'VITE_SUPABASE_ANON_KEY' : null,
11
+ ].filter((name): name is string => name !== null);
12
+ renderConfigError(missing);
8
13
  throw new Error('Missing Supabase environment variables');
9
14
  }
10
15
 
16
+ // This module is evaluated before React mounts, so a bare throw leaves #root
17
+ // empty — a black page with the only clue in the console. Paint a readable
18
+ // error with plain DOM APIs first, then let the throw halt the app.
19
+ function renderConfigError(missing: string[]): void {
20
+ if (typeof document === 'undefined') return;
21
+
22
+ const screen = document.createElement('div');
23
+ screen.style.cssText =
24
+ 'position:fixed;inset:0;display:flex;align-items:center;justify-content:center;' +
25
+ 'background:#0a0f14;color:#e6edf3;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;' +
26
+ 'padding:2rem;z-index:2147483647;text-align:left';
27
+
28
+ const box = document.createElement('div');
29
+ box.style.cssText = 'max-width:40rem';
30
+
31
+ const heading = document.createElement('h1');
32
+ heading.textContent = 'Configuration error';
33
+ heading.style.cssText = 'font-size:1.25rem;margin:0 0 0.75rem;color:#f47067';
34
+
35
+ const detail = document.createElement('p');
36
+ detail.textContent =
37
+ `This build is missing required environment variables: ${missing.join(', ')}. ` +
38
+ 'The frontend cannot start without them.';
39
+ detail.style.cssText = 'margin:0 0 0.75rem;line-height:1.5';
40
+
41
+ const hint = document.createElement('p');
42
+ hint.textContent =
43
+ 'Rebuild with the variables set — deploys inject them automatically; ' +
44
+ 'for local development, check your .env file.';
45
+ hint.style.cssText = 'margin:0;line-height:1.5;opacity:0.7';
46
+
47
+ box.append(heading, detail, hint);
48
+ screen.append(box);
49
+ document.body.append(screen);
50
+ }
51
+
11
52
  /**
12
53
  * Cookie-based storage adapter for Supabase auth.
13
54
  * Stores auth tokens in cookies so they're sent with requests to /admin/* routes,
@@ -1,25 +1,25 @@
1
1
  // Default values mirroring index.css
2
2
  export const DEFAULT_THEME = {
3
3
  light: {
4
- gradientStart: 'oklch(1 0 0)',
5
- gradientEnd: 'oklch(0.98 0 0)',
6
- card: 'oklch(1 0 0)',
7
- primary: 'oklch(0.82 0.14 192)',
8
- primaryDim: 'oklch(0.55 0.12 192)',
9
- primaryForeground: 'oklch(0.98 0.01 181)',
10
- secondaryAccent: 'oklch(0.45 0.2 350)',
4
+ gradientStart: 'oklch(0.985 0.005 220)',
5
+ gradientEnd: 'oklch(0.945 0.014 215)',
6
+ card: 'oklch(0.995 0.004 210 / 0.92)',
7
+ primary: 'oklch(0.52 0.124 192)',
8
+ primaryDim: 'oklch(0.42 0.1 192)',
9
+ primaryForeground: 'oklch(0.99 0.005 192)',
10
+ secondaryAccent: 'oklch(0.47 0.2 350)',
11
11
  secondaryAccentForeground: 'oklch(0.99 0 0)',
12
- destructive: 'oklch(0.525 0.223 3.958)',
13
- warning: 'oklch(0.769 0.188 70.08)',
14
- success: 'oklch(0.704 0.14 181)',
12
+ destructive: 'oklch(0.5 0.21 3.958)',
13
+ warning: 'oklch(0.62 0.13 75)',
14
+ success: 'oklch(0.5 0.12 181)',
15
15
  },
16
16
  dark: {
17
- gradientStart: 'oklch(0.12 0.02 265)',
18
- gradientEnd: 'oklch(0.17 0.03 260)',
19
- card: 'oklch(0.28 0.04 255 / 0.5)',
17
+ gradientStart: 'oklch(0.105 0.018 252)',
18
+ gradientEnd: 'oklch(0.145 0.028 228)',
19
+ card: 'oklch(0.19 0.024 242 / 0.78)',
20
20
  primary: 'oklch(0.82 0.14 192)',
21
21
  primaryDim: 'oklch(0.65 0.12 192)',
22
- primaryForeground: 'oklch(0.08 0 0)',
22
+ primaryForeground: 'oklch(0.1 0.02 220)',
23
23
  secondaryAccent: 'oklch(0.65 0.26 350)',
24
24
  secondaryAccentForeground: 'oklch(0.99 0 0)',
25
25
  },
@@ -293,7 +293,7 @@
293
293
  },
294
294
  "deploy": {
295
295
  "label": "Deployen",
296
- "description": "In Produktion pushen mit Auto-Scaling, Hochverfügbarkeit, Monitoring, CI/CD und mehr."
296
+ "description": "In Produktion pushen mit Skalierung, Hochverfügbarkeit, Monitoring, CI/CD und mehr."
297
297
  }
298
298
  },
299
299
  "techStack": {
@@ -352,7 +352,7 @@
352
352
  "traffic": {
353
353
  "risk": "Traffic überlastet",
354
354
  "protection": "Auto-Scaling",
355
- "how": "Kubernetes-Autoscaling über Pods und VPS-Knoten"
355
+ "how": "Kubernetes-Pod-Autoscaling plus Worker-Skalierung per Befehl"
356
356
  },
357
357
  "redundancy": {
358
358
  "risk": "Keine Redundanz",
@@ -401,25 +401,40 @@
401
401
  "compose": {
402
402
  "name": "Docker Compose",
403
403
  "tagline": "Ein VPS, dein kompletter Stack inklusive.",
404
- "features": ["1 Server", "Einzelne Region"],
404
+ "features": [
405
+ "1 Server",
406
+ "Einzelne Region"
407
+ ],
405
408
  "bestFor": "MVPs & interne Tools"
406
409
  },
407
410
  "composeHa": {
408
411
  "name": "Compose HA",
409
- "tagline": "Zwei Server, automatisches Failover.",
410
- "features": ["2 Server", "Streaming-Replikation", "Auto-Failover"],
412
+ "tagline": "Zwei Regionen, Failover per Befehl.",
413
+ "features": [
414
+ "2 Server",
415
+ "Streaming-Replikation",
416
+ "Failover per Befehl"
417
+ ],
411
418
  "bestFor": "Produktions-SaaS ohne Kubernetes"
412
419
  },
413
420
  "k8s": {
414
421
  "name": "Kubernetes",
415
- "tagline": "Automatisch skalierender k3s-Cluster.",
416
- "features": ["Einzelne Region", "Horizontale Autoskalierung", "Rollierende Updates"],
422
+ "tagline": "Produktionsreifer k3s-Cluster.",
423
+ "features": [
424
+ "Einzelne Region",
425
+ "Horizontale Autoskalierung",
426
+ "Rollierende Updates"
427
+ ],
417
428
  "bestFor": "Anwendungen mit hohem Traffic"
418
429
  },
419
430
  "k8sHa": {
420
431
  "name": "Kubernetes HA",
421
432
  "tagline": "Multi-Region-Cluster, maximale Verfügbarkeit.",
422
- "features": ["2 Regionen", "Regions-Failover", "Autoskalierung"],
433
+ "features": [
434
+ "2 Regionen",
435
+ "Regions-Failover",
436
+ "Pod-Autoscaling"
437
+ ],
423
438
  "bestFor": "Geschäftskritisch & global"
424
439
  }
425
440
  },
@@ -459,7 +474,7 @@
459
474
  "backups": "Automatische S3-Backups",
460
475
  "cicd": "CI/CD via GitHub Actions",
461
476
  "unlimited": "Unbegrenzte Server & Projekte",
462
- "commercial": "Kommerzielle Nutzungsrechte"
477
+ "commercial": "FSL-Ausnahme (Einbetten, White-Label, Weiterverkauf)"
463
478
  }
464
479
  },
465
480
  "fullerene": {
@@ -467,7 +482,7 @@
467
482
  "cta": "Fullerene kaufen",
468
483
  "features": {
469
484
  "diamond": "Alles in Diamond",
470
- "commercial": "Kommerzielle Nutzungsrechte (AGPL-Ausnahme)",
485
+ "commercial": "FSL-Ausnahme: Vibecarbon einbetten, als White-Label anbieten oder Vibecarbon-basierte Services weiterverkaufen",
471
486
  "clients": "Bereitstellung für Kunden & Organisationen",
472
487
  "corporate": "Agenturen & Enterprise-Teams",
473
488
  "support": "Prioritäts-E-Mail-Support",
@@ -496,7 +511,7 @@
496
511
  },
497
512
  "q5": {
498
513
  "question": "Wie funktioniert die Preisgestaltung?",
499
- "answer": "Graphite ist dauerhaft kostenlos — Projekte erstellen, lokal entwickeln, alle Add-ons nutzen. Diamond (149 $ einmalig) bietet volle Automatisierung für Indie-Entwickler und Startups — alle Deployment-Modi, Backups, CI/CD und Skalierung. Fullerene (499 $ einmalig) ist die kommerzielle Lizenz für Agenturen und Unternehmen — für Kunden deployen, ohne AGPL-Auflagen. Einmalige Zahlung, kein Abo."
514
+ "answer": "Graphite ist dauerhaft kostenlos — Projekte erstellen, lokal entwickeln, alle Add-ons nutzen. Diamond (149 $ einmalig) bietet volle Automatisierung für Indie-Entwickler und Startups — alle Deployment-Modi, Backups, CI/CD und Skalierung. Fullerene (499 $ einmalig) richtet sich an Agenturen und Unternehmen — für Kunden deployen, plus die FSL-Ausnahme, um Vibecarbon einzubetten, als White-Label anzubieten oder Vibecarbon-basierte Services weiterzuverkaufen. Einmalige Zahlung, kein Abo."
500
515
  },
501
516
  "q6": {
502
517
  "question": "Wie viele Projekte kann ich mit einer Lizenz erstellen?",
@@ -508,7 +523,7 @@
508
523
  },
509
524
  "q8": {
510
525
  "question": "Was passiert, wenn Vibecarbon eingestellt wird?",
511
- "answer": "Für deine App ändert sich nichts. Der generierte Code steht unter MIT-Lizenz ohne jegliche Laufzeitabhängigkeit von Vibecarbon — es ist einfach eine Codebasis, die dir vollständig gehört. Die CLI ist Open Source unter AGPL, du kannst sie also dauerhaft aus dem Quellcode ausführen. Deine Infrastruktur läuft auf deinen eigenen Servern."
526
+ "answer": "Für deine App ändert sich nichts. Der generierte Code steht unter MIT-Lizenz ohne jegliche Laufzeitabhängigkeit von Vibecarbon — es ist einfach eine Codebasis, die dir vollständig gehört. Die CLI ist Fair Source (FSL) du kannst sie für jede nicht konkurrierende Nutzung dauerhaft aus dem Quellcode ausführen, und jedes Release wird zwei Jahre nach Veröffentlichung MIT-Open-Source. Deine Infrastruktur läuft auf deinen eigenen Servern."
512
527
  },
513
528
  "q9": {
514
529
  "question": "Brauche ich DevOps-Erfahrung, um bereitzustellen?",