ci: use internal registry (bypass Cloudflare upload limit)
CI/CD / deploy (push) Failing after 41s
Details
CI/CD / deploy (push) Failing after 41s
Details
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7e3ed3da6c
commit
c7b97aa0cc
|
|
@ -9,8 +9,8 @@ on:
|
|||
branches: [main]
|
||||
|
||||
env:
|
||||
REGISTRY: gitea.jeffemmett.com
|
||||
IMAGE: gitea.jeffemmett.com/jeffemmett/xhivart-mirror
|
||||
REGISTRY: localhost:3000
|
||||
IMAGE: localhost:3000/jeffemmett/xhivart-mirror
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ RUN mkdir -p /data && chown nextjs:nodejs /data
|
|||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Create CMS data directories
|
||||
RUN mkdir -p /app/data/content /app/data/uploads
|
||||
RUN chown -R nextjs:nodejs /app/data
|
||||
|
||||
# Copy standalone output
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ services:
|
|||
- SMTP_FROM=${SMTP_FROM}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
volumes:
|
||||
- xhivart-data:/data
|
||||
- xhivart-data:/app/data
|
||||
networks:
|
||||
- traefik-public
|
||||
labels:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ function escapeHtml(str: string): string {
|
|||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { name, email, message } = body;
|
||||
const { name, email, service, message } = body;
|
||||
|
||||
if (!name || typeof name !== 'string' || name.trim().length === 0) {
|
||||
return NextResponse.json({ error: 'Name is required' }, { status: 400 });
|
||||
|
|
@ -36,8 +36,16 @@ export async function POST(request: Request) {
|
|||
|
||||
const safeName = escapeHtml(name.trim());
|
||||
const safeEmail = escapeHtml(email.trim());
|
||||
const safeService = service && typeof service === 'string' ? escapeHtml(service.trim()) : '';
|
||||
const safeMessage = escapeHtml(message.trim());
|
||||
|
||||
const serviceSection = safeService
|
||||
? `<div style="margin-bottom: 24px;">
|
||||
<p style="font-family: Montserrat, sans-serif; font-size: 11px; letter-spacing: 0.15em; text-transform: uppercase; color: #6b6b6b; margin: 0 0 4px 0;">SERVICE</p>
|
||||
<p style="font-size: 16px; color: #2d2d2d; margin: 0;">${safeService}</p>
|
||||
</div>`
|
||||
: '';
|
||||
|
||||
await transporter.sendMail({
|
||||
from: `XHIVA Art <${process.env.SMTP_FROM || process.env.SMTP_USER}>`,
|
||||
to: 'xhivart@gmail.com',
|
||||
|
|
@ -60,13 +68,14 @@ export async function POST(request: Request) {
|
|||
<a href="mailto:${safeEmail}" style="color: #c9a962;">${safeEmail}</a>
|
||||
</p>
|
||||
</div>
|
||||
${serviceSection}
|
||||
<div style="margin-bottom: 24px;">
|
||||
<p style="font-family: Montserrat, sans-serif; font-size: 11px; letter-spacing: 0.15em; text-transform: uppercase; color: #6b6b6b; margin: 0 0 4px 0;">MESSAGE</p>
|
||||
<p style="font-size: 16px; color: #2d2d2d; margin: 0; white-space: pre-wrap;">${safeMessage}</p>
|
||||
</div>
|
||||
<div style="border-top: 1px solid #e5e5e5; padding-top: 20px; margin-top: 30px;">
|
||||
<p style="font-family: Montserrat, sans-serif; font-size: 10px; letter-spacing: 0.1em; color: #6b6b6b; margin: 0;">
|
||||
Sent from xhivart.jeffemmett.com contact form
|
||||
Sent from xhiva.art contact form
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -89,12 +89,71 @@
|
|||
color: var(--text-dark);
|
||||
transition: color 0.3s ease;
|
||||
padding: 0.5rem 1rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--accent-gold);
|
||||
}
|
||||
|
||||
.nav-link-active {
|
||||
color: var(--accent-gold);
|
||||
}
|
||||
|
||||
.nav-chevron {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
/* Dropdown wrapper */
|
||||
.nav-dropdown-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
min-width: 200px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 0.75rem;
|
||||
padding: 0.5rem 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.nav-dropdown-wrapper:hover .nav-dropdown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.nav-dropdown-wrapper:hover .nav-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.nav-dropdown-item {
|
||||
display: block;
|
||||
padding: 0.5rem 1.5rem;
|
||||
font-family: var(--font-montserrat), 'Montserrat', sans-serif;
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
color: var(--text-dark);
|
||||
transition: color 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-dropdown-item:hover {
|
||||
color: var(--accent-gold);
|
||||
background: rgba(201, 169, 98, 0.05);
|
||||
}
|
||||
|
||||
/* Elegant button styles */
|
||||
.btn-outline {
|
||||
display: inline-block;
|
||||
|
|
@ -178,6 +237,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Page hero — shorter than homepage hero */
|
||||
.page-hero {
|
||||
min-height: 60vh;
|
||||
padding: 8rem 2rem 4rem;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.page-hero {
|
||||
padding: 10rem 4rem 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Decorative elements */
|
||||
.divider {
|
||||
width: 60px;
|
||||
|
|
@ -219,6 +290,41 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
/* Price tag */
|
||||
.price-tag {
|
||||
font-family: var(--font-montserrat), 'Montserrat', sans-serif;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--accent-gold);
|
||||
}
|
||||
|
||||
/* Methodology step */
|
||||
.methodology-step {
|
||||
padding: 1.5rem;
|
||||
border-left: 2px solid rgba(201, 169, 98, 0.3);
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.methodology-step:hover {
|
||||
border-color: var(--accent-gold);
|
||||
}
|
||||
|
||||
/* Role card */
|
||||
.role-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.role-card:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-color: var(--accent-gold);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer-link {
|
||||
font-family: var(--font-montserrat), 'Montserrat', sans-serif;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Cormorant_Garamond, Montserrat } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import Navigation from "@/components/Navigation";
|
||||
import Footer from "@/components/Footer";
|
||||
|
||||
const cormorant = Cormorant_Garamond({
|
||||
variable: "--font-cormorant",
|
||||
|
|
@ -34,7 +36,9 @@ export default function RootLayout({
|
|||
<body
|
||||
className={`${cormorant.variable} ${montserrat.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
<Navigation />
|
||||
<main>{children}</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
|
|
|||
410
src/app/page.tsx
410
src/app/page.tsx
|
|
@ -1,12 +1,408 @@
|
|||
import { getArtworks, getEvents, getServices } from '@/lib/data';
|
||||
import HomeClient from './home-client';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { services as defaultServices } from '@/lib/data/services';
|
||||
import { testimonials as defaultTestimonials } from '@/lib/data/testimonials';
|
||||
import { readData } from '@/lib/cms';
|
||||
import type { Service } from '@/lib/data/services';
|
||||
import type { Testimonial } from '@/lib/data/testimonials';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export default function Home() {
|
||||
const artworks = getArtworks();
|
||||
const events = getEvents();
|
||||
const services = getServices();
|
||||
// Hero Section
|
||||
function HeroSection() {
|
||||
return (
|
||||
<section className="min-h-screen relative flex flex-col items-center justify-center text-center px-6 pt-20">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src="/images/hero/hero-main.jpg"
|
||||
alt="Ximena Xaguar"
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
/>
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-[var(--bg-cream)]/90 via-[var(--bg-cream)]/70 to-[var(--bg-cream)]/90" />
|
||||
</div>
|
||||
|
||||
return <HomeClient artworks={artworks} events={events} services={services} />;
|
||||
<div className="max-w-4xl mx-auto relative z-10">
|
||||
<p className="font-sans-alt text-xs tracking-[0.3em] text-[var(--text-muted)] mb-6 fade-in">
|
||||
MULTIDISCIPLINARY VISIONARY ARTIST
|
||||
</p>
|
||||
<h1 className="text-5xl md:text-7xl lg:text-8xl font-light tracking-wider mb-8 fade-in stagger-1">
|
||||
XIMENA XAGUAR
|
||||
</h1>
|
||||
<div className="divider fade-in stagger-2"></div>
|
||||
<p className="text-lg md:text-xl max-w-2xl mx-auto mb-16 leading-relaxed opacity-80 fade-in stagger-2">
|
||||
Working at the intersection of art, ritual and embodied presence, creating experiences
|
||||
that support clarity, integration, exploration and transformation.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center fade-in stagger-3">
|
||||
<Link href="/art" className="btn-outline">
|
||||
Explore Art & Ritual Sessions
|
||||
</Link>
|
||||
<Link href="/re-evolution-art" className="btn-filled">
|
||||
Discover Re Evolution Art
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 animate-bounce opacity-50 z-10">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
<path d="M12 5v14M5 12l7 7 7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Ritual Art Alchemy Preview
|
||||
function RitualArtSection() {
|
||||
return (
|
||||
<section className="section bg-white">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="grid md:grid-cols-2 gap-12 items-center">
|
||||
<div className="relative aspect-[3/4] rounded-2xl overflow-hidden shadow-xl">
|
||||
<Image
|
||||
src="/images/art/featured.jpg?v=2"
|
||||
alt="Visionary Art by Ximena Xaguar"
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<p className="font-sans-alt text-xs tracking-[0.2em] text-[var(--accent-gold)] mb-4">
|
||||
VISUAL ALCHEMY
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-light mb-6">
|
||||
Ritual Art Alchemy
|
||||
</h2>
|
||||
<div className="divider"></div>
|
||||
<p className="text-lg leading-relaxed mb-6 opacity-80">
|
||||
My art is my ritual — a journey of transformation. Each painting emerges from
|
||||
cycles of death and rebirth, shadow work, intuitive vision and ancestral memory.
|
||||
</p>
|
||||
<p className="text-lg leading-relaxed mb-12 opacity-80">
|
||||
Through symbolic language and universal cosmovision, I translate inner processes
|
||||
into living images. These artworks are not objects. They are portals.
|
||||
</p>
|
||||
<div className="pt-2">
|
||||
<Link href="/art" className="btn-outline">
|
||||
Enter the Gallery
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Services Preview (abbreviated)
|
||||
function ServicesPreview({ services }: { services: Service[] }) {
|
||||
const previewServices = services.slice(0, 4);
|
||||
|
||||
return (
|
||||
<section className="section">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<p className="font-sans-alt text-xs tracking-[0.2em] text-[var(--accent-gold)] mb-4">
|
||||
OFFERINGS
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-light mb-6">
|
||||
Ritual Healing & Integration
|
||||
</h2>
|
||||
<div className="divider"></div>
|
||||
<p className="text-lg max-w-2xl mx-auto opacity-80">
|
||||
Decades of embodied practice since 2009. Current focus is integration and embodiment —
|
||||
supporting people to anchor insight, develop inner coherence and regulate nervous systems
|
||||
within deep transformation processes.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{previewServices.map((service, index) => (
|
||||
<div key={index} className={`group ${service.highlighted ? 'ring-2 ring-[var(--accent-gold)] rounded-xl' : ''}`}>
|
||||
<div className="relative aspect-[4/3] rounded-t-xl overflow-hidden">
|
||||
<Image
|
||||
src={service.image}
|
||||
alt={service.title}
|
||||
fill
|
||||
className="object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
<div className={`service-card ${service.color} rounded-t-none`}>
|
||||
<h3 className="text-2xl font-light mb-2">{service.title}</h3>
|
||||
<p className="font-sans-alt text-xs tracking-widest text-[var(--text-muted)] mb-1">
|
||||
{service.subtitle}
|
||||
</p>
|
||||
<p className="font-sans-alt text-xs tracking-widest text-[var(--accent-gold)] mb-6">
|
||||
{service.duration}
|
||||
</p>
|
||||
<p className="text-sm leading-relaxed opacity-80">
|
||||
{service.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center mt-20">
|
||||
<Link href="/services" className="btn-outline">
|
||||
View All Services
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Re Evolution Art Preview
|
||||
function ReEvolutionPreview() {
|
||||
return (
|
||||
<section className="section dark-section">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<div className="relative w-full max-w-96 aspect-[3/2] mx-auto mb-2 overflow-hidden">
|
||||
<Image
|
||||
src="/images/reevolution/logo.png"
|
||||
alt="Re Evolution Art Logo"
|
||||
fill
|
||||
className="object-contain object-center scale-150"
|
||||
/>
|
||||
</div>
|
||||
<p style={{ fontFamily: "'Narrenschiff', sans-serif" }} className="text-xs tracking-[0.2em] text-[var(--accent-gold)] mb-4">
|
||||
CULTURAL PLATFORM
|
||||
</p>
|
||||
<h2 style={{ fontFamily: "'Krown', sans-serif", fontWeight: 700 }} className="text-4xl md:text-5xl mb-6">
|
||||
Re Evolution Art
|
||||
</h2>
|
||||
<div className="divider"></div>
|
||||
<p style={{ fontFamily: "'Narrenschiff', sans-serif" }} className="text-lg max-w-3xl mx-auto opacity-80">
|
||||
A visionary cultural platform based in Switzerland and rooted in Bolivia. Through exhibitions,
|
||||
immersive gatherings, TRIBAL events and collaborative projects, we bring together artists,
|
||||
ritualists, musicians and independent creators engaged in inner work and cultural dialogue.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center gap-4 mt-8">
|
||||
<Link href="/re-evolution-art" className="btn-outline btn-outline-light" style={{ fontFamily: "'Narrenschiff', sans-serif" }}>
|
||||
Discover the Platform
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Testimonials (showing 3 on homepage)
|
||||
function TestimonialsSection({ testimonials }: { testimonials: Testimonial[] }) {
|
||||
const homeTestimonials = testimonials.slice(0, 3);
|
||||
|
||||
return (
|
||||
<section className="section bg-white">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<p className="font-sans-alt text-xs tracking-[0.2em] text-[var(--accent-gold)] mb-4">
|
||||
TESTIMONIALS
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-light mb-6">
|
||||
Words of Gratitude
|
||||
</h2>
|
||||
<div className="divider"></div>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{homeTestimonials.map((testimonial, index) => (
|
||||
<div key={index} className="testimonial-card">
|
||||
<span className="quote-mark">“</span>
|
||||
<p className="testimonial mb-6">
|
||||
{testimonial.quote}
|
||||
</p>
|
||||
<p className="font-sans-alt text-xs tracking-widest text-[var(--accent-gold)]">
|
||||
— {testimonial.author}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Work With Me
|
||||
function WorkWithMeSection() {
|
||||
return (
|
||||
<section className="section relative overflow-hidden">
|
||||
<div className="absolute inset-0 z-0 bg-[#0f0f0f]">
|
||||
<Image
|
||||
src="/images/art/wayra-bg.webp"
|
||||
alt="Wayra"
|
||||
fill
|
||||
className="object-contain opacity-40"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="max-w-3xl mx-auto relative z-10">
|
||||
<div className="text-center">
|
||||
<h2 className="text-4xl md:text-5xl font-light mb-6 text-[var(--text-light)]">
|
||||
Work With Me
|
||||
</h2>
|
||||
<div className="divider"></div>
|
||||
<p className="text-lg leading-relaxed mb-16 opacity-80 max-w-2xl mx-auto text-[var(--text-light)]">
|
||||
This work is for those ready to meet themselves honestly with courage,
|
||||
sensitivity and presence.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||
<Link href="/services" className="btn-outline btn-outline-light">
|
||||
Explore Sessions
|
||||
</Link>
|
||||
<Link href="/contact" className="btn-filled" style={{ background: 'var(--accent-gold)', borderColor: 'var(--accent-gold)' }}>
|
||||
Contact Me
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Social Section
|
||||
function SocialSection() {
|
||||
const instagramPosts = [
|
||||
{ image: '/images/art/soul-agreement.webp', alt: 'Soul Agreement' },
|
||||
{ image: '/images/art/goddess.webp?v=2', alt: 'Goddess' },
|
||||
{ image: '/images/art/twin-flames.webp', alt: 'Twin Flames' },
|
||||
{ image: '/images/art/featured.jpg?v=2', alt: 'Featured Art' },
|
||||
{ image: '/images/about/portrait-1.jpg', alt: 'Portrait' },
|
||||
{ image: '/images/reevolution/dj-xhiva.jpg', alt: 'DJ XHIVA' },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="section bg-[var(--bg-cream)]">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<p className="font-sans-alt text-xs tracking-[0.2em] text-[var(--accent-gold)] mb-4">
|
||||
FOLLOW THE JOURNEY
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-light mb-6">
|
||||
Connect on Socials
|
||||
</h2>
|
||||
<div className="divider"></div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 mb-10 max-w-4xl mx-auto">
|
||||
{instagramPosts.map((post, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href="https://www.instagram.com/xhiva_art"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group bg-white rounded-xl shadow-md hover:shadow-xl transition-shadow duration-300 overflow-hidden border border-gray-100"
|
||||
>
|
||||
<div className="flex items-center gap-2 px-3 py-2.5">
|
||||
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-[#f09433] via-[#e6683c] to-[#bc1888] p-[2px]">
|
||||
<div className="w-full h-full rounded-full bg-white p-[1px]">
|
||||
<div className="relative w-full h-full rounded-full overflow-hidden">
|
||||
<Image src="/images/about/portrait-1.jpg" alt="xhiva_art" fill className="object-cover object-[30%_center]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-xs font-semibold text-[var(--text-dark)]">xhiva_art</span>
|
||||
</div>
|
||||
<div className="relative aspect-square">
|
||||
<Image
|
||||
src={post.image}
|
||||
alt={post.alt}
|
||||
fill
|
||||
className="object-cover"
|
||||
style={post.image.includes('portrait-1') ? { objectPosition: '30% center' } : post.image.includes('dj-xhiva') ? { objectPosition: 'center top' } : undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className="px-3 py-2.5 flex items-center gap-4">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[var(--text-dark)] group-hover:text-red-500 transition-colors">
|
||||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
|
||||
</svg>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[var(--text-dark)]">
|
||||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="text-[var(--text-dark)]">
|
||||
<line x1="22" y1="2" x2="11" y2="13" /><polygon points="22 2 15 22 11 13 2 9 22 2" />
|
||||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row justify-center items-center gap-4">
|
||||
<a
|
||||
href="https://www.instagram.com/xhiva_art"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn-filled"
|
||||
>
|
||||
Follow @xhiva_art on Instagram
|
||||
</a>
|
||||
<a
|
||||
href="https://www.facebook.com/XimenaXhivart"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn-outline"
|
||||
>
|
||||
Follow on Facebook
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
// Newsletter Section
|
||||
function NewsletterSection() {
|
||||
return (
|
||||
<section className="section dark-section">
|
||||
<div className="max-w-3xl mx-auto text-center">
|
||||
<p className="font-sans-alt text-xs tracking-[0.2em] text-[var(--accent-gold)] mb-4">
|
||||
STAY CONNECTED
|
||||
</p>
|
||||
<h2 className="text-4xl md:text-5xl font-light mb-6">
|
||||
Be Part of the RE EVOLUTION
|
||||
</h2>
|
||||
<div className="divider"></div>
|
||||
<p className="text-lg leading-relaxed mb-10 opacity-80 max-w-2xl mx-auto">
|
||||
Receive updates on exhibitions, events, new artworks and offerings.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||
<Link href="/contact" className="btn-outline btn-outline-light">
|
||||
Get in Touch
|
||||
</Link>
|
||||
<a
|
||||
href="https://www.instagram.com/xhiva_art"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn-outline btn-outline-light"
|
||||
>
|
||||
Follow on Instagram
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
const services = readData<Service>('services', defaultServices);
|
||||
const testimonials = readData<Testimonial>('testimonials', defaultTestimonials);
|
||||
|
||||
return (
|
||||
<>
|
||||
<HeroSection />
|
||||
<RitualArtSection />
|
||||
<ServicesPreview services={services} />
|
||||
<ReEvolutionPreview />
|
||||
<TestimonialsSection testimonials={testimonials} />
|
||||
<WorkWithMeSection />
|
||||
<SocialSection />
|
||||
<NewsletterSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue