mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-25 02:32:47 +00:00
feat(website): refine responsive product mockups
Keep desktop and mobile scenes visually consistent while improving phone depth, diff realism, alignment, and interaction polish.
This commit is contained in:
@@ -1238,7 +1238,7 @@ function PhoneShowcase() {
|
||||
role="img"
|
||||
aria-label="Paseo workspace drawer"
|
||||
>
|
||||
<PhoneFrame time="18:54">
|
||||
<PhoneFrame time="18:54" depth="right">
|
||||
<MobileSidebar />
|
||||
</PhoneFrame>
|
||||
</motion.div>
|
||||
@@ -1264,7 +1264,7 @@ function PhoneShowcase() {
|
||||
role="img"
|
||||
aria-label="Paseo diff view"
|
||||
>
|
||||
<PhoneFrame time="18:55">
|
||||
<PhoneFrame time="18:55" depth="left">
|
||||
<MobileDiff />
|
||||
</PhoneFrame>
|
||||
</motion.div>
|
||||
|
||||
@@ -128,9 +128,11 @@ function Composer() {
|
||||
<span className="block text-[16px] text-mock-fg-xmuted">Message, @files, /commands</span>
|
||||
<div className="flex items-center gap-[13px] pt-[26px]">
|
||||
<Plus size={20} className="shrink-0 text-mock-fg-muted" strokeWidth={1.9} />
|
||||
<CodexIcon size={19} className="shrink-0 text-mock-fg" />
|
||||
<span className="shrink-0 whitespace-nowrap text-[16px] text-mock-fg">GPT-5.6-Sol</span>
|
||||
<span className="shrink-0 whitespace-nowrap text-[16px] text-mock-fg-muted">Medium</span>
|
||||
<span className="flex shrink-0 items-center gap-[5px]">
|
||||
<CodexIcon size={19} className="shrink-0 text-mock-fg" />
|
||||
<span className="whitespace-nowrap text-[16px] text-mock-fg">GPT-5.6-Sol</span>
|
||||
<span className="whitespace-nowrap text-[16px] text-mock-fg-muted">Medium</span>
|
||||
</span>
|
||||
<span className="flex-1" />
|
||||
<LoaderCircle size={19} className="shrink-0 text-mock-fg-muted" strokeWidth={1.8} />
|
||||
<Mic size={19} className="shrink-0 text-mock-fg-muted" strokeWidth={1.8} />
|
||||
|
||||
@@ -4,57 +4,89 @@
|
||||
// an addition, so the whole gutter runs green). Reuses the shared `Code`
|
||||
// highlighter and the `--color-mock-diff-*` tokens.
|
||||
|
||||
import { ChevronDown, GitBranch, MoreHorizontal, RefreshCw, SquarePlus, X } from "lucide-react";
|
||||
import {
|
||||
ChevronDown,
|
||||
FileDiff,
|
||||
Files,
|
||||
GitBranch,
|
||||
GitPullRequest,
|
||||
MoreHorizontal,
|
||||
RefreshCw,
|
||||
SquareDot,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { Code } from "../atoms";
|
||||
import { ReactFileIcon } from "../icons";
|
||||
|
||||
// Every visible line of the new atoms.tsx — all additions. Long lines are
|
||||
// clipped by the phone width exactly as they are in the app.
|
||||
const ADDED_LINES: { n: number; text: string }[] = [
|
||||
{ n: 1, text: "// Shared bits of the Paseo UI, drawn small. Every" },
|
||||
{ n: 2, text: "// no state, no interactivity. Colors come from th" },
|
||||
{ n: 3, text: "// styles.css, which are copied from the app's def" },
|
||||
{ n: 4, text: "" },
|
||||
{ n: 5, text: 'import type * as React from "react";' },
|
||||
{ n: 6, text: "" },
|
||||
{ n: 7, text: "/**" },
|
||||
{ n: 8, text: " * The `+12 −18` footnote. A diff stat is a *statu" },
|
||||
{ n: 9, text: " * uses statusSuccess/statusDanger — see the comme" },
|
||||
{ n: 10, text: " * packages/app/src/styles/theme.ts." },
|
||||
{ n: 11, text: " */" },
|
||||
{ n: 12, text: "export function DiffStat({ add, remove }: { add: st" },
|
||||
{ n: 13, text: " return (" },
|
||||
{ n: 14, text: ' <span className="flex shrink-0 items-center ga' },
|
||||
{ n: 15, text: ' <span className="text-mock-success">+{add}</s' },
|
||||
{ n: 16, text: ' <span className="text-mock-danger">-{remove}<' },
|
||||
{ n: 17, text: " </span>" },
|
||||
{ n: 18, text: " );" },
|
||||
{ n: 19, text: "}" },
|
||||
{ n: 20, text: "" },
|
||||
{ n: 21, text: 'export type DotTone = "success" | "danger" | "warni' },
|
||||
{ n: 22, text: "" },
|
||||
{ n: 23, text: "const DOT_TONE: Record<DotTone, string> = {" },
|
||||
{ n: 24, text: ' success: "bg-mock-dot-success",' },
|
||||
{ n: 25, text: ' danger: "bg-mock-dot-danger",' },
|
||||
{ n: 26, text: ' warning: "bg-mock-dot-warning",' },
|
||||
{ n: 27, text: ' running: "bg-mock-dot-running",' },
|
||||
{ n: 28, text: ' idle: "bg-mock-surface3",' },
|
||||
{ n: 29, text: "};" },
|
||||
{ n: 30, text: "" },
|
||||
{ n: 31, text: "/** The 6pt disc on a sidebar row that carries the" },
|
||||
{ n: 32, text: "export function StatusDot({ tone }: { tone: DotTone" },
|
||||
{ n: 33, text: " return <span className={`size-[7px] shrink-0 rou" },
|
||||
type MobileDiffMark = "add" | "remove" | "context";
|
||||
|
||||
interface MobileDiffLine {
|
||||
n: number;
|
||||
text: string;
|
||||
mark: MobileDiffMark;
|
||||
}
|
||||
|
||||
const ROW_COLOR: Record<MobileDiffMark, string> = {
|
||||
add: "bg-mock-diff-add",
|
||||
remove: "bg-mock-diff-remove",
|
||||
context: "",
|
||||
};
|
||||
|
||||
const NUMBER_COLOR: Record<MobileDiffMark, string> = {
|
||||
add: "text-mock-success",
|
||||
remove: "text-mock-danger",
|
||||
context: "text-mock-fg-muted",
|
||||
};
|
||||
|
||||
const DIFF_LINES: MobileDiffLine[] = [
|
||||
{ n: 7, text: 'type DotTone = "success" | "danger" | "idle";', mark: "remove" },
|
||||
{ n: 7, text: "export type DotTone =", mark: "add" },
|
||||
{ n: 8, text: ' | "success"', mark: "add" },
|
||||
{ n: 9, text: ' | "danger"', mark: "add" },
|
||||
{ n: 10, text: ' | "warning"', mark: "add" },
|
||||
{ n: 11, text: ' | "running"', mark: "add" },
|
||||
{ n: 12, text: ' | "idle";', mark: "add" },
|
||||
{ n: 13, text: "", mark: "context" },
|
||||
{ n: 14, text: "interface DiffStatProps {", mark: "context" },
|
||||
{ n: 15, text: " add: string;", mark: "context" },
|
||||
{ n: 16, text: " remove: string;", mark: "context" },
|
||||
{ n: 17, text: "}", mark: "context" },
|
||||
{ n: 18, text: "", mark: "context" },
|
||||
{ n: 19, text: "/** Compact diff summary. */", mark: "add" },
|
||||
{ n: 20, text: "export function DiffStat({", mark: "add" },
|
||||
{ n: 21, text: " add,", mark: "add" },
|
||||
{ n: 22, text: " remove,", mark: "add" },
|
||||
{ n: 23, text: "}: DiffStatProps) {", mark: "add" },
|
||||
{ n: 24, text: " return (", mark: "context" },
|
||||
{ n: 25, text: ' <span className="flex gap-1">', mark: "remove" },
|
||||
{ n: 25, text: ' <span className="flex gap-[4px]">', mark: "add" },
|
||||
{ n: 26, text: ' <span className="text-mock-success">', mark: "context" },
|
||||
{ n: 27, text: " +{add}", mark: "context" },
|
||||
{ n: 28, text: " </span>", mark: "context" },
|
||||
{ n: 29, text: ' <span className="text-mock-danger">', mark: "context" },
|
||||
{ n: 30, text: " -{remove}", mark: "context" },
|
||||
{ n: 31, text: " </span>", mark: "context" },
|
||||
{ n: 32, text: " </span>", mark: "context" },
|
||||
{ n: 33, text: " )", mark: "remove" },
|
||||
{ n: 33, text: " );", mark: "add" },
|
||||
{ n: 34, text: "}", mark: "context" },
|
||||
];
|
||||
|
||||
function Tabs() {
|
||||
return (
|
||||
<div className="flex shrink-0 items-center gap-[16px] px-[18px] pt-[4px] pb-[14px]">
|
||||
<div className="flex items-center gap-[6px] rounded-[11px] bg-mock-surface2 p-[3px]">
|
||||
<span className="rounded-[8px] bg-mock-fg px-[15px] py-[6px] text-[16px] font-semibold text-mock-surface0">
|
||||
Changes
|
||||
</span>
|
||||
<span className="px-[13px] py-[6px] text-[16px] text-mock-fg-muted">Files</span>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-[5px] px-[18px] pt-[4px] pb-[14px]">
|
||||
<span className="flex items-center gap-[7px] rounded-[8px] px-[10px] py-[6px] text-[16px] text-mock-fg-muted">
|
||||
<Files size={17} strokeWidth={1.8} />
|
||||
Files
|
||||
</span>
|
||||
<span className="flex items-center gap-[7px] rounded-[8px] bg-mock-surface2 px-[10px] py-[6px] text-[16px] text-mock-fg">
|
||||
<FileDiff size={17} strokeWidth={1.8} />
|
||||
Changes
|
||||
</span>
|
||||
<span className="flex items-center gap-[7px] rounded-[8px] px-[10px] py-[6px] text-[16px] text-mock-fg-muted tabular-nums">
|
||||
<GitPullRequest size={17} strokeWidth={1.8} />
|
||||
3981
|
||||
</span>
|
||||
<span className="flex-1" />
|
||||
<X size={22} className="text-mock-fg-muted" strokeWidth={1.9} />
|
||||
</div>
|
||||
@@ -106,25 +138,26 @@ function FileHeader() {
|
||||
packages/website/src/componen…
|
||||
</span>
|
||||
<span className="flex shrink-0 items-center gap-[3px] text-[14px] tabular-nums">
|
||||
<span className="text-mock-success">+175</span>
|
||||
<span className="text-mock-fg-xmuted">-0</span>
|
||||
<span className="text-mock-success">+56</span>
|
||||
<span className="text-mock-danger">-18</span>
|
||||
</span>
|
||||
<SquarePlus size={17} className="shrink-0 text-mock-fg-muted" strokeWidth={1.7} />
|
||||
<SquareDot size={17} className="shrink-0 text-mock-warning" strokeWidth={1.7} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DiffLine({ n, text }: { n: number; text: string }) {
|
||||
function DiffLine({ n, text, mark }: MobileDiffLine) {
|
||||
const comment = text.trimStart().startsWith("/") || text.trimStart().startsWith("*");
|
||||
return (
|
||||
<div className="flex items-center bg-mock-diff-add" style={ROW_STYLE}>
|
||||
<div className={`flex items-center ${ROW_COLOR[mark]}`} style={ROW_STYLE}>
|
||||
<span
|
||||
className="shrink-0 pr-[10px] text-right font-mono text-[13px] tabular-nums text-mock-success"
|
||||
className={`shrink-0 pr-[10px] text-right font-mono text-[13px] tabular-nums ${NUMBER_COLOR[mark]}`}
|
||||
style={GUTTER_STYLE}
|
||||
>
|
||||
{n}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 overflow-hidden whitespace-pre pl-[12px] font-mono text-[14px] leading-[26px]">
|
||||
<Code line={text} />
|
||||
{comment ? <span className="text-mock-fg-muted">{text}</span> : <Code line={text} />}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@@ -151,11 +184,11 @@ export function MobileDiff() {
|
||||
<div className="flex items-center bg-mock-surface-diff-empty" style={ROW_STYLE}>
|
||||
<span className="shrink-0" style={GUTTER_STYLE} />
|
||||
<span className="pl-[12px] font-mono text-[13px] text-mock-fg-muted">
|
||||
@@ -0,0 +1,175 @@
|
||||
@@ -7,20 +7,28 @@
|
||||
</span>
|
||||
</div>
|
||||
{ADDED_LINES.map((line) => (
|
||||
<DiffLine key={line.n} n={line.n} text={line.text} />
|
||||
{DIFF_LINES.map((line) => (
|
||||
<DiffLine key={`${line.n}-${line.mark}-${line.text}`} {...line} />
|
||||
))}
|
||||
</div>
|
||||
<div className="flex h-[52px] shrink-0 items-center border-t border-mock-border px-[18px] pb-[16px] text-[16px] text-mock-fg">
|
||||
|
||||
@@ -15,6 +15,21 @@ const SCREEN_SCALE = {
|
||||
height: PHONE_H,
|
||||
transform: `scale(tan(atan2(100cqw, ${PHONE_W}px)))`,
|
||||
};
|
||||
type PhoneDepth = "front" | "left" | "right";
|
||||
|
||||
const SLAB_STYLE: Record<Exclude<PhoneDepth, "front">, React.CSSProperties> = {
|
||||
left: { transform: "translate3d(-5px, 0, -8px)" },
|
||||
right: { transform: "translate3d(5px, 0, -8px)" },
|
||||
};
|
||||
const CONTACT_SHADOW_STYLE: Record<PhoneDepth, React.CSSProperties> = {
|
||||
front: { transform: "translate3d(0, 4px, -12px)" },
|
||||
left: { transform: "translate3d(-2px, 3px, -12px)" },
|
||||
right: { transform: "translate3d(2px, 3px, -12px)" },
|
||||
};
|
||||
const GLASS_STYLE = {
|
||||
background:
|
||||
"linear-gradient(122deg, rgba(255,255,255,0.048) 0%, rgba(255,255,255,0.016) 25%, transparent 43%), radial-gradient(circle at 78% 10%, rgba(255,255,255,0.026), transparent 31%)",
|
||||
};
|
||||
|
||||
/** iOS status bar: time, dynamic island, and the signal/wifi/battery cluster. */
|
||||
function StatusBar({ time }: { time: string }) {
|
||||
@@ -71,20 +86,54 @@ function HomeIndicator() {
|
||||
);
|
||||
}
|
||||
|
||||
export function PhoneFrame({ time, children }: { time: string; children: React.ReactNode }) {
|
||||
function FrontButtons() {
|
||||
return (
|
||||
<div
|
||||
className="relative w-full overflow-hidden rounded-[13.5%/6.2%] border-[3px] border-black bg-black shadow-2xl outline outline-[3px] outline-white/20"
|
||||
style={FRAME_ASPECT}
|
||||
>
|
||||
<div className="absolute -inset-[3px] [container-type:inline-size]">
|
||||
<div className="absolute top-0 left-0 origin-top-left" style={SCREEN_SCALE}>
|
||||
<div className="relative flex h-[874px] w-[402px] flex-col overflow-hidden bg-mock-surface0 text-mock-fg antialiased">
|
||||
<StatusBar time={time} />
|
||||
<div className="relative flex min-h-0 flex-1 flex-col overflow-hidden">{children}</div>
|
||||
<HomeIndicator />
|
||||
<>
|
||||
<span className="pointer-events-none absolute top-[18%] -left-[2px] h-[8%] w-[3px] rounded-l-[2px] bg-[#1b1e1d]" />
|
||||
<span className="pointer-events-none absolute top-[28%] -left-[2px] h-[5%] w-[3px] rounded-l-[2px] bg-[#1b1e1d]" />
|
||||
<span className="pointer-events-none absolute top-[25%] -right-[2px] h-[13%] w-[3px] rounded-r-[2px] bg-[#151716]" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function PhoneFrame({
|
||||
time,
|
||||
children,
|
||||
depth = "front",
|
||||
}: {
|
||||
time: string;
|
||||
children: React.ReactNode;
|
||||
depth?: PhoneDepth;
|
||||
}) {
|
||||
return (
|
||||
<div className="relative w-full select-none [transform-style:preserve-3d]" style={FRAME_ASPECT}>
|
||||
<div
|
||||
className="pointer-events-none absolute right-[8%] -bottom-[0.8%] left-[8%] h-[3.5%] rounded-full bg-black/25 blur-[10px]"
|
||||
style={CONTACT_SHADOW_STYLE[depth]}
|
||||
/>
|
||||
|
||||
{depth === "front" ? (
|
||||
<FrontButtons />
|
||||
) : (
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 rounded-[13.5%/6.2%] border border-white/7 bg-[linear-gradient(180deg,#303432_0%,#1b1e1d_42%,#0b0d0c_100%)] shadow-[0_8px_22px_rgba(0,0,0,0.2)]"
|
||||
style={SLAB_STYLE[depth]}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="absolute inset-0 overflow-hidden rounded-[13.5%/6.2%] border-[3px] border-black bg-black shadow-[inset_0_0_0_1px_rgba(255,255,255,0.14)] outline outline-[1px] outline-white/25">
|
||||
<div className="absolute -inset-[3px] [container-type:inline-size]">
|
||||
<div className="absolute top-0 left-0 origin-top-left" style={SCREEN_SCALE}>
|
||||
<div className="relative flex h-[874px] w-[402px] flex-col overflow-hidden bg-mock-surface0 text-mock-fg antialiased">
|
||||
<StatusBar time={time} />
|
||||
<div className="relative flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
{children}
|
||||
</div>
|
||||
<HomeIndicator />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-none absolute inset-0 z-20" style={GLASS_STYLE} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -267,13 +267,15 @@ export function Sidebar({ state }: { state: MockupStateId }) {
|
||||
return (
|
||||
<div className="flex shrink-0 flex-col overflow-hidden bg-mock-sidebar" style={SIDEBAR_STYLE}>
|
||||
{/* The sidebar is one full-height column with no header border of its own. */}
|
||||
<div className="flex shrink-0 items-center gap-[10px] px-[13px]" style={TITLEBAR_STYLE}>
|
||||
<div className="flex items-center gap-[7px]">
|
||||
<div className="flex shrink-0 items-center gap-[8px] px-[18px]" style={TITLEBAR_STYLE}>
|
||||
<div className="flex h-[24px] items-center gap-[7px]">
|
||||
<i className="block size-[10px] rounded-full bg-[#ff5f57]" />
|
||||
<i className="block size-[10px] rounded-full bg-[#febc2e]" />
|
||||
<i className="block size-[10px] rounded-full bg-[#28c840]" />
|
||||
</div>
|
||||
<PanelLeftGlyph />
|
||||
<span className="flex size-[24px] shrink-0 items-center justify-center">
|
||||
<PanelLeftGlyph />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="px-[8px] pt-[7px]">
|
||||
@@ -334,5 +336,5 @@ export function Sidebar({ state }: { state: MockupStateId }) {
|
||||
}
|
||||
|
||||
function PanelLeftGlyph() {
|
||||
return <PanelLeft size={15} strokeWidth={1.8} className="text-mock-fg-muted" />;
|
||||
return <PanelLeft size={15} strokeWidth={1.8} className="translate-y-px text-mock-fg-muted" />;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ export function MockupWindow({ state }: { state: MockupStateId }) {
|
||||
return (
|
||||
<MotionConfig transition={reduceMotion ? INSTANT_TRANSITION : MOCKUP_TRANSITION}>
|
||||
<div
|
||||
className="relative flex overflow-hidden bg-mock-surface0 text-mock-fg antialiased"
|
||||
className="relative flex select-none overflow-hidden bg-mock-surface0 text-mock-fg antialiased"
|
||||
style={WINDOW_STYLE}
|
||||
>
|
||||
{/* Three full-height columns, each owning its own header. The middle
|
||||
|
||||
Reference in New Issue
Block a user