love-ui 1.2.17 → 1.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +20 -20
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
- package/registry/__index__.tsx +73 -0
- package/registry/default/blocks/404-1/app/page.tsx +5 -0
- package/registry/default/blocks/404-1/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/404-1/components/not-found.tsx +51 -0
- package/registry/default/blocks/404-2/app/page.tsx +5 -0
- package/registry/default/blocks/404-2/components/not-found.tsx +44 -0
- package/registry/default/blocks/auth1/app/page.tsx +5 -0
- package/registry/default/blocks/auth1/components/auth-page.tsx +66 -0
- package/registry/default/blocks/auth1/components/icons/github-icon.tsx +10 -0
- package/registry/default/blocks/auth1/components/icons/google-icon.tsx +14 -0
- package/registry/default/blocks/auth1/components/logo.tsx +88 -0
- package/registry/default/blocks/auth1/components/particles.tsx +321 -0
- package/registry/default/blocks/auth2/app/page.tsx +5 -0
- package/registry/default/blocks/auth2/components/auth.tsx +87 -0
- package/registry/default/blocks/auth2/components/icons/github-icon.tsx +10 -0
- package/registry/default/blocks/auth2/components/icons/google-icon.tsx +14 -0
- package/registry/default/blocks/auth2/components/ui/auth-divider.tsx +16 -0
- package/registry/default/blocks/auth2/components/ui/decor-icon.tsx +45 -0
- package/registry/default/blocks/auth3/app/page.tsx +5 -0
- package/registry/default/blocks/auth3/components/auth-page.tsx +84 -0
- package/registry/default/blocks/auth3/components/icons/github-icon.tsx +10 -0
- package/registry/default/blocks/auth3/components/icons/google-icon.tsx +14 -0
- package/registry/default/blocks/auth3/components/logo.tsx +88 -0
- package/registry/default/blocks/auth3/components/ui/auth-divider.tsx +16 -0
- package/registry/default/blocks/auth3/components/ui/decor-icon.tsx +45 -0
- package/registry/default/blocks/blogs1/app/page.tsx +5 -0
- package/registry/default/blocks/blogs1/components/blogs.tsx +97 -0
- package/registry/default/blocks/blogs1/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/blogs2/app/page.tsx +5 -0
- package/registry/default/blocks/blogs2/components/blogs.tsx +158 -0
- package/registry/default/blocks/blogs2/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/blogs2/components/grid-filler.tsx +74 -0
- package/registry/default/blocks/blogs3/app/page.tsx +5 -0
- package/registry/default/blocks/blogs3/components/aspect-ratio.tsx +22 -0
- package/registry/default/blocks/blogs3/components/blogs.tsx +224 -0
- package/registry/default/blocks/blogs3/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/blogs3/components/lazy-image.tsx +94 -0
- package/registry/default/blocks/contact1/app/page.tsx +9 -0
- package/registry/default/blocks/contact1/components/contact.tsx +65 -0
- package/registry/default/blocks/contact1/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/contact2/app/page.tsx +9 -0
- package/registry/default/blocks/contact2/components/contact.tsx +74 -0
- package/registry/default/blocks/contact2/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/contact2/components/icons/x-icon.tsx +12 -0
- package/registry/default/blocks/contact3/app/page.tsx +9 -0
- package/registry/default/blocks/contact3/components/contact.tsx +152 -0
- package/registry/default/blocks/contact3/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/contact3/components/icons/github-icon.tsx +10 -0
- package/registry/default/blocks/contact3/components/icons/x-icon.tsx +12 -0
- package/registry/default/blocks/contact4/app/page.tsx +9 -0
- package/registry/default/blocks/contact4/components/contact.tsx +120 -0
- package/registry/default/blocks/contact5/app/page.tsx +9 -0
- package/registry/default/blocks/contact5/components/contact.tsx +119 -0
- package/registry/default/blocks/contact5/components/decor-icon.tsx +45 -0
- package/registry/default/blocks/cta-1/app/page.tsx +9 -0
- package/registry/default/blocks/cta-1/components/cta.tsx +20 -0
- package/registry/default/blocks/cta-1/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/cta-2/app/page.tsx +9 -0
- package/registry/default/blocks/cta-2/components/cta.tsx +27 -0
- package/registry/default/blocks/cta-2/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/cta-3/app/page.tsx +9 -0
- package/registry/default/blocks/cta-3/components/cta.tsx +35 -0
- package/registry/default/blocks/cta-3/components/decor-icon.tsx +45 -0
- package/registry/default/blocks/cta-4/app/page.tsx +9 -0
- package/registry/default/blocks/cta-4/components/cta.tsx +28 -0
- package/registry/default/blocks/cta-5/app/page.tsx +9 -0
- package/registry/default/blocks/cta-5/components/cta.tsx +72 -0
- package/registry/default/blocks/cta-5/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/faq-1/app/page.tsx +9 -0
- package/registry/default/blocks/faq-1/components/faq.tsx +86 -0
- package/registry/default/blocks/faq-2/app/page.tsx +9 -0
- package/registry/default/blocks/faq-2/components/faq.tsx +93 -0
- package/registry/default/blocks/faq-3/app/page.tsx +9 -0
- package/registry/default/blocks/faq-3/components/decor-icon.tsx +45 -0
- package/registry/default/blocks/faq-3/components/faq.tsx +110 -0
- package/registry/default/blocks/faq-4/app/page.tsx +9 -0
- package/registry/default/blocks/faq-4/components/faq.tsx +181 -0
- package/registry/default/blocks/faq-5/app/page.tsx +9 -0
- package/registry/default/blocks/faq-5/components/faq.tsx +211 -0
- package/registry/default/blocks/faq-5/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/features1/app/page.tsx +9 -0
- package/registry/default/blocks/features1/components/feature-section.tsx +73 -0
- package/registry/default/blocks/features2/app/page.tsx +9 -0
- package/registry/default/blocks/features2/components/decor-icon.tsx +45 -0
- package/registry/default/blocks/features2/components/feature-section.tsx +97 -0
- package/registry/default/blocks/features3/app/page.tsx +9 -0
- package/registry/default/blocks/features3/components/feature-section.tsx +93 -0
- package/registry/default/blocks/features3/components/full-width-divider.tsx +21 -0
- package/registry/default/blocks/features4/app/page.tsx +9 -0
- package/registry/default/blocks/features4/components/decor-icon.tsx +45 -0
- package/registry/default/blocks/features4/components/feature-section.tsx +114 -0
- package/registry/default/blocks/features5/app/page.tsx +9 -0
- package/registry/default/blocks/features5/components/feature-section.tsx +115 -0
- package/registry/default/blocks/features5/components/grid-pattern.tsx +70 -0
- package/registry/default/blocks/features6/app/page.tsx +9 -0
- package/registry/default/blocks/features6/components/cobe-globe.tsx +86 -0
- package/registry/default/blocks/features6/components/feature-section.tsx +309 -0
- package/registry/default/blocks/footer1/app/page.tsx +9 -0
- package/registry/default/blocks/footer1/components/footer.tsx +85 -0
- package/registry/default/blocks/footer1/components/icons/github-icon.tsx +10 -0
- package/registry/default/blocks/footer1/components/icons/x-icon.tsx +12 -0
- package/registry/default/blocks/footer1/components/logo.tsx +88 -0
- package/registry/default/blocks/footer2/app/page.tsx +9 -0
- package/registry/default/blocks/footer2/components/footer.tsx +139 -0
- package/registry/default/blocks/footer2/components/full-width-divider.tsx +21 -0
- package/registry/default/blocks/footer2/components/icons/github-icon.tsx +10 -0
- package/registry/default/blocks/footer2/components/icons/instagram-icon.tsx +12 -0
- package/registry/default/blocks/footer2/components/icons/x-icon.tsx +12 -0
- package/registry/default/blocks/footer2/components/logo.tsx +88 -0
- package/registry/default/blocks/footer3/app/page.tsx +9 -0
- package/registry/default/blocks/footer3/components/footer.tsx +155 -0
- package/registry/default/blocks/footer3/components/icons/github-icon.tsx +10 -0
- package/registry/default/blocks/footer3/components/icons/instagram-icon.tsx +12 -0
- package/registry/default/blocks/footer3/components/icons/linkedin-icon.tsx +12 -0
- package/registry/default/blocks/footer3/components/icons/x-icon.tsx +12 -0
- package/registry/default/blocks/footer3/components/logo.tsx +88 -0
- package/registry/default/blocks/footer4/app/page.tsx +9 -0
- package/registry/default/blocks/footer4/components/footer.tsx +137 -0
- package/registry/default/blocks/footer4/components/icons/github-icon.tsx +10 -0
- package/registry/default/blocks/footer4/components/icons/instagram-icon.tsx +12 -0
- package/registry/default/blocks/footer4/components/icons/linkedin-icon.tsx +12 -0
- package/registry/default/blocks/footer4/components/icons/x-icon.tsx +12 -0
- package/registry/default/blocks/footer4/components/logo.tsx +88 -0
- package/registry/default/blocks/footer5/app/page.tsx +9 -0
- package/registry/default/blocks/footer5/components/footer.tsx +152 -0
- package/registry/default/blocks/footer5/components/icons/apple-icon.tsx +18 -0
- package/registry/default/blocks/footer5/components/icons/facebook-icon.tsx +12 -0
- package/registry/default/blocks/footer5/components/icons/google-play-icon.tsx +10 -0
- package/registry/default/blocks/footer5/components/icons/instagram-icon.tsx +12 -0
- package/registry/default/blocks/footer5/components/icons/linkedin-icon.tsx +12 -0
- package/registry/default/blocks/footer5/components/icons/x-icon.tsx +12 -0
- package/registry/default/blocks/footer5/components/logo.tsx +88 -0
- package/registry/default/blocks/footer6/app/page.tsx +16 -0
- package/registry/default/blocks/footer6/components/footer.tsx +220 -0
- package/registry/default/blocks/footer6/components/icons/apple-icon.tsx +18 -0
- package/registry/default/blocks/footer6/components/icons/facebook-icon.tsx +12 -0
- package/registry/default/blocks/footer6/components/icons/github-icon.tsx +10 -0
- package/registry/default/blocks/footer6/components/icons/google-play-icon.tsx +10 -0
- package/registry/default/blocks/footer6/components/icons/instagram-icon.tsx +12 -0
- package/registry/default/blocks/footer6/components/icons/linkedin-icon.tsx +12 -0
- package/registry/default/blocks/footer6/components/icons/x-icon.tsx +12 -0
- package/registry/default/blocks/footer6/components/logo.tsx +88 -0
- package/registry/default/blocks/header1/app/page.tsx +12 -0
- package/registry/default/blocks/header1/components/demo-layout.tsx +33 -0
- package/registry/default/blocks/header1/components/header.tsx +58 -0
- package/registry/default/blocks/header1/components/logo.tsx +88 -0
- package/registry/default/blocks/header1/components/mobile-nav.tsx +61 -0
- package/registry/default/blocks/header1/components/ui/portal.tsx +27 -0
- package/registry/default/blocks/header1/hooks/use-scroll.ts +54 -0
- package/registry/default/blocks/header2/app/page.tsx +11 -0
- package/registry/default/blocks/header2/components/demo-layout.tsx +33 -0
- package/registry/default/blocks/header2/components/header.tsx +70 -0
- package/registry/default/blocks/header2/components/logo.tsx +88 -0
- package/registry/default/blocks/header2/components/mobile-nav.tsx +61 -0
- package/registry/default/blocks/header2/components/ui/portal.tsx +27 -0
- package/registry/default/blocks/header2/hooks/use-scroll.ts +54 -0
- package/registry/default/blocks/header3/app/page.tsx +11 -0
- package/registry/default/blocks/header3/components/demo-layout.tsx +33 -0
- package/registry/default/blocks/header3/components/desktop-nav.tsx +70 -0
- package/registry/default/blocks/header3/components/header.tsx +41 -0
- package/registry/default/blocks/header3/components/logo.tsx +88 -0
- package/registry/default/blocks/header3/components/mobile-nav.tsx +88 -0
- package/registry/default/blocks/header3/components/nav-links.tsx +132 -0
- package/registry/default/blocks/header3/components/sheard.tsx +41 -0
- package/registry/default/blocks/header3/components/ui/navigation-menu.tsx +171 -0
- package/registry/default/blocks/header3/components/ui/portal.tsx +27 -0
- package/registry/default/blocks/header3/hooks/use-scroll.ts +54 -0
- package/registry/default/blocks/hero1/app/page.tsx +15 -0
- package/registry/default/blocks/hero1/components/header.tsx +70 -0
- package/registry/default/blocks/hero1/components/hero.tsx +80 -0
- package/registry/default/blocks/hero1/components/infinite-slider.tsx +109 -0
- package/registry/default/blocks/hero1/components/logo-cloud.tsx +69 -0
- package/registry/default/blocks/hero1/components/logo.tsx +88 -0
- package/registry/default/blocks/hero1/components/logos/claude-wordmark.svg +1 -0
- package/registry/default/blocks/hero1/components/logos/clerk-wordmark.svg +1 -0
- package/registry/default/blocks/hero1/components/logos/github-wordmark.svg +6 -0
- package/registry/default/blocks/hero1/components/logos/nvidia-wordmark.svg +1 -0
- package/registry/default/blocks/hero1/components/logos/openai-wordmark.svg +1 -0
- package/registry/default/blocks/hero1/components/logos/supabase-wordmark.svg +23 -0
- package/registry/default/blocks/hero1/components/logos/turso-wordmark.svg +1 -0
- package/registry/default/blocks/hero1/components/logos/vercel-wordmark.svg +1 -0
- package/registry/default/blocks/hero1/components/logos-section.tsx +14 -0
- package/registry/default/blocks/hero1/components/mobile-nav.tsx +61 -0
- package/registry/default/blocks/hero1/components/ui/portal.tsx +27 -0
- package/registry/default/blocks/hero1/hooks/use-scroll.ts +54 -0
- package/registry/default/blocks/hero2/app/page.tsx +23 -0
- package/registry/default/blocks/hero2/components/decor-icon.tsx +45 -0
- package/registry/default/blocks/hero2/components/full-width-divider.tsx +21 -0
- package/registry/default/blocks/hero2/components/header.tsx +70 -0
- package/registry/default/blocks/hero2/components/hero.tsx +104 -0
- package/registry/default/blocks/hero2/components/infinite-slider.tsx +109 -0
- package/registry/default/blocks/hero2/components/logo-cloud.tsx +120 -0
- package/registry/default/blocks/hero2/components/logo.tsx +88 -0
- package/registry/default/blocks/hero2/components/logos/claude-wordmark.svg +1 -0
- package/registry/default/blocks/hero2/components/logos/clerk-wordmark.svg +1 -0
- package/registry/default/blocks/hero2/components/logos/github-wordmark.svg +6 -0
- package/registry/default/blocks/hero2/components/logos/nvidia-wordmark.svg +1 -0
- package/registry/default/blocks/hero2/components/logos/openai-wordmark.svg +1 -0
- package/registry/default/blocks/hero2/components/logos/supabase-wordmark.svg +23 -0
- package/registry/default/blocks/hero2/components/logos/turso-wordmark.svg +1 -0
- package/registry/default/blocks/hero2/components/logos/vercel-wordmark.svg +1 -0
- package/registry/default/blocks/hero2/components/logos-section.tsx +23 -0
- package/registry/default/blocks/hero2/components/mobile-nav.tsx +61 -0
- package/registry/default/blocks/hero2/components/ui/portal.tsx +27 -0
- package/registry/default/blocks/hero2/hooks/use-scroll.ts +54 -0
- package/registry/default/blocks/hero3/app/page.tsx +15 -0
- package/registry/default/blocks/hero3/components/header.tsx +70 -0
- package/registry/default/blocks/hero3/components/hero.tsx +104 -0
- package/registry/default/blocks/hero3/components/infinite-slider.tsx +109 -0
- package/registry/default/blocks/hero3/components/logo-cloud.tsx +67 -0
- package/registry/default/blocks/hero3/components/logo.tsx +88 -0
- package/registry/default/blocks/hero3/components/logos/claude-wordmark.svg +1 -0
- package/registry/default/blocks/hero3/components/logos/clerk-wordmark.svg +1 -0
- package/registry/default/blocks/hero3/components/logos/github-wordmark.svg +6 -0
- package/registry/default/blocks/hero3/components/logos/nvidia-wordmark.svg +1 -0
- package/registry/default/blocks/hero3/components/logos/openai-wordmark.svg +1 -0
- package/registry/default/blocks/hero3/components/logos/supabase-wordmark.svg +23 -0
- package/registry/default/blocks/hero3/components/logos/turso-wordmark.svg +1 -0
- package/registry/default/blocks/hero3/components/logos/vercel-wordmark.svg +1 -0
- package/registry/default/blocks/hero3/components/logos-section.tsx +12 -0
- package/registry/default/blocks/hero3/components/mobile-nav.tsx +61 -0
- package/registry/default/blocks/hero3/components/ui/portal.tsx +27 -0
- package/registry/default/blocks/hero3/hooks/use-scroll.ts +54 -0
- package/registry/default/blocks/image-gallery-1/app/page.tsx +5 -0
- package/registry/default/blocks/image-gallery-1/components/aspect-ratio.tsx +22 -0
- package/registry/default/blocks/image-gallery-1/components/image-gallery.tsx +135 -0
- package/registry/default/blocks/image-gallery-1/components/lazy-image.tsx +94 -0
- package/registry/default/blocks/integrations1/app/page.tsx +9 -0
- package/registry/default/blocks/integrations1/components/integrations.tsx +86 -0
- package/registry/default/blocks/integrations1/components/logos/adobe.svg +1 -0
- package/registry/default/blocks/integrations1/components/logos/canva.svg +1 -0
- package/registry/default/blocks/integrations1/components/logos/cursor.svg +1 -0
- package/registry/default/blocks/integrations1/components/logos/gmail.svg +1 -0
- package/registry/default/blocks/integrations1/components/logos/notion.svg +1 -0
- package/registry/default/blocks/integrations1/components/logos/planetscale.svg +1 -0
- package/registry/default/blocks/integrations1/components/logos/polar.svg +1 -0
- package/registry/default/blocks/integrations1/components/logos/supabase.svg +1 -0
- package/registry/default/blocks/integrations1/components/logos/vercel.svg +1 -0
- package/registry/default/blocks/integrations2/app/page.tsx +9 -0
- package/registry/default/blocks/integrations2/components/decor-icon.tsx +45 -0
- package/registry/default/blocks/integrations2/components/integrations.tsx +118 -0
- package/registry/default/blocks/integrations2/components/logos/adobe.svg +1 -0
- package/registry/default/blocks/integrations2/components/logos/canva.svg +1 -0
- package/registry/default/blocks/integrations2/components/logos/cursor.svg +1 -0
- package/registry/default/blocks/integrations2/components/logos/gmail.svg +1 -0
- package/registry/default/blocks/integrations2/components/logos/notion.svg +1 -0
- package/registry/default/blocks/integrations2/components/logos/planetscale.svg +1 -0
- package/registry/default/blocks/integrations2/components/logos/polar.svg +1 -0
- package/registry/default/blocks/integrations2/components/logos/supabase.svg +1 -0
- package/registry/default/blocks/integrations2/components/logos/vercel.svg +1 -0
- package/registry/default/blocks/integrations3/app/page.tsx +9 -0
- package/registry/default/blocks/integrations3/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/integrations3/components/integrations.tsx +190 -0
- package/registry/default/blocks/integrations3/components/logos/adobe.svg +1 -0
- package/registry/default/blocks/integrations3/components/logos/canva.svg +1 -0
- package/registry/default/blocks/integrations3/components/logos/cursor.svg +1 -0
- package/registry/default/blocks/integrations3/components/logos/gmail.svg +1 -0
- package/registry/default/blocks/integrations3/components/logos/notion.svg +1 -0
- package/registry/default/blocks/integrations3/components/logos/planetscale.svg +1 -0
- package/registry/default/blocks/integrations3/components/logos/polar.svg +1 -0
- package/registry/default/blocks/integrations3/components/logos/supabase.svg +1 -0
- package/registry/default/blocks/integrations3/components/logos/vercel.svg +1 -0
- package/registry/default/blocks/integrations4/app/page.tsx +9 -0
- package/registry/default/blocks/integrations4/components/integrations.tsx +177 -0
- package/registry/default/blocks/integrations4/components/logos/adobe.svg +1 -0
- package/registry/default/blocks/integrations4/components/logos/canva.svg +1 -0
- package/registry/default/blocks/integrations4/components/logos/cursor.svg +1 -0
- package/registry/default/blocks/integrations4/components/logos/gmail.svg +1 -0
- package/registry/default/blocks/integrations4/components/logos/notion.svg +1 -0
- package/registry/default/blocks/integrations4/components/logos/planetscale.svg +1 -0
- package/registry/default/blocks/integrations4/components/logos/polar.svg +1 -0
- package/registry/default/blocks/integrations4/components/logos/supabase.svg +1 -0
- package/registry/default/blocks/integrations4/components/logos/vercel.svg +1 -0
- package/registry/default/blocks/integrations5/app/page.tsx +9 -0
- package/registry/default/blocks/integrations5/components/integrations.tsx +112 -0
- package/registry/default/blocks/integrations5/components/logos/adobe.svg +1 -0
- package/registry/default/blocks/integrations5/components/logos/canva.svg +1 -0
- package/registry/default/blocks/integrations5/components/logos/cursor.svg +1 -0
- package/registry/default/blocks/integrations5/components/logos/gmail.svg +1 -0
- package/registry/default/blocks/integrations5/components/logos/notion.svg +1 -0
- package/registry/default/blocks/integrations5/components/logos/planetscale.svg +1 -0
- package/registry/default/blocks/integrations5/components/logos/polar.svg +1 -0
- package/registry/default/blocks/integrations5/components/logos/supabase.svg +1 -0
- package/registry/default/blocks/integrations5/components/logos/vercel.svg +1 -0
- package/registry/default/blocks/logo-cloud-1/app/page.tsx +17 -0
- package/registry/default/blocks/logo-cloud-1/components/logo-cloud.tsx +70 -0
- package/registry/default/blocks/logo-cloud-1/components/logos/claude-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-1/components/logos/clerk-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-1/components/logos/github-wordmark.svg +6 -0
- package/registry/default/blocks/logo-cloud-1/components/logos/nvidia-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-1/components/logos/openai-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-1/components/logos/supabase-wordmark.svg +23 -0
- package/registry/default/blocks/logo-cloud-1/components/logos/turso-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-1/components/logos/vercel-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-2/app/page.tsx +19 -0
- package/registry/default/blocks/logo-cloud-2/components/decor-icon.tsx +45 -0
- package/registry/default/blocks/logo-cloud-2/components/logo-cloud.tsx +120 -0
- package/registry/default/blocks/logo-cloud-2/components/logos/claude-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-2/components/logos/clerk-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-2/components/logos/github-wordmark.svg +6 -0
- package/registry/default/blocks/logo-cloud-2/components/logos/nvidia-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-2/components/logos/openai-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-2/components/logos/supabase-wordmark.svg +23 -0
- package/registry/default/blocks/logo-cloud-2/components/logos/turso-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-2/components/logos/vercel-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-3/app/page.tsx +18 -0
- package/registry/default/blocks/logo-cloud-3/components/infinite-slider.tsx +109 -0
- package/registry/default/blocks/logo-cloud-3/components/logo-cloud.tsx +69 -0
- package/registry/default/blocks/logo-cloud-3/components/logos/claude-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-3/components/logos/clerk-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-3/components/logos/github-wordmark.svg +6 -0
- package/registry/default/blocks/logo-cloud-3/components/logos/nvidia-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-3/components/logos/openai-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-3/components/logos/supabase-wordmark.svg +23 -0
- package/registry/default/blocks/logo-cloud-3/components/logos/turso-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-3/components/logos/vercel-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-4/app/page.tsx +23 -0
- package/registry/default/blocks/logo-cloud-4/components/infinite-slider.tsx +109 -0
- package/registry/default/blocks/logo-cloud-4/components/logo-cloud.tsx +83 -0
- package/registry/default/blocks/logo-cloud-4/components/logos/claude-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-4/components/logos/clerk-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-4/components/logos/github-wordmark.svg +6 -0
- package/registry/default/blocks/logo-cloud-4/components/logos/nvidia-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-4/components/logos/openai-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-4/components/logos/supabase-wordmark.svg +23 -0
- package/registry/default/blocks/logo-cloud-4/components/logos/turso-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-4/components/logos/vercel-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-4/components/progressive-blur.tsx +63 -0
- package/registry/default/blocks/logo-cloud-5/app/page.tsx +17 -0
- package/registry/default/blocks/logo-cloud-5/components/logo-cloud.tsx +67 -0
- package/registry/default/blocks/logo-cloud-5/components/logos/claude-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-5/components/logos/clerk-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-5/components/logos/github-wordmark.svg +6 -0
- package/registry/default/blocks/logo-cloud-5/components/logos/nvidia-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-5/components/logos/openai-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-5/components/logos/supabase-wordmark.svg +23 -0
- package/registry/default/blocks/logo-cloud-5/components/logos/turso-wordmark.svg +1 -0
- package/registry/default/blocks/logo-cloud-5/components/logos/vercel-wordmark.svg +1 -0
- package/registry/default/blocks/pricing1/app/page.tsx +9 -0
- package/registry/default/blocks/pricing1/components/decor-icon.tsx +45 -0
- package/registry/default/blocks/pricing1/components/pricing.tsx +94 -0
- package/registry/default/blocks/pricing2/app/page.tsx +9 -0
- package/registry/default/blocks/pricing2/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/pricing2/components/pricing.tsx +117 -0
- package/registry/default/blocks/pricing3/app/page.tsx +9 -0
- package/registry/default/blocks/pricing3/components/pricing-card.tsx +171 -0
- package/registry/default/blocks/pricing3/components/pricing.tsx +143 -0
- package/registry/default/blocks/pricing4/app/page.tsx +9 -0
- package/registry/default/blocks/pricing4/components/frequency-toggle.tsx +47 -0
- package/registry/default/blocks/pricing4/components/pricing.tsx +230 -0
- package/registry/default/blocks/testimonials1/app/page.tsx +9 -0
- package/registry/default/blocks/testimonials1/components/logo.tsx +74 -0
- package/registry/default/blocks/testimonials1/components/testimonials.tsx +41 -0
- package/registry/default/blocks/testimonials2/app/page.tsx +9 -0
- package/registry/default/blocks/testimonials2/components/testimonials.tsx +65 -0
- package/registry/default/blocks/testimonials3/app/page.tsx +9 -0
- package/registry/default/blocks/testimonials3/components/decor-icon.tsx +45 -0
- package/registry/default/blocks/testimonials3/components/testimonials.tsx +114 -0
- package/registry/default/blocks/testimonials4/app/page.tsx +9 -0
- package/registry/default/blocks/testimonials4/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/testimonials4/components/testimonials.tsx +81 -0
- package/registry/default/blocks/testimonials5/app/page.tsx +9 -0
- package/registry/default/blocks/testimonials5/components/full-width-divider.tsx +33 -0
- package/registry/default/blocks/testimonials5/components/grid-filler.tsx +74 -0
- package/registry/default/blocks/testimonials5/components/grid-pattern.tsx +70 -0
- package/registry/default/blocks/testimonials5/components/testimonials.tsx +186 -0
- package/registry/default/blocks/testimonials6/app/page.tsx +9 -0
- package/registry/default/blocks/testimonials6/components/infinite-slider.tsx +109 -0
- package/registry/default/blocks/testimonials6/components/testimonials.tsx +193 -0
- package/registry/default/examples/accordion-disabled.tsx +58 -0
- package/registry/default/examples/accordion-in-card.tsx +92 -0
- package/registry/default/examples/accordion-leading-icon.tsx +51 -0
- package/registry/default/examples/accordion-user-roles.tsx +97 -0
- package/registry/default/examples/accordion-with-icons.tsx +67 -0
- package/registry/default/examples/avatar-badge-icons.tsx +60 -0
- package/registry/default/examples/avatar-badge-position.tsx +47 -0
- package/registry/default/examples/avatar-empty-collaborators.tsx +55 -0
- package/registry/default/examples/avatar-group-trust.tsx +49 -0
- package/registry/default/examples/avatar-loading.tsx +33 -0
- package/registry/default/examples/avatar-menu.tsx +81 -0
- package/registry/default/examples/avatar-profile-badge.tsx +30 -0
- package/registry/default/examples/avatar-status.tsx +47 -0
- package/registry/default/examples/breadcrumb-bullet-separator.tsx +38 -0
- package/registry/default/examples/breadcrumb-buttons.tsx +61 -0
- package/registry/default/examples/breadcrumb-card.tsx +40 -0
- package/registry/default/examples/button-default-icons.tsx +18 -0
- package/registry/default/examples/button-default.tsx +5 -0
- package/registry/default/examples/button-demo.tsx +11 -1
- package/registry/default/examples/button-destructive-icons.tsx +18 -0
- package/registry/default/examples/button-destructive-outline-icons.tsx +18 -0
- package/registry/default/examples/button-ghost-icons.tsx +18 -0
- package/registry/default/examples/button-link-icons.tsx +18 -0
- package/registry/default/examples/button-outline-icons.tsx +18 -0
- package/registry/default/examples/button-secondary-icons.tsx +18 -0
- package/registry/default/examples/button-theme-toggle.tsx +38 -0
- package/registry/default/examples/card-author-profile.tsx +66 -0
- package/registry/default/examples/card-default-size.tsx +33 -0
- package/registry/default/examples/card-depth.tsx +37 -0
- package/registry/default/examples/card-help-link.tsx +32 -0
- package/registry/default/examples/card-help-menu.tsx +78 -0
- package/registry/default/examples/card-image-centered.tsx +38 -0
- package/registry/default/examples/card-image-feature.tsx +44 -0
- package/registry/default/examples/card-metric-actions.tsx +102 -0
- package/registry/default/examples/card-resource-link.tsx +40 -0
- package/registry/default/examples/card-team-member.tsx +38 -14
- package/registry/default/examples/card-usage-expandable.tsx +98 -0
- package/registry/default/examples/card-with-borders.tsx +29 -0
- package/registry/default/ui/accordion.tsx +82 -2
- package/registry/default/ui/avatar.tsx +45 -1
- package/registry/default/ui/input-group.tsx +158 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
|
|
3
|
+
type GridFillerProps = React.ComponentProps<"div"> & {
|
|
4
|
+
/**
|
|
5
|
+
* The number of items in the grid.
|
|
6
|
+
*/
|
|
7
|
+
totalItems: number;
|
|
8
|
+
/**
|
|
9
|
+
* Number of columns for small screens.
|
|
10
|
+
*/
|
|
11
|
+
smColumns?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Number of columns for medium screens.
|
|
14
|
+
*/
|
|
15
|
+
mdColumns?: number;
|
|
16
|
+
/**
|
|
17
|
+
* Number of columns for large screens.
|
|
18
|
+
*/
|
|
19
|
+
lgColumns?: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function GridFiller({
|
|
23
|
+
totalItems,
|
|
24
|
+
className,
|
|
25
|
+
smColumns = 2,
|
|
26
|
+
mdColumns,
|
|
27
|
+
lgColumns,
|
|
28
|
+
...props
|
|
29
|
+
}: GridFillerProps) {
|
|
30
|
+
const actualMdColumns = mdColumns ?? smColumns;
|
|
31
|
+
const actualLgColumns = lgColumns ?? actualMdColumns;
|
|
32
|
+
|
|
33
|
+
// We need enough iterations to cover the maximum possible remainder.
|
|
34
|
+
// For N columns, the remainder can be at most N-1.
|
|
35
|
+
const maxFillers = Math.max(smColumns, actualMdColumns, actualLgColumns) - 1;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
{Array.from({ length: maxFillers }).map((_, i) => {
|
|
40
|
+
const neededSm = (smColumns - (totalItems % smColumns)) % smColumns;
|
|
41
|
+
const neededMd =
|
|
42
|
+
(actualMdColumns - (totalItems % actualMdColumns)) % actualMdColumns;
|
|
43
|
+
const neededLg =
|
|
44
|
+
(actualLgColumns - (totalItems % actualLgColumns)) % actualLgColumns;
|
|
45
|
+
|
|
46
|
+
const showSm = i < neededSm ? "sm:block" : "sm:hidden";
|
|
47
|
+
const showMd = i < neededMd ? "md:block" : "md:hidden";
|
|
48
|
+
const showLg = i < neededLg ? "lg:block" : "lg:hidden";
|
|
49
|
+
|
|
50
|
+
if (
|
|
51
|
+
showSm === "sm:hidden" &&
|
|
52
|
+
showMd === "md:hidden" &&
|
|
53
|
+
showLg === "lg:hidden"
|
|
54
|
+
) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
className={cn(
|
|
61
|
+
"pointer-events-none hidden",
|
|
62
|
+
showSm,
|
|
63
|
+
showMd,
|
|
64
|
+
showLg,
|
|
65
|
+
className
|
|
66
|
+
)}
|
|
67
|
+
key={`filler-${i}`}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
})}
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
|
|
3
|
+
function AspectRatio({
|
|
4
|
+
ratio,
|
|
5
|
+
className,
|
|
6
|
+
...props
|
|
7
|
+
}: React.ComponentProps<"div"> & { ratio: number }) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="aspect-ratio"
|
|
11
|
+
style={
|
|
12
|
+
{
|
|
13
|
+
"--ratio": ratio,
|
|
14
|
+
} as React.CSSProperties
|
|
15
|
+
}
|
|
16
|
+
className={cn("relative aspect-(--ratio)", className)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { AspectRatio }
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import { FullWidthDivider } from "./full-width-divider";
|
|
3
|
+
import { LazyImage } from "./lazy-image";
|
|
4
|
+
|
|
5
|
+
type BlogType = {
|
|
6
|
+
title: string;
|
|
7
|
+
href: string;
|
|
8
|
+
description: string;
|
|
9
|
+
author: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
readTime: string;
|
|
12
|
+
image: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const wikimediaImage = (fileName: string, width = 900) =>
|
|
16
|
+
`https://commons.wikimedia.org/wiki/Special:Redirect/file/${encodeURIComponent(
|
|
17
|
+
fileName
|
|
18
|
+
)}?width=${width}`;
|
|
19
|
+
|
|
20
|
+
const blogs: BlogType[] = [
|
|
21
|
+
{
|
|
22
|
+
title: "Why LoveUI Ships Components as Source",
|
|
23
|
+
href: "#",
|
|
24
|
+
description:
|
|
25
|
+
"How copied source gives product teams the confidence to inspect, change, and own their interface layer.",
|
|
26
|
+
image: wikimediaImage(
|
|
27
|
+
"University Hall, The Ohio State University (Columbus, Ohio).jpg"
|
|
28
|
+
),
|
|
29
|
+
createdAt: "2026-05-20",
|
|
30
|
+
author: "Connor Love",
|
|
31
|
+
readTime: "7 min read",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
title: "Designing Blocks That Feel Ready",
|
|
35
|
+
href: "#",
|
|
36
|
+
description:
|
|
37
|
+
"The decisions behind LoveUI sections that look polished on day one and stay easy to reshape on day two.",
|
|
38
|
+
image: wikimediaImage("View of Ohio Stadium.jpg"),
|
|
39
|
+
createdAt: "2026-05-14",
|
|
40
|
+
author: "Connor Love",
|
|
41
|
+
readTime: "5 min read",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
title: "Building Better Interfaces with AI Agents",
|
|
45
|
+
href: "#",
|
|
46
|
+
description:
|
|
47
|
+
"Why LoveUI includes agent skills with concrete design rules, implementation steps, and quality checks.",
|
|
48
|
+
image: wikimediaImage(
|
|
49
|
+
"Mirror Lake at The Ohio State University - DPLA - f1276865f9c52083353de2c6b0068290.jpg"
|
|
50
|
+
),
|
|
51
|
+
createdAt: "2026-04-29",
|
|
52
|
+
author: "Connor Love",
|
|
53
|
+
readTime: "6 min read",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
title: "Composing Product Pages from Primitives",
|
|
57
|
+
href: "#",
|
|
58
|
+
description:
|
|
59
|
+
"How buttons, accordions, empty states, and layout helpers become complete LoveUI product sections.",
|
|
60
|
+
image: wikimediaImage(
|
|
61
|
+
"Thompson Library at The Ohio State University - DPLA - d4a313f4ce0cca7538bf0ea6508722ae.jpg"
|
|
62
|
+
),
|
|
63
|
+
createdAt: "2026-04-12",
|
|
64
|
+
author: "Connor Love",
|
|
65
|
+
readTime: "8 min read",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
title: "Keeping Design Systems Close to the App",
|
|
69
|
+
href: "#",
|
|
70
|
+
description:
|
|
71
|
+
"LoveUI keeps components in your repo so teams can review, version, and adapt them with the rest of the product.",
|
|
72
|
+
image: wikimediaImage("Ohio State University-Ohio Stadium-Rotunda.jpg"),
|
|
73
|
+
createdAt: "2026-03-23",
|
|
74
|
+
author: "Connor Love",
|
|
75
|
+
readTime: "4 min read",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
title: "What Makes a Registry Example Useful",
|
|
79
|
+
href: "#",
|
|
80
|
+
description:
|
|
81
|
+
"Examples should teach real composition patterns, not just show isolated visuals. That idea shapes LoveUI docs.",
|
|
82
|
+
image: wikimediaImage(
|
|
83
|
+
"The Spring and Mirror Lake at Ohio State University - DPLA - 238a754a6579c3fd1788745a85caaf4c (page 1).jpg"
|
|
84
|
+
),
|
|
85
|
+
createdAt: "2026-03-05",
|
|
86
|
+
author: "Connor Love",
|
|
87
|
+
readTime: "9 min read",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
title: "From Component to Full Section",
|
|
91
|
+
href: "#",
|
|
92
|
+
description:
|
|
93
|
+
"The practical path from reusable primitives to CTA, FAQ, pricing, testimonial, and 404 blocks.",
|
|
94
|
+
image: wikimediaImage(
|
|
95
|
+
"Ohio Stadium at The Ohio State University - DPLA - fa20937cd54b73c1c34c09ac5d4d3e6d.jpg"
|
|
96
|
+
),
|
|
97
|
+
createdAt: "2026-02-18",
|
|
98
|
+
author: "Connor Love",
|
|
99
|
+
readTime: "10 min read",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
title: "The Case for Owning Your UI",
|
|
103
|
+
href: "#",
|
|
104
|
+
description:
|
|
105
|
+
"When product details matter, editable source makes styling, behavior, and structure easier to reason about.",
|
|
106
|
+
image: wikimediaImage(
|
|
107
|
+
"Ohio State University - Thompson Library from First Floor.jpg"
|
|
108
|
+
),
|
|
109
|
+
createdAt: "2026-02-02",
|
|
110
|
+
author: "Connor Love",
|
|
111
|
+
readTime: "6 min read",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
title: "Why Blocks Need Real Constraints",
|
|
115
|
+
href: "#",
|
|
116
|
+
description:
|
|
117
|
+
"LoveUI blocks are built around realistic copy, responsive behavior, and controls that work in product contexts.",
|
|
118
|
+
image: wikimediaImage("OSU Ohio Stadium.JPG"),
|
|
119
|
+
createdAt: "2026-01-22",
|
|
120
|
+
author: "Connor Love",
|
|
121
|
+
readTime: "7 min read",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
title: "Making Components Easier to Customize",
|
|
125
|
+
href: "#",
|
|
126
|
+
description:
|
|
127
|
+
"A look at naming, file structure, and examples that help LoveUI components stay understandable after install.",
|
|
128
|
+
image: wikimediaImage("Ohio Stadium (37516165921).jpg", 1200),
|
|
129
|
+
createdAt: "2026-01-09",
|
|
130
|
+
author: "Connor Love",
|
|
131
|
+
readTime: "5 min read",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
title: "The LoveUI Approach to Product Polish",
|
|
135
|
+
href: "#",
|
|
136
|
+
description:
|
|
137
|
+
"Polish comes from spacing, states, hierarchy, and composition. LoveUI tries to make those choices reusable.",
|
|
138
|
+
image: wikimediaImage(
|
|
139
|
+
"Ohio State University campus - DPLA - 74170bd1ea1086f74720703e80e0dc4c.jpg"
|
|
140
|
+
),
|
|
141
|
+
createdAt: "2025-12-18",
|
|
142
|
+
author: "Connor Love",
|
|
143
|
+
readTime: "8 min read",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
title: "A Source-First UI Roadmap",
|
|
147
|
+
href: "#",
|
|
148
|
+
description:
|
|
149
|
+
"What is next for LoveUI: more blocks, stronger examples, and better guidance for teams building with AI.",
|
|
150
|
+
image: wikimediaImage(
|
|
151
|
+
"Ohio State University campus aerial view looking west - DPLA - cb1a04591333ede0f6ac506328c1c460.jpg"
|
|
152
|
+
),
|
|
153
|
+
createdAt: "2025-12-02",
|
|
154
|
+
author: "Connor Love",
|
|
155
|
+
readTime: "6 min read",
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
export function BlogsSection() {
|
|
160
|
+
return (
|
|
161
|
+
<div className="mx-auto w-full max-w-5xl grow">
|
|
162
|
+
<div className="space-y-1 px-4 py-8 md:px-6">
|
|
163
|
+
<h1 className="font-semibold text-2xl tracking-wide md:text-4xl">
|
|
164
|
+
LoveUI Field Notes
|
|
165
|
+
</h1>
|
|
166
|
+
<p className="text-muted-foreground text-sm md:text-base">
|
|
167
|
+
Writing from Connor Love on source-first UI, practical blocks, and
|
|
168
|
+
building better product surfaces.
|
|
169
|
+
</p>
|
|
170
|
+
</div>
|
|
171
|
+
<FullWidthDivider contained={true} />
|
|
172
|
+
<div className="z-10 grid p-4 md:grid-cols-2 lg:grid-cols-3">
|
|
173
|
+
{blogs.map((blog) => (
|
|
174
|
+
<BlogCard {...blog} key={blog.title} />
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function BlogCard({
|
|
182
|
+
title,
|
|
183
|
+
description,
|
|
184
|
+
createdAt,
|
|
185
|
+
readTime,
|
|
186
|
+
image,
|
|
187
|
+
author,
|
|
188
|
+
className,
|
|
189
|
+
...props
|
|
190
|
+
}: React.ComponentProps<"a"> & BlogType) {
|
|
191
|
+
return (
|
|
192
|
+
<a
|
|
193
|
+
className={cn(
|
|
194
|
+
"group cn-rounded flex flex-col gap-2 p-3 hover:bg-muted/50 active:bg-muted",
|
|
195
|
+
className
|
|
196
|
+
)}
|
|
197
|
+
key={title}
|
|
198
|
+
{...props}
|
|
199
|
+
>
|
|
200
|
+
<LazyImage
|
|
201
|
+
alt={title}
|
|
202
|
+
className="transition-all duration-500 group-hover:scale-105"
|
|
203
|
+
containerClassName="cn-rounded shadow-md outline outline-offset-3 outline-border/50"
|
|
204
|
+
fallback="https://placehold.co/640x360?text=fallback-image"
|
|
205
|
+
inView={true}
|
|
206
|
+
ratio={16 / 9}
|
|
207
|
+
src={image}
|
|
208
|
+
/>
|
|
209
|
+
<div className="space-y-2 px-2 pb-2">
|
|
210
|
+
<div className="flex items-center gap-2 text-[11px] text-muted-foreground group-hover:text-foreground sm:text-xs">
|
|
211
|
+
<p>by {author}</p>
|
|
212
|
+
<div className="size-1 rounded-full bg-muted-foreground" />
|
|
213
|
+
<p>{createdAt}</p>
|
|
214
|
+
<div className="size-1 rounded-full bg-muted-foreground" />
|
|
215
|
+
<p>{readTime}</p>
|
|
216
|
+
</div>
|
|
217
|
+
<h2 className="line-clamp-2 font-semibold text-lg">{title}</h2>
|
|
218
|
+
<p className="line-clamp-3 text-muted-foreground text-sm group-active:text-foreground">
|
|
219
|
+
{description}
|
|
220
|
+
</p>
|
|
221
|
+
</div>
|
|
222
|
+
</a>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
|
|
3
|
+
type FullWidthDividerProps = React.ComponentProps<"div"> & {
|
|
4
|
+
contained?: boolean;
|
|
5
|
+
position?: "top" | "bottom";
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function FullWidthDivider({
|
|
9
|
+
className,
|
|
10
|
+
contained = false,
|
|
11
|
+
position,
|
|
12
|
+
...props
|
|
13
|
+
}: FullWidthDividerProps) {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
className={cn(
|
|
18
|
+
"pointer-events-none absolute h-px bg-border",
|
|
19
|
+
// full-bleed (default)
|
|
20
|
+
"data-[contained=false]:left-1/2 data-[contained=false]:w-screen data-[contained=false]:-translate-x-1/2",
|
|
21
|
+
// contained
|
|
22
|
+
"data-[contained=true]:inset-x-0 data-[contained=true]:w-full",
|
|
23
|
+
// position
|
|
24
|
+
position &&
|
|
25
|
+
"data-[position=top]:-top-px data-[position=bottom]:-bottom-px",
|
|
26
|
+
className
|
|
27
|
+
)}
|
|
28
|
+
data-contained={contained}
|
|
29
|
+
data-position={position}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { useInView } from "motion/react";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { AspectRatio } from "./aspect-ratio";
|
|
7
|
+
|
|
8
|
+
type LazyImageProps = {
|
|
9
|
+
alt: string;
|
|
10
|
+
src: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
containerClassName?: string;
|
|
13
|
+
/** URL of the fallback image. default: undefined */
|
|
14
|
+
fallback?: string;
|
|
15
|
+
/** The ratio of the image. */
|
|
16
|
+
ratio: number;
|
|
17
|
+
/** Whether the image should only load when it is in view. default: false */
|
|
18
|
+
inView?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function LazyImage({
|
|
22
|
+
alt,
|
|
23
|
+
src,
|
|
24
|
+
ratio,
|
|
25
|
+
fallback,
|
|
26
|
+
inView = false,
|
|
27
|
+
className,
|
|
28
|
+
containerClassName,
|
|
29
|
+
}: LazyImageProps) {
|
|
30
|
+
const ref = React.useRef<HTMLDivElement | null>(null);
|
|
31
|
+
const imgRef = React.useRef<HTMLImageElement | null>(null);
|
|
32
|
+
const isInView = useInView(ref, { once: true });
|
|
33
|
+
|
|
34
|
+
const [imgSrc, setImgSrc] = React.useState<string | undefined>(
|
|
35
|
+
inView ? undefined : src
|
|
36
|
+
);
|
|
37
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
38
|
+
|
|
39
|
+
const handleError = () => {
|
|
40
|
+
if (fallback) {
|
|
41
|
+
setImgSrc(fallback);
|
|
42
|
+
}
|
|
43
|
+
setIsLoading(false);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleLoad = React.useCallback(() => {
|
|
47
|
+
setIsLoading(false);
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
// Load image only when inView
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
if (inView && isInView && !imgSrc) {
|
|
53
|
+
setImgSrc(src);
|
|
54
|
+
}
|
|
55
|
+
}, [inView, isInView, src, imgSrc]);
|
|
56
|
+
|
|
57
|
+
// Handle cached images instantly
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
if (imgRef.current?.complete) {
|
|
60
|
+
handleLoad();
|
|
61
|
+
}
|
|
62
|
+
}, [handleLoad]);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<AspectRatio
|
|
66
|
+
className={cn(
|
|
67
|
+
"relative size-full overflow-hidden border bg-accent/30",
|
|
68
|
+
containerClassName
|
|
69
|
+
)}
|
|
70
|
+
ratio={ratio}
|
|
71
|
+
ref={ref}
|
|
72
|
+
>
|
|
73
|
+
{imgSrc && (
|
|
74
|
+
// biome-ignore lint/correctness/useImageSize: dynamic image size
|
|
75
|
+
<img
|
|
76
|
+
alt={alt}
|
|
77
|
+
className={cn(
|
|
78
|
+
"size-full object-cover transition-opacity duration-500",
|
|
79
|
+
isLoading ? "opacity-0" : "opacity-100",
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
decoding="async"
|
|
83
|
+
fetchPriority={inView ? "high" : "low"}
|
|
84
|
+
loading="lazy"
|
|
85
|
+
onError={handleError}
|
|
86
|
+
onLoad={handleLoad}
|
|
87
|
+
ref={imgRef}
|
|
88
|
+
role="presentation" // Changed from "img" to "presentation" since it's decorative
|
|
89
|
+
src={imgSrc}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
</AspectRatio>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import { FullWidthDivider } from "./full-width-divider";
|
|
3
|
+
import { Phone, Mail, MapPin } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
const data = [
|
|
6
|
+
{
|
|
7
|
+
title: "Ask about LoveUI",
|
|
8
|
+
value: "+1 (555) 014-2026",
|
|
9
|
+
icon: (
|
|
10
|
+
<Phone
|
|
11
|
+
/>
|
|
12
|
+
),
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
title: "Email the team",
|
|
16
|
+
value: "hello@loveui.dev",
|
|
17
|
+
icon: (
|
|
18
|
+
<Mail
|
|
19
|
+
/>
|
|
20
|
+
),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
title: "Product studio",
|
|
24
|
+
value: "Built by Connor Love",
|
|
25
|
+
icon: (
|
|
26
|
+
<MapPin
|
|
27
|
+
/>
|
|
28
|
+
),
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
export function Contact() {
|
|
33
|
+
return (
|
|
34
|
+
<div className="mx-auto max-w-4xl">
|
|
35
|
+
<h2 className="mb-6 font-medium text-lg md:text-2xl">
|
|
36
|
+
Need help building with LoveUI?
|
|
37
|
+
</h2>
|
|
38
|
+
<div className="relative">
|
|
39
|
+
<FullWidthDivider position="top" />
|
|
40
|
+
<div className="grid gap-px overflow-hidden bg-border px-px md:grid-cols-3">
|
|
41
|
+
{data.map((item) => (
|
|
42
|
+
<div
|
|
43
|
+
className="flex items-center gap-3 bg-background p-2 shadow-xs"
|
|
44
|
+
key={item.title}
|
|
45
|
+
>
|
|
46
|
+
<div
|
|
47
|
+
className={cn(
|
|
48
|
+
"flex size-12 shrink-0 items-center justify-center rounded-lg bg-muted/50",
|
|
49
|
+
"[&_svg]:size-4 [&_svg]:text-muted-foreground"
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
{item.icon}
|
|
53
|
+
</div>
|
|
54
|
+
<div className={cn("flex flex-col gap-y-0.5")}>
|
|
55
|
+
<h2 className="text-sm">{item.title}</h2>
|
|
56
|
+
<p className="text-muted-foreground text-xs">{item.value}</p>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
))}
|
|
60
|
+
</div>
|
|
61
|
+
<FullWidthDivider position="bottom" />
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
|
|
3
|
+
type FullWidthDividerProps = React.ComponentProps<"div"> & {
|
|
4
|
+
contained?: boolean;
|
|
5
|
+
position?: "top" | "bottom";
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function FullWidthDivider({
|
|
9
|
+
className,
|
|
10
|
+
contained = false,
|
|
11
|
+
position,
|
|
12
|
+
...props
|
|
13
|
+
}: FullWidthDividerProps) {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
className={cn(
|
|
18
|
+
"pointer-events-none absolute h-px bg-border",
|
|
19
|
+
// full-bleed (default)
|
|
20
|
+
"data-[contained=false]:left-1/2 data-[contained=false]:w-screen data-[contained=false]:-translate-x-1/2",
|
|
21
|
+
// contained
|
|
22
|
+
"data-[contained=true]:inset-x-0 data-[contained=true]:w-full",
|
|
23
|
+
// position
|
|
24
|
+
position &&
|
|
25
|
+
"data-[position=top]:-top-px data-[position=bottom]:-bottom-px",
|
|
26
|
+
className
|
|
27
|
+
)}
|
|
28
|
+
data-contained={contained}
|
|
29
|
+
data-position={position}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import { XIcon } from "./icons/x-icon";
|
|
3
|
+
import { Button } from "@/registry/default/ui/button";
|
|
4
|
+
import { Mail, Users } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
const APP_EMAIL = "hello@loveui.dev";
|
|
7
|
+
|
|
8
|
+
const data = [
|
|
9
|
+
{
|
|
10
|
+
title: "Email LoveUI",
|
|
11
|
+
description: "Ask about components, blocks, registry setup, or implementation details.",
|
|
12
|
+
icon: (
|
|
13
|
+
<Mail
|
|
14
|
+
/>
|
|
15
|
+
),
|
|
16
|
+
href: `mailto:${APP_EMAIL}`,
|
|
17
|
+
label: APP_EMAIL,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
title: "Send a DM",
|
|
21
|
+
description: "Reach out on X with LoveUI questions, ideas, or bug reports.",
|
|
22
|
+
icon: <XIcon />,
|
|
23
|
+
href: "#",
|
|
24
|
+
label: "@connorlove",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
title: "Join the community",
|
|
28
|
+
description: "Connect with builders using LoveUI in real product interfaces.",
|
|
29
|
+
icon: (
|
|
30
|
+
<Users
|
|
31
|
+
/>
|
|
32
|
+
),
|
|
33
|
+
href: "#",
|
|
34
|
+
label: "Join Discord",
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export function Contact() {
|
|
39
|
+
return (
|
|
40
|
+
<div className="mx-auto max-w-4xl">
|
|
41
|
+
<div className="mb-12 flex max-w-md flex-col justify-center gap-2">
|
|
42
|
+
<h1 className="font-bold text-2xl md:text-3xl">Contact Us</h1>
|
|
43
|
+
<p className="text-base text-muted-foreground">
|
|
44
|
+
Get help with LoveUI components, blocks, registry installs, and
|
|
45
|
+
source-first UI workflows.
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="grid gap-0.5 overflow-hidden rounded-lg bg-muted p-0.5 md:grid-cols-3 dark:bg-muted/50">
|
|
49
|
+
{data.map((item) => (
|
|
50
|
+
<div
|
|
51
|
+
className="flex flex-col gap-3 rounded-lg bg-background px-6 py-6 shadow-xs"
|
|
52
|
+
key={item.title}
|
|
53
|
+
>
|
|
54
|
+
<div
|
|
55
|
+
className={cn(
|
|
56
|
+
"flex items-center gap-x-2",
|
|
57
|
+
"[&_svg]:size-4 [&_svg]:text-muted-foreground"
|
|
58
|
+
)}
|
|
59
|
+
>
|
|
60
|
+
{item.icon}
|
|
61
|
+
<h2 className="text-sm">{item.title}</h2>
|
|
62
|
+
</div>
|
|
63
|
+
<p className="text-muted-foreground text-sm">{item.description}</p>
|
|
64
|
+
<div className="mt-1 flex items-center gap-x-2">
|
|
65
|
+
<Button asChild variant="link">
|
|
66
|
+
<a href={item.href}>{item.label}</a>
|
|
67
|
+
</Button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
|
|
3
|
+
type FullWidthDividerProps = React.ComponentProps<"div"> & {
|
|
4
|
+
contained?: boolean;
|
|
5
|
+
position?: "top" | "bottom";
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function FullWidthDivider({
|
|
9
|
+
className,
|
|
10
|
+
contained = false,
|
|
11
|
+
position,
|
|
12
|
+
...props
|
|
13
|
+
}: FullWidthDividerProps) {
|
|
14
|
+
return (
|
|
15
|
+
<div
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
className={cn(
|
|
18
|
+
"pointer-events-none absolute h-px bg-border",
|
|
19
|
+
// full-bleed (default)
|
|
20
|
+
"data-[contained=false]:left-1/2 data-[contained=false]:w-screen data-[contained=false]:-translate-x-1/2",
|
|
21
|
+
// contained
|
|
22
|
+
"data-[contained=true]:inset-x-0 data-[contained=true]:w-full",
|
|
23
|
+
// position
|
|
24
|
+
position &&
|
|
25
|
+
"data-[position=top]:-top-px data-[position=bottom]:-bottom-px",
|
|
26
|
+
className
|
|
27
|
+
)}
|
|
28
|
+
data-contained={contained}
|
|
29
|
+
data-position={position}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function XIcon(props: React.ComponentProps<"svg">) {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
fill="currentColor"
|
|
5
|
+
viewBox="0 0 24 24"
|
|
6
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
7
|
+
{...props}
|
|
8
|
+
>
|
|
9
|
+
<path d="m18.9,1.153h3.682l-8.042,9.189,9.46,12.506h-7.405l-5.804-7.583-6.634,7.583H.469l8.6-9.831L0,1.153h7.593l5.241,6.931,6.065-6.931Zm-1.293,19.494h2.039L6.482,3.239h-2.19l13.314,17.408Z" />
|
|
10
|
+
</svg>
|
|
11
|
+
);
|
|
12
|
+
}
|