diff --git a/vibn-frontend/app/components/NextAuthComponent.tsx b/vibn-frontend/app/components/NextAuthComponent.tsx index 7ea62122..e230c9fe 100644 --- a/vibn-frontend/app/components/NextAuthComponent.tsx +++ b/vibn-frontend/app/components/NextAuthComponent.tsx @@ -105,7 +105,19 @@ function NextAuthForm() { > { - const edits = typeof keyOrEdits === 'object' && keyOrEdits !== null - ? keyOrEdits : { [keyOrEdits]: val }; + const edits = + typeof keyOrEdits === "object" && keyOrEdits !== null + ? keyOrEdits + : { [keyOrEdits]: val }; setValues((prev) => ({ ...prev, ...edits })); - window.parent.postMessage({ type: '__edit_mode_set_keys', edits }, '*'); + window.parent.postMessage({ type: "__edit_mode_set_keys", edits }, "*"); // Same-window signal so in-page listeners (deck-stage rail thumbnails) // can react — the parent message only reaches the host, not peers. - window.dispatchEvent(new CustomEvent('tweakchange', { detail: edits })); + window.dispatchEvent(new CustomEvent("tweakchange", { detail: edits })); }, []); return [values, setTweak]; } @@ -189,7 +190,7 @@ function useTweaks(defaults) { // The close button posts __edit_mode_dismissed so the host's toolbar toggle // flips off in lockstep; the host echoes __deactivate_edit_mode back which // is what actually hides the panel. -function TweaksPanel({ title = 'Tweaks', noDeckControls = false, children }) { +function TweaksPanel({ title = "Tweaks", noDeckControls = false, children }) { const [open, setOpen] = useState(false); const dragRef = useRef(null); // Auto-inject a rail toggle when a is on the page. The @@ -199,7 +200,8 @@ function TweaksPanel({ title = 'Tweaks', noDeckControls = false, children }) { // message — authors who want custom placement can post it directly // and pass noDeckControls to suppress this one. const hasDeckStage = React.useMemo( - () => typeof document !== 'undefined' && !!document.querySelector('deck-stage'), + () => + typeof document !== "undefined" && !!document.querySelector("deck-stage"), [], ); // deck-stage enables its rail in connectedCallback, but this panel can @@ -208,22 +210,27 @@ function TweaksPanel({ title = 'Tweaks', noDeckControls = false, children }) { // copies still wait for the host's __omelette_rail_enabled postMessage — // same listener handles those.) const [railEnabled, setRailEnabled] = useState( - () => hasDeckStage && !!document.querySelector('deck-stage')?._railEnabled, + () => hasDeckStage && !!document.querySelector("deck-stage")?._railEnabled, ); useEffect(() => { if (!hasDeckStage || railEnabled) return undefined; const onMsg = (e) => { - if (e.data && e.data.type === '__omelette_rail_enabled') setRailEnabled(true); + if (e.data && e.data.type === "__omelette_rail_enabled") + setRailEnabled(true); }; - window.addEventListener('message', onMsg); - return () => window.removeEventListener('message', onMsg); + window.addEventListener("message", onMsg); + return () => window.removeEventListener("message", onMsg); }, [hasDeckStage, railEnabled]); const [railVisible, setRailVisible] = useState(() => { - try { return localStorage.getItem('deck-stage.railVisible') !== '0'; } catch (e) { return true; } + try { + return localStorage.getItem("deck-stage.railVisible") !== "0"; + } catch (e) { + return true; + } }); const toggleRail = (on) => { setRailVisible(on); - window.postMessage({ type: '__deck_rail_visible', on }, '*'); + window.postMessage({ type: "__deck_rail_visible", on }, "*"); }; const offsetRef = useRef({ x: 16, y: 16 }); const PAD = 16; @@ -231,23 +238,24 @@ function TweaksPanel({ title = 'Tweaks', noDeckControls = false, children }) { const clampToViewport = React.useCallback(() => { const panel = dragRef.current; if (!panel) return; - const w = panel.offsetWidth, h = panel.offsetHeight; + const w = panel.offsetWidth, + h = panel.offsetHeight; const maxRight = Math.max(PAD, window.innerWidth - w - PAD); const maxBottom = Math.max(PAD, window.innerHeight - h - PAD); offsetRef.current = { x: Math.min(maxRight, Math.max(PAD, offsetRef.current.x)), y: Math.min(maxBottom, Math.max(PAD, offsetRef.current.y)), }; - panel.style.right = offsetRef.current.x + 'px'; - panel.style.bottom = offsetRef.current.y + 'px'; + panel.style.right = offsetRef.current.x + "px"; + panel.style.bottom = offsetRef.current.y + "px"; }, []); useEffect(() => { if (!open) return; clampToViewport(); - if (typeof ResizeObserver === 'undefined') { - window.addEventListener('resize', clampToViewport); - return () => window.removeEventListener('resize', clampToViewport); + if (typeof ResizeObserver === "undefined") { + window.addEventListener("resize", clampToViewport); + return () => window.removeEventListener("resize", clampToViewport); } const ro = new ResizeObserver(clampToViewport); ro.observe(document.documentElement); @@ -257,24 +265,25 @@ function TweaksPanel({ title = 'Tweaks', noDeckControls = false, children }) { useEffect(() => { const onMsg = (e) => { const t = e?.data?.type; - if (t === '__activate_edit_mode') setOpen(true); - else if (t === '__deactivate_edit_mode') setOpen(false); + if (t === "__activate_edit_mode") setOpen(true); + else if (t === "__deactivate_edit_mode") setOpen(false); }; - window.addEventListener('message', onMsg); - window.parent.postMessage({ type: '__edit_mode_available' }, '*'); - return () => window.removeEventListener('message', onMsg); + window.addEventListener("message", onMsg); + window.parent.postMessage({ type: "__edit_mode_available" }, "*"); + return () => window.removeEventListener("message", onMsg); }, []); const dismiss = () => { setOpen(false); - window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*'); + window.parent.postMessage({ type: "__edit_mode_dismissed" }, "*"); }; const onDragStart = (e) => { const panel = dragRef.current; if (!panel) return; const r = panel.getBoundingClientRect(); - const sx = e.clientX, sy = e.clientY; + const sx = e.clientX, + sy = e.clientY; const startRight = window.innerWidth - r.right; const startBottom = window.innerHeight - r.bottom; const move = (ev) => { @@ -285,30 +294,43 @@ function TweaksPanel({ title = 'Tweaks', noDeckControls = false, children }) { clampToViewport(); }; const up = () => { - window.removeEventListener('mousemove', move); - window.removeEventListener('mouseup', up); + window.removeEventListener("mousemove", move); + window.removeEventListener("mouseup", up); }; - window.addEventListener('mousemove', move); - window.addEventListener('mouseup', up); + window.addEventListener("mousemove", move); + window.addEventListener("mouseup", up); }; if (!open) return null; return ( <> -
+
{title} - +
{children} {hasDeckStage && railEnabled && !noDeckControls && ( - + )}
@@ -330,7 +352,7 @@ function TweakSection({ label, children }) { function TweakRow({ label, value, children, inline = false }) { return ( -
+
{label} {value != null && {value}} @@ -342,11 +364,26 @@ function TweakRow({ label, value, children, inline = false }) { // ── Controls ──────────────────────────────────────────────────────────────── -function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = '', onChange }) { +function TweakSlider({ + label, + value, + min = 0, + max = 100, + step = 1, + unit = "", + onChange, +}) { return ( - onChange(Number(e.target.value))} /> + onChange(Number(e.target.value))} + /> ); } @@ -354,10 +391,19 @@ function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = '', on function TweakToggle({ label, value, onChange }) { return (
-
{label}
- +
+ {label} +
+
); } @@ -375,21 +421,34 @@ function TweakRadio({ label, value, options, onChange }) { // to its own padding, and 11.5px system-ui averages ~6.3px/char — so 2 // options fit ~16 chars each, 3 fit ~10. Past that (or >3 options), fall // back to a dropdown rather than wrap. - const labelLen = (o) => String(typeof o === 'object' ? o.label : o).length; + const labelLen = (o) => String(typeof o === "object" ? o.label : o).length; const maxLen = options.reduce((m, o) => Math.max(m, labelLen(o)), 0); const fitsAsSegments = maxLen <= ({ 2: 16, 3: 10 }[options.length] ?? 0); if (!fitsAsSegments) { // onChange(e.target.value)}> + @@ -451,13 +531,26 @@ function TweakSelect({ label, value, options, onChange }) { function TweakText({ label, value, placeholder, onChange }) { return ( - onChange(e.target.value)} /> + onChange(e.target.value)} + /> ); } -function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) { +function TweakNumber({ + label, + value, + min, + max, + step = 1, + unit = "", + onChange, +}) { const clamp = (n) => { if (min != null && n < min) return min; if (max != null && n > max) return max; @@ -467,7 +560,7 @@ function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) const onScrubStart = (e) => { e.preventDefault(); startRef.current = { x: e.clientX, val: value }; - const decimals = (String(step).split('.')[1] || '').length; + const decimals = (String(step).split(".")[1] || "").length; const move = (ev) => { const dx = ev.clientX - startRef.current.x; const raw = startRef.current.val + dx * step; @@ -475,17 +568,25 @@ function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) onChange(clamp(Number(snapped.toFixed(decimals)))); }; const up = () => { - window.removeEventListener('pointermove', move); - window.removeEventListener('pointerup', up); + window.removeEventListener("pointermove", move); + window.removeEventListener("pointerup", up); }; - window.addEventListener('pointermove', move); - window.addEventListener('pointerup', up); + window.addEventListener("pointermove", move); + window.addEventListener("pointerup", up); }; return (
- {label} - onChange(clamp(Number(e.target.value)))} /> + + {label} + + onChange(clamp(Number(e.target.value)))} + /> {unit && {unit}}
); @@ -495,19 +596,26 @@ function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) // read on both #111 and #fafafa without per-option configuration. Hex input // only (#rgb / #rrggbb); named or rgb()/hsl() colors fall through to "light". function __twkIsLight(hex) { - const h = String(hex).replace('#', ''); - const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, '0'); + const h = String(hex).replace("#", ""); + const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, "0"); const n = parseInt(x.slice(0, 6), 16); if (Number.isNaN(n)) return true; - const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255; + const r = (n >> 16) & 255, + g = (n >> 8) & 255, + b = n & 255; return r * 299 + g * 587 + b * 114 > 148000; } const __TwkCheck = ({ light }) => ( ); @@ -521,9 +629,15 @@ function TweakColor({ label, value, options, onChange }) { if (!options || !options.length) { return (
-
{label}
- onChange(e.target.value)} /> +
+ {label} +
+ onChange(e.target.value)} + />
); } @@ -541,14 +655,23 @@ function TweakColor({ label, value, options, onChange }) { const sup = rest.slice(0, 4); const on = key(o) === cur; return ( - + ); } - - // --- primitives.jsx --- // Small shared primitives: logo, arrow icon, ambient glow, eyebrow, trust strip. @@ -580,7 +706,8 @@ function LogoMark({ size = 26, blink = true }) { @@ -608,8 +739,21 @@ function Logo({ size = 26 }) { function Arrow({ size = 14 }) { return ( - - +
); } function PromptIcon({ name }) { - const props = { width: 13, height: 13, viewBox: "0 0 16 16", fill: "none", - stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" }; - if (name === "paperclip") return ( - - ); - if (name === "mic") return ( - - ); - if (name === "grid") return ( - - ); + const props = { + width: 13, + height: 13, + viewBox: "0 0 16 16", + fill: "none", + stroke: "currentColor", + strokeWidth: 1.5, + strokeLinecap: "round", + strokeLinejoin: "round", + }; + if (name === "paperclip") + return ( + + + + ); + if (name === "mic") + return ( + + + + + ); + if (name === "grid") + return ( + + + + + + + ); return null; } - - // --- wall.jsx --- // The Wall — recreates the moment the vibe dies. Faux chat from a "generic" AI // coding tool that hands back a homework list. Ends on the punchline. @@ -1238,13 +1456,18 @@ function Wall() { Every other tool stops right here.

- You built it. It works on your laptop. Then the chat hands you a list. + You built it. It works on your laptop. Then the chat hands you a + list.

-
+
+ + + +
untitled-project · main generic ai coder · chat
@@ -1254,7 +1477,10 @@ function Wall() {
YOU
You · just now
-

okay it works!! how do i put this online so my customers can use it?

+

+ okay it works!! how do i put this online so my customers can + use it? +

@@ -1263,17 +1489,45 @@ function Wall() {
Generic AI · just now

- Great job 🎉 Your app is running locally. To take it live, you'll need to set a few things up first: + Great job 🎉 Your app is running locally. To take it live, + you'll need to set a few things up first:

    -
  1. Sign up for Supabase and create a project for your database.↗ external
  2. -
  3. Configure authentication with Supabase Auth or Clerk — pick one.↗ external
  4. -
  5. Create a GitHub repo, commit your code, and push it.↗ external
  6. -
  7. Deploy to Vercel: connect repo, configure framework preset.↗ external
  8. -
  9. Add environment variables for your API keys and DB url in the Vercel dashboard.↗ external
  10. -
  11. Set up DNS for your custom domain and verify nameservers with your registrar.↗ external
  12. -
  13. Configure SSL / TLS certificates for HTTPS (or use Vercel's automatic provisioning).↗ external
  14. -
  15. Set up Stripe if you want to take payments, and configure webhooks.↗ external
  16. +
  17. + Sign up for Supabase and create a project for your + database.↗ external +
  18. +
  19. + Configure authentication with Supabase Auth or Clerk + — pick one.↗ external +
  20. +
  21. + Create a GitHub repo, commit your code, and push it. + ↗ external +
  22. +
  23. + Deploy to Vercel: connect repo, configure framework + preset.↗ external +
  24. +
  25. + Add environment variables for your API keys and DB + url in the Vercel dashboard. + ↗ external +
  26. +
  27. + Set up DNS for your custom domain and verify + nameservers with your registrar. + ↗ external +
  28. +
  29. + Configure SSL / TLS certificates for HTTPS (or use + Vercel's automatic provisioning). + ↗ external +
  30. +
  31. + Set up Stripe if you want to take payments, and + configure webhooks.↗ external +
↓ 23 more steps
@@ -1284,7 +1538,11 @@ function Wall() {
You · now

- + + + + +

@@ -1302,8 +1560,6 @@ function Wall() { ); } - - // --- crossed.jsx --- // Crossed-out list — technical terms struck through, ending in "Your AI handles // all of it. You just keep building." @@ -1438,8 +1694,6 @@ function CrossedOut() { ); } - - // --- journey.jsx --- // The Journey — 4 steps from idea → first 100 customers. A visual marker shows // where most tools stop. Each step shows a tiny "demo" snippet of what Vibn does. @@ -1673,10 +1927,12 @@ function Journey() { The journey

From idea to first 100 customers. -
In one chat. +
+ In one chat.

- Other tools take you to step two and wave goodbye. Vibn keeps building with you. + Other tools take you to step two and wave goodbye. Vibn keeps + building with you.

@@ -1694,7 +1950,8 @@ function Journey() {

- One tool. One chat. From "wouldn't it be cool if…" to real customers paying you money. + One tool. One chat. From "wouldn't it be cool if…" to{" "} + real customers paying you money.

@@ -1719,8 +1976,16 @@ function StepDemo({ demo }) { if (demo === "describe") { return (
-
YOUbuild a booking site for my dog grooming biz
-
VIBNon it — designing screens…
+
+ YOU + + build a booking site for my dog grooming biz + +
+
+ VIBN + on it — designing screens… +
VIBN ✓ booking flow ready @@ -1735,21 +2000,41 @@ function StepDemo({ demo }) { VIBN put it online
-
-
- pawsandposh.vibn.app +
+ +
+
+ + pawsandposh.vibn.app + +
+
+ ✓ logins · ✓ saving · ✓ live
-
✓ logins · ✓ saving · ✓ live
); } if (demo === "seen") { return (
-
VIBNdraft a launch post for Instagram + email blast
-
↳ scheduled for Tue 9:00 AM
-
↳ scheduled for Thu 6:00 PM
-
✓ 3 channels on autopilot
+
+ VIBN + + draft a launch post for Instagram + email blast + +
+
+ ↳ scheduled for Tue 9:00 AM +
+
+ ↳ scheduled for Thu 6:00 PM +
+
+ ✓ 3 channels on autopilot +
); } @@ -1757,7 +2042,9 @@ function StepDemo({ demo }) { return (
- +47this week + + +47this week +
@@ -1766,15 +2053,15 @@ function StepDemo({ demo }) { found you via Google
-
✓ tracking toward 100
+
+ ✓ tracking toward 100 +
); } return null; } - - // --- audience.jsx --- // Who it's for — three audience cards, each with a Reddit-style customer quote // and Vibn's answer. @@ -1783,23 +2070,29 @@ const AUDIENCE = [ { label: "Small business owners", icon: "shop", - quote: "I'm paying $312/month for software that does 60% of what I need and zero of the rest.", + quote: + "I'm paying $312/month for software that does 60% of what I need and zero of the rest.", source: "u/coffeeshop_owner · r/smallbusiness", - answer: "Build the tool that actually fits your shop — exactly your workflow, no monthly fee bleed.", + answer: + "Build the tool that actually fits your shop — exactly your workflow, no monthly fee bleed.", }, { label: "Freelancers building for clients", icon: "spark", - quote: "My client wants a quote tool. I can mock the frontend in a day. The backend? Two weeks I don't have.", + quote: + "My client wants a quote tool. I can mock the frontend in a day. The backend? Two weeks I don't have.", source: "u/agency_of_one · r/freelance", - answer: "Deliver the whole thing — login, data, hosting — in the same chat where you built the screens.", + answer: + "Deliver the whole thing — login, data, hosting — in the same chat where you built the screens.", }, { label: "Anyone with an idea", icon: "spark2", - quote: "I built the homepage in an afternoon. Then the AI told me to 'just deploy it' and I cried.", + quote: + "I built the homepage in an afternoon. Then the AI told me to 'just deploy it' and I cried.", source: "u/first_time_builder · r/sideproject", - answer: "No deploys. No GitHub. No fear. The thing you described is online, with logins, ready for users.", + answer: + "No deploys. No GitHub. No fear. The thing you described is online, with logins, ready for users.", }, ]; @@ -1917,12 +2210,13 @@ function Audience() {
{AUDIENCE.map((a) => (
-
+
+ +
{a.label}
- "{a.quote}" -
— {a.source}
+ "{a.quote}"
— {a.source}
@@ -1938,22 +2232,39 @@ function Audience() { } function AudienceIcon({ name }) { - const p = { width: 20, height: 20, viewBox: "0 0 20 20", fill: "none", - stroke: "currentColor", strokeWidth: 1.5, strokeLinecap: "round", strokeLinejoin: "round" }; - if (name === "shop") return ( - - ); - if (name === "spark") return ( - - ); - if (name === "spark2") return ( - - ); + const p = { + width: 20, + height: 20, + viewBox: "0 0 20 20", + fill: "none", + stroke: "currentColor", + strokeWidth: 1.5, + strokeLinecap: "round", + strokeLinejoin: "round", + }; + if (name === "shop") + return ( + + + + + ); + if (name === "spark") + return ( + + + + ); + if (name === "spark2") + return ( + + + + + ); return null; } - - // --- closing.jsx --- // Closing CTA + Footer. @@ -2006,19 +2317,27 @@ function Closing() { } `} - - + +

If you can describe it, -
you can build it. +
+ you can build it.

And you can keep building it — all the way to customers. -
No new tools. No homework. No going back to the wall. +
+ No new tools. No homework. No going back to the wall.

@@ -2026,9 +2345,13 @@ function Closing() { Request invite - See how it works + + See how it works +
- +
@@ -2104,31 +2427,29 @@ function Footer() { ); } - - // --- app.jsx --- // App — composes the page. Includes the sticky nav, the success modal that // appears when the user submits the hero prompt, and the Tweaks panel. -const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ - "accent": ["#ff6b47", "#ffae9a", "#9c3a1f"], - "heroVariant": "promise", - "showStopMarker": true, - "showLivePill": false -}/*EDITMODE-END*/; +const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/ { + accent: ["#ff6b47", "#ffae9a", "#9c3a1f"], + heroVariant: "promise", + showStopMarker: true, + showLivePill: false, +}; /*EDITMODE-END*/ const ACCENT_PRESETS = { - coral: ["#ff6b47", "#ffae9a", "#9c3a1f"], // warm coral (default) - amber: ["#ffb347", "#ffd9a3", "#9c6e1f"], // soft amber - lime: ["#9ee649", "#d2f3a6", "#3f7a1c"], // electric lime - violet: ["#b07cff", "#dabfff", "#5a2fa3"], // violet + coral: ["#ff6b47", "#ffae9a", "#9c3a1f"], // warm coral (default) + amber: ["#ffb347", "#ffd9a3", "#9c6e1f"], // soft amber + lime: ["#9ee649", "#d2f3a6", "#3f7a1c"], // electric lime + violet: ["#b07cff", "#dabfff", "#5a2fa3"], // violet }; function applyAccent(arr) { // arr[0] is the hero color we map to var(--accent); compute soft + glow + fg. const hero = arr[0]; - const soft = `${hero}24`; // 14% alpha - const glow = `${hero}59`; // 35% alpha + const soft = `${hero}24`; // 14% alpha + const glow = `${hero}59`; // 35% alpha const root = document.documentElement; root.style.setProperty("--accent", hero); root.style.setProperty("--accent-soft", soft); @@ -2193,7 +2514,7 @@ function App() { label="Headline" value={t.heroVariant} options={[ - { value: "quote", label: "Reddit quote" }, + { value: "quote", label: "Reddit quote" }, { value: "promise", label: "The promise" }, ]} onChange={(v) => setTweak("heroVariant", v)} @@ -2234,8 +2555,14 @@ function Nav({ scrolled }) { Stories
@@ -2248,7 +2575,9 @@ function Nav({ scrolled }) { // vibe will be honored — playfully sells the rest of the flow. function LaunchModal({ prompt, onClose }) { useEffect(() => { - const onKey = (e) => { if (e.key === "Escape") onClose(); }; + const onKey = (e) => { + if (e.key === "Escape") onClose(); + }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [onClose]); @@ -2303,23 +2632,44 @@ function LaunchModal({ prompt, onClose }) { `}
e.stopPropagation()}> - -
Vibn is on it
+ +
+ Vibn is on it +

Keep vibing — we've got the rest.

"{prompt}"
- {["Drafting the screens", "Setting up logins", "Saving your stuff", "Putting it online"].map((s, i) => ( + {[ + "Drafting the screens", + "Setting up logins", + "Saving your stuff", + "Putting it online", + ].map((s, i) => (
{i < step ? ( - + ) : i === step ? ( ) : ( - + )} {s} @@ -2327,18 +2677,20 @@ function LaunchModal({ prompt, onClose }) { ))}
-
No homework · No setup · No new tools to learn
+
+ No homework · No setup · No new tools to learn +
); } - - - export default function NewSite() { return ( -
+
);