TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import * as React from "react";2import { cn } from "@/lib/utils";34/**5 * Phone-shaped frame (390 × 844 proportions) for the mobile product mocks. The screen is a flex6 * column: mocks render a header, a scrolling body (`min-h-0 flex-1`) and a fixed bottom bar.7 * Nothing inside is interactive by default; callers opt in.8 */9export function PhoneFrame({ children, className, screenClassName, label }: { children: React.ReactNode; className?: string; screenClassName?: string; label?: string }) {10 return (11 <div className={cn("relative mx-auto w-full max-w-[300px]", className)} role={label ? "img" : undefined} aria-label={label}>12 <div className="relative aspect-[390/844] w-full rounded-[44px] border border-border-strong/70 bg-[#0e0f14] p-[7px] shadow-lg dark:border-border-strong">13 {/* Side buttons */}14 <span aria-hidden className="absolute -left-[3px] top-[18%] h-[6%] w-[3px] rounded-l-sm bg-border-strong/80" />15 <span aria-hidden className="absolute -left-[3px] top-[27%] h-[10%] w-[3px] rounded-l-sm bg-border-strong/80" />16 <span aria-hidden className="absolute -right-[3px] top-[22%] h-[14%] w-[3px] rounded-r-sm bg-border-strong/80" />17 <div className={cn("relative flex h-full w-full flex-col overflow-hidden rounded-[37px] bg-bg text-fg", screenClassName)}>18 {/* Status bar + dynamic island */}19 <div className="relative flex h-[42px] shrink-0 items-end justify-between px-6 pb-1.5 text-[11px] font-semibold tabular-nums">20 <span>9:41</span>21 <span aria-hidden className="absolute left-1/2 top-2 h-[24px] w-[84px] -translate-x-1/2 rounded-full bg-[#0e0f14]" />22 <span className="flex items-center gap-1" aria-hidden>23 <svg width="14" height="10" viewBox="0 0 14 10" fill="currentColor">24 <rect x="0" y="6" width="2.5" height="4" rx="0.6" />25 <rect x="3.8" y="4" width="2.5" height="6" rx="0.6" />26 <rect x="7.6" y="2" width="2.5" height="8" rx="0.6" />27 <rect x="11.4" y="0" width="2.5" height="10" rx="0.6" />28 </svg>29 <svg width="22" height="11" viewBox="0 0 22 11" fill="none" stroke="currentColor" strokeWidth="1">30 <rect x="0.5" y="0.5" width="18" height="10" rx="2.5" />31 <rect x="2" y="2" width="14" height="7" rx="1.2" fill="currentColor" stroke="none" />32 <path d="M20 4v3" strokeLinecap="round" />33 </svg>34 </span>35 </div>36 {children}37 {/* Home indicator */}38 <div aria-hidden className="pointer-events-none absolute inset-x-0 bottom-1.5 flex justify-center">39 <span className="h-[4px] w-[96px] rounded-full bg-fg/70" />40 </div>41 </div>42 </div>43 </div>44 );45}4647/** Desktop app window chrome. Same look as the marketing `Window`, sized for the product mocks. */48export function DesktopFrame({ title, children, className, url }: { title?: string; children: React.ReactNode; className?: string; url?: string }) {49 return (50 <div className={cn("overflow-hidden rounded-xl border border-border bg-bg shadow-lg", className)}>51 <div className="flex h-9 items-center gap-2 border-b border-border bg-bg-subtle/80 px-3">52 <span className="flex gap-1.5" aria-hidden>53 <span className="size-2.5 rounded-full bg-border-strong" />54 <span className="size-2.5 rounded-full bg-border-strong" />55 <span className="size-2.5 rounded-full bg-border-strong" />56 </span>57 {url ? (58 <span className="mx-auto hidden h-6 min-w-[40%] items-center justify-center rounded-md bg-bg px-3 font-mono text-[11px] text-fg-subtle sm:inline-flex">{url}</span>59 ) : title ? (60 <span className="ml-2 truncate font-mono text-[11px] text-fg-subtle">{title}</span>61 ) : null}62 </div>63 {children}64 </div>65 );66}67