diff --git a/vibn-frontend/app/(marketing)/AccentSwitcher.tsx b/vibn-frontend/app/(marketing)/AccentSwitcher.tsx new file mode 100644 index 00000000..62eedfbc --- /dev/null +++ b/vibn-frontend/app/(marketing)/AccentSwitcher.tsx @@ -0,0 +1,237 @@ +"use client"; + +import { useEffect, useState } from "react"; + +/** + * Dev-only accent color switcher for the marketing site. + * + * Flips the homepage accent live by overriding the `--accent-h` (hue) and + * `--accent-cm` (chroma) CSS variables on `.new-site-wrapper`. Choice persists + * in localStorage so it survives reloads while you experiment. + * + * Visibility: shows only in development, or in any environment when the URL has + * `?theme`. So it's hidden in production by default — nothing to do to hide it. + * To remove entirely later, delete this file and its usage + * in app/(marketing)/page.tsx. + */ + +const PRESETS: { label: string; h: number; cm: number }[] = [ + { label: "Coral", h: 35, cm: 1 }, + { label: "Silver", h: 35, cm: 0 }, + { label: "Crimson", h: 25, cm: 1 }, + { label: "Amber", h: 75, cm: 1 }, + { label: "Emerald", h: 150, cm: 1 }, + { label: "Teal", h: 195, cm: 1 }, + { label: "Sky", h: 235, cm: 1 }, + { label: "Royal", h: 255, cm: 1 }, + { label: "Indigo", h: 270, cm: 1 }, + { label: "Violet", h: 300, cm: 1 }, + { label: "Pink", h: 350, cm: 1 }, +]; + +const swatchColor = (h: number, cm: number) => + `oklch(0.74 ${(0.175 * cm).toFixed(3)} ${h})`; + +export default function AccentSwitcher() { + const [visible, setVisible] = useState(false); + const [open, setOpen] = useState(true); + const [h, setH] = useState(35); + const [cm, setCm] = useState(1); + + useEffect(() => { + const isDev = process.env.NODE_ENV !== "production"; + const forced = + typeof window !== "undefined" && + new URLSearchParams(window.location.search).has("theme"); + setVisible(isDev || forced); + try { + const saved = JSON.parse(localStorage.getItem("vibn:accent") || "null"); + if (saved && typeof saved.h === "number") { + setH(saved.h); + setCm(typeof saved.cm === "number" ? saved.cm : 1); + } + } catch { + /* ignore */ + } + }, []); + + useEffect(() => { + if (!visible) return; + const el = document.querySelector(".new-site-wrapper") as HTMLElement | null; + if (el) { + el.style.setProperty("--accent-h", String(h)); + el.style.setProperty("--accent-cm", String(cm)); + } + try { + localStorage.setItem("vibn:accent", JSON.stringify({ h, cm })); + } catch { + /* ignore */ + } + }, [visible, h, cm]); + + if (!visible) return null; + + const panel: React.CSSProperties = { + position: "fixed", + left: 16, + bottom: 16, + zIndex: 2147483647, + width: 232, + padding: 14, + borderRadius: 14, + background: "rgba(18,17,15,0.92)", + border: "1px solid rgba(255,255,255,0.12)", + backdropFilter: "blur(16px)", + boxShadow: "0 18px 50px rgba(0,0,0,0.5)", + color: "#f5f4f2", + fontFamily: "ui-monospace, 'SF Mono', Menlo, monospace", + fontSize: 11, + userSelect: "none", + }; + + if (!open) { + return ( + + + +
+ {PRESETS.map((p) => { + const active = p.h === h && p.cm === cm; + return ( +
+ + + + + +
+ + h:{h} cm:{cm} + + +
+ + ); +} diff --git a/vibn-frontend/app/(marketing)/new-site.tsx b/vibn-frontend/app/(marketing)/new-site.tsx index 4ca1646d..a7365dad 100644 --- a/vibn-frontend/app/(marketing)/new-site.tsx +++ b/vibn-frontend/app/(marketing)/new-site.tsx @@ -915,10 +915,23 @@ function Hero({ onStart, variant = "quote" }) { line-height: 0.98; text-wrap: balance; position: relative; - color: var(--fg); + /* Silver / brushed-metal heading: bright at the top, fading to a soft + pewter at the bottom, clipped to the text. */ + background-image: linear-gradient( + 180deg, + oklch(0.99 0.003 80) 0%, + oklch(0.86 0.004 80) 42%, + oklch(0.66 0.006 80) 100% + ); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + -webkit-text-fill-color: transparent; } .hero-quote .mark { + /* keep the accent word in full color inside the silver heading */ color: var(--accent); + -webkit-text-fill-color: var(--accent); font-family: "Geist", serif; font-weight: 500; line-height: 0; @@ -963,13 +976,62 @@ function Hero({ onStart, variant = "quote" }) { border-radius: var(--r-xl); padding: 1px; background: linear-gradient(180deg, - oklch(0.50 0.06 35 / 0.6), + oklch(0.50 calc(0.06 * var(--accent-cm)) var(--accent-h) / 0.6), oklch(0.30 0.012 60 / 0.4) 40%, oklch(0.25 0.012 60 / 0.4)); box-shadow: 0 30px 80px -20px oklch(0 0 0 / 0.6), 0 0 80px -20px var(--accent-glow); } + /* Animated border beam (Magic UI style): a light comet that travels + the border, masked to a thin ring and themed to the accent. */ + .prompt-beam { + position: absolute; + inset: 0; + border-radius: inherit; + padding: 1.5px; + pointer-events: none; + z-index: 2; + -webkit-mask: + linear-gradient(#000 0 0) content-box, + linear-gradient(#000 0 0); + mask: + linear-gradient(#000 0 0) content-box, + linear-gradient(#000 0 0); + -webkit-mask-composite: xor; + mask-composite: exclude; + overflow: hidden; + } + .prompt-beam::before { + content: ""; + position: absolute; + width: 170px; + aspect-ratio: 1; + offset-path: rect(0 auto auto 0 round var(--r-xl)); + offset-distance: 0%; + offset-anchor: 50% 50%; + background: linear-gradient( + 90deg, + transparent, + var(--accent) 38%, + oklch(0.95 calc(0.05 * var(--accent-cm)) var(--accent-h)) 50%, + var(--accent) 62%, + transparent + ); + filter: blur(3px); + animation: prompt-beam 11s linear infinite; + } + @keyframes prompt-beam { + to { + offset-distance: 100%; + } + } + @media (prefers-reduced-motion: reduce) { + .prompt-beam::before { + animation: none; + opacity: 0; + } + } .prompt-inner { background: linear-gradient(180deg, oklch(0.19 0.009 60 / 0.92), oklch(0.17 0.008 60 / 0.92)); border-radius: calc(var(--r-xl) - 1px); @@ -1037,7 +1099,7 @@ function Hero({ onStart, variant = "quote" }) { background: var(--accent); color: var(--accent-fg); font-weight: 500; font-size: 14px; - box-shadow: 0 0 0 1px oklch(0.84 0.16 35 / 0.5) inset, 0 8px 28px -8px var(--accent-glow); + box-shadow: 0 0 0 1px oklch(0.84 calc(0.16 * var(--accent-cm)) var(--accent-h) / 0.5) inset, 0 8px 28px -8px var(--accent-glow); transition: transform .12s; } .prompt-send:hover { transform: translateY(-1px); } @@ -1095,17 +1157,17 @@ function Hero({ onStart, variant = "quote" }) { {/* ambient glows behind hero */} @@ -1156,6 +1218,7 @@ function Hero({ onStart, variant = "quote" }) { {/* Prompt */}
+