Files
vibn-agent-runner/design-templates/VIBN (2)/app.jsx

231 lines
9.0 KiB
JavaScript

// 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": "owner",
"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
};
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 root = document.documentElement;
root.style.setProperty("--accent", hero);
root.style.setProperty("--accent-soft", soft);
root.style.setProperty("--accent-glow", glow);
// Foreground on accent: derive a dark-on-accent for primary buttons.
root.style.setProperty("--accent-fg", "#1a0f0a");
}
function App() {
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
const [scrolled, setScrolled] = React.useState(false);
const [showLaunch, setShowLaunch] = React.useState(null);
React.useEffect(() => {
applyAccent(t.accent);
}, [t.accent]);
React.useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 8);
onScroll();
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
const handleStart = (prompt) => {
setShowLaunch(prompt || "Build me a tool for my business.");
};
return (
<>
<Nav scrolled={scrolled} />
<main>
<Hero onStart={handleStart} variant={t.heroVariant} />
<Wall />
<CrossedOut />
<Stack />
<Journey />
<Audience />
<Mission />
<Closing />
</main>
<Footer />
{showLaunch !== null && (
<LaunchModal prompt={showLaunch} onClose={() => setShowLaunch(null)} />
)}
<TweaksPanel title="Tweaks">
<TweakSection label="Look">
<TweakColor
label="Accent"
value={t.accent}
options={[
ACCENT_PRESETS.coral,
ACCENT_PRESETS.amber,
ACCENT_PRESETS.lime,
ACCENT_PRESETS.violet,
]}
onChange={(v) => setTweak("accent", v)}
/>
</TweakSection>
<TweakSection label="Hero">
<TweakSelect
label="Headline"
value={t.heroVariant}
options={[
{ value: "owner", label: "Owner — 'Stop paying rent'" },
{ value: "promise", label: "Promise — 'Keep vibing'" },
{ value: "quote", label: "Quote — 'I built my product…'" },
]}
onChange={(v) => setTweak("heroVariant", v)}
/>
<TweakToggle
label="Live pill"
value={t.showLivePill}
onChange={(v) => setTweak("showLivePill", v)}
/>
</TweakSection>
<TweakSection label="Journey">
<TweakToggle
label="Show 'where others stop' marker"
value={t.showStopMarker}
onChange={(v) => setTweak("showStopMarker", v)}
/>
</TweakSection>
</TweaksPanel>
{/* Tweak-driven CSS overrides */}
<style>{`
${t.showLivePill ? "" : ".live-pill { display: none !important; }"}
${t.showStopMarker ? "" : ".stop-marker { display: none !important; }"}
`}</style>
</>
);
}
function Nav({ scrolled }) {
return (
<nav className={`nav${scrolled ? " scrolled" : ""}`}>
<div className="wrap nav-inner">
<Logo />
<div className="nav-links">
<a href="#how">How it works</a>
<a href="#">Templates</a>
<a href="#">Pricing</a>
<a href="#">Stories</a>
</div>
<div className="nav-cta">
<a href="Sign In.html" className="btn btn-ghost" style={{ display: "inline-flex" }}>Sign in</a>
<a href="Beta Signup.html" className="btn btn-primary">
Request invite <Arrow size={12} />
</a>
</div>
</div>
</nav>
);
}
// Modal that fires when the user submits the hero prompt. Reassures them their
// vibe will be honored — playfully sells the rest of the flow.
function LaunchModal({ prompt, onClose }) {
React.useEffect(() => {
const onKey = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, [onClose]);
const [step, setStep] = React.useState(0);
React.useEffect(() => {
if (step >= 4) return undefined;
const t = setTimeout(() => setStep(step + 1), 700);
return () => clearTimeout(t);
}, [step]);
return (
<div className="modal-backdrop" onClick={onClose}>
<style>{`
.modal-backdrop {
position: fixed; inset: 0; z-index: 100;
background: oklch(0.10 0.005 60 / 0.7);
backdrop-filter: blur(8px);
display: grid; place-items: center;
padding: 24px;
animation: fadein .2s ease;
}
@keyframes fadein { from { opacity: 0; } }
.modal {
position: relative;
width: 100%; max-width: 540px;
background: linear-gradient(180deg, oklch(0.20 0.009 60), oklch(0.17 0.008 60));
border: 1px solid var(--hairline-2);
border-radius: 20px;
padding: 28px 28px 24px;
box-shadow: 0 30px 80px -20px oklch(0 0 0 / 0.6), 0 0 60px -20px var(--accent-glow);
}
.modal-close {
position: absolute; top: 14px; right: 14px;
width: 28px; height: 28px;
color: var(--fg-mute);
border-radius: 6px;
}
.modal-close:hover { color: var(--fg); background: oklch(0.25 0.01 60); }
.modal-eye { display: flex; align-items: center; gap: 10px; color: var(--accent); font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; }
.modal-eye i { width: 6px; height: 6px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 12px var(--accent-glow); animation: pulse 2s ease-out infinite; }
.modal-title { margin-top: 12px; font-size: 24px; font-weight: 500; letter-spacing: -0.018em; line-height: 1.15; }
.modal-prompt { margin-top: 14px; padding: 12px 14px; border-radius: 10px; background: oklch(0.16 0.008 60); border: 1px solid var(--hairline); font-family: var(--font-mono); font-size: 13px; color: var(--fg-dim); line-height: 1.5; }
.modal-steps { margin-top: 18px; display: flex; flex-direction: column; gap: 10px; }
.modal-step { display: flex; align-items: center; gap: 12px; padding: 11px 14px; border-radius: 10px; background: oklch(0.165 0.008 60); border: 1px solid var(--hairline); font-size: 14px; color: var(--fg-dim); transition: all .25s; }
.modal-step.done { color: var(--fg); }
.modal-step.done .check { color: var(--ok); }
.modal-step .check { width: 18px; height: 18px; color: var(--fg-faint); flex-shrink: 0; }
.modal-step .spinner { width: 14px; height: 14px; border-radius: 50%; border: 2px solid oklch(0.30 0.01 60); border-top-color: var(--accent); animation: spin .9s linear infinite; flex-shrink: 0; }
@keyframes spin { to { transform: rotate(360deg); } }
.modal-foot { margin-top: 18px; text-align: center; font-family: var(--font-mono); font-size: 11px; color: var(--fg-faint); letter-spacing: 0.04em; }
`}</style>
<div className="modal" onClick={(e) => e.stopPropagation()}>
<button type="button" className="modal-close" onClick={onClose}></button>
<div className="modal-eye"><i /> Vibn is on it</div>
<h3 className="modal-title">Keep vibing we've got the rest.</h3>
<div className="modal-prompt">"{prompt}"</div>
<div className="modal-steps">
{["Drafting the screens", "Setting up logins", "Saving your stuff", "Putting it online"].map((s, i) => (
<div key={s} className={`modal-step${i < step ? " done" : ""}`}>
{i < step ? (
<svg className="check" viewBox="0 0 20 20" fill="none">
<path d="M4 10.5 8 14.5 16 6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
) : i === step ? (
<span className="spinner" />
) : (
<svg className="check" viewBox="0 0 20 20" fill="none">
<circle cx="10" cy="10" r="6" stroke="currentColor" strokeWidth="1.5" />
</svg>
)}
<span>{s}</span>
</div>
))}
</div>
<div className="modal-foot">No homework · No setup · No new tools to learn</div>
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);