51 Commits

Author SHA1 Message Date
1ba2908279 Merge version_19_1781563648361 into main
Merge version_19_1781563648361 into main
2026-06-15 22:47:46 +00:00
040a7fd535 Update src/pages/HomePage/sections/Hero.tsx 2026-06-15 22:47:42 +00:00
15c6856558 Merge version_18_1781563623180 into main
Merge version_18_1781563623180 into main
2026-06-15 22:47:10 +00:00
9fb1e00c11 Update theme colors 2026-06-15 22:47:03 +00:00
e32e760e99 Merge version_17_1781563616874 into main
Merge version_17_1781563616874 into main
2026-06-15 22:47:00 +00:00
8b201cbe6c Update theme colors 2026-06-15 22:46:57 +00:00
283adff54f Merge version_16_1781519540042 into main
Merge version_16_1781519540042 into main
2026-06-15 10:53:42 +00:00
c49e96a7c3 Add analytics tracking script 2026-06-15 10:53:39 +00:00
8a3d08ed7f Merge version_16_1781519540042 into main
Merge version_16_1781519540042 into main
2026-06-15 10:34:36 +00:00
kudinDmitriyUp
4bd9d5f39b Bob AI: Populate src/pages/BlogPage.tsx (snippet builder, 2 sections) 2026-06-15 10:33:53 +00:00
kudinDmitriyUp
c4d2a0b52d Bob AI: Add blog page 2026-06-15 10:33:03 +00:00
21f2c691bb Merge version_15_1781419601769 into main
Merge version_15_1781419601769 into main
2026-06-15 10:29:46 +00:00
d4ad543155 Update src/components/Layout.tsx 2026-06-15 10:29:36 +00:00
061ef09376 Merge version_15_1781419601769 into main
Merge version_15_1781419601769 into main
2026-06-15 10:29:30 +00:00
558ac5ef57 Update src/App.tsx 2026-06-15 10:29:21 +00:00
8c930d8c2c Merge version_15_1781419601769 into main
Merge version_15_1781419601769 into main
2026-06-14 21:14:25 +00:00
51817f8b8a Add public/favicon.png 2026-06-14 21:14:13 +00:00
900e502413 Update index.html 2026-06-14 21:14:13 +00:00
0c0da7d82f Merge version_15_1781419601769 into main
Merge version_15_1781419601769 into main
2026-06-14 06:50:00 +00:00
kudinDmitriyUp
54ebac3115 Bob AI: Added a fourth carousel of testimonial cards to the testimon 2026-06-14 06:49:18 +00:00
354cdc7a53 Merge version_14_1781418721165 into main
Merge version_14_1781418721165 into main
2026-06-14 06:32:18 +00:00
9d36ba87f6 Update src/pages/HomePage/sections/Hero.tsx 2026-06-14 06:32:11 +00:00
79bc470c0f Merge version_13_1781412111677 into main
Merge version_13_1781412111677 into main
2026-06-14 04:44:21 +00:00
kudinDmitriyUp
006a6229a0 Bob AI: Revert accidental button addition on HomePage Projects secti 2026-06-14 04:43:25 +00:00
e851769a06 Merge version_12_1781411627540 into main
Merge version_12_1781411627540 into main
2026-06-14 04:40:52 +00:00
kudinDmitriyUp
c1e727a163 Bob AI: Add View Project buttons to portfolio cards 2026-06-14 04:40:05 +00:00
kudinDmitriyUp
1bc8f87a62 Bob AI: Remove buttons from home page projects section 2026-06-14 04:37:54 +00:00
9c5793758d Merge version_11_1781411395189 into main
Merge version_11_1781411395189 into main
2026-06-14 04:31:52 +00:00
kudinDmitriyUp
3e1a113d0d Bob AI: add buttons to portfolio cards and link penthouse project 2026-06-14 04:31:11 +00:00
43afe69979 Merge version_10_1781411138455 into main
Merge version_10_1781411138455 into main
2026-06-14 04:27:48 +00:00
kudinDmitriyUp
d1e35626bd Bob AI: Populate src/pages/BarrioDeSalamancaPenthousePage.tsx (snippet builder, 3 sections) 2026-06-14 04:27:13 +00:00
kudinDmitriyUp
c6e6f46f09 Bob AI: Add barrio-de-salamanca-penthouse page 2026-06-14 04:26:32 +00:00
448a58d8e7 Merge version_9_1781411001435 into main
Merge version_9_1781411001435 into main
2026-06-14 04:24:34 +00:00
kudinDmitriyUp
ae98bd05d7 Bob AI: Connect projects page to navigation bar 2026-06-14 04:23:54 +00:00
fc1ec4482f Merge version_8_1781410851390 into main
Merge version_8_1781410851390 into main
2026-06-14 04:22:47 +00:00
kudinDmitriyUp
cc81d23459 Bob AI: Populate src/pages/ProjectsPage.tsx (snippet builder, 2 sections) 2026-06-14 04:22:12 +00:00
kudinDmitriyUp
901829923e Bob AI: Add projects page 2026-06-14 04:21:34 +00:00
d7f653d26a Merge version_7_1781410560553 into main
Merge version_7_1781410560553 into main
2026-06-14 04:19:02 +00:00
kudinDmitriyUp
19fe53bc27 Bob AI: Added a third carousel of testimonial cards to the testimoni 2026-06-14 04:18:27 +00:00
242a1dab6f Merge version_6_1781410145622 into main
Merge version_6_1781410145622 into main
2026-06-14 04:12:48 +00:00
kudinDmitriyUp
18776dd778 Bob AI: Fix draggable interaction on testimonial carousels 2026-06-14 04:12:09 +00:00
bc1bd652b0 Merge version_5_1781409452974 into main
Merge version_5_1781409452974 into main
2026-06-14 04:04:43 +00:00
kudinDmitriyUp
1ff31b5d08 Bob AI: add an interaction layer to the testimonial carousel where i 2026-06-14 04:04:08 +00:00
1732a985b3 Merge version_4_1781409297042 into main
Merge version_4_1781409297042 into main
2026-06-14 03:56:25 +00:00
kudinDmitriyUp
91dde837af Bob AI: Add contact button to testimonial cards 2026-06-14 03:55:47 +00:00
0efd3fbeaf Merge version_3_1781409178638 into main
Merge version_3_1781409178638 into main
2026-06-14 03:54:25 +00:00
kudinDmitriyUp
f4bc14b49f Bob AI: Added verified badge to testimonial cards 2026-06-14 03:53:45 +00:00
5bd5d5f15f Merge version_2_1781409046753 into main
Merge version_2_1781409046753 into main
2026-06-14 03:52:13 +00:00
kudinDmitriyUp
994d9bd996 Bob AI: Replaced testimonial section with TestimonialMarqueeCards 2026-06-14 03:51:33 +00:00
713464fc94 Merge version_1_1781408852611 into main
Merge version_1_1781408852611 into main
2026-06-14 03:49:17 +00:00
4cf5ea50c6 Merge version_1_1781408852611 into main
Merge version_1_1781408852611 into main
2026-06-14 03:48:37 +00:00
18 changed files with 871 additions and 286 deletions

View File

@@ -5,7 +5,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&family=Inter+Tight:ital,wght@0,100..900;1,100..900&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap" rel="stylesheet">
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Studio Madrid | Luxury Interior Design</title>
<meta property="og:title" content="Studio Madrid | Luxury Interior Design" />
@@ -13,9 +13,13 @@
<meta name="description" content="Studio Madrid specializes in high-end interior design for luxury residences, flats, and villas throughout Madrid. Transform your home today." />
<meta property="og:description" content="Studio Madrid specializes in high-end interior design for luxury residences, flats, and villas throughout Madrid. Transform your home today." />
<meta name="twitter:description" content="Studio Madrid specializes in high-end interior design for luxury residences, flats, and villas throughout Madrid. Transform your home today." />
<meta property="og:image" content="https://storage.googleapis.com/webild/users/user_3AFDRpptPx4buwNV34xBpWy9jNG/uploaded-1781471648940-wopf0ep1.png" />
<meta name="twitter:image" content="https://storage.googleapis.com/webild/users/user_3AFDRpptPx4buwNV34xBpWy9jNG/uploaded-1781471648940-wopf0ep1.png" />
<link rel="icon" type="image/png" href="/favicon.png" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script defer src="https://analytics.webild.io/wb" data-website-id="118223fc-0bc0-49da-a4b4-fc8388d2274c" data-webild-analytics></script>
</body>
</html>

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

View File

@@ -2,11 +2,19 @@ import { Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import HomePage from './pages/HomePage';
import BlogPage from './pages/blog/BlogPage';
import BlogPostPage from './pages/blog/BlogPostPage';
import ProjectsPage from "@/pages/ProjectsPage";
import BarrioDeSalamancaPenthousePage from "@/pages/BarrioDeSalamancaPenthousePage";
export default function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<HomePage />} />
<Route path="/projects" element={<ProjectsPage />} />
<Route path="/barrio-de-salamanca-penthouse" element={<BarrioDeSalamancaPenthousePage />} />
<Route path="/blog" element={<BlogPage />} />
<Route path="/blog/:slug" element={<BlogPostPage />} />
</Route>
</Routes>
);

View File

@@ -7,13 +7,16 @@ import { StyleProvider } from "@/components/ui/StyleProvider";
export default function Layout() {
const navItems = [
{ name: "About", href: "#about" },
{ name: "Projects", href: "#projects" },
{ name: "Contact", href: "#contact" },
{ name: "Hero", href: "#hero" },
{ name: "Features", href: "#features" },
{ name: "Metrics", href: "#metrics" },
{ name: "Testimonials", href: "#testimonials" }
{ name: "About", href: "/#about" },
{ name: "Projects", href: "/projects" },
{ name: "Features", href: "/#features" },
{ name: "Metrics", href: "/#metrics" },
{ name: "Testimonials", href: "/#testimonials" },
{ name: "Contact", href: "/#contact" },
{ name: "Barrio De Salamanca Penthouse", href: "/barrio-de-salamanca-penthouse" },
{ name: "Blog", href: "/blog" },
];
return (

View File

@@ -5,15 +5,15 @@
:root {
/* @colorThemes/lightTheme/grayNavyBlue */
--background: #f6f0e9;
--card: #efe7dd;
--foreground: #2b180a;
--primary-cta: #2b180a;
--primary-cta-text: #f6f0e9;
--secondary-cta: #efe7dd;
--secondary-cta-text: #2b180a;
--accent: #94877c;
--background-accent: #afa094;
--background: #f5faff;
--card: #ffffff;
--foreground: #001122;
--primary-cta: #15479c;
--primary-cta-text: #f5faff;
--secondary-cta: #ffffff;
--secondary-cta-text: #001122;
--accent: #a8cce8;
--background-accent: #7ba3cf;
/* @layout/border-radius/rounded */
--radius: 1rem;

View File

@@ -0,0 +1,25 @@
import Button from "@/components/ui/Button";
import HeroBackgroundSlot from "@/components/ui/HeroBackgroundSlot";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import ScrollReveal from "@/components/ui/ScrollReveal";
import AvatarGroup from "@/components/ui/AvatarGroup";
import { cls } from "@/lib/utils";
export default function BarrioDeSalamancaPenthousePage() {
return (
<>
<div data-webild-section="HeroBillboard"><section aria-label="Hero section" className="relative pt-25 pb-20 md:pt-30"><HeroBackgroundSlot /><div className="flex flex-col gap-12 md:gap-15 w-content-width mx-auto"><div className="flex flex-col items-center gap-3 text-center"><div className="px-3 py-1 mb-1 text-sm card rounded w-fit"><p>Residential Portfolio</p></div><TextAnimation text="Barrio de Salamanca Penthouse" variant="fade-blur" gradientText={true} tag="h1" className="md:max-w-8/10 text-7xl 2xl:text-8xl leading-[1.15] font-semibold text-center text-balance" /><TextAnimation text="A bespoke sanctuary in the heart of Madrid. We transformed this penthouse into a sophisticated retreat, blending natural textures with abundant light to tell a unique personal story." variant="fade-blur" gradientText={false} tag="p" className="md:max-w-7/10 text-lg md:text-xl leading-snug text-balance" /><div className="flex flex-wrap justify-center gap-3 mt-2 md:mt-3"><Button text="Explore Project" href="#details" variant="primary" /><Button text="All Projects" href="/projects" variant="secondary" animationDelay={0.1} /></div></div><ScrollReveal variant="fade-blur" delay={0.2} className="w-full p-2 xl:p-3 2xl:p-4 card rounded overflow-hidden"><ImageOrVideo imageSrc="https://img.freepik.com/free-photo/3d-rendering-modern-dining-room-living-room-with-luxury-decor_105762-2216.jpg" className="aspect-4/5 md:aspect-video" /></ScrollReveal></div></section></div>
<div data-webild-section="AboutTextSplit"><section aria-label="About section" className="py-20"><div className="flex flex-col gap-20 mx-auto w-content-width"><div className="flex flex-col md:flex-row gap-3 md:gap-15"><div className="w-full md:w-1/2"><TextAnimation text="A Sanctuary Above Salamanca" variant="fade-blur" gradientText={true} tag="h2" className="text-7xl 2xl:text-8xl leading-[1.15] font-semibold text-balance" /></div><div className="flex flex-col gap-2 w-full md:w-1/2"><TextAnimation key={0} text="Perched high above Madrid's most exclusive district, this penthouse is a masterclass in understated luxury. We transformed a classic layout into a fluid, light-filled sanctuary." variant="fade-blur" gradientText={false} tag="p" className="text-xl md:text-2xl leading-snug text-balance" />
<TextAnimation key={1} text="Rich textures of natural stone and bespoke oak cabinetry anchor the space. Expansive windows invite the golden Spanish sun to dance across the minimalist forms." variant="fade-blur" gradientText={false} tag="p" className="text-xl md:text-2xl leading-snug text-balance" />
<TextAnimation key={2} text="Every detail was selected to reflect the client's refined taste, creating a personal narrative woven through bespoke design and timeless elegance." variant="fade-blur" gradientText={false} tag="p" className="text-xl md:text-2xl leading-snug text-balance" /><div className="flex flex-wrap gap-3 mt-2 md:mt-3"><Button text="Back to Projects" href="/projects" variant="primary" /><Button text="Inquire Now" href="/contact" variant="secondary" animationDelay={0.1} /></div></div></div><div className="w-full border-b border-foreground/5" /></div></section></div>
<div data-webild-section="FeaturesImageBento"><section aria-label="Features image bento section" className="py-20"><div className="flex flex-col gap-8 md:gap-10"><div className="flex flex-col items-center w-content-width mx-auto gap-2"><div className="px-3 py-1 mb-1 text-sm card rounded w-fit"><p>Featured Project</p></div><TextAnimation text="Barrio de Salamanca Penthouse" variant="fade-blur" gradientText={true} tag="h2" className="md:max-w-8/10 text-6xl 2xl:text-7xl leading-[1.15] font-semibold text-center text-balance" /><TextAnimation text="A masterclass in bespoke luxury, blending classic Madrid architecture with contemporary elegance and curated textures." variant="fade-blur" gradientText={false} tag="p" className="md:max-w-7/10 text-lg md:text-xl leading-snug text-center text-balance" /><div className="flex flex-wrap justify-center gap-3 mt-2 md:mt-3"><Button text="View Gallery" href="#gallery" variant="primary" /><Button text="All Projects" href="/projects" variant="secondary" animationDelay={0.1} /></div></div><div className="w-content-width mx-auto grid grid-cols-1 md:grid-cols-6 gap-3"><ScrollReveal key={0} variant="fade" delay={0} className="col-span-1 group md:col-span-2"><div className="overflow-hidden rounded"></div></ScrollReveal>
<ScrollReveal key={1} variant="fade" delay={0.1} className="col-span-1 group md:col-span-4"><div className="overflow-hidden rounded"></div></ScrollReveal>
<ScrollReveal key={2} variant="fade" delay={0} className="col-span-1 group md:col-span-3"><div className="overflow-hidden rounded"></div></ScrollReveal>
<ScrollReveal key={3} variant="fade" delay={0.1} className="col-span-1 group md:col-span-3"><div className="overflow-hidden rounded"></div></ScrollReveal>
<ScrollReveal key={4} variant="fade" delay={0} className="col-span-1 group md:col-span-2"><div className="overflow-hidden rounded"></div></ScrollReveal>
<ScrollReveal key={5} variant="fade" delay={0.1} className="col-span-1 group md:col-span-2"><div className="overflow-hidden rounded"></div></ScrollReveal>
<ScrollReveal key={6} variant="fade" delay={0.2} className="col-span-1 group md:col-span-2"><div className="overflow-hidden rounded"></div></ScrollReveal></div></div></section></div>
</>
);
}

19
src/pages/BlogPage.tsx Normal file
View File

@@ -0,0 +1,19 @@
import Button from "@/components/ui/Button";
import HeroBackgroundSlot from "@/components/ui/HeroBackgroundSlot";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import ScrollReveal from "@/components/ui/ScrollReveal";
import AvatarGroup from "@/components/ui/AvatarGroup";
import { ArrowUpRight, Loader2 } from "lucide-react";
import GridOrCarousel from "@/components/ui/GridOrCarousel";
import { useButtonClick } from "@/hooks/useButtonClick";
import useBlogPosts from "@/hooks/useBlogPosts";
export default function BlogPage() {
return (
<>
<div data-webild-section="HeroBillboard"><section aria-label="Hero section" className="relative pt-25 pb-20 md:pt-30"><HeroBackgroundSlot /><div className="flex flex-col gap-12 md:gap-15 w-content-width mx-auto"><div className="flex flex-col items-center gap-3 text-center"><div className="px-3 py-1 mb-1 text-sm card rounded w-fit"><p>Design Journal</p></div><TextAnimation text="Stories of Space and Light" variant="fade-blur" gradientText={true} tag="h1" className="md:max-w-8/10 text-7xl 2xl:text-8xl leading-[1.15] font-semibold text-center text-balance" /><TextAnimation text="Explore our latest thoughts on bespoke interiors, architectural trends, and the art of curating spaces that reflect your personal essence." variant="fade-blur" gradientText={false} tag="p" className="md:max-w-7/10 text-lg md:text-xl leading-snug text-balance" /><div className="flex flex-wrap justify-center gap-3 mt-2 md:mt-3"><Button text="Read Latest" href="#latest" variant="primary" /><Button text="Explore Topics" href="#topics" variant="secondary" animationDelay={0.1} /></div></div><ScrollReveal variant="fade-blur" delay={0.2} className="w-full p-2 xl:p-3 2xl:p-4 card rounded overflow-hidden"><ImageOrVideo imageSrc="https://picsum.photos/seed/2089910226/1200/800" className="aspect-4/5 md:aspect-video" /></ScrollReveal></div></section></div>
<div data-webild-section="BlogMediaCards"><section aria-label="Blog section" className="py-20"><div className="w-content-width mx-auto flex justify-center"><Loader2 className="size-8 animate-spin text-foreground" strokeWidth={1.5} /></div></section></div>
</>
);
}

View File

@@ -1,281 +1,36 @@
import AboutFeaturesSplit from '@/components/sections/about/AboutFeaturesSplit';
import ContactCta from '@/components/sections/contact/ContactCta';
import FaqTabbedAccordion from '@/components/sections/faq/FaqTabbedAccordion';
import FeaturesBentoGrid from '@/components/sections/features/FeaturesBentoGrid';
import FeaturesImageBento from '@/components/sections/features/FeaturesImageBento';
import HeroCenteredLogos from '@/components/sections/hero/HeroCenteredLogos';
import MetricsIconCards from '@/components/sections/metrics/MetricsIconCards';
import TestimonialQuoteCards from '@/components/sections/testimonial/TestimonialQuoteCards';
import { Award, CheckCircle, Shield, Sparkles, Star, Users } from "lucide-react";
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
// AUTO-GENERATED shell by per-section-migrate.
// Section bodies live in ./<PageBase>/sections/<X>.tsx. Edit the section
// files directly. Non-block content (wrappers, non-inlinable sections) is
// preserved inline; extracted section blocks become <XSection/> refs.
export default function HomePage() {
import React from 'react';
import HeroSection from './HomePage/sections/Hero';
import AboutSection from './HomePage/sections/About';
import FeaturesSection from './HomePage/sections/Features';
import ProjectsSection from './HomePage/sections/Projects';
import MetricsSection from './HomePage/sections/Metrics';
import TestimonialsSection from './HomePage/sections/Testimonials';
import FaqSection from './HomePage/sections/Faq';
import ContactSection from './HomePage/sections/Contact';
export default function HomePage(): React.JSX.Element {
return (
<>
<div id="hero" data-section="hero">
<SectionErrorBoundary name="hero">
<HeroCenteredLogos
avatarsSrc={[
"http://img.b2bpic.net/free-photo/young-people-having-fun_23-2149832986.jpg",
"http://img.b2bpic.net/free-photo/family-moving-using-boxes_1157-35479.jpg",
"http://img.b2bpic.net/free-photo/beautiful-young-african-american-woman-smiling-cheerful-friendly-face-laughing-confident_839833-29956.jpg",
"http://img.b2bpic.net/free-photo/male-real-estate-agent-doing-business-showing-house-potential-buying-couple_23-2150164714.jpg",
"http://img.b2bpic.net/free-photo/colleagues-sharing-high-five-office_482257-119313.jpg",
]}
avatarText="Trusted by 500+ families"
title="Crafting Timeless Spaces in the Heart of Madrid"
description="We blend Mediterranean warmth with contemporary luxury to transform your home into a sanctuary of elegance and comfort."
primaryButton={{
text: "View Projects",
href: "#projects",
}}
secondaryButton={{
text: "Contact Us",
href: "#contact",
}}
names={[
"Madrid",
"Barcelona",
"Seville",
"Valencia",
"Bilbao",
"Malaga",
]}
imageSrc="http://img.b2bpic.net/free-photo/close-up-happy-healthy-young-woman-with-dark-curly-hair-lying-comfortable-sofa-terrace_197531-22796.jpg"
/>
</SectionErrorBoundary>
</div>
<>
<HeroSection />
<div id="about" data-section="about">
<SectionErrorBoundary name="about">
<AboutFeaturesSplit
tag="Our Philosophy"
title="Design that Reflects Your Essence"
description="With over two decades of experience in the Madrid architecture and design scene, we specialize in curating bespoke interiors that tell your personal story through textures, light, and form."
items={[
{
icon: Sparkles,
title: "Bespoke Curation",
description: "Every element is hand-selected to match your vision.",
},
{
icon: CheckCircle,
title: "Precision Execution",
description: "From planning to installation, we manage every detail.",
},
{
icon: Shield,
title: "Quality Assurance",
description: "We partner only with the finest local craftsmen.",
},
]}
imageSrc="http://img.b2bpic.net/free-photo/cropped-top-view-young-beautiful-female-architect-hands-doing-blueprints-with-ruler-pen-white-table-coworking-space-business-concept_176420-8525.jpg"
/>
</SectionErrorBoundary>
</div>
<AboutSection />
<div id="features" data-section="features">
<SectionErrorBoundary name="features">
<FeaturesBentoGrid
tag="Our Expertise"
title="Comprehensive Design Services"
description="End-to-end solutions tailored to your unique lifestyle."
features={[
{
title: "Residential Design",
description: "Full-scale home transformations and renovations.",
imageSrc: "http://img.b2bpic.net/free-photo/modern-kitchen-surface-pull-out-drawer-loft-style-interior-design-details_169016-72762.jpg",
},
{
title: "Space Optimization",
description: "Maximizing functionality for urban Madrid flats.",
imageSrc: "http://img.b2bpic.net/free-photo/young-woman-using-home-technology_23-2149216655.jpg",
},
{
title: "Eco-Conscious Selection",
description: "Sustainable sourcing for modern homes.",
imageSrc: "http://img.b2bpic.net/free-photo/close-up-arrangement-modern-vases_23-2149646500.jpg",
},
{
title: "Luxury Finishing",
description: "Premium lighting, textures, and bespoke furniture.",
imageSrc: "http://img.b2bpic.net/free-photo/lobby-living-room-hotel_1150-11124.jpg",
},
]}
/>
</SectionErrorBoundary>
</div>
<FeaturesSection />
<div id="projects" data-section="projects">
<SectionErrorBoundary name="projects">
<FeaturesImageBento
tag="Featured Work"
title="Selected Projects in Madrid"
description="Discover our latest transformations across the city."
items={[
{
title: "Luxury Flat",
description: "Barrio de Salamanca renovation",
imageSrc: "http://img.b2bpic.net/free-photo/woman-reading-book-reading-club-library_23-2150293528.jpg",
},
{
title: "Urban Oasis",
description: "Modern terrace concept",
imageSrc: "http://img.b2bpic.net/free-photo/modern-spacious-room-with-large-panoramic-window_7502-7289.jpg",
},
{
title: "Boutique Villa",
description: "Contemporary architecture",
imageSrc: "http://img.b2bpic.net/free-photo/young-coworkers-gathering-table-office_23-2147668778.jpg",
},
{
title: "Townhouse Staging",
description: "Modern living transformation",
imageSrc: "http://img.b2bpic.net/free-photo/side-view-blurry-man-walking_23-2150378941.jpg",
},
{
title: "Minimalist Loft",
description: "Open plan urban living",
imageSrc: "http://img.b2bpic.net/free-photo/ai-generated-house-design_23-2150666254.jpg",
},
{
title: "Traditional Fusion",
description: "Renovating history",
imageSrc: "http://img.b2bpic.net/free-photo/bathroom-interior-background-white-spa-towels-wood_1258-111485.jpg",
},
{
title: "Private Residence",
description: "Custom luxury detail",
imageSrc: "http://img.b2bpic.net/free-photo/abstract-blur-furniture-shop-store-interior_74190-4976.jpg",
},
]}
/>
</SectionErrorBoundary>
</div>
<ProjectsSection />
<div id="metrics" data-section="metrics">
<SectionErrorBoundary name="metrics">
<MetricsIconCards
tag="By the Numbers"
title="A Track Record of Excellence"
description="Results that speak for themselves."
metrics={[
{
icon: Award,
title: "Completed Projects",
value: "350+",
},
{
icon: Star,
title: "Satisfied Clients",
value: "500+",
},
{
icon: Users,
title: "Design Experts",
value: "25+",
},
]}
/>
</SectionErrorBoundary>
</div>
<MetricsSection />
<div id="testimonials" data-section="testimonials">
<SectionErrorBoundary name="testimonials">
<TestimonialQuoteCards
tag="Client Stories"
title="What Homeowners Say"
description="Our passion is building your dream."
testimonials={[
{
name: "Elena R.",
role: "Salamanca Resident",
quote: "The design team turned my outdated flat into a modern masterpiece. Truly exceptional quality.",
imageSrc: "http://img.b2bpic.net/free-photo/woman-sitting-couch_23-2148751504.jpg",
},
{
name: "Carlos M.",
role: "Villa Owner",
quote: "They captured our vision perfectly. The attention to detail is unmatched in Madrid.",
imageSrc: "http://img.b2bpic.net/free-photo/overhead-view-young-couple-with-their-baby-sitting-cardboard-boxes-their-new-home_23-2148060078.jpg",
},
{
name: "Sofia G.",
role: "Art Collector",
quote: "A seamless process from concept to completion. I couldn't be happier with the results.",
imageSrc: "http://img.b2bpic.net/free-photo/portrait-beautiful-woman-bathrobe-indoors_1153-8058.jpg",
},
{
name: "Diego V.",
role: "Architect",
quote: "Their eye for texture and balance is superb. Highly recommended for any interior project.",
imageSrc: "http://img.b2bpic.net/free-photo/lovely-woman-drawing-looking-camera_23-2147770011.jpg",
},
{
name: "Isabel P.",
role: "Business Owner",
quote: "Simply wonderful to work with. They made the renovation process stress-free and exciting.",
imageSrc: "http://img.b2bpic.net/free-photo/colleagues-smiling-speaking-discussing-new-ideas_176420-1665.jpg",
},
]}
/>
</SectionErrorBoundary>
</div>
<TestimonialsSection />
<div id="faq" data-section="faq">
<SectionErrorBoundary name="faq">
<FaqTabbedAccordion
tag="Questions"
title="Everything You Need to Know"
description="Clear answers to start your design journey."
categories={[
{
name: "Process",
items: [
{
question: "How long does a renovation take?",
answer: "Projects typically range from 2 to 6 months depending on scope.",
},
{
question: "Do you offer consultations?",
answer: "Yes, we provide initial site consultations to discuss your goals.",
},
],
},
{
name: "Services",
items: [
{
question: "Do you design commercial spaces?",
answer: "Our focus is primarily high-end residential interiors.",
},
{
question: "Do you source furniture?",
answer: "We manage sourcing, procurement, and installation as part of our service.",
},
],
},
]}
/>
</SectionErrorBoundary>
</div>
<FaqSection />
<div id="contact" data-section="contact">
<SectionErrorBoundary name="contact">
<ContactCta
tag="Get Started"
text="Ready to transform your home in Madrid? Let's discuss your vision over a private consultation."
primaryButton={{
text: "Contact Us",
href: "mailto:hello@studiomadrid.es",
}}
secondaryButton={{
text: "Book a Tour",
href: "#",
}}
/>
</SectionErrorBoundary>
</div>
<ContactSection />
</>
);
}

View File

@@ -0,0 +1,39 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "about" section.
import React from 'react';
import AboutFeaturesSplit from '@/components/sections/about/AboutFeaturesSplit';
import { Award, CheckCircle, Shield, Sparkles, Star, Users } from "lucide-react";
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function AboutSection(): React.JSX.Element {
return (
<div id="about" data-section="about">
<SectionErrorBoundary name="about">
<AboutFeaturesSplit
tag="Our Philosophy"
title="Design that Reflects Your Essence"
description="With over two decades of experience in the Madrid architecture and design scene, we specialize in curating bespoke interiors that tell your personal story through textures, light, and form."
items={[
{
icon: Sparkles,
title: "Bespoke Curation",
description: "Every element is hand-selected to match your vision.",
},
{
icon: CheckCircle,
title: "Precision Execution",
description: "From planning to installation, we manage every detail.",
},
{
icon: Shield,
title: "Quality Assurance",
description: "We partner only with the finest local craftsmen.",
},
]}
imageSrc="http://img.b2bpic.net/free-photo/cropped-top-view-young-beautiful-female-architect-hands-doing-blueprints-with-ruler-pen-white-table-coworking-space-business-concept_176420-8525.jpg"
/>
</SectionErrorBoundary>
</div>
);
}

View File

@@ -0,0 +1,27 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "contact" section.
import React from 'react';
import ContactCta from '@/components/sections/contact/ContactCta';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function ContactSection(): React.JSX.Element {
return (
<div id="contact" data-section="contact">
<SectionErrorBoundary name="contact">
<ContactCta
tag="Get Started"
text="Ready to transform your home in Madrid? Let's discuss your vision over a private consultation."
primaryButton={{
text: "Contact Us",
href: "mailto:hello@studiomadrid.es",
}}
secondaryButton={{
text: "Book a Tour",
href: "#",
}}
/>
</SectionErrorBoundary>
</div>
);
}

View File

@@ -0,0 +1,48 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "faq" section.
import React from 'react';
import FaqTabbedAccordion from '@/components/sections/faq/FaqTabbedAccordion';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function FaqSection(): React.JSX.Element {
return (
<div id="faq" data-section="faq">
<SectionErrorBoundary name="faq">
<FaqTabbedAccordion
tag="Questions"
title="Everything You Need to Know"
description="Clear answers to start your design journey."
categories={[
{
name: "Process",
items: [
{
question: "How long does a renovation take?",
answer: "Projects typically range from 2 to 6 months depending on scope.",
},
{
question: "Do you offer consultations?",
answer: "Yes, we provide initial site consultations to discuss your goals.",
},
],
},
{
name: "Services",
items: [
{
question: "Do you design commercial spaces?",
answer: "Our focus is primarily high-end residential interiors.",
},
{
question: "Do you source furniture?",
answer: "We manage sourcing, procurement, and installation as part of our service.",
},
],
},
]}
/>
</SectionErrorBoundary>
</div>
);
}

View File

@@ -0,0 +1,42 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "features" section.
import React from 'react';
import FeaturesBentoGrid from '@/components/sections/features/FeaturesBentoGrid';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function FeaturesSection(): React.JSX.Element {
return (
<div id="features" data-section="features">
<SectionErrorBoundary name="features">
<FeaturesBentoGrid
tag="Our Expertise"
title="Comprehensive Design Services"
description="End-to-end solutions tailored to your unique lifestyle."
features={[
{
title: "Residential Design",
description: "Full-scale home transformations and renovations.",
imageSrc: "http://img.b2bpic.net/free-photo/modern-kitchen-surface-pull-out-drawer-loft-style-interior-design-details_169016-72762.jpg",
},
{
title: "Space Optimization",
description: "Maximizing functionality for urban Madrid flats.",
imageSrc: "http://img.b2bpic.net/free-photo/young-woman-using-home-technology_23-2149216655.jpg",
},
{
title: "Eco-Conscious Selection",
description: "Sustainable sourcing for modern homes.",
imageSrc: "http://img.b2bpic.net/free-photo/close-up-arrangement-modern-vases_23-2149646500.jpg",
},
{
title: "Luxury Finishing",
description: "Premium lighting, textures, and bespoke furniture.",
imageSrc: "http://img.b2bpic.net/free-photo/lobby-living-room-hotel_1150-11124.jpg",
},
]}
/>
</SectionErrorBoundary>
</div>
);
}

View File

@@ -0,0 +1,44 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "hero" section.
import React from 'react';
import HeroCenteredLogos from '@/components/sections/hero/HeroCenteredLogos';
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function HeroSection(): React.JSX.Element {
return (
<div id="hero" data-section="hero">
<SectionErrorBoundary name="hero">
<HeroCenteredLogos
avatarsSrc={[
"http://img.b2bpic.net/free-photo/young-people-having-fun_23-2149832986.jpg",
"http://img.b2bpic.net/free-photo/family-moving-using-boxes_1157-35479.jpg",
"http://img.b2bpic.net/free-photo/beautiful-young-african-american-woman-smiling-cheerful-friendly-face-laughing-confident_839833-29956.jpg",
"http://img.b2bpic.net/free-photo/male-real-estate-agent-doing-business-showing-house-potential-buying-couple_23-2150164714.jpg",
"http://img.b2bpic.net/free-photo/colleagues-sharing-high-five-office_482257-119313.jpg",
]}
avatarText="Trusted by 500+ families"
title="Crafting Timeless Spaces in the Heart of Madrid"
description="We blend Mediterranean warmth with contemporary luxury to transform your home into a sanctuary of elegance and comfort."
primaryButton={{
text: "View Projects",
href: "#projects",
}}
secondaryButton={{
text: "Contact Us",
href: "#contact",
}}
names={[
"Madrid",
"Barcelona",
"Seville",
"Valencia",
"Bilbao",
"Malaga",
]}
imageSrc="https://images.pexels.com/photos/28484958/pexels-photo-28484958.jpeg?auto=compress&cs=tinysrgb&h=650&w=940&id=28484958"
/>
</SectionErrorBoundary>
</div>
);
}

View File

@@ -0,0 +1,38 @@
// AUTO-GENERATED by per-section-migrate. Edit freely — Bob will treat this
// file as the canonical source for the "metrics" section.
import React from 'react';
import MetricsIconCards from '@/components/sections/metrics/MetricsIconCards';
import { Award, CheckCircle, Shield, Sparkles, Star, Users } from "lucide-react";
import SectionErrorBoundary from "@/components/ui/SectionErrorBoundary";
export default function MetricsSection(): React.JSX.Element {
return (
<div id="metrics" data-section="metrics">
<SectionErrorBoundary name="metrics">
<MetricsIconCards
tag="By the Numbers"
title="A Track Record of Excellence"
description="Results that speak for themselves."
metrics={[
{
icon: Award,
title: "Completed Projects",
value: "350+",
},
{
icon: Star,
title: "Satisfied Clients",
value: "500+",
},
{
icon: Users,
title: "Design Experts",
value: "25+",
},
]}
/>
</SectionErrorBoundary>
</div>
);
}

View File

@@ -0,0 +1,159 @@
/* eslint-disable */
// @ts-nocheck — generated by catalog-eject; runtime-correct but TS strict-mode false-positives on inlined catalog body
import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import ScrollReveal from "@/components/ui/ScrollReveal";
import { cls } from "@/lib/utils";
const items = [
{
title: "Luxury Flat",
description: "Barrio de Salamanca penthouse",
imageSrc: "http://img.b2bpic.net/free-photo/woman-reading-book-reading-club-library_23-2150293528.jpg",
href: "/barrio-de-salamanca-penthouse"
},
{
title: "Urban Oasis",
description: "Modern terrace concept",
imageSrc: "http://img.b2bpic.net/free-photo/modern-spacious-room-with-large-panoramic-window_7502-7289.jpg"
},
{
title: "Boutique Villa",
description: "Contemporary architecture",
imageSrc: "http://img.b2bpic.net/free-photo/young-coworkers-gathering-table-office_23-2147668778.jpg"
},
{
title: "Townhouse Staging",
description: "Modern living transformation",
imageSrc: "http://img.b2bpic.net/free-photo/side-view-blurry-man-walking_23-2150378941.jpg"
},
{
title: "Minimalist Loft",
description: "Open plan urban living",
imageSrc: "http://img.b2bpic.net/free-photo/ai-generated-house-design_23-2150666254.jpg"
},
{
title: "Traditional Fusion",
description: "Renovating history",
imageSrc: "http://img.b2bpic.net/free-photo/bathroom-interior-background-white-spa-towels-wood_1258-111485.jpg"
},
{
title: "Private Residence",
description: "Custom luxury detail",
imageSrc: "http://img.b2bpic.net/free-photo/abstract-blur-furniture-shop-store-interior_74190-4976.jpg"
}
];
type FeatureItem = {
title: string;
description: string;
href?: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
interface FeaturesImageBentoProps {
tag: string;
title: string;
description: string;
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
items: [FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem, FeatureItem];
}
const ProjectsInline = () => {
const gridClasses = [
"md:col-span-2",
"md:col-span-4",
"md:col-span-3",
"md:col-span-3",
"md:col-span-2",
"md:col-span-2",
"md:col-span-2",
];
const staggerDelays = [
0,
0.1,
0,
0.1,
0,
0.1,
0.2,
];
return (
<section aria-label="Features image bento section" className="py-20">
<div className="flex flex-col gap-8 md:gap-10">
<div className="flex flex-col items-center w-content-width mx-auto gap-2">
<div className="px-3 py-1 mb-1 text-sm card rounded w-fit">
<p>{"Featured Work"}</p>
</div>
<TextAnimation
text={"Selected Projects in Madrid"}
variant="fade-blur"
gradientText={true}
tag="h2"
className="md:max-w-8/10 text-6xl 2xl:text-7xl leading-[1.15] font-semibold text-center text-balance"
/>
<TextAnimation
text={"Discover our latest transformations across the city."}
variant="fade-blur"
gradientText={false}
tag="p"
className="md:max-w-7/10 text-lg md:text-xl leading-snug text-center text-balance"
/>
{(undefined || undefined) && (
<div className="flex flex-wrap justify-center gap-3 mt-2 md:mt-3">
{undefined && <Button text={undefined.text} href={undefined.href} variant="primary"/>}
{undefined && <Button text={undefined.text} href={undefined.href} variant="secondary" animationDelay={0.1} />}
</div>
)}
</div>
<div className="w-content-width mx-auto grid grid-cols-1 md:grid-cols-6 gap-3">
{items.map((item, index) => {
const content = (
<div className="relative h-80 xl:h-100 2xl:h-120 overflow-hidden">
<ImageOrVideo
imageSrc={item.imageSrc}
videoSrc={item.videoSrc}
className="rounded group-hover:scale-105 transition-transform duration-500"
/>
<div className="absolute inset-x-5 bottom-5 xl:inset-x-6 xl:bottom-6 2xl:inset-x-7 2xl:bottom-7 flex flex-col text-background">
<span className="text-2xl font-semibold leading-snug truncate">{item.title}</span>
<span className="text-base leading-snug truncate">{item.description}</span>
</div>
</div>
);
return (
<ScrollReveal key={index} variant="fade" delay={staggerDelays[index]} className={cls("col-span-1 group", gridClasses[index])}>
{item.href ? (
<a href={item.href} className="block overflow-hidden rounded">
{content}
</a>
) : (
<div className="overflow-hidden rounded">
{content}
</div>
)}
</ScrollReveal>
);
})}
</div>
</div>
</section>
);
};
export default function ProjectsSection() {
return (
<div data-webild-section="projects" id="projects">
<ProjectsInline />
</div>
);
}

View File

@@ -0,0 +1,353 @@
/* eslint-disable */
// @ts-nocheck — generated by catalog-eject; runtime-correct but TS strict-mode false-positives on inlined catalog body
import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import ScrollReveal from "@/components/ui/ScrollReveal";
import { BadgeCheck } from "lucide-react";
import { useRef, useState, useEffect } from "react";
const DraggableMarqueeRow = ({ items, duration, reverse = false }: { items: any[], duration: string, reverse?: boolean }) => {
const containerRef = useRef(null);
const trackRef = useRef(null);
const isInitialized = useRef(false);
const [isHovered, setIsHovered] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [startX, setStartX] = useState(0);
const [scrollLeft, setScrollLeft] = useState(0);
useEffect(() => {
let animationFrameId: number;
let lastTime = performance.now();
const durationMs = parseFloat(duration) * 1000;
const animate = (time: number) => {
const deltaTime = time - lastTime;
lastTime = time;
if (containerRef.current && trackRef.current) {
const container = containerRef.current as any;
const track = trackRef.current as any;
const singleCopyWidth = track.scrollWidth / 4;
if (singleCopyWidth > 0) {
if (!isInitialized.current) {
if (reverse) {
container.scrollLeft = singleCopyWidth * 2;
}
isInitialized.current = true;
}
if (!isHovered && !isDragging) {
const speed = singleCopyWidth / durationMs;
const delta = speed * deltaTime;
if (reverse) {
container.scrollLeft -= delta;
if (container.scrollLeft <= 0) {
container.scrollLeft += singleCopyWidth * 2;
}
} else {
container.scrollLeft += delta;
if (container.scrollLeft >= singleCopyWidth * 2) {
container.scrollLeft -= singleCopyWidth * 2;
}
}
}
}
}
animationFrameId = requestAnimationFrame(animate);
};
animationFrameId = requestAnimationFrame(animate);
return () => cancelAnimationFrame(animationFrameId);
}, [isHovered, isDragging, duration, reverse]);
const handleMouseDown = (e: React.MouseEvent) => {
if (!containerRef.current) return;
setIsDragging(true);
setStartX(e.pageX - (containerRef.current as any).offsetLeft);
setScrollLeft((containerRef.current as any).scrollLeft);
};
const handleMouseLeave = () => {
setIsDragging(false);
setIsHovered(false);
};
const handleMouseUp = () => {
setIsDragging(false);
};
const handleMouseMove = (e: React.MouseEvent) => {
if (!isDragging || !containerRef.current || !trackRef.current) return;
e.preventDefault();
const x = e.pageX - (containerRef.current as any).offsetLeft;
const walk = (x - startX) * 2;
let newScrollLeft = scrollLeft - walk;
const singleCopyWidth = (trackRef.current as any).scrollWidth / 4;
if (singleCopyWidth > 0) {
if (newScrollLeft <= 0) {
newScrollLeft += singleCopyWidth * 2;
setStartX(e.pageX - (containerRef.current as any).offsetLeft);
setScrollLeft(newScrollLeft + walk);
} else if (newScrollLeft >= singleCopyWidth * 2) {
newScrollLeft -= singleCopyWidth * 2;
setStartX(e.pageX - (containerRef.current as any).offsetLeft);
setScrollLeft(newScrollLeft + walk);
}
}
(containerRef.current as any).scrollLeft = newScrollLeft;
};
return (
<div
className="overflow-hidden mask-fade-x cursor-grab active:cursor-grabbing"
ref={containerRef}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={handleMouseLeave}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseMove={handleMouseMove}
style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
>
<div
ref={trackRef}
className="flex w-max mb-5"
>
{[...items, ...items, ...items, ...items].map((item, i) => (
<div key={i} className="flex-none w-[300px] md:w-[400px] mx-2.5">
<div className="card rounded-lg p-6 h-full flex flex-col justify-between">
<div>
<p className="text-lg md:text-xl text-foreground mb-6">"{item.quote}"</p>
</div>
<div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-full overflow-hidden flex-shrink-0">
<ImageOrVideo imageSrc={item.imageSrc} videoSrc={item.videoSrc} className="w-full h-full object-cover" />
</div>
<div className="flex-1">
<div className="flex items-center gap-1">
<p className="font-semibold text-foreground">{item.name}</p>
<BadgeCheck className="w-4 h-4 text-blue-500" />
</div>
<p className="text-sm text-muted-foreground">{item.role}</p>
</div>
<Button text="Contact" variant="secondary" className="text-xs px-3 py-1" />
</div>
</div>
</div>
))}
</div>
</div>
);
};
const testimonials = [
{
imageSrc: "http://img.b2bpic.net/free-photo/woman-sitting-couch_23-2148751504.jpg",
quote: "The design team turned my outdated flat into a modern masterpiece. Truly exceptional quality.",
role: "Salamanca Resident",
name: "Elena R."
},
{
imageSrc: "http://img.b2bpic.net/free-photo/overhead-view-young-couple-with-their-baby-sitting-cardboard-boxes-their-new-home_23-2148060078.jpg",
quote: "They captured our vision perfectly. The attention to detail is unmatched in Madrid.",
role: "Villa Owner",
name: "Carlos M."
},
{
imageSrc: "http://img.b2bpic.net/free-photo/portrait-beautiful-woman-bathrobe-indoors_1153-8058.jpg",
quote: "A seamless process from concept to completion. I couldn't be happier with the results.",
name: "Sofia G.",
role: "Art Collector"
},
{
imageSrc: "http://img.b2bpic.net/free-photo/lovely-woman-drawing-looking-camera_23-2147770011.jpg",
quote: "Their eye for texture and balance is superb. Highly recommended for any interior project.",
role: "Architect",
name: "Diego V."
},
{
imageSrc: "http://img.b2bpic.net/free-photo/colleagues-smiling-speaking-discussing-new-ideas_176420-1665.jpg",
quote: "Simply wonderful to work with. They made the renovation process stress-free and exciting.",
role: "Business Owner",
name: "Isabel P."
}
];
type Testimonial = {
name: string;
role: string;
quote: string;
} & ({ imageSrc: string; videoSrc?: never } | { videoSrc: string; imageSrc?: never });
const TestimonialsInline = () => {
const half = Math.ceil(testimonials.length / 2);
const topRow = testimonials.slice(0, half);
const bottomRow = testimonials.slice(half);
return (
<section aria-label="Testimonials section" className="pt-20 pb-10">
<div className="flex flex-col gap-8 md:gap-10">
<div className="flex flex-col items-center gap-2 w-content-width mx-auto">
<div className="px-3 py-1 mb-1 text-sm card rounded w-fit">
<p>{"Client Stories"}</p>
</div>
<TextAnimation
text={"What Homeowners Say"}
variant="fade"
gradientText={true}
tag="h2"
className="md:max-w-8/10 text-6xl 2xl:text-7xl leading-[1.15] font-semibold text-center text-balance"
/>
<TextAnimation
text={"Our passion is building your dream."}
variant="fade"
gradientText={false}
tag="p"
className="md:max-w-7/10 text-lg md:text-xl leading-snug text-center text-balance"
/>
{(undefined || undefined) && (
<div className="flex flex-wrap justify-center gap-3 mt-2 md:mt-3">
{undefined && <Button text={undefined.text} href={undefined.href} variant="primary"/>}
{undefined && <Button text={undefined.text} href={undefined.href} variant="secondary"animationDelay={0.1} />}
</div>
)}
</div>
<ScrollReveal variant="fade-blur" className="flex flex-col w-content-width mx-auto">
<div className="overflow-hidden mask-fade-x">
<div className="flex w-max animate-marquee-horizontal mb-5" style={{ animationDuration: "30s" }}>
{[...topRow, ...topRow, ...topRow, ...topRow].map((testimonial, index) => (
<div key={`top-${index}`} className="flex flex-col justify-between gap-4 xl:gap-5 2xl:gap-6 shrink-0 w-72 md:w-80 mr-5 p-6 xl:p-7 2xl:p-8 rounded card">
<p className="text-lg leading-snug line-clamp-3">{testimonial.quote}</p>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<ImageOrVideo
imageSrc={testimonial.imageSrc}
videoSrc={testimonial.videoSrc}
className="size-10 md:size-11 2xl:size-12 rounded-full object-cover"
/>
<div className="flex flex-col min-w-0">
<div className="flex items-center gap-1">
<span className="text-base text-foreground font-semibold leading-snug truncate">{testimonial.name}</span>
<BadgeCheck className="size-4 text-blue-500 shrink-0" />
</div>
<span className="text-base text-foreground/75 leading-snug truncate">{testimonial.role}</span>
</div>
</div>
<Button text="Contact" variant="secondary" className="w-full py-2 text-sm" />
</div>
</div>
))}
</div>
</div>
<div className="overflow-hidden mask-fade-x">
<div className="flex w-max animate-marquee-horizontal-reverse mb-10" style={{ animationDuration: "30s" }}>
{[...bottomRow, ...bottomRow, ...bottomRow, ...bottomRow].map((testimonial, index) => (
<div key={`bottom-${index}`} className="flex flex-col justify-between gap-4 xl:gap-5 2xl:gap-6 shrink-0 w-72 md:w-80 mr-5 p-6 xl:p-7 2xl:p-8 rounded card">
<p className="text-lg leading-snug line-clamp-3">{testimonial.quote}</p>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<ImageOrVideo
imageSrc={testimonial.imageSrc}
videoSrc={testimonial.videoSrc}
className="size-10 md:size-11 2xl:size-12 rounded-full object-cover"
/>
<div className="flex flex-col min-w-0">
<div className="flex items-center gap-1">
<span className="text-base text-foreground font-semibold leading-snug truncate">{testimonial.name}</span>
<BadgeCheck className="size-4 text-blue-500 shrink-0" />
</div>
<span className="text-base text-foreground/75 leading-snug truncate">{testimonial.role}</span>
</div>
</div>
<Button text="Contact" variant="secondary" className="w-full py-2 text-sm" />
</div>
</div>
))}
</div>
</div>
<div className="overflow-hidden mask-fade-x">
<div className="flex w-max animate-marquee-horizontal mb-10 hover:[animation-play-state:paused]" style={{ animationDuration: "35s" }}>
{[...bottomRow, ...bottomRow, ...bottomRow, ...bottomRow].map((testimonial, index) => (
<div key={`third-${index}`} className="flex flex-col justify-between gap-4 xl:gap-5 2xl:gap-6 shrink-0 w-72 md:w-80 mr-5 p-6 xl:p-7 2xl:p-8 rounded card">
<p className="text-lg leading-snug line-clamp-3">{testimonial.quote}</p>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<ImageOrVideo
imageSrc={testimonial.imageSrc}
videoSrc={testimonial.videoSrc}
className="size-10 md:size-11 2xl:size-12 rounded-full object-cover"
/>
<div className="flex flex-col min-w-0">
<div className="flex items-center gap-1">
<span className="text-base text-foreground font-semibold leading-snug truncate">{testimonial.name}</span>
<BadgeCheck className="size-4 text-blue-500 shrink-0" />
</div>
<span className="text-base text-foreground/75 leading-snug truncate">{testimonial.role}</span>
</div>
</div>
<Button text="Contact" variant="secondary" className="w-full py-2 text-sm" />
</div>
</div>
))}
</div>
</div>
</ScrollReveal>
<ScrollReveal variant="fade-blur" delay={0.4}>
<div className="relative w-full overflow-hidden flex group mt-5">
<div className="flex animate-marquee group-hover:[animation-play-state:paused]">
{[...testimonials, ...testimonials].map((testimonial, index) => (
<div key={`fourth-${index}`} className="flex flex-col justify-between gap-4 xl:gap-5 2xl:gap-6 shrink-0 w-72 md:w-80 mr-5 p-6 xl:p-7 2xl:p-8 rounded card">
<p className="text-lg leading-snug line-clamp-3">{testimonial.quote}</p>
<div className="flex flex-col gap-4">
<div className="flex items-center gap-3">
<ImageOrVideo
imageSrc={testimonial.imageSrc}
videoSrc={testimonial.videoSrc}
className="size-10 md:size-11 2xl:size-12 rounded-full object-cover"
/>
<div className="flex flex-col min-w-0">
<div className="flex items-center gap-1">
<span className="text-base text-foreground font-semibold leading-snug truncate">{testimonial.name}</span>
<BadgeCheck className="size-4 text-blue-500 shrink-0" />
</div>
<span className="text-base text-foreground/75 leading-snug truncate">{testimonial.role}</span>
</div>
</div>
<Button text="Contact" variant="secondary" className="w-full py-2 text-sm" />
</div>
</div>
))}
</div>
</div>
</ScrollReveal>
</div>
</section>
);
};
export default function TestimonialsSection() {
return (
<div data-webild-section="testimonials" id="testimonials">
<TestimonialsInline />
</div>
);
}

View File

@@ -0,0 +1,18 @@
import Button from "@/components/ui/Button";
import HeroBackgroundSlot from "@/components/ui/HeroBackgroundSlot";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import ScrollReveal from "@/components/ui/ScrollReveal";
import GridOrCarousel from "@/components/ui/GridOrCarousel";
export default function ProjectsPage() {
return (
<>
<div data-webild-section="HeroSplit"><section aria-label="Hero section" className="relative flex items-center h-fit md:h-svh pt-25 pb-20 md:py-0"><HeroBackgroundSlot /><div className="flex flex-col md:flex-row items-center gap-12 md:gap-20 w-content-width mx-auto"><div className="w-full md:w-1/2"><div className="flex flex-col items-center md:items-start gap-3"><div className="px-3 py-1 mb-1 text-sm card rounded w-fit"><p>Selected Works</p></div><TextAnimation text="Curated Madrid Interiors" variant="fade" gradientText={true} tag="h1" className="text-7xl 2xl:text-8xl leading-[1.15] font-semibold text-center md:text-left text-balance" /><TextAnimation text="Explore our portfolio of high-end residential and commercial projects. Each space is a testament to our dedication to texture, light, and personal storytelling." variant="fade" gradientText={false} tag="p" className="md:max-w-8/10 text-lg md:text-xl leading-snug text-center md:text-left text-balance" /><div className="flex flex-wrap max-md:justify-center gap-3 mt-2 md:mt-3"><Button text="View Catalogue" href="#catalogue" variant="primary" /><Button text="Discuss a Project" href="/contact" variant="secondary" animationDelay={0.1} /></div></div></div><ScrollReveal variant="fade-blur" delay={0.2} className="w-full md:w-1/2 h-100 md:h-[65vh] md:max-h-[75svh] p-2 xl:p-3 2xl:p-4 card rounded overflow-hidden"><ImageOrVideo imageSrc="https://img.freepik.com/free-photo/modern-luxury-living-room-interior-design_53876-128916.jpg" /></ScrollReveal></div></section></div>
<div data-webild-section="FeaturesMediaGrid"><section aria-label="Features section" className="py-20"><div className="flex flex-col gap-8 md:gap-10"><div className="flex flex-col items-center w-content-width mx-auto gap-2"><div className="px-3 py-1 mb-1 text-sm card rounded w-fit"><p>Portfolio</p></div><TextAnimation text="Curated Spaces" variant="slide-up" gradientText={true} tag="h2" className="md:max-w-8/10 text-6xl 2xl:text-7xl leading-[1.15] font-semibold text-center text-balance" /><TextAnimation text="Explore our collection of bespoke interiors and architectural transformations across Madrid. Each project is a unique narrative of light, texture, and form." variant="slide-up" gradientText={false} tag="p" className="md:max-w-7/10 text-lg md:text-xl leading-snug text-center text-balance" /><div className="flex flex-wrap justify-center gap-3 mt-2 md:mt-3"><Button text="Start Your Project" href="/contact" variant="primary" /><Button text="Our Process" href="/about" variant="secondary" animationDelay={0.1} /></div></div><ScrollReveal variant="slide-up"><GridOrCarousel><div key="Barrio de Salamanca Penthouse" className="flex flex-col gap-4 xl:gap-5 2xl:gap-6 h-full"><div className="aspect-square overflow-hidden"><ImageOrVideo imageSrc="https://picsum.photos/seed/2066822201/1200/800" className="rounded-none" /></div><div className="flex flex-col gap-1"><h3 className="text-3xl font-semibold leading-snug text-balance">Barrio de Salamanca Penthouse</h3><p className="text-base leading-snug text-balance">A sunlit sanctuary blending classic moldings with contemporary minimalism.</p></div></div>
<div key="Chamberí Townhouse" className="flex flex-col gap-4 xl:gap-5 2xl:gap-6 h-full"><div className="aspect-square overflow-hidden"><ImageOrVideo imageSrc="https://img.freepik.com/free-photo/3d-rendering-modern-dining-room-living-room-with-luxury-decor_105762-2009.jpg" className="rounded-none" /></div><div className="flex flex-col gap-1"><h3 className="text-3xl font-semibold leading-snug text-balance">Chamberí Townhouse</h3><p className="text-base leading-snug text-balance">Restoring historical elegance while introducing fluid, modern living spaces.</p></div></div>
<div key="Retiro Park Loft" className="flex flex-col gap-4 xl:gap-5 2xl:gap-6 h-full"><div className="aspect-square overflow-hidden"><ImageOrVideo imageSrc="https://img.freepik.com/free-photo/interior-design-with-photoframes-couch_23-2149385435.jpg" className="rounded-none" /></div><div className="flex flex-col gap-1"><h3 className="text-3xl font-semibold leading-snug text-balance">Retiro Park Loft</h3><p className="text-base leading-snug text-balance">An open-concept haven featuring raw textures and panoramic park views.</p></div></div>
<div key="Justicia Studio" className="flex flex-col gap-4 xl:gap-5 2xl:gap-6 h-full"><div className="aspect-square overflow-hidden"><ImageOrVideo imageSrc="https://img.freepik.com/free-photo/minimalist-living-room-interior-design_23-2150794655.jpg" className="rounded-none" /></div><div className="flex flex-col gap-1"><h3 className="text-3xl font-semibold leading-snug text-balance">Justicia Studio</h3><p className="text-base leading-snug text-balance">Maximizing natural light and bespoke joinery in a compact, elegant footprint.</p></div></div></GridOrCarousel></ScrollReveal></div></section></div>
</>
);
}

View File

@@ -6,4 +6,7 @@ export interface Route {
export const routes: Route[] = [
{ path: '/', label: 'Home', pageFile: 'HomePage' },
{ path: '/projects', label: 'Projects', pageFile: 'ProjectsPage' },
{ path: '/barrio-de-salamanca-penthouse', label: 'Barrio De Salamanca Penthouse', pageFile: 'BarrioDeSalamancaPenthousePage' },
{ path: '/blog', label: 'Blog', pageFile: 'BlogPage' },
];