Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions apps/web/src/app/(site)/[locale]/create/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { notFound } from "next/navigation";
import { CreateWizard } from "@/components/CreateWizard";
import { getDict, isLocale, type Locale } from "@/lib/i18n";
import "../../../prototype.css";
import "../../../create.css";

/**
* Carries a free prototype into the paid wizard.
Expand Down Expand Up @@ -44,12 +46,16 @@ export default async function CreatePage({
const initialIdea = sp.idea ? sp.idea.slice(0, 800) : await ideaFrom(sp.from);

return (
<main className="wrap" style={{ padding: "40px 24px 72px" }}>
<h1>{d.wizard.title}</h1>
<p className="muted" style={{ fontSize: 17, marginBottom: 32 }}>
{d.wizard.lede}
</p>
<CreateWizard locale={locale} d={d} initialIdea={initialIdea} />
<main className="cw">
<div className="pp-band cw-band">
<div className="wrap pp-head">
<h1>{d.wizard.title}</h1>
<p className="pp-lead">{d.wizard.lede}</p>
</div>
</div>
<div className="wrap cw-body">
<CreateWizard locale={locale} d={d} initialIdea={initialIdea} />
</div>
</main>
);
}
64 changes: 64 additions & 0 deletions apps/web/src/app/create.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* The app wizard (/[locale]/create), in the look of /prototype: head band, the questions in one
white card, the live estimate beside it. The wizard's classes (.choice, .est-panel, ...) are
shared with checkout and the project page, so everything here is scoped under `.cw`. */

.cw { padding-bottom: 80px; }
.cw .wrap { max-width: 1200px; }
.cw-band { padding: 44px 0 96px; }
.cw-body { margin-top: -64px; }
.cw .pp-lead { font-size: 1.1rem; color: var(--ink-soft); max-width: 34em; margin: 0 auto; }
.cw .wizard-cols { grid-template-columns: minmax(0, 1fr) 360px; gap: 28px; }

/* ---------- Questions card ---------- */
.cw-card { background: #fff; border: 1px solid var(--border); border-radius: 20px; padding: 30px 32px 8px; box-shadow: 0 30px 70px rgba(15, 23, 42, 0.1); min-width: 0; }
.cw .field { margin-bottom: 30px; }
.cw .field > .pp-label { display: flex; margin-bottom: 12px; }
.cw .idea-tools { gap: 8px; margin-top: 12px; }
.cw .idea-tools > .small { font-size: 0.84rem; }
.cw .pp-chip { font-size: 0.84rem; padding: 7px 12px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cw .field > p.small { font-size: 0.84rem; }

.cw-refine { display: inline-flex; align-items: center; gap: 8px; background: var(--accent-soft); color: var(--accent-hover); border: 1px solid #dbe3ff; }
.cw-refine:hover:not(:disabled) { background: #e0e7ff; }
.cw .idea-tools .cw-refine { padding: 10px 16px; font-size: 0.92rem; }
.cw-refine:disabled { opacity: 0.5; cursor: not-allowed; }

.cw .note { border-radius: 12px; margin-top: 12px; }
.cw-suggest { margin-top: 16px; gap: 12px; background: var(--tint); border: 1px solid #dbe3ff; border-radius: 16px; padding: 20px; }
.cw-suggest .choice { padding: 7px 13px; font-size: 0.88rem; }
.cw-suggest .btn-ghost { background: #fff; border: 1px solid var(--border); }

/* Choice tiles: the native control stays (keyboard, screen readers), tinted in the accent. */
.cw .choices { gap: 10px; }
.cw .choice { gap: 10px; padding: 12px 16px; border-radius: 12px; border-color: #d5dae4; font-size: 0.95rem; font-weight: 500; transition: border-color 0.15s ease, background 0.15s ease; }
.cw .choice:hover { border-color: #c7d2fe; }
.cw .choice input { accent-color: var(--accent); width: 16px; height: 16px; margin: 0; }
.cw .choice:has(input:checked) { background: var(--accent-soft); border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); color: var(--accent-hover); }
.cw .choice:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.cw .cw-features { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cw-features .choice .cost { margin-left: auto; font-size: 0.82rem; }
.cw .lang-toggle.hint { cursor: pointer; }

/* ---------- Estimate ---------- */
.cw .est-panel { top: 92px; border-radius: 20px; padding: 24px; gap: 10px; box-shadow: var(--shadow); }
.cw .est-panel h3 { font-size: 1.1rem; margin: 0 0 4px; }
.cw .est-panel .est-empty { font-size: 0.9rem; line-height: 1.55; margin: 0; }
.cw-price { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; background: var(--tint); border-radius: 12px; padding: 14px 16px; }
.cw-price span { font-size: 0.88rem; color: var(--ink-soft); }
.cw-price strong { font-family: var(--font-display); font-size: 1.7rem; font-weight: 800; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.cw .est-panel .row { font-size: 0.9rem; }
.cw-note { font-size: 0.82rem; color: var(--ink-soft); line-height: 1.5; margin: 0; }
.cw .est-panel .btn-block { display: flex; justify-content: center; margin-top: 6px; padding: 14px 20px; }
.cw .est-panel .btn[aria-disabled="true"] { background: var(--accent); color: #fff; opacity: 0.4; }

@media (max-width: 860px) {
.cw .wizard-cols { grid-template-columns: minmax(0, 1fr); }
.cw .est-panel { position: static; }
}
@media (max-width: 640px) {
.cw-band { padding: 28px 0 72px; }
.cw-body { margin-top: -52px; }
.cw-card { padding: 22px 18px 4px; border-radius: 18px; }
.cw .cw-features { grid-template-columns: minmax(0, 1fr); }
.cw .choice { flex: 1 1 auto; }
}
33 changes: 17 additions & 16 deletions apps/web/src/components/CreateWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { trackOnce } from "@/lib/analytics";
import { api, ApiError } from "@/lib/api";
import { featureHints } from "@/lib/featureHints";
import { eur, type Dict, type Locale } from "@/lib/i18n";
import { Icon } from "./LineIcon";

interface Refinement {
off_topic: boolean;
Expand Down Expand Up @@ -121,19 +122,20 @@ export function CreateWizard({

return (
<div className="wizard-cols">
<div>
<div className="cw-card">
<div className="field">
<label htmlFor="idea">{w.idea}</label>
<label htmlFor="idea" className="pp-label"><span className="pp-step">1</span>{w.idea}</label>
<textarea
id="idea"
className="pp-textarea"
value={idea}
placeholder={w.ideaPh}
onChange={(e) => setIdea(e.target.value)}
/>
<div className="idea-tools">
<span className="small muted">{w.ideaExamplesLabel}</span>
{w.ideaExamples.map((ex) => (
<button type="button" className="lang-toggle" key={ex} onClick={() => setIdea(ex)}>
<button type="button" className="pp-chip" key={ex} onClick={() => setIdea(ex)}>
{ex.length > 46 ? `${ex.slice(0, 44)}…` : ex}
</button>
))}
Expand All @@ -142,15 +144,16 @@ export function CreateWizard({
<p className="small muted" style={{ margin: "8px 0 0" }}>{w.ideaHint}</p>
)}
<div className="idea-tools" style={{ marginTop: 10 }}>
<button type="button" className="btn btn-ghost" disabled={!canRefine} onClick={() => refine(false)}>
<button type="button" className="btn cw-refine" disabled={!canRefine} onClick={() => refine(false)}>
<Icon name="spark" className="pp-btn-ico" />
{refining ? w.refining : w.refine}
</button>
<span className="small muted">{w.refineNote}</span>
</div>
{refineError && <p className="note" style={{ marginTop: 10 }}>{refineError}</p>}

{refinement && (
<div className="card" style={{ marginTop: 14, gap: 12 }}>
<div className="card cw-suggest">
{refinement.off_topic ? (
<p className="small muted" style={{ margin: 0 }}>{w.refineOffTopic}</p>
) : (
Expand Down Expand Up @@ -206,7 +209,7 @@ export function CreateWizard({
</div>

<div className="field">
<span className="field-label">{w.audience}</span>
<span className="field-label pp-label"><span className="pp-step">2</span>{w.audience}</span>
<div className="choices" role="radiogroup" aria-label={w.audience}>
{(Object.keys(w.audOpts) as Audience[]).map((v) => (
<label className="choice" key={v}>
Expand All @@ -223,7 +226,7 @@ export function CreateWizard({
</div>

<div className="field">
<span className="field-label">{w.platform}</span>
<span className="field-label pp-label"><span className="pp-step">3</span>{w.platform}</span>
<div className="choices" role="radiogroup" aria-label={w.platform}>
{(Object.keys(w.platOpts) as Platform[]).map((v) => (
<label className="choice" key={v}>
Expand All @@ -240,7 +243,7 @@ export function CreateWizard({
</div>

<div className="field">
<span className="field-label">{w.features}</span>
<span className="field-label pp-label"><span className="pp-step">4</span>{w.features}</span>
{hints.length > 0 && (
<div className="idea-tools" style={{ margin: "0 0 10px" }}>
<span className="small muted">{w.hintsLabel}</span>
Expand All @@ -249,12 +252,12 @@ export function CreateWizard({
+ {w.featureLabels[f]} · {eur(FEATURES[f].cost, locale)}
</button>
))}
<button type="button" className="small muted" style={{ background: "none", border: 0, cursor: "pointer" }} onClick={() => setDismissedHints((d) => [...d, ...hints])}>
<button type="button" className="pp-link" onClick={() => setDismissedHints((d) => [...d, ...hints])}>
{w.hintsDismiss}
</button>
</div>
)}
<div className="choices">
<div className="choices cw-features">
{FEATURE_KEYS.map((f) => (
<label className="choice" key={f}>
<input
Expand All @@ -271,15 +274,15 @@ export function CreateWizard({
</div>

<aside className="est-panel">
<h3 style={{ margin: 0 }}>{w.estTitle}</h3>
<h3>{w.estTitle}</h3>
{!est ? (
<p className="est-empty">
{w.estMissing} <strong>{missing.join(" · ")}</strong>
</p>
) : (
<>
<div className="row">
<span className="muted">{w.estPrice}</span>
<div className="cw-price">
<span>{w.estPrice}</span>
<strong>{eur(est.price, locale)}</strong>
</div>
<div className="row">
Expand All @@ -304,9 +307,7 @@ export function CreateWizard({
</strong>
</div>
<hr />
<p className="small muted" style={{ margin: 0 }}>
{w.estNote}
</p>
<p className="cw-note">{w.estNote}</p>
{ready ? (
<Link
className="btn btn-primary btn-block"
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/dictionaries/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export const de = {
ideaHint: "Tipp: Wer nutzt die App, was macht sie, und welche drei Dinge kann man darin tun?",
hintsLabel: "Aus deiner Beschreibung:",
hintsDismiss: "ausblenden",
refine: "✨ Idee schärfen",
refine: "Idee schärfen",
refining: "Wird geschärft …",
refineNote: "KI-Vorschlag. Du entscheidest, was übernommen wird. Bis zu 3 Runden pro Idee.",
refineTitle: "Vorschlag",
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/dictionaries/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ export const en = {
ideaHint: "Tip: who uses the app, what does it do, and which three things can a user do in it?",
hintsLabel: "From your description:",
hintsDismiss: "hide",
refine: "✨ Sharpen my idea",
refine: "Sharpen my idea",
refining: "Sharpening…",
refineNote: "AI suggestion. You decide what to keep. Up to 3 rounds per idea.",
refineTitle: "Suggestion",
Expand Down
Loading