This commit addresses the issue where DeepSeek's raw XML markup (like <tool_calls> and <think>) was leaking into chat history, causing hallucinations in subsequent turns. It also patches a vulnerability in the git commit tool where arbitrary shell injection was possible. Additionally, it includes UX copy and color contrast adjustments for the marketing homepage breadcrumbs.
178 lines
5.8 KiB
JavaScript
178 lines
5.8 KiB
JavaScript
// Who it's for — three audience cards, each with a Reddit-style customer quote
|
|
// and Vibn's answer.
|
|
|
|
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.",
|
|
source: "u/coffeeshop_owner · r/smallbusiness",
|
|
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.",
|
|
source: "u/agency_of_one · r/freelance",
|
|
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.",
|
|
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.",
|
|
},
|
|
];
|
|
|
|
function Audience() {
|
|
return (
|
|
<section className="section audience">
|
|
<style>{`
|
|
.audience-head { text-align: center; max-width: 820px; margin: 0 auto 56px; }
|
|
.audience-title {
|
|
font-size: clamp(36px, 4.8vw, 64px);
|
|
font-weight: 500; letter-spacing: -0.025em; line-height: 1.02;
|
|
text-wrap: balance;
|
|
}
|
|
.audience-sub {
|
|
margin-top: 20px;
|
|
color: var(--fg-mute);
|
|
font-size: 17px;
|
|
}
|
|
|
|
.audience-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 18px;
|
|
}
|
|
@media (max-width: 1000px) { .audience-grid { grid-template-columns: 1fr; } }
|
|
|
|
.a-card {
|
|
position: relative;
|
|
padding: 28px 26px 26px;
|
|
border-radius: 18px;
|
|
background: linear-gradient(180deg, oklch(0.20 0.009 60 / 0.55), oklch(0.17 0.008 60 / 0.55));
|
|
border: 1px solid var(--hairline);
|
|
display: flex; flex-direction: column;
|
|
min-height: 380px;
|
|
overflow: hidden;
|
|
}
|
|
.a-card::after {
|
|
content: "";
|
|
position: absolute;
|
|
top: 0; left: 24px; right: 24px;
|
|
height: 1px;
|
|
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
|
opacity: .6;
|
|
}
|
|
|
|
.a-icon {
|
|
width: 40px; height: 40px;
|
|
border-radius: 10px;
|
|
display: grid; place-items: center;
|
|
background: oklch(0.22 0.011 60);
|
|
border: 1px solid var(--hairline);
|
|
color: var(--accent);
|
|
margin-bottom: 18px;
|
|
}
|
|
.a-label {
|
|
font-size: 19px; font-weight: 500;
|
|
letter-spacing: -0.015em;
|
|
color: var(--fg);
|
|
}
|
|
|
|
.a-quote {
|
|
margin: 18px 0 0;
|
|
padding: 16px 18px;
|
|
background: oklch(0.16 0.008 60 / 0.55);
|
|
border-left: 2px solid var(--accent);
|
|
border-radius: 4px 10px 10px 4px;
|
|
font-style: italic;
|
|
color: var(--fg-dim);
|
|
font-size: 14.5px;
|
|
line-height: 1.5;
|
|
position: relative;
|
|
}
|
|
.a-source {
|
|
margin-top: 8px;
|
|
font-family: var(--font-mono);
|
|
font-size: 11px;
|
|
color: var(--fg-faint);
|
|
letter-spacing: 0.02em;
|
|
}
|
|
|
|
.a-answer {
|
|
margin-top: auto;
|
|
padding-top: 22px;
|
|
font-size: 15px;
|
|
color: var(--fg);
|
|
line-height: 1.5;
|
|
display: flex; gap: 10px; align-items: flex-start;
|
|
}
|
|
.a-answer .label {
|
|
font-family: var(--font-mono);
|
|
font-size: 10px;
|
|
letter-spacing: 0.12em;
|
|
text-transform: uppercase;
|
|
color: var(--accent);
|
|
padding: 3px 7px;
|
|
background: oklch(0.74 0.175 35 / 0.12);
|
|
border: 1px solid oklch(0.74 0.175 35 / 0.4);
|
|
border-radius: 4px;
|
|
margin-top: 1px;
|
|
flex-shrink: 0;
|
|
}
|
|
`}</style>
|
|
|
|
<div className="wrap">
|
|
<div className="audience-head">
|
|
<Eyebrow>Who Vibn is for</Eyebrow>
|
|
<h2 className="audience-title" style={{ marginTop: 18 }}>
|
|
People who have an idea — not a stack.
|
|
</h2>
|
|
<p className="audience-sub">
|
|
If you've ever felt this, Vibn was built for you.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="audience-grid">
|
|
{AUDIENCE.map((a) => (
|
|
<div className="a-card" key={a.label}>
|
|
<div className="a-icon"><AudienceIcon name={a.icon} /></div>
|
|
<div className="a-label">{a.label}</div>
|
|
|
|
<div className="a-quote">
|
|
"{a.quote}"
|
|
<div className="a-source">— {a.source}</div>
|
|
</div>
|
|
|
|
<div className="a-answer">
|
|
<span className="label">Vibn</span>
|
|
<span>{a.answer}</span>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<svg {...p}><path d="M3.5 6.5h13l-1 9.5h-11l-1-9.5Z"/><path d="M7 6.5V5a3 3 0 0 1 6 0v1.5"/></svg>
|
|
);
|
|
if (name === "spark") return (
|
|
<svg {...p}><path d="M10 3v4M10 13v4M3 10h4M13 10h4M5.3 5.3l2.8 2.8M11.9 11.9l2.8 2.8M14.7 5.3l-2.8 2.8M8.1 11.9l-2.8 2.8"/></svg>
|
|
);
|
|
if (name === "spark2") return (
|
|
<svg {...p}><path d="M10 2.5v3M10 14.5v3M2.5 10h3M14.5 10h3"/><circle cx="10" cy="10" r="3"/></svg>
|
|
);
|
|
return null;
|
|
}
|
|
|
|
Object.assign(window, { Audience });
|