-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Onlook designer Product mockup for the front-page #2649
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
| export const DesignMockupIcons = { | ||
| Add: ({ className, ...props }: IconProps) => ( | ||
| <svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> | ||
| <g clip-path="url(#clip0_9157_282219)"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SVG attributes should use camelCase. Replace 'clip-path' with 'clipPath'.
| <g clip-path="url(#clip0_9157_282219)"> | |
| <g clipPath="url(#clip0_9157_282219)"> |
|
|
||
| function ImageCard({ src, alt, caption, isSelected = false, lightMode = false }: ImageCardProps) { | ||
| return ( | ||
| <div className={`break-inside-avoid mb-2 ${isSelected ? 'border-1 border-purple-500' : ''}`}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class 'border-1' is not a standard Tailwind utility. Consider using 'border' or 'border-[1px]' instead.
| <div className={`break-inside-avoid mb-2 ${isSelected ? 'border-1 border-purple-500' : ''}`}> | |
| <div className={`break-inside-avoid mb-2 ${isSelected ? 'border border-purple-500' : ''}}"> |
| alt={alt} | ||
| className="w-full mb-1.5 object-cover relative z-10" | ||
| onError={(e) => { | ||
| e.currentTarget.style.display = 'none'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider providing a fallback UI or placeholder when an image fails to load instead of just hiding it.
| Add: ({ className, ...props }: IconProps) => ( | ||
| <svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> | ||
| <g clip-path="url(#clip0_9157_282219)"> | ||
| <path d="M12 9V14.9998" stroke="currentColor" stroke-width="2" stroke-linecap="square"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typographical: 'stroke-width' and 'stroke-linecap' should be written in camelCase as 'strokeWidth' and 'strokeLinecap'.
| <path d="M12 9V14.9998" stroke="currentColor" stroke-width="2" stroke-linecap="square"/> | |
| <path d="M12 9V14.9998" stroke="currentColor" strokeWidth="2" strokeLinecap="square"/> |
| Explore: ({ className, ...props }: IconProps) => ( | ||
| <svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> | ||
| <g clip-path="url(#clip0_9157_282181)"> | ||
| <path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="2"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typographical: 'stroke-width' should be 'strokeWidth' in JSX.
| ), | ||
| Home: ({ className, ...props }: IconProps) => ( | ||
| <svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> | ||
| <g clip-path="url(#clip0_9157_282227)"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typographical: 'clip-path' should be 'clipPath' in JSX.
| <g clip-path="url(#clip0_9157_282227)"> | |
| <g clipPath="url(#clip0_9157_282227)"> |
| ), | ||
| Messages: ({ className, ...props }: IconProps) => ( | ||
| <svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> | ||
| <g clip-path="url(#clip0_9157_282235)"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typographical: 'clip-path' on the element should be 'clipPath' in JSX.
| <g clip-path="url(#clip0_9157_282235)"> | |
| <g clipPath="url(#clip0_9157_282235)"> |
| Messages: ({ className, ...props }: IconProps) => ( | ||
| <svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> | ||
| <g clip-path="url(#clip0_9157_282235)"> | ||
| <path d="M20.0001 4H4V21.5L8 19H20.0001V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="square"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typographical: 'stroke-width' and 'stroke-linecap' should be 'strokeWidth' and 'strokeLinecap'.
| <path d="M20.0001 4H4V21.5L8 19H20.0001V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="square"/> | |
| <path d="M20.0001 4H4V21.5L8 19H20.0001V4Z" stroke="currentColor" strokeWidth="2" strokeLinecap="square"/> |
| ), | ||
| Notifications: ({ className, ...props }: IconProps) => ( | ||
| <svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> | ||
| <g clip-path="url(#clip0_9157_282249)"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typographical: 'clip-path' should be 'clipPath' in JSX.
| <g clip-path="url(#clip0_9157_282249)"> | |
| <g clipPath="url(#clip0_9157_282249)"> |
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds new landing-page mockups (desktop + mobile), integrates them into the main page, updates the hero with an animated hiring banner and animated mobile notice, replaces the features section, adjusts minor UI text/styling, adds a FEATURES route constant, updates localization strings, and swaps a modal loading icon. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Page as page.tsx
participant Resp as ResponsiveMockupSection
participant Mock as OnlookInterfaceMockup
participant DM as DesignMockup
User->>Page: Request landing page
Page->>Resp: Render responsive mockup container
Resp->>Mock: Mount OnlookInterfaceMockup (md+ or mobile halves)
Mock->>DM: Render DesignMockup / DesignMockupMobile previews
User-->>Mock: Interact (pan, hover, select)
Mock->>Mock: Update pan/selection state and re-render
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
| transition={{ duration: 0.6, delay: 1.2, ease: "easeOut" }} | ||
| > | ||
| <a | ||
| href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" target="_blank" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add rel="noopener noreferrer" to the external link for security.
| href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" target="_blank" | |
| href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" target="_blank" rel="noopener noreferrer" |
| export const DesignMockupIcons = { | ||
| Add: ({ className, ...props }: IconProps) => ( | ||
| <svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" {...props}> | ||
| <g clip-path="url(#clip0_9157_282219)"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use camelCase for SVG attributes (e.g., clipPath, strokeWidth) to align with React's conventions.
| <g clip-path="url(#clip0_9157_282219)"> | |
| <g clipPath="url(#clip0_9157_282219)"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🔭 Outside diff range comments (3)
apps/web/client/src/app/_components/landing-page/contributor-section.tsx (1)
187-201: Add rel="noopener noreferrer" to external links opened with target="_blank"Prevents tab-napping and avoids giving the new page access to window.opener.
Apply this diff:
<Link href={githubLink} target="_blank" + rel="noopener noreferrer" className="bg-foreground-primary text-background-primary text-regularPlus rounded-lg px-6 py-3 flex items-center gap-2 shadow hover:bg-foreground-primary/80 transition cursor-pointer" > @@ <Link href={discordLink} target="_blank" + rel="noopener noreferrer" className="border border-foreground-primary/50 text-foreground-primary text-regularPlus rounded-lg px-6 py-3 flex items-center gap-2 hover:bg-foreground-primary/10 transition cursor-pointer" >apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/index.tsx (1)
11-15: Remove redundant ImagesProvider wrapper in ImagesTabContentThe nested
<ImagesProvider>insideImagesTabContentduplicates the one applied inImagesTab, creating two separate contexts and unexpected behavior. Only wrap at the top level;ImagesTabContentshould consume the existing context.Locations to update:
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/index.tsx
- Lines ~33–61 (the inner
<ImagesProvider>inImagesTabContent)Proposed diff:
export const ImagesTabContent = observer(() => { const { uploadOperations, operations, images } = useImagesContext(); const isOperating = operations.some(...); - return ( - <ImagesProvider> - <div className="w-full h-full flex flex-col gap-2 p-3 overflow-x-hidden"> - {uploadOperations.uploadState.error && ( - <div className="mb-2 px-3 py-2 text-sm text-red-500 bg-red-50 dark:bg-red-950/50 rounded-md"> - {/* error UI */} - </div> - )} - {!isOperating && <Folder />} - </div> - </ImagesProvider> - ); + return ( + <div className="w-full h-full flex flex-col gap-2 p-3 overflow-x-hidden"> + {uploadOperations.uploadState.error && ( + <div className="mb-2 px-3 py-2 text-sm text-red-500 bg-red-50 dark:bg-red-950/50 rounded-md"> + {/* error UI */} + </div> + )} + {!isOperating && <Folder />} + </div> + ); });apps/web/client/src/utils/constants/index.ts (1)
1-20: Non-existent/featuresroute will 404
A search didn’t reveal anyapp/features/page.tsx,pages/features.tsx, or similarly named file—soRoutes.FEATURES('/features') has no matching page. Please address this by either:
- Implementing a Features page at
•apps/web/client/src/app/features/page.tsx
or
•apps/web/client/src/pages/features.tsx- Or updating the constant and all consumers to use an in-page anchor (e.g. change in
apps/web/client/src/utils/constants/index.tsand update any- FEATURES: '/features', + FEATURES: '/#features', // scrolls to Features section on Home<Link>/<a>accordingly)
♻️ Duplicate comments (9)
apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup-icons.tsx (7)
11-21: Use camelCase SVG attributes in JSX: clipPath, strokeWidth, strokeLinecapHyphenated SVG attributes in JSX can be problematic and are flagged by prior reviews. Switch to camelCase.
Apply this diff:
- <g clip-path="url(#clip0_9157_282219)"> - <path d="M12 9V14.9998" stroke="currentColor" stroke-width="2" stroke-linecap="square"/> - <path d="M15 12.0002L9 12" stroke="currentColor" stroke-width="2" stroke-linecap="square"/> - <path d="M20 20V4H4V20H20Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"/> + <g clipPath="url(#clip0_9157_282219)"> + <path d="M12 9V14.9998" stroke="currentColor" strokeWidth="2" strokeLinecap="square"/> + <path d="M15 12.0002L9 12" stroke="currentColor" strokeWidth="2" strokeLinecap="square"/> + <path d="M20 20V4H4V20H20Z" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
25-27: JSX SVG: camelCase stroke propsConvert stroke-width and stroke-linejoin to strokeWidth and strokeLinejoin.
-<path d="M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> -<path d="M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M18 6L6 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/> +<path d="M6 6L18 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
31-34: Use clipPath and strokeWidth within Explore iconConsistent camelCase across SVG props.
-<g clip-path="url(#clip0_9157_282181)"> - <path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" stroke-width="2"/> - <path d="M14.8287 9.17188L10.0003 10.0003L9.17188 14.8287L14.0003 14.0003L14.8287 9.17188Z" stroke="currentColor" stroke-width="2"/> +<g clipPath="url(#clip0_9157_282181)"> + <path d="M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="currentColor" strokeWidth="2"/> + <path d="M14.8287 9.17188L10.0003 10.0003L9.17188 14.8287L14.0003 14.0003L14.8287 9.17188Z" stroke="currentColor" strokeWidth="2"/>Also applies to: 36-39
44-47: Gear icon: camelCase clipPath and strokeWidth-<g clip-path="url(#clip0_9157_282242)"> - <path d="M9.3 5.7L6.375 5.025L5.025 6.375L5.7 9.3L3 11.1V12.9L5.7 14.7L5.025 17.625L6.375 18.975L9.3 18.3L11.1 21H12.9L14.7 18.3L17.625 18.975L18.975 17.625L18.3 14.7L21 12.9V11.1L18.3 9.3L18.975 6.375L17.625 5.025L14.7 5.7L12.9 3H11.1L9.3 5.7Z" stroke="currentColor" stroke-width="2"/> - <path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" stroke="currentColor" stroke-width="2"/> +<g clipPath="url(#clip0_9157_282242)"> + <path d="M9.3 5.7L6.375 5.025L5.025 6.375L5.7 9.3L3 11.1V12.9L5.7 14.7L5.025 17.625L6.375 18.975L9.3 18.3L11.1 21H12.9L14.7 18.3L17.625 18.975L18.975 17.625L18.3 14.7L21 12.9V11.1L18.3 9.3L18.975 6.375L17.625 5.025L14.7 5.7L12.9 3H11.1L9.3 5.7Z" stroke="currentColor" strokeWidth="2"/> + <path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" stroke="currentColor" strokeWidth="2"/>Also applies to: 49-52
58-61: Home icon: use clipPath and strokeWidth-<g clip-path="url(#clip0_9157_282227)"> - <path d="M4 9L12 2.5L20 9V20H4V9Z" stroke="currentColor" stroke-width="2"/> - <path d="M9 13H15V20H9V13Z" stroke="currentColor" stroke-width="2"/> +<g clipPath="url(#clip0_9157_282227)"> + <path d="M4 9L12 2.5L20 9V20H4V9Z" stroke="currentColor" strokeWidth="2"/> + <path d="M9 13H15V20H9V13Z" stroke="currentColor" strokeWidth="2"/>Also applies to: 63-66
72-75: Messages icon: camelCase clipPath, strokeWidth, strokeLinecap-<g clip-path="url(#clip0_9157_282235)"> - <path d="M20.0001 4H4V21.5L8 19H20.0001V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="square"/> - <path d="M8 10.625C8.48325 10.625 8.875 11.0168 8.875 11.5C8.875 11.9832 8.48325 12.375 8 12.375C7.51675 12.375 7.125 11.9832 7.125 11.5C7.125 11.0168 7.51675 10.625 8 10.625ZM12 10.625C12.4832 10.625 12.875 11.0168 12.875 11.5C12.875 11.9832 12.4832 12.375 12 12.375C11.5168 12.375 11.125 11.9832 11.125 11.5C11.125 11.0168 11.5168 10.625 12 10.625ZM16 10.625C16.4832 10.625 16.875 11.0168 16.875 11.5C16.875 11.9832 16.4832 12.375 16 12.375C15.5168 12.375 15.125 11.9832 15.125 11.5C15.125 11.0168 15.5168 10.625 16 10.625Z" fill="currentColor" stroke="currentColor" stroke-width="0.75" stroke-linecap="square"/> +<g clipPath="url(#clip0_9157_282235)"> + <path d="M20.0001 4H4V21.5L8 19H20.0001V4Z" stroke="currentColor" strokeWidth="2" strokeLinecap="square"/> + <path d="M8 10.625C8.48325 10.625 8.875 11.0168 8.875 11.5C8.875 11.9832 8.48325 12.375 8 12.375C7.51675 12.375 7.125 11.9832 7.125 11.5C7.125 11.0168 7.51675 10.625 8 10.625ZM12 10.625C12.4832 10.625 12.875 11.0168 12.875 11.5C12.875 11.9832 12.4832 12.375 12 12.375C11.5168 12.375 11.125 11.9832 11.125 11.5C11.125 11.0168 11.5168 10.625 12 10.625ZM16 10.625C16.4832 10.625 16.875 11.0168 16.875 11.5C16.875 11.9832 16.4832 12.375 16 12.375C15.5168 12.375 15.125 11.9832 15.125 11.5C15.125 11.0168 15.5168 10.625 16 10.625Z" fill="currentColor" stroke="currentColor" strokeWidth="0.75" strokeLinecap="square"/>Also applies to: 77-80
86-89: Notifications icon: camelCase clipPath, strokeWidth, strokeLinecap-<g clip-path="url(#clip0_9157_282249)"> - <path d="M20 17H4V16L5.5 13L5.70037 8.99251C5.86822 5.63561 8.6389 3 12 3C15.3611 3 18.1318 5.63561 18.2996 8.99251L18.5 13L20 16V17Z" stroke="currentColor" stroke-width="2" stroke-linecap="square"/> - <path d="M8.03125 17.5C8.2773 19.4732 9.9605 21 12.0003 21C14.0401 21 15.7233 19.4732 15.9694 17.5" stroke="currentColor" stroke-width="2" stroke-linecap="square"/> +<g clipPath="url(#clip0_9157_282249)"> + <path d="M20 17H4V16L5.5 13L5.70037 8.99251C5.86822 5.63561 8.6389 3 12 3C15.3611 3 18.1318 5.63561 18.2996 8.99251L18.5 13L20 16V17Z" stroke="currentColor" strokeWidth="2" strokeLinecap="square"/> + <path d="M8.03125 17.5C8.2773 19.4732 9.9605 21 12.0003 21C14.0401 21 15.7233 19.4732 15.9694 17.5" stroke="currentColor" strokeWidth="2" strokeLinecap="square"/>Also applies to: 91-94
apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx (2)
16-16: Replace invalid Tailwind utilityborder-1withborder- <div className={`break-inside-avoid mb-2 ${isSelected ? 'border-1 border-purple-500' : ''}`}> + <div className={`break-inside-avoid mb-2 ${isSelected ? 'border border-purple-500' : ''}`}>
19-26: Provide a fallback instead of hiding broken imagesHiding the image on error leaves a blank tile. Show a simple placeholder or badge.
I can wire a small fallback state to render a placeholder with an icon/text. Want me to push a minimal patch?
🧹 Nitpick comments (23)
apps/web/client/src/app/_components/landing-page/contributor-section.tsx (1)
57-66: Optional: make mockup size responsive to reduce offscreen DOM on small viewportsThe fixed size 840px container renders many positioned nodes offscreen on smaller devices. Consider scaling size and ring counts when not isMd.
Example tweak:
-const innerRadius = isMd ? 260 * 1.4 : 260; -const outerRadius = isMd ? 340 * 1.4 : 380; -const size = 840; +const size = isMd ? 840 : 560; +const innerRadius = isMd ? 260 * 1.4 : 200; +const outerRadius = isMd ? 340 * 1.4 : 300;apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/move-modal.tsx (1)
41-42: Prefer state-setting handler over toggle in onOpenChangeRadix’s onOpenChange passes the intended open boolean. Calling a toggle can desync state if multiple events fire. Prefer a setter-style callback.
Apply this diff (adapting to your parent API if possible):
-<AlertDialog open={isOpen} onOpenChange={handleClose}> +<AlertDialog + open={isOpen} + onOpenChange={(next) => { + if (!isLoading && next !== isOpen) { + toggleOpen(); + } + }} +>If the parent can expose setOpen(next: boolean), use that directly instead of toggling:
onOpenChange={(next) => !isLoading && setOpen(next)}apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/index.tsx (1)
26-29: Unify indexing spinner for consistencyConsider switching the remaining Icons.Reload to Icons.LoadingSpinner to keep all progress states visually consistent.
-<Icons.Reload className="w-4 h-4 animate-spin" /> +<Icons.LoadingSpinner className="w-4 h-4 animate-spin" />apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup-icons.tsx (2)
9-9: Type safety: prefer SVGProps and avoid React.FC in satisfies (no import needed)Using an index signature ([key: string]: any) weakens type safety; React.FC in satisfies also ties you to importing React types. Consider SVGProps plus a function type to keep the file import-free.
Apply this diff:
-export interface IconProps { - className?: string; - [key: string]: any; -} +export type IconProps = React.SVGProps<SVGSVGElement> & { className?: string };Then update the satisfies clause to avoid React.FC:
-} satisfies { [key: string]: React.FC<IconProps> }; +} satisfies { [key: string]: (props: IconProps) => JSX.Element };If you prefer, add a type import at the top instead of referencing React namespace directly:
import type { SVGProps } from 'react'; export type IconProps = SVGProps<SVGSVGElement> & { className?: string };Also applies to: 99-99
11-21: Avoid duplicate clipPath IDs when rendering multiple instancesStatic ids like clip0_9157_... can collide if the icon appears multiple times on a page. Generate per-instance ids with useId and interpolate them in both id and url(#...).
Example pattern for one icon:
const Add = ({ className, ...props }: IconProps) => { const clipId = React.useId(); return ( <svg className={className} viewBox="0 0 24 24" fill="none" {...props}> <g clipPath={`url(#${clipId})`}> {/* paths */} </g> <defs> <clipPath id={clipId}> <rect width="24" height="24" fill="currentColor" /> </clipPath> </defs> </svg> ); };Also applies to: 31-39, 44-53, 58-67, 72-81, 86-95
apps/web/client/src/app/_components/landing-page/features-section.tsx (2)
7-9: Use h2 instead of h1 to maintain proper heading hierarchy below the heroThe hero likely owns the page’s primary h1. Making this section’s title an h2 avoids multiple h1s and keeps semantics/SEO clean.
- <h1 className="text-4xl lg:text-5xl font-light text-foreground-primary leading-tight"> + <h2 className="text-4xl lg:text-5xl font-light text-foreground-primary leading-tight"> <span className="bg-gradient-to-l from-white/20 via-white/90 to-white/20 bg-[length:200%_100%] bg-clip-text text-transparent animate-shimmer filter drop-shadow-[0_0_14px_rgba(255,255,255,1)]">AI</span> <span className="text-foreground-tertiary">•</span> <span className="font-mono">Code</span> <span className="text-foreground-tertiary">•</span> <span className="font-['Vujahday_Script'] not-italic text-6xl">Design</span><br /> Side-by-side-by-side - </h1> + </h2>
14-16: Remove duplicated Tailwind class on the paragraphThe
text-regularis repeated. Trim the duplicate for clarity.- <p className="text-regular lg:text-regular text-foreground-secondary leading-relaxed text-balance"> + <p className="text-regular text-foreground-secondary leading-relaxed text-balance"> Craft and prototype real designs with the highest fidelity. Get into the flow-state in a workspace where everything can be used in-context with AI. </p>apps/web/client/src/app/_components/landing-page/villainstagram-website-mockup.tsx (2)
7-8: Mark the mockup container as decorative to avoid screen reader noiseThis is a purely illustrative component; hide it from assistive tech to prevent redundant verbosity.
- <div className="bg-white w-140 h-140 rounded-xs flex flex-row overflow-hidden"> + <div className="bg-white w-140 h-140 rounded-xs flex flex-row overflow-hidden" aria-hidden="true" role="presentation">
85-86: Consider Next/Image for large post images to improve performanceThe post images are static and large. Using next/image (with remote patterns configured) would give built-in optimization, lazy-loading, and proper sizing. If keeping backgrounds for layout simplicity, at least ensure images are heavily compressed and cacheable.
Example (additional code outside this hunk):
// At top of file import Image from 'next/image';Possible replacement for one post image section:
<div className="relative w-full aspect-square"> <Image src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&h=800&fit=crop" alt="" fill sizes="(max-width: 768px) 100vw, 256px" priority={false} loading="lazy" decoding="async" className="object-cover" /> </div>Also applies to: 112-113
apps/web/client/src/app/_components/landing-page/villainstagram-mobile-mockup.tsx (4)
9-10: Align branding with the desktop mockup (“Villainstagram”) and avoid trademark confusionDesktop title uses “Villainstagram”; mobile header says “Instagram”. Unify to the same label.
- <span className="text-white font-semibold text-base">Instagram</span> + <span className="text-white font-semibold text-base">Villainstagram</span>
2-2: Remove unused import
Iconsisn’t used in this component.-import { Icons } from '@onlook/ui/icons';
6-6: Mark mockup as decorative for accessibility parity with desktopHide the mobile mock from assistive tech as well to avoid reading out illustrative content.
- <div className="bg-black w-64 h-140 rounded-xs border border-neutral-800 flex flex-col"> + <div className="bg-black w-64 h-140 rounded-xs border border-neutral-800 flex flex-col" aria-hidden="true" role="presentation">
20-27: Optional: DRY up shared story data across mobile and desktop mocksBoth mocks duplicate the stories array. Consider extracting a small shared constant to keep them in sync.
apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx (3)
17-19: Hide mobile mockup from a11y tree and prevent accidental interactionsThese mobile mockup instances are purely decorative. Add aria-hidden on the wrapper and disable pointer events to avoid double-reading by screen readers and accidental taps.
- <div className="absolute top-1/2 right-10 transform -translate-y-1/2 h-[800px] w-[1000px]"> + <div + className="absolute top-1/2 right-10 transform -translate-y-1/2 h-[800px] w-[1000px] pointer-events-none" + aria-hidden="true" + > <OnlookInterfaceMockup /> </div>
35-37: Same as above for the second mobile sectionApply the same aria-hidden and pointer-events to the left-side mobile mock.
- <div className="absolute top-1/2 left-10 transform -translate-y-1/2 h-[800px] w-[1000px]"> + <div + className="absolute top-1/2 left-10 transform -translate-y-1/2 h-[800px] w-[1000px] pointer-events-none" + aria-hidden="true" + > <OnlookInterfaceMockup /> </div>
12-49: Consider reducing duplicate heavy mockup renders on mobileRendering OnlookInterfaceMockup twice on mobile is expensive (animations, large DOM, many images). Consider:
- A lighter “static” variant for mobile (e.g., OnlookInterfaceMockupStatic) with simplified DOM and no state/handlers, or
- A prop to disable animations/panning and heavy subtrees for mobile,
- Or replacing one instance with a compressed screenshot to cut CPU/GPU and memory.
This will improve scroll perf and first paint on low-end devices.
I can draft a light “static” variant or refactor OnlookInterfaceMockup to accept a lightweight mode prop. Want a quick patch?
apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx (4)
18-18: Verifyrounded-xsexists or use a standard radius
rounded-xsis not a standard Tailwind class. Unless you extended the theme, this won’t apply.- <div className={`${lightMode ? 'bg-gray-200' : 'bg-gray-800'} rounded-xs mb-1 absolute inset-0`}></div> + <div className={`${lightMode ? 'bg-gray-200' : 'bg-gray-800'} rounded-sm mb-1 absolute inset-0`}></div>If you do have
rounded-xsin your theme, feel free to ignore.
19-25: Add lazy-loading hints to images to reduce LCP and memoryThese assets are heavy; add loading/decoding hints.
- <img + <img src={src} alt={alt} - className="w-full mb-1.5 object-cover relative z-10" + className="w-full mb-1.5 object-cover relative z-10" + loading="lazy" + decoding="async" + fetchpriority="low" onError={(e) => { e.currentTarget.style.display = 'none'; }} />If you can, prefer Next.js
<Image>withfill+sizesfor better optimization.
200-204: Prevent content from being covered by the mobile tab barThe tab bar is 2.5rem tall; bump bottom padding to avoid overlap.
- <div className="flex-1 pt-3 px-2 pb-2 h-full"> + <div className="flex-1 pt-3 px-2 pb-12 h-full">
73-153: De-duplicate the hardcoded image listThe repeated ImageCard entries make maintenance harder. Store metadata in an array and map over it.
I can extract a shared array (with lightMode and isSelected flags) and map in both desktop and mobile variants to remove ~150 lines of duplication. Want me to send a patch?
Also applies to: 204-289
apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx (3)
61-76: Unused proptoolNamein ToolCallDisplay
toolNameis accepted but unused. Either remove the prop or surface it in the UI (e.g., label).Example: include the tool label next to the spinner.
-function ToolCallDisplay({ toolName }: { toolName: string }) { +function ToolCallDisplay({ toolName }: { toolName: string }) { return ( <div className="px-2"> <div className="border rounded-lg bg-black/40 backdrop-blur-lg relative"> <div className="flex items-center justify-between text-foreground-secondary transition-colors pl-3 py-2"> <div className="flex items-center gap-2"> <Icons.LoadingSpinner className="h-4 w-4 text-foreground-secondary animate-spin" /> - <span className="text-xs pointer-events-none select-none bg-gradient-to-l from-white/20 via-white/90 to-white/20 bg-[length:200%_100%] bg-clip-text text-transparent animate-shimmer filter drop-shadow-[0_0_14px_rgba(255,255,255,1)]"> - Website.tsx - </span> + <span className="text-xs pointer-events-none select-none"> + {toolName || 'Working…'} + </span> </div> <Icons.ChevronDown className="h-4 w-4 text-foreground-tertiary mr-2" /> </div> </div> </div> ); }
96-145: Avoid re-creating static mock data on every render
mockLayersis static and fairly large. Define it outside the component or memoize withuseMemoto reduce allocations during re-renders.I can move it to a top-level const and keep types consistent. Want me to push it?
155-174: Optional: add touch support for panning on mobileCurrently supports mouse only. Adding touch handlers improves the “infinite canvas” feel on touch devices.
High-level sketch:
- Mirror the mouse handlers with onTouchStart/onTouchMove/onTouchEnd.
- Use
e.touches[0].clientX/Yfor coordinates.- Prevent default in move to avoid page scroll when panning.
Also applies to: 376-382
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (15)
apps/web/client/public/assets/profile-picture.pngis excluded by!**/*.pngapps/web/client/public/assets/the___daniel_httpss.mj.run0PLeZ5UEQR0_Metallic_chair_with_a_b_df9ea8f5-80e6-494a-9275-e01030e157b9_2.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.run4iXjsEavMyQ_minimalist_interior_wit_310f6c88-ea16-448f-af55-16f51782132a_1.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.run8EdjwNCSTns_black_leather_bar_stool_013dbd70-a2cc-44f7-a314-bbe275b86563_3.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runAp9BRoSUG-Q_silhouette_of_a_black_b_66ef6127-e9fd-4a70-8769-989d81178b23_1.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runF-rvbzgq3F8_brutalist_dark_glass_va_748a3c8c-a6a5-4840-93e7-efbbffcce8f1_1.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runIdTptP6hhTA_a_metallic_brushed_meta_76b89c75-015d-4127-bef8-cbf67bf63e3d_2 (1).jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runKt88JTu6rPQ_golden_muted_sharp_inte_324633e8-d399-4025-9a0f-a6010f1c4df2_0.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runMVYWfDFX_XE_realistic_contemporary__fe607fdd-058e-47bf-ab6f-d22b732c2f66_2.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runPguWEsbeT0k_two_minimalist_zig-zag__7f026ef1-b94a-4fce-a7c3-551f51cf0726_0.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runSh8wz2Xb28A_lounge_chair_against_a__ddb9bc7d-889d-4ea1-9129-48646172c544_2.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runcW6Xiv8NRPs_close-up_of_two_ceramic_de5b83cb-7ee5-4430-af5b-f868d487ebe4_2.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runpYhbRvhCgnQ_minimalist_metalic_shee_8daa01a7-2b6e-4bb4-9e13-6c6ac6f05959_1.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runtlX3nAWDPCk_Living_room_with_metal__53bf67c2-354a-4072-bd46-abf7aa0e555e_0.jpgis excluded by!**/*.jpgapps/web/client/public/assets/the___daniel_httpss.mj.runzX8_66H3mvo_spikey_ceramic_iron_met_4e9c30d0-e0cd-4f56-87f7-f62c2f691c08_2.jpgis excluded by!**/*.jpg
📒 Files selected for processing (13)
apps/web/client/src/app/_components/hero/index.tsx(3 hunks)apps/web/client/src/app/_components/landing-page/contributor-section.tsx(1 hunks)apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup-icons.tsx(1 hunks)apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx(1 hunks)apps/web/client/src/app/_components/landing-page/features-section.tsx(1 hunks)apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx(1 hunks)apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx(1 hunks)apps/web/client/src/app/_components/landing-page/villainstagram-mobile-mockup.tsx(1 hunks)apps/web/client/src/app/_components/landing-page/villainstagram-website-mockup.tsx(1 hunks)apps/web/client/src/app/page.tsx(2 hunks)apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/index.tsx(2 hunks)apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/move-modal.tsx(1 hunks)apps/web/client/src/utils/constants/index.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/move-modal.tsx (1)
packages/ui/src/components/icons/index.tsx (1)
Icons(131-3449)
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/index.tsx (1)
packages/ui/src/components/icons/index.tsx (1)
Icons(131-3449)
apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx (1)
apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx (1)
OnlookInterfaceMockup(80-454)
apps/web/client/src/app/_components/landing-page/villainstagram-website-mockup.tsx (1)
packages/ui/src/components/icons/index.tsx (1)
Icons(131-3449)
apps/web/client/src/app/_components/hero/index.tsx (1)
packages/ui/src/components/icons/index.tsx (1)
Icons(131-3449)
apps/web/client/src/app/page.tsx (2)
apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx (1)
ResponsiveMockupSection(4-52)apps/web/client/src/app/_components/landing-page/features-section.tsx (1)
FeaturesSection(1-21)
apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx (2)
apps/web/client/src/app/fonts.ts (1)
vujahdayScript(3-6)apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup-icons.tsx (1)
DesignMockupIcons(8-99)
apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx (3)
packages/ui/src/components/icons/index.tsx (1)
Icons(131-3449)apps/web/template/lib/utils.ts (1)
cn(4-6)apps/web/client/src/app/project/[id]/_components/left-panel/layers-tab/tree/node-icon.tsx (1)
NodeIcon(8-62)
🪛 Biome (2.1.2)
apps/web/client/src/app/_components/hero/index.tsx
[error] 26-26: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.
(lint/security/noBlankTarget)
apps/web/client/src/app/_components/landing-page/villainstagram-mobile-mockup.tsx
[error] 105-105: expected ) but instead found <
Remove <
(parse)
🔇 Additional comments (13)
apps/web/client/src/app/_components/landing-page/contributor-section.tsx (2)
175-175: Padding tweak LGTMAdding px-4 to the outer container improves mobile spacing without side effects.
181-183: Copy policing: lowercase “you” is goodMinor copy edit reads more naturally in context.
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/move-modal.tsx (1)
68-70: Spinner swap LGTMThe new LoadingSpinner icon reads better as a progress indicator; disabled gating remains correct.
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/index.tsx (2)
43-45: Spinner icon update LGTMUsing LoadingSpinner for “Uploading image...” matches the visual language introduced elsewhere in the PR.
54-56: Spinner icon update LGTMConsistent “Updating images...” feedback with LoadingSpinner looks good.
apps/web/client/src/app/_components/hero/index.tsx (2)
19-33: Hire banner animation and icon usage LGTMThe motion timing and styling are restrained; Icons.ArrowRight integration is clean.
80-87: Mobile notice animation LGTMSubtle blur-in with backdrop and border reads well on small screens.
apps/web/client/src/utils/constants/index.ts (1)
8-8: LGTM: FEATURES route addedRoute constant looks consistent with the existing pattern.
apps/web/client/src/app/page.tsx (2)
14-16: LGTM: New landing sections imported cleanlyImports are scoped and clear. No concerns here.
24-27: Composition looks good; IDs match the new sectionsThe full-bleed responsive mock and the md+ text section are placed with sensible IDs and layout utilities.
apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx (1)
187-197: Same flex issue in mobile sidebar wrapperAdd
flexso alignment applies.- <div className="w-11 flex flex-col items-center py-3 h-full justify-between hidden"> + <div className="w-11 flex flex-col items-center py-3 h-full justify-between hidden">Note: It already has
flex flex-colhere; if “hidden” is intentional, you might remove unused alignment utils to reduce CSS noise. If it’s meant to be shown later, keep as is.Likely an incorrect or invalid review comment.
apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx (2)
230-264: Top bar and actions look solidNeat composition and visual polish; undo/redo mirroring and “Live” button states read well.
384-451: Chat mockup block is clean and robustGood separation of concerns (UserMessage/AiMessage/ToolCallDisplay), safe readonly textarea, and consistent iconography.
| <a | ||
| href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" target="_blank" | ||
| className="inline-flex items-center gap-2 px-3 py-1.5 hover:bg-foreground-secondary/20 backdrop-blur-sm border border-foreground-secondary/20 rounded-full text-xs text-foreground-secondary transition-all duration-200 hover:scale-102" | ||
| > | ||
| We're hiring engineers | ||
| <Icons.ArrowRight className="w-4 h-4" /> | ||
| </a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add rel="noopener noreferrer" to external link with target="_blank"
Security hardening and aligns with lint/security/noBlankTarget.
Apply this diff:
-<a
- href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" target="_blank"
+<a
+ href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack"
+ target="_blank"
+ rel="noopener noreferrer"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <a | |
| href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" target="_blank" | |
| className="inline-flex items-center gap-2 px-3 py-1.5 hover:bg-foreground-secondary/20 backdrop-blur-sm border border-foreground-secondary/20 rounded-full text-xs text-foreground-secondary transition-all duration-200 hover:scale-102" | |
| > | |
| We're hiring engineers | |
| <Icons.ArrowRight className="w-4 h-4" /> | |
| </a> | |
| <a | |
| href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="inline-flex items-center gap-2 px-3 py-1.5 hover:bg-foreground-secondary/20 backdrop-blur-sm border border-foreground-secondary/20 rounded-full text-xs text-foreground-secondary transition-all duration-200 hover:scale-102" | |
| > | |
| We're hiring engineers | |
| <Icons.ArrowRight className="w-4 h-4" /> | |
| </a> |
🧰 Tools
🪛 Biome (2.1.2)
[error] 26-26: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.
(lint/security/noBlankTarget)
🤖 Prompt for AI Agents
In apps/web/client/src/app/_components/hero/index.tsx around lines 25 to 31, the
external anchor uses target="_blank" but is missing rel="noopener noreferrer";
update the <a> element to include rel="noopener noreferrer" alongside the
existing target attribute to satisfy security best practices and
lint/security/noBlankTarget rules.
| className="inline-flex items-center gap-2 px-3 py-1.5 hover:bg-foreground-secondary/20 backdrop-blur-sm border border-foreground-secondary/20 rounded-full text-xs text-foreground-secondary transition-all duration-200 hover:scale-102" | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Confirm Tailwind class “hover:scale-102” exists in your config
This isn’t a default Tailwind scale value. If not configured, it will be ignored; use hover:scale-105 or configure the custom scale.
To check for a custom scale class:
🏁 Script executed:
#!/bin/bash
# Search Tailwind config for custom scale values
fd -a --type f tailwind.config.* | xargs -I{} rg -n "scale\s*:\s*{"
rg -n "scale-102" -g '!**/node_modules/**'Length of output: 608
Configure or replace the non-standard “hover:scale-102” utility
We couldn’t find a custom scale.102 value in your Tailwind config, and hover:scale-102 isn’t a default utility—so it will be ignored at runtime. Please either replace it with an existing scale (e.g. hover:scale-105) or add the custom scale to your tailwind.config.js under theme.extend.scale.
• Location: apps/web/client/src/app/_components/hero/index.tsx (line 27)
• Example config addition:
// tailwind.config.js
module.exports = {
theme: {
extend: {
scale: {
'102': '1.02',
},
},
},
}🤖 Prompt for AI Agents
In apps/web/client/src/app/_components/hero/index.tsx around lines 27-28 the
class uses a non-standard Tailwind utility `hover:scale-102` which will be
ignored at runtime; either replace it with a supported scale utility such as
`hover:scale-105` (or another existing scale) in that class string, or add the
custom scale key to your Tailwind config under theme.extend.scale (e.g., add a
'102': '1.02' entry) and rebuild so `hover:scale-102` becomes valid.
| @@ -0,0 +1,313 @@ | |||
| import React from 'react'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mark this file as a Client Component
This module is imported by OnlookInterfaceMockup (a Client Component using hooks). Client Components cannot import Server Components; without the directive, this will error at build/hydration.
+'use client';
import React from 'react';🤖 Prompt for AI Agents
In
apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx
around line 1, this module is used by a Client Component but is missing the
Client Component directive; add the "use client" directive as the very first
line of the file (before any imports) so Next.js treats this file as a Client
Component and prevents server-to-client import errors during build/hydration.
| <div className="items-center justify-end h-12"> | ||
| <DesignMockupIcons.Gear className="w-4 h-4 text-gray-500" /> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing flex — alignment classes have no effect
items-center and justify-end don’t work without display: flex.
- <div className="items-center justify-end h-12">
+ <div className="flex items-center justify-end h-12">
<DesignMockupIcons.Gear className="w-4 h-4 text-gray-500" />
</div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="items-center justify-end h-12"> | |
| <DesignMockupIcons.Gear className="w-4 h-4 text-gray-500" /> | |
| </div> | |
| <div className="flex items-center justify-end h-12"> | |
| <DesignMockupIcons.Gear className="w-4 h-4 text-gray-500" /> | |
| </div> |
🤖 Prompt for AI Agents
In
apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx
around lines 67 to 69, the div uses Tailwind alignment classes items-center and
justify-end which do nothing without display:flex; add the flex class (e.g.,
className="flex items-center justify-end h-12") to enable the intended
horizontal flex alignment, or replace with an appropriate display utility if a
different layout is desired.
|
|
||
| export function DesignMockupMobile() { | ||
| return ( | ||
| <div className="bg-[#FFFFEE] w-50 h-116 rounded-sm flex flex-col overflow-hidden border border-gray-200"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Position context needed for absolute mobile tab bar
The bottom tab bar is absolutely positioned; add relative to this container to anchor it to the card instead of an outer ancestor.
- <div className="bg-[#FFFFEE] w-50 h-116 rounded-sm flex flex-col overflow-hidden border border-gray-200">
+ <div className="relative bg-[#FFFFEE] w-50 h-116 rounded-sm flex flex-col overflow-hidden border border-gray-200">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="bg-[#FFFFEE] w-50 h-116 rounded-sm flex flex-col overflow-hidden border border-gray-200"> | |
| <div className="relative bg-[#FFFFEE] w-50 h-116 rounded-sm flex flex-col overflow-hidden border border-gray-200"> |
🤖 Prompt for AI Agents
In
apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx
around line 162, the card container lacks a positioning context so the
absolutely positioned mobile tab bar attaches to an outer ancestor; add a
positioning context by including "relative" in the div's className (e.g., append
"relative" to the existing classes) so the absolute bottom tab bar is anchored
to this card.
| @@ -0,0 +1,454 @@ | |||
| import React from 'react'; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add the “use client” directive
This file uses React hooks and event handlers. Without 'use client', Next.js will treat it as a Server Component and error during build/hydration.
+'use client';
import React from 'react';🤖 Prompt for AI Agents
In apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx
around line 1, the file is missing the `'use client'` directive required for
React hooks and event handlers; add a top-level line with exactly 'use client'
(single quotes) as the very first line in the file, before any imports or code,
then save and rebuild to ensure Next.js treats the file as a Client Component.
| import React from 'react'; | ||
| import { Icons } from '@onlook/ui/icons'; | ||
| import { useState } from 'react'; | ||
| import { cn } from '@onlook/ui/utils'; | ||
| import { NodeIcon } from '../../project/[id]/_components/left-panel/layers-tab/tree/node-icon'; | ||
| import { DesignMockup, DesignMockupMobile } from './design-mockup/design-mockup'; | ||
|
|
||
| // Notes Component for Villainstagram | ||
| function NotesComponent() { | ||
| const notes = [ | ||
| "Implement evil pin creation (mwahaha)", | ||
| "Add 'light mode' (begrudgingly)", | ||
| "Build villain-to-villain messaging (evil DMs)", | ||
| "Create villain collaboration boards", | ||
| "Add villain lair location sharing (evil meetups)", | ||
| "Create devious recommendation page", | ||
| ]; | ||
|
|
||
| return ( | ||
| <div className="bg-black/80 backdrop-blur-sm border border-foreground-secondary/20 rounded-lg p-3 w-96 min-w-64 h-fit mt-10"> | ||
| <div className="flex items-center gap-2 mb-1"> | ||
| <h3 className="text-foreground-secondary text-xs font-mono">Villainterest - Product Notes</h3> | ||
| </div> | ||
| <div className="space-y-2"> | ||
| {notes.map((note, index) => ( | ||
| <div key={index} className="flex items-start gap-2 text-[10px]"> | ||
| <div className="w-1.5 h-1.5 bg-gray-500 rounded-full mt-1.5 flex-shrink-0"></div> | ||
| <span className={`leading-relaxed font-mono ${index < 3 ? 'text-gray-500 line-through' : 'text-gray-300'}`}>{note}</span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| // --- Chat mockup code from AiChatPreviewBlock --- | ||
| const chatMessages = [ | ||
| { sender: 'user', type: 'text', text: 'The masonry layout is breaking on mobile - can you fix the responsive columns?' }, | ||
| { sender: 'ai', type: 'text', text: "I see the issue! The CSS columns aren't adapting properly to smaller screens. Let me update the responsive breakpoints and add proper mobile optimization." }, | ||
| { sender: 'ai', type: 'tool', tool: 'Generate code', toolName: 'generateCode', args: { component: 'ImageGrid', fix: 'mobile-responsive' }}, | ||
| ]; | ||
| const PRESET_SENTENCE = "Add a villain verification badge system"; | ||
| function UserMessage({ text }: { text: string }) { | ||
| return ( | ||
| <div className="relative group w-full flex flex-row justify-end px-2"> | ||
| <div className="w-[80%] flex flex-col ml-8 p-2 rounded-lg shadow-sm rounded-br-none border-[0.5px] bg-background-secondary text-foreground-secondary relative"> | ||
| <div className="text-xs font-light">{text ?? ''}</div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| function AiMessage({ text }: { text: string }) { | ||
| return ( | ||
| <div className="relative group w-full flex flex-row justify-start px-2"> | ||
| <div className="w-[90%] flex flex-col mr-8 p-1 rounded-lg shadow-sm rounded-bl-none bg-none text-foreground-primary relative"> | ||
| <div className="text-xs leading-4.5 font-light mt-1">{text ?? ''}</div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| function ToolCallDisplay({ toolName }: { toolName: string }) { | ||
| return ( | ||
| <div className="px-2"> | ||
| <div className="border rounded-lg bg-black/40 backdrop-blur-lg relative"> | ||
| <div className="flex items-center justify-between text-foreground-secondary transition-colors pl-3 py-2"> | ||
| <div className="flex items-center gap-2"> | ||
| <Icons.LoadingSpinner className="h-4 w-4 text-foreground-secondary animate-spin" /> | ||
| <span className="text-xs pointer-events-none select-none bg-gradient-to-l from-white/20 via-white/90 to-white/20 bg-[length:200%_100%] bg-clip-text text-transparent animate-shimmer filter drop-shadow-[0_0_14px_rgba(255,255,255,1)]"> | ||
| Website.tsx | ||
| </span> | ||
| </div> | ||
| <Icons.ChevronDown className="h-4 w-4 text-foreground-tertiary mr-2" /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| // --- End chat mockup code --- | ||
|
|
||
| export function OnlookInterfaceMockup() { | ||
| const [isVisible, setIsVisible] = useState(false); | ||
|
|
||
| // Trigger animation after a delay to match jumbotron text timing | ||
| React.useEffect(() => { | ||
| const timer = setTimeout(() => { | ||
| setIsVisible(true); | ||
| }, 1200); // Delay to allow jumbotron text to animate first | ||
|
|
||
| return () => clearTimeout(timer); | ||
| }, []); | ||
|
|
||
| // For the mockup, the chat input is static (not interactive) | ||
| const displayedText = PRESET_SENTENCE; | ||
| const currentIndex = PRESET_SENTENCE.length; | ||
|
|
||
| // Mock data for layers panel | ||
| const mockLayers = [ | ||
| { id: '1', name: 'Design Mockup Container', tagName: 'DIV', selected: false, level: 0, isInstance: false }, | ||
| { id: '1.1', name: 'Top Navigation Bar', tagName: 'COMPONENT', selected: false, level: 1, isInstance: false }, | ||
| { id: '1.1.1', name: 'Logo', tagName: 'DIV', selected: false, level: 2, isInstance: false }, | ||
| { id: '1.1.2', name: 'Search Bar', tagName: 'DIV', selected: false, level: 2, isInstance: false }, | ||
| { id: '1.1.2.1', name: 'Search Text', tagName: 'SPAN', selected: false, level: 3, isInstance: false }, | ||
| { id: '1.1.2.2', name: 'Clear Button', tagName: 'DIV', selected: false, level: 3, isInstance: false }, | ||
| { id: '1.1.3', name: 'User Button', tagName: 'DIV', selected: false, level: 2, isInstance: false }, | ||
| { id: '1.2', name: 'Main Content', tagName: 'DIV', selected: false, level: 1, isInstance: false }, | ||
| { id: '1.2.1', name: 'Left Sidebar', tagName: 'DIV', selected: false, level: 2, isInstance: false }, | ||
| { id: '1.2.1.1', name: 'Navigation Icons', tagName: 'DIV', selected: false, level: 3, isInstance: false }, | ||
| { id: '1.2.1.1.1', name: 'Magnifying Glass', tagName: 'SVG', selected: false, level: 4, isInstance: false }, | ||
| { id: '1.2.1.1.2', name: 'Sparkles', tagName: 'SVG', selected: false, level: 4, isInstance: false }, | ||
| { id: '1.2.1.1.3', name: 'Chat Bubble', tagName: 'SVG', selected: false, level: 4, isInstance: false }, | ||
| { id: '1.2.1.1.4', name: 'Person', tagName: 'SVG', selected: false, level: 4, isInstance: false }, | ||
| { id: '1.2.1.2', name: 'Settings Icon', tagName: 'DIV', selected: false, level: 3, isInstance: false }, | ||
| { id: '1.2.2', name: 'Image Grid', tagName: 'DIV', selected: false, level: 2, isInstance: false }, | ||
| { id: '1.2.2.1', name: 'Image Columns', tagName: 'DIV', selected: false, level: 3, isInstance: false }, | ||
| { id: '1.2.2.1.1', name: 'Image Card 1', tagName: 'COMPONENT', selected: true, level: 4, isInstance: false }, | ||
| { id: '1.2.2.1.1.1', name: 'Image Container', tagName: 'DIV', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.1.1.1', name: 'Background Overlay', tagName: 'DIV', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.1.1.2', name: 'Image', tagName: 'IMG', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.1.2', name: 'Caption', tagName: 'P', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.2', name: 'Image Card 2', tagName: 'COMPONENT', selected: false, level: 4, isInstance: false }, | ||
| { id: '1.2.2.1.2.1', name: 'Image Container', tagName: 'DIV', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.2.1.1', name: 'Background Overlay', tagName: 'DIV', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.2.1.2', name: 'Image', tagName: 'IMG', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.2.2', name: 'Caption', tagName: 'P', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.3', name: 'Image Card 3', tagName: 'COMPONENT', selected: false, level: 4, isInstance: false }, | ||
| { id: '1.2.2.1.3.1', name: 'Image Container', tagName: 'DIV', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.3.1.1', name: 'Background Overlay', tagName: 'DIV', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.3.1.2', name: 'Image', tagName: 'IMG', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.3.2', name: 'Caption', tagName: 'P', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.4', name: 'Image Card 4', tagName: 'COMPONENT', selected: false, level: 4, isInstance: false }, | ||
| { id: '1.2.2.1.4.1', name: 'Image Container', tagName: 'DIV', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.4.1.1', name: 'Background Overlay', tagName: 'DIV', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.4.1.2', name: 'Image', tagName: 'IMG', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.4.2', name: 'Caption', tagName: 'P', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.5', name: 'Image Card 5', tagName: 'DIV', selected: false, level: 4, isInstance: false }, | ||
| { id: '1.2.2.1.5.1', name: 'Image Container', tagName: 'DIV', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.5.1.1', name: 'Background Overlay', tagName: 'DIV', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.5.1.2', name: 'Image', tagName: 'IMG', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.5.2', name: 'Caption', tagName: 'P', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.6', name: 'Image Card 6', tagName: 'DIV', selected: false, level: 4, isInstance: false }, | ||
| { id: '1.2.2.1.6.1', name: 'Image Container', tagName: 'DIV', selected: false, level: 5, isInstance: false }, | ||
| { id: '1.2.2.1.6.1.1', name: 'Background Overlay', tagName: 'DIV', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.6.1.2', name: 'Image', tagName: 'IMG', selected: false, level: 6, isInstance: false }, | ||
| { id: '1.2.2.1.6.2', name: 'Caption', tagName: 'P', selected: false, level: 5, isInstance: false }, | ||
| ]; | ||
| const [hoveredId, setHoveredId] = useState<string | null>(null); | ||
| const [selectedId, setSelectedId] = useState<string>('1.2.2.1.1'); | ||
| const [layersPanelOpen, setLayersPanelOpen] = useState(true); | ||
|
|
||
| // Canvas panning state | ||
| const [isPanning, setIsPanning] = useState(false); | ||
| const [panOffset, setPanOffset] = useState({ x: 60, y: -30 }); // Center on first mockup (accounting for layers panel) | ||
| const [lastMousePos, setLastMousePos] = useState({ x: 0, y: 0 }); | ||
|
|
||
| // Handle mouse down on canvas | ||
| const handleMouseDown = (e: React.MouseEvent) => { | ||
| setIsPanning(true); | ||
| setLastMousePos({ x: e.clientX, y: e.clientY }); | ||
| }; | ||
|
|
||
| // Handle mouse move for panning | ||
| const handleMouseMove = (e: React.MouseEvent) => { | ||
| if (!isPanning) return; | ||
|
|
||
| const deltaX = e.clientX - lastMousePos.x; | ||
| const deltaY = e.clientY - lastMousePos.y; | ||
|
|
||
| // Constrain panning to larger bounds for infinite canvas feel (±600px from center) | ||
| const newX = Math.max(-600, Math.min(600, panOffset.x + deltaX)); | ||
| const newY = Math.max(-400, Math.min(400, panOffset.y + deltaY)); | ||
|
|
||
| setPanOffset({ x: newX, y: newY }); | ||
| setLastMousePos({ x: e.clientX, y: e.clientY }); | ||
| }; | ||
|
|
||
| // Handle mouse up | ||
| const handleMouseUp = () => { | ||
| setIsPanning(false); | ||
| }; | ||
|
|
||
| // Handle mouse leave | ||
| const handleMouseLeave = () => { | ||
| setIsPanning(false); | ||
| }; | ||
|
|
||
| return ( | ||
| <div className={cn( | ||
| "relative w-full max-w-6xl mx-auto aspect-[16/10] rounded-xl overflow-hidden shadow-2xl border border-neutral-800 bg-background-onlook select-none -mt-10 transition-all duration-1000 ease-out", | ||
| isVisible | ||
| ? "opacity-100 translate-y-0" | ||
| : "opacity-0 translate-y-8" | ||
| )}> | ||
| <div | ||
| className="absolute inset-0 flex items-start mt-30 justify-center pointer-events-none z-0 right-36 select-none gap-12" | ||
| style={{ | ||
| transform: `translate(${panOffset.x}px, ${panOffset.y}px)`, | ||
| transition: isPanning ? 'none' : 'transform 0.1s ease-out' | ||
| }} | ||
| > | ||
| <NotesComponent /> | ||
| <div className="relative flex flex-col items-center border-1 border-teal-300 rounded-sm shadow-xl shadow-black/50"> | ||
| <div | ||
| className="absolute left-1/2 -translate-x-1/2 -top-7 z-50 w-full flex flex-row items-center rounded-lg h-6 text-xs px-1 gap-2.5 backdrop-blur-lg" | ||
| > | ||
| <div className="flex-1 flex flex-row items-center gap-1.5 overflow-hidden text-ellipsis whitespace-nowrap text-[12px] text-teal-300"> | ||
| Home | ||
| <Icons.ChevronDown className="w-4 h-4 text-teal-400 mb-0.5" /> | ||
| </div> | ||
| <button className="w-3 h-3 flex items-center justify-center cursor-pointer" tabIndex={-1} style={{ pointerEvents: 'none' }}> | ||
| <Icons.DotsHorizontal className="w-3.5 h-3.5 text-teal-300" /> | ||
| </button> | ||
| </div> | ||
| <DesignMockup /> | ||
| </div> | ||
| <div className="relative flex flex-col items-center border-[0.5px] border-foreground-border rounded-sm shadow-xl shadow-black/50 ml-8"> | ||
| <div | ||
| className="absolute left-1/2 -translate-x-1/2 -top-7 z-50 w-full flex flex-row items-center rounded-lg h-6 text-xs px-1 gap-2.5 backdrop-blur-lg" | ||
| > | ||
| <div className="flex-1 flex flex-row items-center gap-1.5 overflow-hidden text-ellipsis whitespace-nowrap text-[12px] text-foreground-secondary"> | ||
| Home | ||
| <Icons.ChevronDown className="w-4 h-4 text-foreground-secondary mb-0.5" /> | ||
| </div> | ||
| <button className="w-3 h-3 flex items-center justify-center cursor-pointer" tabIndex={-1} style={{ pointerEvents: 'none' }}> | ||
| <Icons.DotsHorizontal className="w-3.5 h-3.5 text-foreground-secondary" /> | ||
| </button> | ||
| </div> | ||
| <DesignMockupMobile /> | ||
| </div> | ||
| </div> | ||
| {/* Top Bar */} | ||
| <div className="grid grid-cols-3 items-center h-10 px-2.5 relative z-10"> | ||
| {/* Left: Logo + Project Name + Chevron */} | ||
| <div className="flex items-center gap-1 min-w-0"> | ||
| <Icons.OnlookLogo className="w-5 h-5 shrink-0" /> | ||
| <span className="text-xs text-foreground-secondary ml-1 truncate max-w-[100px]">Villainterest</span> | ||
| <Icons.ChevronDown className="w-4 h-4 text-neutral-400 ml-0.5" /> | ||
| </div> | ||
| {/* Center: Design/Preview toggle */} | ||
| <div className="flex justify-center"> | ||
| <div className="relative"> | ||
| <div className="flex items-center gap-0 font-normal h-6 mt-1"> | ||
| <button className="text-[12px] text-foreground-primary px-4 py-1 whitespace-nowrap bg-transparent cursor-pointer transition-all duration-150 ease-in-out">Design</button> | ||
| <button className="text-[12px] text-foreground-secondary px-4 py-1 whitespace-nowrap bg-transparent cursor-pointer transition-all duration-150 ease-in-out hover:text-neutral-300">Preview</button> | ||
| </div> | ||
| <div className="absolute -top-1 h-0.5 bg-white w-1/2" /> | ||
| </div> | ||
| </div> | ||
| {/* Right: Undo/Redo, Live, Avatar */} | ||
| <div className="flex items-center gap-1.5 justify-end"> | ||
| <button className="p-1 rounded hover:bg-neutral-800"> | ||
| <Icons.Reset className="w-4 h-4 text-foreground-secondary" /> | ||
| </button> | ||
| <button className="p-1 rounded hover:bg-neutral-800"> | ||
| <Icons.Reset className="w-4 h-4 text-foreground-secondary scale-x-[-1]" /> | ||
| </button> | ||
| <button className="bg-teal-900 border-[1px] border-teal-200 text-teal-200 text-xs px-2.5 py-1 rounded ml-2 flex flex-row items-center gap-1.5"> | ||
| <Icons.Globe className="w-3.5 h-3.5" /> | ||
| Live | ||
| </button> | ||
| <div className="w-7.5 h-7.5 rounded-full bg-neutral-700 flex items-center justify-center text-xs text-white ml-1 mt-0.5"> | ||
| <img src="/assets/profile-picture.png" alt="Profile Picture" className="w-full h-full object-cover rounded-full" /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| {/* Main Content */} | ||
| <div className="flex h-[calc(100%-2.5rem)] relative"> | ||
| {/* Sidebar */} | ||
| <div className="w-14 h-full flex flex-col items-center justify-between bg-background-onlook/80 backdrop-blur-xl mr-[-4] px-2"> | ||
| <div className="flex flex-col items-center py-4 gap-5"> | ||
| {/* Active: Layers */} | ||
| <div | ||
| className="flex flex-col items-center gap-0.5 rounded-md px-2 py-1.5 bg-background-tertiary/50 ring-1 ring-white/5 border-[0.5px] border-foreground-primary/20 cursor-pointer hover:bg-background-tertiary/70 transition-colors" | ||
| onClick={() => setLayersPanelOpen(!layersPanelOpen)} | ||
| > | ||
| <Icons.Layers className="w-4.5 h-4.5 text-foreground-primary" /> | ||
| <p className="text-[10px] text-foreground-primary">Layers</p> | ||
| </div> | ||
| <div className="flex flex-col items-center gap-1"> | ||
| <Icons.Brand className="w-4.5 h-4.5 text-foreground-secondary" /> | ||
| <p className="text-[10px] text-foreground-secondary">Brand</p> | ||
| </div> | ||
| <div className="flex flex-col items-center gap-1"> | ||
| <Icons.File className="w-4.5 h-4.5 text-foreground-secondary" /> | ||
| <p className="text-[10px] text-foreground-secondary">Pages</p> | ||
| </div> | ||
| <div className="flex flex-col items-center gap-1"> | ||
| <Icons.Image className="w-4.5 h-4.5 text-foreground-secondary" /> | ||
| <p className="text-[10px] text-foreground-secondary">Assets</p> | ||
| </div> | ||
| <div className="flex flex-col items-center gap-1"> | ||
| <Icons.Component className="w-4.5 h-4.5 text-foreground-secondary" /> | ||
| <p className="text-[10px] text-foreground-secondary">Elements</p> | ||
| </div> | ||
| <div className="flex flex-col items-center gap-1"> | ||
| <Icons.ViewGrid className="w-4.5 h-4.5 text-foreground-secondary" /> | ||
| <p className="text-[10px] text-foreground-secondary">Apps</p> | ||
| </div> | ||
| </div> | ||
| <div className="flex flex-row items-center justify-center w-full mb-6"> | ||
| <Icons.QuestionMarkCircled className="w-4.5 h-4.5 text-foreground-secondary" /> | ||
| </div> | ||
| </div> | ||
| {/* Floating bottom toolbar (absolute, does not affect layout) */} | ||
| <div className="absolute left-1/2 -translate-x-1/2 bottom-3 z-10 pointer-events-none"> | ||
| <div className="flex flex-col border-[0.5px] border-border p-1 px-1 bg-black/60 backdrop-blur-2xl rounded-lg backdrop-blur drop-shadow-xl overflow-hidden pointer-events-auto"> | ||
| <div className="flex flex-row gap-0.5"> | ||
| {/* Selected icon */} | ||
| <div className="h-8 w-8 flex items-center justify-center rounded-md border border-transparent bg-background-tertiary/50 text-foreground-primary"> | ||
| <Icons.CursorArrow className="w-4 h-4 text-foreground-primary" /> | ||
| </div> | ||
| {/* Unselected icons */} | ||
| <div className="h-8 w-8 flex items-center justify-center rounded-md border border-transparent text-foreground-tertiary hover:text-foreground-hover hover:bg-background-tertiary/50"> | ||
| <Icons.Hand className="w-4 h-4 text-foreground-tertiary" /> | ||
| </div> | ||
| <div className="h-8 w-8 flex items-center justify-center rounded-md border border-transparent text-foreground-tertiary hover:text-foreground-hover hover:bg-background-tertiary/50"> | ||
| <Icons.Square className="w-4 h-4 text-foreground-tertiary" /> | ||
| </div> | ||
| <div className="h-8 w-8 flex items-center justify-center rounded-md border border-transparent text-foreground-tertiary hover:text-foreground-hover hover:bg-background-tertiary/50"> | ||
| <Icons.Text className="w-4 h-4 text-foreground-tertiary" /> | ||
| </div> | ||
| <div className="h-8 w-8 flex items-center justify-center rounded-md border border-transparent text-foreground-tertiary hover:text-foreground-hover hover:bg-background-tertiary/50"> | ||
| <Icons.Terminal className="w-4 h-4 text-foreground-tertiary" /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| {/* Layers Side Panel (mini mockup) */} | ||
| {layersPanelOpen && ( | ||
| <div className="w-52 h-full px-1 pt-1"> | ||
| <div className="w-full h-[98%] rounded-xl overflow-hidden flex flex-col items-center justify-start bg-black/60 backdrop-blur-2xl border-[0.5px] border-foreground-primary/20"> | ||
| <div className="w-full p-2 overflow-hidden"> | ||
| <div className="flex flex-col gap-0.5 w-full"> | ||
| {mockLayers.map((layer) => { | ||
| const isComponent = layer.tagName === 'COMPONENT'; | ||
| const isSelected = selectedId === layer.id; | ||
| const isHovered = hoveredId === layer.id; | ||
|
|
||
| return ( | ||
| <div | ||
| key={layer.id} | ||
| className={cn( | ||
| 'flex items-center h-5.25 px-1.5 cursor-pointer transition-colors select-none text-xs', | ||
| // Component styling | ||
| isComponent && !layer.isInstance && !isHovered && 'text-purple-600 dark:text-purple-300', | ||
| isComponent && !layer.isInstance && isHovered && 'text-purple-500 bg-purple-800/50 dark:text-purple-200', | ||
| isComponent && !layer.isInstance && isSelected && 'bg-purple-500 dark:bg-purple-500/90 text-white dark:text-primary', | ||
| // Instance styling | ||
| layer.isInstance && isSelected && 'text-purple-100 dark:text-purple-100 bg-purple-700/70 dark:bg-purple-500/50', | ||
| layer.isInstance && !isSelected && 'text-purple-500 dark:text-purple-300', | ||
| layer.isInstance && !isSelected && isHovered && 'text-purple-800 dark:text-purple-200 bg-purple-400/30 dark:bg-purple-900/60', | ||
| // Regular selection styling | ||
| !isComponent && !layer.isInstance && isSelected && 'bg-[#FA003C] dark:bg-[#FA003C]/90 text-white dark:text-primary', | ||
| !isComponent && !layer.isInstance && isHovered && !isSelected && 'bg-background-onlook text-foreground-onlook', | ||
| !isComponent && !layer.isInstance && !isSelected && !isHovered && 'text-foreground-onlook', | ||
| // Rounded corners | ||
| isHovered && !isSelected && 'rounded', | ||
| isSelected && 'rounded', | ||
| )} | ||
| onMouseEnter={() => setHoveredId(layer.id)} | ||
| onMouseLeave={() => setHoveredId(null)} | ||
| onClick={() => setSelectedId(layer.id)} | ||
| style={{ userSelect: 'none' }} | ||
| > | ||
| <div style={{ width: `${layer.level * 16}px` }} /> | ||
| <NodeIcon iconClass="w-3.5 h-3.5 mr-1.5" tagName={layer.tagName} /> | ||
| <span className="truncate">{layer.name}</span> | ||
| </div> | ||
| ); | ||
| })} | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| )} | ||
| {/* Canvas Area - Panning enabled */} | ||
| <div | ||
| className="flex-1 flex flex-col items-center justify-start relative cursor-grab active:cursor-grabbing" | ||
| onMouseDown={handleMouseDown} | ||
| onMouseMove={handleMouseMove} | ||
| onMouseUp={handleMouseUp} | ||
| onMouseLeave={handleMouseLeave} | ||
| /> | ||
| <div className="w-64 bg-black/70 backdrop-blur-2xl border-l border-t rounded-tl-xl border-foreground-border flex flex-col justify-end p-0 relative"> | ||
| <div className="absolute inset-0 flex flex-col"> | ||
| <div className="flex items-center justify-between h-9 px-0.5 border-b border-foreground-border z-20"> | ||
| {/* Tabs */} | ||
| <div className="flex items-center"> | ||
| <button className="text-xs font-semibold text-foreground-primary px-2 py-1 rounded flex flex-row items-center gap-1"> | ||
| <Icons.Sparkles className="w-4 h-4" /> | ||
| Chat | ||
| </button> | ||
| <button className="text-xs text-foreground-secondary px-2 py-1 rounded hover:text-foreground-primary flex flex-row items-center gap-1"> | ||
| <Icons.Code className="w-4 h-4" /> | ||
| Code | ||
| </button> | ||
| </div> | ||
| <div className="flex items-center justify-between h-9 px-0.5 border-b border-foreground-border z-20"> | ||
| <button className="text-xs text-foreground-secondary px-2 py-1 rounded hover:text-foreground-primary flex flex-row items-center gap-1"> | ||
| <Icons.Plus className="w-3.5 h-3.5 text-foreground-secondary" /> | ||
| </button> | ||
| </div> | ||
| </div> | ||
| <div className="flex-1 flex flex-col justify-end"> | ||
| <div className="py-2 space-y-2 pt-24"> | ||
| {chatMessages.map((msg, idx) => { | ||
| if (msg.type === 'text' && msg.sender === 'user') { | ||
| return <UserMessage key={idx} text={msg.text ?? ''} />; | ||
| } | ||
| if (msg.type === 'text' && msg.sender === 'ai') { | ||
| return <AiMessage key={idx} text={msg.text ?? ''} />; | ||
| } | ||
| if (msg.type === 'tool') { | ||
| return ( | ||
| <ToolCallDisplay | ||
| key={idx} | ||
| toolName={msg.toolName ?? ''} | ||
| /> | ||
| ); | ||
| } | ||
| return null; | ||
| })} | ||
| </div> | ||
| <div className="border-t border-foreground-primary/10 px-2.5 py-2 flex flex-col items-start gap-1"> | ||
| <textarea | ||
| value={displayedText} | ||
| readOnly | ||
| className="flex-1 text-foreground-primary placeholder-foreground-tertiary text-xs px-0.5 pt-2 h-20 mb-5 w-full resize-none rounded-lg outline-none bg-transparent" | ||
| placeholder="Type a message..." | ||
| rows={3} | ||
| maxLength={PRESET_SENTENCE.length} | ||
| disabled | ||
| /> | ||
| <div className="flex flex-row items-center justify-between w-full gap-2"> | ||
| <button className="px-1 py-2 rounded-lg flex flex-row items-center gap-2" disabled> | ||
| <Icons.Build className="h-4 w-4 text-foreground-tertiary/50" /> | ||
| <p className="text-foreground-secondary/50 text-xs">Build</p> | ||
| </button> | ||
| <div className="flex flex-row gap-1"> | ||
| <button className="px-2 py-2 rounded-lg bg-background-secondary/0 hover:bg-background-secondary group cursor-copy" disabled> | ||
| <Icons.Image className="h-4 w-4 text-foreground-tertiary/50 group-hover:text-foreground-primary" /> | ||
| </button> | ||
| <button className={`px-2 py-2 rounded-full cursor-pointer ${currentIndex === PRESET_SENTENCE.length ? 'bg-foreground-primary' : 'bg-foreground-onlook'}`} disabled> | ||
| <Icons.ArrowRight className="h-3.5 w-3.5 text-background-secondary" /> | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify Client Component boundaries and stray Tailwind tokens
- Ensure both this file and design-mockup.tsx have
'use client'. - Optionally scan for non-standard Tailwind tokens like
border-1and fix.
Run:
Expected:
- Both files listed under “use client”.
- No matches for
border-1after fixes.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Checking for 'use client' directives..."
rg -n --no-heading -C0 $'^\\s*[\'"]use client[\'"]' apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx || true
echo -e "\nScanning for non-standard Tailwind 'border-1' usage..."
rg -n --no-heading -C1 '\bborder-1\b' --type=ts --type=tsx || trueLength of output: 562
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Searching for 'use client' directives..."
rg -n "^[\"']use client[\"']" apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx || true
echo -e "\nSearching for 'border-1' usage..."
rg -n "\bborder-1\b" apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx || trueLength of output: 976
Add missing 'use client' directives and replace non-standard Tailwind classes
Please address the following critical fixes:
• apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx
– Add "use client" as the very first line (above all imports).
– Replace the border-1 class on line 201 with the standard border utility (e.g. border border-teal-300).
• apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx
– Add "use client" as the very first line (above all imports).
– On line 16, change 'border-1 border-purple-500' to border border-purple-500.
These changes ensure React hooks work in this component and Tailwind applies the intended 1px borders correctly.
🤖 Prompt for AI Agents
In apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx
(lines 1-454): add "use client" as the very first line before any imports, and
replace the non-standard Tailwind class border-1 at the top-level mockup
container (around line ~201 where the teal container is defined) with the
standard utilities e.g. "border border-teal-300". In
apps/web/client/src/app/_components/landing-page/design-mockup/design-mockup.tsx
(line 1+ and line 16): add "use client" as the very first line before imports,
and change the class string "border-1 border-purple-500" on line 16 to "border
border-purple-500" so Tailwind applies a 1px border.
| <div className="relative flex flex-col items-center border-1 border-teal-300 rounded-sm shadow-xl shadow-black/50"> | ||
| <div | ||
| className="absolute left-1/2 -translate-x-1/2 -top-7 z-50 w-full flex flex-row items-center rounded-lg h-6 text-xs px-1 gap-2.5 backdrop-blur-lg" | ||
| > | ||
| <div className="flex-1 flex flex-row items-center gap-1.5 overflow-hidden text-ellipsis whitespace-nowrap text-[12px] text-teal-300"> | ||
| Home | ||
| <Icons.ChevronDown className="w-4 h-4 text-teal-400 mb-0.5" /> | ||
| </div> | ||
| <button className="w-3 h-3 flex items-center justify-center cursor-pointer" tabIndex={-1} style={{ pointerEvents: 'none' }}> | ||
| <Icons.DotsHorizontal className="w-3.5 h-3.5 text-teal-300" /> | ||
| </button> | ||
| </div> | ||
| <DesignMockup /> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace invalid Tailwind border-1 with border
border-1 isn’t a Tailwind utility; use border (1px) or an arbitrary value.
- <div className="relative flex flex-col items-center border-1 border-teal-300 rounded-sm shadow-xl shadow-black/50">
+ <div className="relative flex flex-col items-center border border-teal-300 rounded-sm shadow-xl shadow-black/50">📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div className="relative flex flex-col items-center border-1 border-teal-300 rounded-sm shadow-xl shadow-black/50"> | |
| <div | |
| className="absolute left-1/2 -translate-x-1/2 -top-7 z-50 w-full flex flex-row items-center rounded-lg h-6 text-xs px-1 gap-2.5 backdrop-blur-lg" | |
| > | |
| <div className="flex-1 flex flex-row items-center gap-1.5 overflow-hidden text-ellipsis whitespace-nowrap text-[12px] text-teal-300"> | |
| Home | |
| <Icons.ChevronDown className="w-4 h-4 text-teal-400 mb-0.5" /> | |
| </div> | |
| <button className="w-3 h-3 flex items-center justify-center cursor-pointer" tabIndex={-1} style={{ pointerEvents: 'none' }}> | |
| <Icons.DotsHorizontal className="w-3.5 h-3.5 text-teal-300" /> | |
| </button> | |
| </div> | |
| <DesignMockup /> | |
| </div> | |
| <div className="relative flex flex-col items-center border border-teal-300 rounded-sm shadow-xl shadow-black/50"> | |
| <div | |
| className="absolute left-1/2 -translate-x-1/2 -top-7 z-50 w-full flex flex-row items-center rounded-lg h-6 text-xs px-1 gap-2.5 backdrop-blur-lg" | |
| > | |
| <div className="flex-1 flex flex-row items-center gap-1.5 overflow-hidden text-ellipsis whitespace-nowrap text-[12px] text-teal-300"> | |
| Home | |
| <Icons.ChevronDown className="w-4 h-4 text-teal-400 mb-0.5" /> | |
| </div> | |
| <button className="w-3 h-3 flex items-center justify-center cursor-pointer" tabIndex={-1} style={{ pointerEvents: 'none' }}> | |
| <Icons.DotsHorizontal className="w-3.5 h-3.5 text-teal-300" /> | |
| </button> | |
| </div> | |
| <DesignMockup /> | |
| </div> |
🤖 Prompt for AI Agents
In apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx
around lines 201 to 214, the container uses an invalid Tailwind class
`border-1`; replace it with the correct utility `border` (or `border-[1px]` if
you explicitly need the arbitrary syntax) so the element gets a 1px border;
update the className string to remove `border-1` and add `border` (or
`border-[1px]`) accordingly.
apps/web/client/src/app/_components/landing-page/villainstagram-mobile-mockup.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/web/client/src/app/_components/hero/index.tsx (1)
30-36: Add rel="noopener noreferrer" and replace non-standard hover:scale-102
- Security: External link opens in a new tab; add rel="noopener noreferrer" to prevent tabnabbing. This is also flagged by Biome.
- Styling: Tailwind’s hover:scale-102 isn’t a default class unless configured; it will be ignored. Replace with an existing scale (e.g., hover:scale-105) or extend Tailwind config.
Apply this diff:
- <a - href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" target="_blank" - className="inline-flex items-center gap-2 px-3 py-1.5 hover:bg-foreground-secondary/20 backdrop-blur-sm border border-foreground-secondary/20 rounded-full text-xs text-foreground-secondary transition-all duration-200 hover:scale-102" - > + <a + href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" + target="_blank" + rel="noopener noreferrer" + className="inline-flex items-center gap-2 px-3 py-1.5 hover:bg-foreground-secondary/20 backdrop-blur-sm border border-foreground-secondary/20 rounded-full text-xs text-foreground-secondary transition-all duration-200 hover:scale-105" + >If you prefer the subtler 1.02 scale, extend Tailwind:
// tailwind.config.js module.exports = { theme: { extend: { scale: { '102': '1.02' }, }, }, };…and keep hover:scale-102 after rebuilding.
🧹 Nitpick comments (2)
apps/web/client/src/app/page.tsx (1)
24-27: Fix anchor target for “#features” on mobile (avoid duplicate IDs and broken links).Currently, the “#features” anchor exists inside ResponsiveMockupSection on a
md:block-only div. On mobile, that element ishidden, so “#features” links won’t scroll to a visible target. Conversely, adding anotherid="features"to the mobile-only node would create duplicate IDs in the DOM.Refactor ResponsiveMockupSection to wrap both desktop and mobile variants in a single parent section with
id="features"and remove the id from the inner desktop-only container. This yields a single, stable anchor across breakpoints.Proposed patch for apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx:
export function ResponsiveMockupSection() { return ( - <> - {/* Desktop/Tablet View - Full Mockup */} - <div className="hidden md:block w-screen h-screen flex items-center justify-center" id="features"> + <section id="features" aria-label="Product features"> + {/* Desktop/Tablet View - Full Mockup */} + <div className="hidden md:block w-screen h-screen flex items-center justify-center"> <OnlookInterfaceMockup /> </div> {/* Mobile View - Split into two sections */} <div className="md:hidden"> {/* First Section - Right half of mockup (chat panel focused) */} <div className="w-screen relative overflow-hidden flex flex-col items-center justify-center py-20" id="features-mobile-1"> {/* Original mockup positioned to show right side */} <div className="absolute top-1/2 right-10 transform -translate-y-1/2 h-[800px] w-[1000px]"> <OnlookInterfaceMockup /> </div> @@ {/* Second Section - Left half of mockup (layers/design tools focused) */} <div className="w-screen relative overflow-hidden flex flex-col items-center justify-center py-20" id="features-mobile-2"> {/* Original mockup positioned to show left side */} <div className="absolute top-1/2 left-10 transform -translate-y-1/2 h-[800px] w-[1000px]"> <OnlookInterfaceMockup /> </div> @@ </div> </div> - </> + </section> ); }After applying, verify:
- “#features” scrolls correctly on both mobile and desktop.
- No duplicate ID warnings in devtools console.
apps/web/client/src/app/_components/hero/index.tsx (1)
24-29: Optional: Respect prefers-reduced-motion for banner animationConsider honoring users’ reduced-motion settings to improve accessibility, especially since this is a hero animation rendered immediately on load.
Add the hook and gate animations:
// Add at top with other imports import { useReducedMotion } from 'framer-motion'; // Inside Hero() const reduceMotion = useReducedMotion(); // Example usage on this motion.div <motion.div className="flex flex-col gap-3 items-center relative z-20 pt-4 pb-2 mb-6" initial={reduceMotion ? { opacity: 1, y: 0 } : { opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} transition={reduceMotion ? undefined : { duration: 0.6, delay: 1.2, ease: 'easeOut' }} > {/* ... */} </motion.div>You can apply the same pattern to the subsequent motion elements where appropriate.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
apps/web/client/src/app/_components/hero/index.tsx(3 hunks)apps/web/client/src/app/page.tsx(2 hunks)apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/move-modal.tsx(1 hunks)apps/web/client/src/utils/constants/index.ts(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/move-modal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/client/src/utils/constants/index.ts
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/web/client/src/app/page.tsx (2)
apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx (1)
ResponsiveMockupSection(4-52)apps/web/client/src/app/_components/landing-page/features-section.tsx (1)
FeaturesSection(1-21)
apps/web/client/src/app/_components/hero/index.tsx (1)
packages/ui/src/components/icons/index.tsx (1)
Icons(138-3503)
🪛 Biome (2.1.2)
apps/web/client/src/app/_components/hero/index.tsx
[error] 31-31: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.
(lint/security/noBlankTarget)
🔇 Additional comments (3)
apps/web/client/src/app/page.tsx (1)
14-15: LGTM: imports for new sections are clear and localizedThe page composition reads well with the new ResponsiveMockupSection and FeaturesSection modules.
apps/web/client/src/app/_components/hero/index.tsx (2)
13-13: LGTM: icons import is correct and aligns with usageIcons.ArrowRight exists in @onlook/ui/icons and is used idiomatically.
95-102: LGTM: mobile-only notice is concise and styled non-intrusivelyAnimated entry and visual treatment look good; content is clear for mobile users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/web/client/src/app/_components/hero/index.tsx (1)
30-33: Fix external link security and non-standard Tailwind utility
- Security: target="_blank" is missing rel="noopener noreferrer", which is flagged by lint/security/noBlankTarget and is a real tabnabbing risk.
- Styling: hover:scale-102 isn’t a default Tailwind utility and likely won’t apply unless explicitly configured. Replace with a supported value (e.g., hover:scale-105) or extend your Tailwind config.
Apply this diff:
- <a - href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" target="_blank" - className="inline-flex items-center gap-2 px-3 py-1.5 hover:bg-foreground-secondary/20 backdrop-blur-sm border border-foreground-secondary/20 rounded-full text-xs text-foreground-secondary transition-all duration-200 hover:scale-102" + <a + href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" + target="_blank" + rel="noopener noreferrer" + className="inline-flex items-center gap-2 px-3 py-1.5 hover:bg-foreground-secondary/20 backdrop-blur-sm border border-foreground-secondary/20 rounded-full text-xs text-foreground-secondary transition-all duration-200 hover:scale-105"
🧹 Nitpick comments (4)
apps/web/client/messages/es.json (1)
94-94: Maintain formal voice consistency in ES locale ("Cree" vs "Crea").The ES locale predominantly uses formal voice (usted). Update imperative to match the tone used elsewhere in this file.
Apply this diff:
- "shareProjects": "Sin tarjeta de crédito • Crea un sitio en segundos" + "shareProjects": "Sin tarjeta de crédito • Cree un sitio en segundos"apps/web/client/messages/ja.json (1)
317-317: Natural Japanese phrasing: prefer “数秒でサイトを作成”.“サイトを秒で作成” is awkward. “数秒でサイトを作成” reads naturally and aligns with the English intent.
Apply this diff:
- "shareProjects": "クレジットカード不要 • サイトを秒で作成" + "shareProjects": "クレジットカード不要 • 数秒でサイトを作成"apps/web/client/src/app/_components/hero/index.tsx (2)
34-34: Consider localizing the new banner textIf the site supports i18n, “We’re hiring engineers” should go through your translation layer for consistency.
86-91: Consider respecting prefers-reduced-motion and easing effects on mobileAnimating opacity + blur and using backdrop-blur-lg on small screens can be heavy on low-end devices. Consider disabling or simplifying motion when users prefer reduced motion.
Proposed approach:
- Use framer-motion’s useReducedMotion to gate animations.
- Keep styling but remove (or reduce) animation/blur when reduced motion is enabled.
Outside the selected lines, add:
// imports import { motion, useReducedMotion } from 'framer-motion'; // inside Hero() const prefersReducedMotion = useReducedMotion();Within the selected lines, conditionally apply animations:
- <motion.div className="sm:hidden text-balance flex flex-col gap-4 items-center relative z-20 px-10 text-foreground-secondary bg-foreground-secondary/10 backdrop-blur-lg rounded-lg border-[0.5px] border-foreground-secondary/20 p-4" - initial={{ opacity: 0, filter: "blur(4px)" }} - animate={{ opacity: 1, filter: "blur(0px)" }} - transition={{ duration: 0.6, delay: 0.6, ease: "easeOut" }} - style={{ willChange: "opacity, filter", transform: "translateZ(0)" }} + <motion.div className="sm:hidden text-balance flex flex-col gap-4 items-center relative z-20 px-10 text-foreground-secondary bg-foreground-secondary/10 backdrop-blur-lg rounded-lg border-[0.5px] border-foreground-secondary/20 p-4" + initial={prefersReducedMotion ? undefined : { opacity: 0, filter: "blur(4px)" }} + animate={prefersReducedMotion ? undefined : { opacity: 1, filter: "blur(0px)" }} + transition={prefersReducedMotion ? undefined : { duration: 0.6, delay: 0.6, ease: "easeOut" }} + style={{ willChange: "opacity, filter", transform: "translateZ(0)" }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (8)
apps/web/client/messages/en.d.json.ts(1 hunks)apps/web/client/messages/en.json(1 hunks)apps/web/client/messages/es.json(1 hunks)apps/web/client/messages/ja.json(1 hunks)apps/web/client/messages/ko.json(1 hunks)apps/web/client/messages/zh.json(1 hunks)apps/web/client/src/app/_components/auth-modal.tsx(1 hunks)apps/web/client/src/app/_components/hero/index.tsx(3 hunks)
✅ Files skipped from review due to trivial changes (4)
- apps/web/client/messages/zh.json
- apps/web/client/messages/ko.json
- apps/web/client/messages/en.d.json.ts
- apps/web/client/src/app/_components/auth-modal.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/web/client/src/app/_components/hero/index.tsx (3)
apps/web/client/src/trpc/react.tsx (1)
api(23-23)apps/web/client/src/app/_components/hero/unicorn-background.tsx (1)
UnicornBackground(73-145)packages/ui/src/components/icons/index.tsx (1)
Icons(138-3503)
🪛 Biome (2.1.2)
apps/web/client/src/app/_components/hero/index.tsx
[error] 31-31: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.
(lint/security/noBlankTarget)
🔇 Additional comments (5)
apps/web/client/messages/en.json (2)
94-94: LGTM on the updated CTA.Clear and succinct. Matches cross-locale updates noted in the PR.
94-94: Locale audit complete: allshareProjectskeys present with bullet “•”
Verified in en.json, es.json, ja.json, ko.json, and zh.json—no missing entries or formatting mismatches. No further action needed.apps/web/client/src/app/_components/hero/index.tsx (3)
13-13: Icons import looks correct and consistent with usageImporting Icons from @onlook/ui/icons aligns with the existing pattern and supports <Icons.ArrowRight /> below.
21-21: Layout spacing change to gap-10 is fineThe adjustment is consistent with the design tweaks introduced in this PR.
75-75: Margin reset to mt-0 LGTMLooks intentional given the new banner above and spacing changes elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
apps/web/client/src/app/_components/hero/index.tsx (2)
32-32: Replace non-standard Tailwind utility hover:scale-102 or configure it.hover:scale-102 isn’t a default Tailwind class and will be ignored unless configured.
Apply this diff (quick fix):
- className="inline-flex items-center gap-2 px-3 py-1.5 hover:bg-foreground-secondary/20 backdrop-blur-sm border border-foreground-secondary/20 rounded-full text-xs text-foreground-secondary transition-all duration-200 hover:scale-102" + className="inline-flex items-center gap-2 px-3 py-1.5 hover:bg-foreground-secondary/20 backdrop-blur-sm border border-foreground-secondary/20 rounded-full text-xs text-foreground-secondary transition-all duration-200 hover:scale-105"Or add theme.extend.scale['102'] = '1.02' in tailwind config.
30-33: Add rel="noopener noreferrer" to external link opened in a new tab.Security hardening and satisfies lint/security/noBlankTarget.
Apply this diff:
- <a - href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" target="_blank" + <a + href="https://www.ycombinator.com/companies/onlook/jobs/e4gHv1n-founding-engineer-fullstack" + target="_blank" + rel="noopener noreferrer"
🧹 Nitpick comments (5)
apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx (4)
17-19: Prevent touch/scroll interference on mobile mockups.The absolutely positioned mockups likely intercept touch/pointer events on mobile, making scrolling and link interaction harder. Since these are decorative on mobile, disable pointer events on the mockup containers.
Apply this diff:
- <div className="absolute top-1/2 right-10 transform -translate-y-1/2 h-[800px] w-[1000px]"> + <div className="absolute top-1/2 right-10 transform -translate-y-1/2 h-[800px] w-[1000px] pointer-events-none"> <OnlookInterfaceMockup /> </div> … - <div className="absolute top-1/2 left-10 transform -translate-y-1/2 h-[800px] w-[1000px]"> + <div className="absolute top-1/2 left-10 transform -translate-y-1/2 h-[800px] w-[1000px] pointer-events-none"> <OnlookInterfaceMockup /> </div>Also applies to: 35-37
2-2: Reduce TTI: lazy-load the heavy mockup component (rendered up to 3x).OnlookInterfaceMockup is complex and mounted once on desktop and twice on mobile. Lazy-loading it cuts initial JS cost and avoids SSR mismatch issues if it uses browser APIs. Consider also deferring the second mobile instance with intersection observers.
Apply this diff to lazy-load:
-import { OnlookInterfaceMockup } from './onlook-interface-mockup'; +import dynamic from 'next/dynamic'; +const OnlookInterfaceMockup = dynamic( + () => import('./onlook-interface-mockup').then(m => ({ default: m.OnlookInterfaceMockup })), + { ssr: false } +);Optionally, mount the second mobile instance on intersection only (outside this hunk), or replace it with a static snapshot image to further reduce cost.
8-8: Avoid horizontal overflow from w-screen.w-screen can introduce a horizontal scrollbar on some platforms due to scrollbar width. Prefer w-full and use a min-height for the viewport.
Apply this diff:
- <div className="hidden md:block w-screen h-screen flex items-center justify-center"> + <div className="hidden md:block w-full min-h-[100svh] flex items-center justify-center">
22-29: Brittle fixed offsets: large absolute sizes + mt-[700px].Hard-coded sizes (h-[800px] w-[1000px]) and mt-[700px] are fragile across small phones and differ by device density. Consider clamping or scaling instead of magic numbers to keep layout stable.
Example approach:
- Wrap the mockup in a container with aspect-[…] and use scale-[…] with clamp() on a parent to control size across breakpoints.
- Replace mt-[700px] with mt-[clamp(28rem,35vh,44rem)] or use grid with fixed row for mockup and auto row for text, avoiding absolute positioning.
Also applies to: 40-47
apps/web/client/src/app/_components/hero/index.tsx (1)
92-92: Copy nit: sentence casing.Consider “Onlook isn’t ready for mobile — please open on a larger screen” for consistent casing and tone.
Apply this diff:
- Onlook isn't ready for Mobile – Please open on a larger screen + Onlook isn't ready for mobile — please open on a larger screen
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
apps/web/client/src/app/_components/hero/index.tsx(4 hunks)apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx (1)
apps/web/client/src/app/_components/landing-page/onlook-interface-mockup.tsx (1)
OnlookInterfaceMockup(80-454)
apps/web/client/src/app/_components/hero/index.tsx (3)
apps/web/client/src/trpc/react.tsx (1)
api(23-23)apps/web/client/src/app/_components/hero/unicorn-background.tsx (1)
UnicornBackground(73-145)packages/ui/src/components/icons/index.tsx (1)
Icons(138-3503)
🪛 Biome (2.1.2)
apps/web/client/src/app/_components/hero/index.tsx
[error] 31-31: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".
Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.
(lint/security/noBlankTarget)
| {/* Desktop/Tablet View - Full Mockup */} | ||
| <div className="hidden md:block w-screen h-screen flex items-center justify-center" id="features"> | ||
| <OnlookInterfaceMockup /> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Fix anchor target: #features is missing on mobile and risks duplicate IDs on desktop.
Currently, id="features" exists only on the desktop container (hidden on mobile), so anchor links to #features won’t work on mobile. If you duplicate the id on the mobile wrapper you’ll end up with invalid duplicate IDs on larger screens (both wrappers are in the DOM). Add a dedicated, single anchor element and remove the id from the desktop wrapper.
Apply this diff:
<>
- {/* Desktop/Tablet View - Full Mockup */}
- <div className="hidden md:block w-screen h-screen flex items-center justify-center" id="features">
+ {/* Anchor for Features section (works across breakpoints) */}
+ <span id="features" className="block h-0 w-0 overflow-hidden" aria-hidden="true" />
+
+ {/* Desktop/Tablet View - Full Mockup */}
+ <div className="hidden md:block w-screen h-screen flex items-center justify-center">
<OnlookInterfaceMockup />
</div>🏁 Script executed:
#!/usr/bin/env bash
# Locate the responsive mockup section file
fd responsive-mockup-section.tsx
# Show context around id="features" across the codebase
rg -n 'id="features"' -C3 -g '*.tsx'Length of output: 940
🏁 Script executed:
#!/usr/bin/env bash
# Display the responsive-mockup-section.tsx file to confirm wrapper structure
sed -n '1,200p' apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsxLength of output: 2532
🏁 Script executed:
#!/usr/bin/env bash
# Search for mobile feature IDs usage
rg -n 'features-mobile' -g '*.tsx'
# Search for anchor links targeting #features
rg -n 'href="#features"' -g '*.tsx'
rg -n 'href="#features"' -g '*.html'
# Search for any CSS referencing #features
rg -n '#features' -g '*.css' -g '*.scss'Length of output: 729
Add a dedicated, single anchor element for the Features section
Currently the id="features" is only on the desktop wrapper (hidden on mobile), so in-page links to #features won’t work on mobile—and duplicating it on both mobile wrappers would introduce invalid duplicate IDs. Introduce a single, visually hidden anchor before the desktop mockup and remove the ID from that wrapper.
• File to update:
– apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx
• Changes:
- Insert a hidden
<span id="features">above the desktop section - Remove
id="features"from the desktop<div>
Proposed diff:
@@ return (
<>
- {/* Desktop/Tablet View - Full Mockup */}
- <div className="hidden md:block w-screen h-screen flex items-center justify-center" id="features">
+ {/* Anchor for Features section (works across all breakpoints) */}
+ <span
+ id="features"
+ className="block h-0 w-0 overflow-hidden"
+ aria-hidden="true"
+ />
+
+ {/* Desktop/Tablet View - Full Mockup */}
+ <div className="hidden md:block w-screen h-screen flex items-center justify-center">
<OnlookInterfaceMockup />
</div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {/* Desktop/Tablet View - Full Mockup */} | |
| <div className="hidden md:block w-screen h-screen flex items-center justify-center" id="features"> | |
| <OnlookInterfaceMockup /> | |
| </div> | |
| // apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx | |
| return ( | |
| <> | |
| {/* Anchor for Features section (works across all breakpoints) */} | |
| <span | |
| id="features" | |
| className="block h-0 w-0 overflow-hidden" | |
| aria-hidden="true" | |
| /> | |
| {/* Desktop/Tablet View - Full Mockup */} | |
| <div className="hidden md:block w-screen h-screen flex items-center justify-center"> | |
| <OnlookInterfaceMockup /> | |
| </div> | |
| {/* …the rest of your JSX… */} | |
| </> | |
| ); |
🤖 Prompt for AI Agents
In
apps/web/client/src/app/_components/landing-page/responsive-mockup-section.tsx
around lines 7–10, the desktop wrapper currently carries id="features" but is
hidden on mobile, so anchor links to #features fail on mobile; add a single
visually-hidden anchor element before the desktop mockup (e.g., <span
id="features" className="sr-only" /> or equivalent hidden/visually-hidden
utility) and remove id="features" from the desktop <div> so there is exactly one
anchor present and it is available on all viewports.
Description
Related Issues
Type of Change
Testing
Screenshots (if applicable)
Additional Notes
Important
This pull request adds an interactive design mockup to the landing page, updates UI components, modifies text across multiple languages, and introduces new routes and icons.
design-mockup.tsxandresponsive-mockup-section.tsx.DesignMockupIconsindesign-mockup-icons.tsxfor UI elements.OnlookInterfaceMockupinonlook-interface-mockup.tsxfor interface demonstration.Herocomponent inhero/index.tsxwith new animations and hiring banner.FeaturesSectioninfeatures-section.tsxwith new headline and description.ContributorSectionincontributor-section.tsxto include dynamic contributor data.auth-modal.tsx.en.json,es.json,ja.json,ko.json,zh.json) to "No credit card required • Get a site in seconds".FEATURESroute inconstants/index.ts.move-modal.tsx.This description was created by
for 9901063. You can customize this summary. It will automatically update as commits are pushed.
Summary by CodeRabbit
New Features
Style
Bug Fixes/Small Improvements
Chores