design: implement beautiful CSS-animated miniature mockup layout previews inside design-style onboarding cards
This commit is contained in:
@@ -172,21 +172,670 @@ function EntrepType({ value, onChange }) {
|
||||
);
|
||||
}
|
||||
|
||||
function LayoutPreview({ styleId }: { styleId: string }) {
|
||||
// Styles are 100% width, 104px tall absolute layouts resembling the wireframes.
|
||||
const isDark = styleId !== "minimal" && styleId !== "swiss";
|
||||
const accentColor = "var(--accent)";
|
||||
|
||||
const wireLine = (w: string | number, opacity = 0.25) => (
|
||||
<div
|
||||
style={{
|
||||
height: 4,
|
||||
width: w,
|
||||
borderRadius: 2,
|
||||
background: isDark ? "rgba(255,255,255,1)" : "rgba(0,0,0,1)",
|
||||
opacity,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
let content;
|
||||
if (styleId === "sidebar") {
|
||||
// Left-side menu column + main dashboard area
|
||||
content = (
|
||||
<div style={{ display: "flex", width: "100%", height: "100%" }}>
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
style={{
|
||||
width: 28,
|
||||
borderRight: "1px solid var(--hairline)",
|
||||
padding: 6,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 5,
|
||||
background: "oklch(0.19 0.009 60 / 0.3)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: "50%",
|
||||
background: accentColor,
|
||||
opacity: 0.7,
|
||||
}}
|
||||
/>
|
||||
{wireLine(16, 0.4)}
|
||||
{wireLine(12, 0.25)}
|
||||
{wireLine(14, 0.25)}
|
||||
</div>
|
||||
{/* Main Dashboard */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: 8,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{wireLine(24, 0.5)}
|
||||
<div
|
||||
style={{
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: "50%",
|
||||
background: "rgba(255,255,255,0.15)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 6, flex: 1 }}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
border: "1px solid var(--hairline)",
|
||||
borderRadius: 6,
|
||||
padding: 6,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{wireLine("100%", 0.3)}
|
||||
{wireLine("60%", 0.15)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: 32,
|
||||
border: "1px solid var(--hairline)",
|
||||
borderRadius: 6,
|
||||
background: "rgba(255,255,255,0.03)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (styleId === "topbar") {
|
||||
// Top horizontal header bar + multi-column page
|
||||
content = (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
{/* Topbar */}
|
||||
<div
|
||||
style={{
|
||||
height: 20,
|
||||
borderBottom: "1px solid var(--hairline)",
|
||||
padding: "0 8px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
background: "oklch(0.19 0.009 60 / 0.3)",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", gap: 6 }}>
|
||||
<div
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: accentColor,
|
||||
opacity: 0.7,
|
||||
}}
|
||||
/>
|
||||
{wireLine(12, 0.3)}
|
||||
{wireLine(12, 0.3)}
|
||||
</div>
|
||||
{/* Miniature ⌘K Bar */}
|
||||
<div
|
||||
style={{
|
||||
width: 44,
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
border: "1px solid var(--hairline)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
paddingLeft: 4,
|
||||
}}
|
||||
>
|
||||
{wireLine(8, 0.15)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: "rgba(255,255,255,0.15)",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* Main Grid */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: 8,
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, 1fr)",
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
border: "1px solid var(--hairline)",
|
||||
borderRadius: 6,
|
||||
padding: 6,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{wireLine("50%", 0.3)}
|
||||
{wireLine("100%", 0.15)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (styleId === "rail") {
|
||||
// Narrow vertical rail + content
|
||||
content = (
|
||||
<div style={{ display: "flex", width: "100%", height: "100%" }}>
|
||||
{/* Rail */}
|
||||
<div
|
||||
style={{
|
||||
width: 18,
|
||||
borderRight: "1px solid var(--hairline)",
|
||||
padding: "6px 0",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
background: "oklch(0.19 0.009 60 / 0.3)",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: 3,
|
||||
background: accentColor,
|
||||
opacity: 0.7,
|
||||
}}
|
||||
/>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 2,
|
||||
background: "rgba(255,255,255,0.15)",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* Content */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: 8,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
{wireLine(36, 0.4)}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
border: "1px solid var(--hairline)",
|
||||
borderRadius: 6,
|
||||
padding: 8,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 5,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", gap: 6 }}>
|
||||
<div
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: 3,
|
||||
background: "rgba(255,255,255,0.1)",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 4,
|
||||
paddingTop: 2,
|
||||
}}
|
||||
>
|
||||
{wireLine("40%", 0.3)}
|
||||
{wireLine("20%", 0.15)}
|
||||
</div>
|
||||
</div>
|
||||
{wireLine("100%", 0.15)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (styleId === "flux") {
|
||||
// Glass aurora: purple background with a floating card
|
||||
content = (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "relative",
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
background: "#0c0a1a",
|
||||
}}
|
||||
>
|
||||
{/* Aurora Glow */}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: 80,
|
||||
height: 80,
|
||||
borderRadius: "50%",
|
||||
background:
|
||||
"radial-gradient(circle, oklch(0.6 0.18 300) 0%, transparent 70%)",
|
||||
filter: "blur(14px)",
|
||||
opacity: 0.7,
|
||||
top: "10%",
|
||||
left: "30%",
|
||||
}}
|
||||
/>
|
||||
{/* Frosted Card */}
|
||||
<div
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "70%",
|
||||
borderRadius: 8,
|
||||
border: "1px solid rgba(255,255,255,0.14)",
|
||||
background: "rgba(255,255,255,0.04)",
|
||||
backdropFilter: "blur(12px)",
|
||||
padding: 8,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 5,
|
||||
boxShadow: "0 8px 32px rgba(0,0,0,0.3)",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", gap: 4 }}>
|
||||
<div
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: "50%",
|
||||
background: accentColor,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
/>
|
||||
{wireLine(18, 0.5)}
|
||||
</div>
|
||||
{wireLine("80%", 0.3)}
|
||||
{wireLine("40%", 0.15)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (styleId === "minimal") {
|
||||
// Classic Minimal: parchment background, gridded rule lines
|
||||
content = (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background: "#f5f4ef",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: 18,
|
||||
borderBottom: "1px solid #d4d0c8",
|
||||
padding: "0 8px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
{wireLine(24, 0.6)}
|
||||
{wireLine(12, 0.4)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: 8,
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
borderRight: "1px dashed #d4d0c8",
|
||||
paddingRight: 4,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{wireLine("90%", 0.5)}
|
||||
{wireLine("50%", 0.25)}
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
{wireLine("100%", 0.5)}
|
||||
{wireLine("70%", 0.25)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (styleId === "bento") {
|
||||
// Dark bento grid
|
||||
content = (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
padding: 8,
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1.2fr 1fr",
|
||||
gridTemplateRows: "1fr 1fr",
|
||||
gap: 5,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
gridRow: "span 2",
|
||||
border: "1px solid var(--hairline)",
|
||||
borderRadius: 6,
|
||||
padding: 6,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{wireLine("50%", 0.4)}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
background: "rgba(255,255,255,0.03)",
|
||||
borderRadius: 4,
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 14,
|
||||
height: 14,
|
||||
borderRadius: "50%",
|
||||
border: "1px solid var(--accent)",
|
||||
opacity: 0.7,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid var(--hairline)",
|
||||
borderRadius: 6,
|
||||
padding: 6,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
{wireLine("80%", 0.3)}
|
||||
{wireLine("40%", 0.15)}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
border: "1px solid var(--hairline)",
|
||||
borderRadius: 6,
|
||||
padding: 6,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 3,
|
||||
}}
|
||||
>
|
||||
{wireLine("100%", 0.3)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (styleId === "swiss") {
|
||||
// Editorial swiss: White background, bold geometric layouts
|
||||
content = (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background: "#ffffff",
|
||||
padding: 10,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "baseline",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 13,
|
||||
fontWeight: 900,
|
||||
color: "#000",
|
||||
fontFamily: "sans-serif",
|
||||
letterSpacing: "-0.04em",
|
||||
lineHeight: 1,
|
||||
}}
|
||||
>
|
||||
VIBN.
|
||||
</span>
|
||||
{wireLine(16, 0.6)}
|
||||
</div>
|
||||
<div style={{ height: 1, background: "#000000", opacity: 0.15 }} />
|
||||
<div style={{ display: "flex", gap: 10, flex: 1 }}>
|
||||
<div
|
||||
style={{
|
||||
flex: 1.2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: 8,
|
||||
width: "100%",
|
||||
background: "#000",
|
||||
opacity: 0.85,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
height: 8,
|
||||
width: "70%",
|
||||
background: "#000",
|
||||
opacity: 0.85,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 4,
|
||||
paddingTop: 2,
|
||||
}}
|
||||
>
|
||||
{wireLine("100%", 0.4)}
|
||||
{wireLine("100%", 0.4)}
|
||||
{wireLine("60%", 0.2)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (styleId === "brutalist") {
|
||||
// Neo-brutalist yellow card, thick borders, heavy offsets
|
||||
content = (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background: "#f0eee9",
|
||||
display: "grid",
|
||||
placeItems: "center",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: "75%",
|
||||
height: "70%",
|
||||
background: "#fcf05a",
|
||||
border: "2px solid #000",
|
||||
borderRadius: 4,
|
||||
boxShadow: "3px 3px 0 #000",
|
||||
padding: 6,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 5,
|
||||
}}
|
||||
>
|
||||
<div style={{ height: 8, width: "100%", background: "#000" }} />
|
||||
<div style={{ display: "flex", gap: 4, alignItems: "center" }}>
|
||||
<div
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: "50%",
|
||||
background: "#ff5bf5",
|
||||
border: "1.5px solid #000",
|
||||
}}
|
||||
/>
|
||||
{wireLine(24, 0.85)}
|
||||
</div>
|
||||
{wireLine("60%", 0.6)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// I'm not sure / Undecided
|
||||
content = (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 6,
|
||||
background:
|
||||
"radial-gradient(circle at center, rgba(255,255,255,0.03) 0%, transparent 60%)",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="var(--accent)"
|
||||
strokeWidth="1.8"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
style={{
|
||||
opacity: 0.75,
|
||||
filter: "drop-shadow(0 0 8px var(--accent-glow))",
|
||||
}}
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3M12 17h.01" />
|
||||
</svg>
|
||||
<span
|
||||
style={{
|
||||
fontSize: 10,
|
||||
color: "var(--fg-faint)",
|
||||
textTransform: "uppercase",
|
||||
letterSpacing: "0.12em",
|
||||
fontFamily: "var(--font-mono)",
|
||||
}}
|
||||
>
|
||||
Automatic
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "relative",
|
||||
background: isDark ? "oklch(0.155 0.008 60)" : "transparent",
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const SAAS_STYLES = [
|
||||
{
|
||||
id: "sidebar",
|
||||
label: "Vertical Sidebar",
|
||||
desc: "Left-side collapsible menu, data-dense. Ideal for CRM/dashboards.",
|
||||
illustration: <LayoutPreview styleId="sidebar" />,
|
||||
},
|
||||
{
|
||||
id: "topbar",
|
||||
label: "Top Horizontal + ⌘K",
|
||||
desc: "Spacious top navigation with global command search bar.",
|
||||
illustration: <LayoutPreview styleId="topbar" />,
|
||||
},
|
||||
{
|
||||
id: "rail",
|
||||
label: "Slim Icon Rail",
|
||||
desc: "Minimalist vertical narrow icon bar, maximizes workspace area.",
|
||||
illustration: <LayoutPreview styleId="rail" />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -195,11 +844,13 @@ const MARKETPLACE_STYLES = [
|
||||
id: "flux",
|
||||
label: "Dark Glass / Flux",
|
||||
desc: "Modern dark-glass panels with glowing fuchsia aurora backdrops.",
|
||||
illustration: <LayoutPreview styleId="flux" />,
|
||||
},
|
||||
{
|
||||
id: "minimal",
|
||||
label: "Classic Minimal",
|
||||
desc: "Warm parchment neutrals, high-contrast typography and clean grids.",
|
||||
illustration: <LayoutPreview styleId="minimal" />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -208,16 +859,19 @@ const GENERAL_STYLES = [
|
||||
id: "bento",
|
||||
label: "Dark Bento",
|
||||
desc: "Modern dark UI, bento-box card clusters.",
|
||||
illustration: <LayoutPreview styleId="bento" />,
|
||||
},
|
||||
{
|
||||
id: "swiss",
|
||||
label: "Editorial Swiss",
|
||||
desc: "Type-led, gridded, lots of white space — clean and academic.",
|
||||
illustration: <LayoutPreview styleId="swiss" />,
|
||||
},
|
||||
{
|
||||
id: "brutalist",
|
||||
label: "Neo-Brutalist",
|
||||
desc: "Bold offsets, thick hand-drawn borders, highly tactile and organic.",
|
||||
illustration: <LayoutPreview styleId="brutalist" />,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -250,6 +904,7 @@ function EntrepStyle({ productType, value, onChange }) {
|
||||
label: s.label,
|
||||
desc: s.desc,
|
||||
icon: undefined,
|
||||
illustration: s.illustration,
|
||||
}))}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
|
||||
Reference in New Issue
Block a user