wip unov landing
@ -3,6 +3,20 @@ import type { NextConfig } from "next";
|
|||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
/* config options here */
|
||||||
reactCompiler: true,
|
reactCompiler: true,
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'unov.be',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'images.unsplash.com',
|
||||||
|
pathname: '/**',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
BIN
public/desk_01.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
public/desk_02.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
public/desk_03.png
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
public/desk_04.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
public/desk_05.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
public/images/myinfomate_logo.png
Normal file
|
After Width: | Height: | Size: 240 KiB |
BIN
public/images/portfolio_namur.png
Normal file
|
After Width: | Height: | Size: 674 KiB |
BIN
public/images/service_ai_v2.png
Normal file
|
After Width: | Height: | Size: 598 KiB |
|
Before Width: | Height: | Size: 25 KiB |
@ -1,12 +1,11 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@variant dark (&:is(.dark *));
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-primary: #309CB0;
|
--color-primary: #309CB0;
|
||||||
--color-primary-light: #72B9C4;
|
--color-primary-light: #72B9C4;
|
||||||
--color-primary-lighter: #ABD3DC;
|
--color-primary-lighter: #ABD3DC;
|
||||||
--color-background-light: #F9F6F9;
|
|
||||||
--color-background-dark: #221610;
|
|
||||||
|
|
||||||
--font-display: "Public Sans", sans-serif;
|
--font-display: "Public Sans", sans-serif;
|
||||||
|
|
||||||
@keyframes marquee {
|
@keyframes marquee {
|
||||||
@ -20,24 +19,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
@layer base {
|
||||||
--background: #F9F6F9;
|
body {
|
||||||
--foreground: #0f172a;
|
background-color: #FFFFFF;
|
||||||
--primary: #309CB0;
|
color: #0f172a;
|
||||||
}
|
font-family: var(--font-display);
|
||||||
|
@apply antialiased;
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--background: #221610;
|
|
||||||
--foreground: #f8fafc;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
.material-symbols-outlined {
|
||||||
background: var(--background);
|
font-variation-settings:
|
||||||
color: var(--foreground);
|
'FILL' 0,
|
||||||
font-family: var(--font-display);
|
'wght' 400,
|
||||||
transition: background-color 0.3s ease, color 0.3s ease;
|
'GRAD' 0,
|
||||||
|
'opsz' 24;
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-marquee {
|
.animate-marquee {
|
||||||
|
|||||||
BIN
src/app/icon.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
@ -23,7 +23,7 @@ export default function RootLayout({
|
|||||||
<head>
|
<head>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght@100..700,0..1&display=swap"
|
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,0&display=swap"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
|
|||||||
570
src/app/page.tsx
@ -2,10 +2,15 @@
|
|||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
import { resolveImage } from "@/data/stitch-images";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [isScrolled, setIsScrolled] = useState(false);
|
const [isScrolled, setIsScrolled] = useState(false);
|
||||||
|
|
||||||
|
const [selectedProject, setSelectedProject] = useState<any>(null);
|
||||||
|
const [showAllProjects, setShowAllProjects] = useState(false);
|
||||||
|
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
setIsScrolled(window.scrollY > 20);
|
setIsScrolled(window.scrollY > 20);
|
||||||
@ -15,7 +20,7 @@ export default function Home() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen">
|
<div className="min-h-screen bg-white text-slate-900">
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<nav
|
<nav
|
||||||
className={`fixed top-0 w-full z-50 transition-all duration-300 ${isScrolled
|
className={`fixed top-0 w-full z-50 transition-all duration-300 ${isScrolled
|
||||||
@ -26,7 +31,7 @@ export default function Home() {
|
|||||||
<div className="max-w-7xl mx-auto px-6 flex items-center justify-between">
|
<div className="max-w-7xl mx-auto px-6 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<Image
|
<Image
|
||||||
src="/unov.png"
|
src={resolveImage("{{DATA:IMAGE:IMAGE_2}}")}
|
||||||
alt="UNOV Logo"
|
alt="UNOV Logo"
|
||||||
width={120}
|
width={120}
|
||||||
height={40}
|
height={40}
|
||||||
@ -36,24 +41,27 @@ export default function Home() {
|
|||||||
<div className="hidden md:flex items-center gap-10">
|
<div className="hidden md:flex items-center gap-10">
|
||||||
<a
|
<a
|
||||||
href="#services"
|
href="#services"
|
||||||
className="text-sm font-medium hover:text-primary transition-colors"
|
className="text-sm font-medium hover:text-primary transition-colors uppercase tracking-widest"
|
||||||
>
|
>
|
||||||
Services
|
Services
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#portfolio"
|
href="#portfolio"
|
||||||
className="text-sm font-medium hover:text-primary transition-colors"
|
className="text-sm font-medium hover:text-primary transition-colors uppercase tracking-widest"
|
||||||
>
|
>
|
||||||
Portfolio
|
Portfolio
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#technologies"
|
href="#technologies"
|
||||||
className="text-sm font-medium hover:text-primary transition-colors"
|
className="text-sm font-medium hover:text-primary transition-colors uppercase tracking-widest"
|
||||||
>
|
>
|
||||||
Technologies
|
Technologies
|
||||||
</a>
|
</a>
|
||||||
<button className="bg-primary hover:bg-primary-light text-white px-6 py-2.5 rounded-lg text-sm font-bold transition-all shadow-lg shadow-primary/20 hover:scale-105 active:scale-95">
|
<button
|
||||||
Let's Build
|
onClick={() => document.getElementById("contact")?.scrollIntoView({ behavior: "smooth" })}
|
||||||
|
className="bg-primary hover:bg-primary-light text-white px-6 py-2.5 rounded-lg text-sm font-bold transition-all shadow-lg shadow-primary/20 hover:scale-105 active:scale-95"
|
||||||
|
>
|
||||||
|
Let's Talk
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button className="md:hidden">
|
<button className="md:hidden">
|
||||||
@ -66,65 +74,67 @@ export default function Home() {
|
|||||||
<section className="relative h-screen flex items-center pt-20 overflow-hidden">
|
<section className="relative h-screen flex items-center pt-20 overflow-hidden">
|
||||||
<div className="absolute inset-0 z-0">
|
<div className="absolute inset-0 z-0">
|
||||||
<div className="absolute inset-0 bg-black/40 z-10"></div>
|
<div className="absolute inset-0 bg-black/40 z-10"></div>
|
||||||
{/* Using a placeholder high-quality image URL as fallback,
|
|
||||||
but since generate_image failed, I'll use a reliable source */}
|
|
||||||
<img
|
<img
|
||||||
src="https://images.unsplash.com/photo-1550751827-4bd374c3f58b?q=80&w=2070&auto=format&fit=crop"
|
src={resolveImage("{{DATA:IMAGE:IMAGE_6}}")}
|
||||||
alt="Hero Background"
|
alt="Hero Background"
|
||||||
className="w-full h-full object-cover opacity-80 dark:opacity-40"
|
className="w-full h-full object-cover opacity-80 dark:opacity-40"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="container mx-auto px-6 relative z-20">
|
<div className="container mx-auto px-6 relative z-20">
|
||||||
<div className="max-w-4xl mx-auto text-center flex flex-col items-center">
|
<div className="max-w-4xl flex flex-col items-start">
|
||||||
<div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full text-primary text-xs font-bold uppercase tracking-widest mb-8 bg-primary/10 backdrop-blur-sm border border-primary/20">
|
<h1 className="text-4xl md:text-7xl font-black leading-[1.1] mb-8 tracking-tight text-white drop-shadow-2xl">
|
||||||
<span className="relative flex h-2 w-2">
|
L'innovation au service <br className="hidden md:block" />
|
||||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75"></span>
|
de <span className="text-primary italic">l'utilisateur</span>
|
||||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-primary"></span>
|
|
||||||
</span>
|
|
||||||
Tech Precision Agency
|
|
||||||
</div>
|
|
||||||
<h1 className="text-5xl md:text-8xl font-black leading-[1.05] mb-8 tracking-tight text-white drop-shadow-2xl">
|
|
||||||
L'innovation au service de{" "}
|
|
||||||
<span className="text-primary italic">l'utilisateur</span>
|
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg md:text-2xl mb-12 leading-relaxed max-w-2xl text-white/90 drop-shadow-lg font-light">
|
<p className="text-lg md:text-2xl mb-12 leading-relaxed max-w-2xl text-white/80 drop-shadow-lg font-light">
|
||||||
Développement web, mobile, desktop & embarqué, intégration IA,
|
Développement web, mobile, desktop & embarqué, intégration IA,
|
||||||
modélisation et impression 3D.
|
modélisation et impression 3D.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-6 justify-center">
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
<button className="bg-primary text-white px-10 py-4 rounded-xl text-lg font-bold hover:brightness-110 hover:scale-[1.02] active:scale-95 transition-all flex items-center justify-center gap-2 shadow-xl shadow-primary/30">
|
<button
|
||||||
|
onClick={() => document.getElementById("contact")?.scrollIntoView({ behavior: "smooth" })}
|
||||||
|
className="bg-primary text-white px-10 py-4 rounded-xl text-lg font-bold hover:brightness-110 hover:scale-[1.02] active:scale-95 transition-all flex items-center justify-center gap-2 shadow-xl shadow-primary/30"
|
||||||
|
>
|
||||||
Start a Project{" "}
|
Start a Project{" "}
|
||||||
<span className="material-symbols-outlined">arrow_forward</span>
|
<span className="material-symbols-outlined font-light">arrow_forward</span>
|
||||||
</button>
|
</button>
|
||||||
<button className="bg-white/10 backdrop-blur-md text-white border border-white/20 px-10 py-4 rounded-xl text-lg font-bold hover:bg-white/20 transition-all">
|
<button
|
||||||
|
onClick={() => document.getElementById("portfolio")?.scrollIntoView({ behavior: "smooth" })}
|
||||||
|
className="bg-slate-500/30 backdrop-blur-md text-white border border-white/20 px-10 py-4 rounded-xl text-lg font-bold hover:bg-slate-500/40 transition-all font-display shadow-lg"
|
||||||
|
>
|
||||||
View Portfolio
|
View Portfolio
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Subtle Scroll Indicator */}
|
{/* Subtle Scroll Indicator */}
|
||||||
<div className="absolute bottom-10 left-1/2 -translate-x-1/2 animate-bounce opacity-70">
|
<button
|
||||||
<span className="material-symbols-outlined text-white text-4xl">
|
onClick={() => document.getElementById("services")?.scrollIntoView({ behavior: "smooth" })}
|
||||||
|
className="absolute bottom-10 left-1/2 -translate-x-1/2 animate-bounce opacity-70 hover:opacity-100 transition-opacity cursor-pointer"
|
||||||
|
>
|
||||||
|
<div className="border border-white/30 rounded-full size-12 flex items-center justify-center backdrop-blur-sm">
|
||||||
|
<span className="material-symbols-outlined text-white text-2xl">
|
||||||
expand_more
|
expand_more
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Services Section */}
|
{/* Services Section */}
|
||||||
<section className="bg-background-light dark:bg-background-dark/50 py-32" id="services">
|
<section className="bg-white dark:bg-background-dark/50 py-32" id="services">
|
||||||
<div className="container mx-auto px-6">
|
<div className="container mx-auto px-6">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-end mb-20 gap-8">
|
<div className="flex flex-col md:flex-row justify-between items-start mb-20 gap-8">
|
||||||
<div className="max-w-xl">
|
<div className="max-w-xl">
|
||||||
<h2 className="text-primary font-bold uppercase tracking-widest text-sm mb-4">
|
<h2 className="text-primary font-bold uppercase tracking-widest text-xs mb-4">
|
||||||
Expertise
|
Expertise
|
||||||
</h2>
|
</h2>
|
||||||
<h3 className="text-4xl md:text-6xl font-black leading-tight text-slate-900 dark:text-white">
|
<h3 className="text-4xl md:text-6xl font-black leading-tight text-slate-900 dark:text-white">
|
||||||
Nos Services Experts
|
Nos Services Experts
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-500 dark:text-slate-400 max-w-sm mb-2 text-lg">
|
<p className="text-slate-500 dark:text-slate-400 max-w-sm mt-8 text-sm leading-relaxed">
|
||||||
Des solutions sur-mesure conçues avec une rigueur mathématique et
|
Des solutions sur-mesure conçues avec une rigueur mathématique et
|
||||||
une vision artistique.
|
une vision centrée utilisateur.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -133,26 +143,26 @@ export default function Home() {
|
|||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
title: "Software Development",
|
title: "Software Development",
|
||||||
desc: "Architectures évolutives et robustes. Nous transformons des concepts complexes en applications fluides.",
|
desc: "Architectures évolutives et robustes. Nous transformons des concepts complexes en applications fluides et performantes.",
|
||||||
icon: "code_blocks",
|
icon: "code",
|
||||||
img: "https://images.unsplash.com/photo-1517694712202-14dd9538aa97?q=80&w=2070&auto=format&fit=crop",
|
img: resolveImage("SERVICE_SOFTWARE"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "AI Integration",
|
title: "AI Integration",
|
||||||
desc: "Automatisation intelligente et machine learning avancé pour propulser votre efficacité opérationnelle.",
|
desc: "Automatisation intelligente et machine learning avancé pour propulser votre efficacité opérationnelle.",
|
||||||
icon: "psychology",
|
icon: "psychology",
|
||||||
img: "https://images.unsplash.com/photo-1677442136019-21780ecad995?q=80&w=2070&auto=format&fit=crop",
|
img: resolveImage("SERVICE_AI"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "3D & Printing",
|
title: "3D & Printing",
|
||||||
desc: "De la conception virtuelle à la réalité physique. Prototypage rapide et ingénierie de précision.",
|
desc: "De la conception virtuelle à la réalité physique. Prototypage rapide et ingénierie de précision pour vos projets.",
|
||||||
icon: "precision_manufacturing",
|
icon: "view_in_ar",
|
||||||
img: "https://images.unsplash.com/photo-1581092160562-40aa08e78837?q=80&w=2070&auto=format&fit=crop",
|
img: resolveImage("SERVICE_3D"),
|
||||||
},
|
},
|
||||||
].map((service, idx) => (
|
].map((service, idx) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className="group bg-white dark:bg-white/5 rounded-[2.5rem] p-10 shadow-sm hover:shadow-2xl transition-all duration-500 border border-slate-100 dark:border-white/10 hover:-translate-y-2"
|
className="group bg-white dark:bg-white/5 rounded-[2.5rem] p-10 shadow-[0_10px_40px_-15px_rgba(0,0,0,0.05)] hover:shadow-2xl transition-all duration-500 border border-slate-100 dark:border-white/10 hover:-translate-y-2"
|
||||||
>
|
>
|
||||||
<div className="size-16 bg-primary/10 rounded-2xl flex items-center justify-center mb-8 group-hover:bg-primary group-hover:text-white transition-colors duration-500">
|
<div className="size-16 bg-primary/10 rounded-2xl flex items-center justify-center mb-8 group-hover:bg-primary group-hover:text-white transition-colors duration-500">
|
||||||
<span className="material-symbols-outlined text-primary group-hover:text-white text-3xl transition-colors duration-500">
|
<span className="material-symbols-outlined text-primary group-hover:text-white text-3xl transition-colors duration-500">
|
||||||
@ -162,10 +172,10 @@ export default function Home() {
|
|||||||
<h4 className="text-2xl font-bold mb-4 dark:text-white">
|
<h4 className="text-2xl font-bold mb-4 dark:text-white">
|
||||||
{service.title}
|
{service.title}
|
||||||
</h4>
|
</h4>
|
||||||
<p className="text-slate-500 dark:text-slate-400 leading-relaxed mb-8">
|
<p className="text-slate-500 dark:text-slate-400 leading-relaxed text-sm mb-8">
|
||||||
{service.desc}
|
{service.desc}
|
||||||
</p>
|
</p>
|
||||||
<div className="overflow-hidden rounded-2xl aspect-video grayscale group-hover:grayscale-0 transition-all duration-700">
|
<div className="overflow-hidden rounded-2xl aspect-[16/10] grayscale group-hover:grayscale-0 transition-all duration-700">
|
||||||
<img
|
<img
|
||||||
src={service.img}
|
src={service.img}
|
||||||
alt={service.title}
|
alt={service.title}
|
||||||
@ -178,26 +188,27 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Technologies Marquee */}
|
{/* Technologies Marquee - UPDATED LIST */}
|
||||||
<section
|
<section
|
||||||
className="py-20 border-y border-slate-200 dark:border-white/5 overflow-hidden bg-white dark:bg-background-dark"
|
className="py-24 border-y border-slate-100 dark:border-white/5 overflow-hidden bg-white dark:bg-background-dark"
|
||||||
id="technologies"
|
id="technologies"
|
||||||
>
|
>
|
||||||
<div className="flex whitespace-nowrap animate-marquee">
|
<div className="flex whitespace-nowrap animate-marquee">
|
||||||
{[...Array(2)].map((_, i) => (
|
{[...Array(2)].map((_, i) => (
|
||||||
<div key={i} className="flex items-center gap-24 px-12">
|
<div key={i} className="flex items-center gap-24 px-12">
|
||||||
{[
|
{[
|
||||||
{ name: "React", icon: "data_object" },
|
{ name: "Python", icon: "code" },
|
||||||
{ name: "Python", icon: "terminal" },
|
{ name: "TypeScript", icon: "terminal" },
|
||||||
{ name: "AWS", icon: "cloud" },
|
|
||||||
{ name: "OpenAI", icon: "smart_toy" },
|
{ name: "OpenAI", icon: "smart_toy" },
|
||||||
{ name: "Three.js", icon: "view_in_ar" },
|
{ name: "Flutter", icon: "mobile_friendly" },
|
||||||
{ name: "TypeScript", icon: "layers" },
|
{ name: "C#", icon: "integration_instructions" },
|
||||||
{ name: "Node.js", icon: "settings_input_component" },
|
{ name: "ASP.NET", icon: "settings_ethernet" },
|
||||||
|
{ name: "Angular", icon: "change_history" },
|
||||||
|
{ name: "IoT", icon: "sensors" },
|
||||||
].map((tech, j) => (
|
].map((tech, j) => (
|
||||||
<span
|
<span
|
||||||
key={j}
|
key={j}
|
||||||
className="text-3xl font-black text-slate-300 dark:text-slate-700 uppercase tracking-tighter flex items-center gap-3 hover:text-primary transition-colors cursor-default"
|
className="text-4xl font-black text-slate-200 dark:text-slate-800 uppercase tracking-tighter flex items-center gap-4 hover:text-primary transition-colors cursor-default"
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-4xl">
|
<span className="material-symbols-outlined text-4xl">
|
||||||
{tech.icon}
|
{tech.icon}
|
||||||
@ -223,37 +234,97 @@ export default function Home() {
|
|||||||
<div className="w-24 h-1.5 mx-auto bg-primary-light rounded-full"></div>
|
<div className="w-24 h-1.5 mx-auto bg-primary-light rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{(() => {
|
||||||
|
const allProjects = [
|
||||||
|
{
|
||||||
|
title: "MyInfoMate",
|
||||||
|
categories: ["Web", "Mobile"],
|
||||||
|
desc: "Solution SaaS pour la digitalisation des musées et sites culturels.",
|
||||||
|
img: resolveImage("PORTFOLIO_MYINFOMATE"),
|
||||||
|
fullDesc: "MyInfoMate est une plateforme innovante permettant aux institutions culturelles de créer, gérer et diffuser du contenu interactif sans connaissances techniques. Cartes interactives, guides multimédias et jeux de piste sont gérés via un CMS intuitif centralisé.",
|
||||||
|
link: "https://myinfomate.be",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_MYINFOMATE"),
|
||||||
|
"https://images.unsplash.com/photo-1542744173-8e7e53415bb0?q=80&w=2070",
|
||||||
|
"https://images.unsplash.com/photo-1551434678-e076c223a692?q=80&w=2070"
|
||||||
|
],
|
||||||
|
offset: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Office du Tourisme de Namur",
|
||||||
|
categories: ["Web", "Mobile"],
|
||||||
|
desc: "Interface tactile interactive sur grande tablette pour l'accueil touristique.",
|
||||||
|
img: resolveImage("PORTFOLIO_NAMUR"),
|
||||||
|
fullDesc: "Déploiement de la technologie MyInfoMate sur les bornes interactives de l'Office du Tourisme de Namur. Cette installation permet aux visiteurs d'explorer la ville de manière tactile, avec des points d'intérêt géo-localisés et une planification d'itinéraire simplifiée.",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_NAMUR"),
|
||||||
|
"https://images.unsplash.com/photo-1517048676732-d65bc937f952?q=80&w=2070",
|
||||||
|
"https://images.unsplash.com/photo-1531403001884-48a690c749ea?q=80&w=2070"
|
||||||
|
],
|
||||||
|
offset: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Carnaval de Marche",
|
||||||
|
categories: ["Impression 3D"],
|
||||||
|
desc: "Design et production de boucles d'oreilles personnalisées pour l'événement.",
|
||||||
|
img: resolveImage("PORTFOLIO_MARCHE"),
|
||||||
|
fullDesc: "Nous avons designé, imprimé et assemblé des boucles d'oreilles pour le Carnaval de Marche. Plus d'une centaine de paires ont été produites, démontrant notre capacité de prototypage et de production en petite série via l'impression 3D.",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_MARCHE"),
|
||||||
|
"https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=1964",
|
||||||
|
"https://images.unsplash.com/photo-1562408590-e32931084e23?q=80&w=2070"
|
||||||
|
],
|
||||||
|
offset: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Fort de Saint-Héribert",
|
||||||
|
categories: ["Mobile", "Embedded", "Web"],
|
||||||
|
desc: "Application mobile immersive pour la visite du fort via QR codes et localisation. Finalisée en mai 2023.",
|
||||||
|
img: resolveImage("PORTFOLIO_HERIBERT"),
|
||||||
|
fullDesc: "Notre réalisation pour le Fort de Saint-Héribert consiste en une application mobile dédiée à la visite du fort. Cette application offre une expérience immersive, où le contenu peut être consulté soit en scannant un QR code, soit en activant l'affichage de contenu basé sur la localisation du visiteur. La détection de proximité permet une interaction fluide avec le patrimoine.",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_HERIBERT"),
|
||||||
|
"https://images.unsplash.com/photo-1533106418989-88406c7cc8ca?q=80&w=2070",
|
||||||
|
"https://images.unsplash.com/photo-1496307653780-42ee777d4833?q=80&w=2070"
|
||||||
|
],
|
||||||
|
offset: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Musée de la fraise",
|
||||||
|
categories: ["Desktop", "Mobile"],
|
||||||
|
desc: "Tablettes interactives et gestionnaire de contenu pour enrichir l'expérience musée.",
|
||||||
|
img: resolveImage("PORTFOLIO_FRAISE"),
|
||||||
|
fullDesc: "Le projet pour le musée de la fraise de Wépion avait pour objectif d'intégrer des tablettes interactives. Nous avons créé un gestionnaire dédié (MyMuseum) permettant au personnel de personnaliser le contenu, ainsi que l'application mobile chargée sur les tablettes pour la consultation des visiteurs.",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_FRAISE"),
|
||||||
|
"https://images.unsplash.com/photo-1581291518062-c07a09ea0937?q=80&w=2070",
|
||||||
|
"https://images.unsplash.com/photo-1558655146-d09347e92766?q=80&w=1964"
|
||||||
|
],
|
||||||
|
offset: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Houblionnière de la Chistrée",
|
||||||
|
categories: ["Web"],
|
||||||
|
desc: "Site vitrine et boutique en ligne pour investir dans une coopérative locale.",
|
||||||
|
img: resolveImage("PORTFOLIO_CHISTREE"),
|
||||||
|
fullDesc: "La coopérative souhaitait un site internet vitrine permettant aux visiteurs d'investir dans ce beau projet via une petite boutique en ligne intégrée. Une solution Web complète alliant esthétique et fonctionnalité e-commerce.",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_CHISTREE"),
|
||||||
|
"https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?q=80&w=2070",
|
||||||
|
"https://images.unsplash.com/photo-1460925895917-afdab827c52f?q=80&w=2015"
|
||||||
|
],
|
||||||
|
offset: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const initialProjects = allProjects.slice(0, 4);
|
||||||
|
const extraProjects = allProjects.slice(4);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-16 relative">
|
||||||
|
{/* Initial 4 projects */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-16">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-16">
|
||||||
{[
|
{initialProjects.map((project, idx) => (
|
||||||
{
|
|
||||||
title: "Solaris Ecosystem",
|
|
||||||
category: "Software",
|
|
||||||
desc: "Une plateforme IoT complète pour la gestion d'énergie renouvelable.",
|
|
||||||
img: "https://images.unsplash.com/photo-1497435334941-8c899ee9e8e9?q=80&w=1974&auto=format&fit=crop",
|
|
||||||
offset: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "InsightCore AI",
|
|
||||||
category: "Artificial Intelligence",
|
|
||||||
desc: "Analyse prédictive en temps réel pour le secteur de la finance.",
|
|
||||||
img: "https://images.unsplash.com/photo-1551288049-bb6c9b468a79?q=80&w=2070&auto=format&fit=crop",
|
|
||||||
offset: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "AeroPro Modules",
|
|
||||||
category: "3D Engineering",
|
|
||||||
desc: "Composants imprimés en 3D haute performance pour l'industrie aéronautique.",
|
|
||||||
img: "https://images.unsplash.com/photo-1581092580497-e0d23cbdf1dc?q=80&w=2070&auto=format&fit=crop",
|
|
||||||
offset: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Vortex Dashboard",
|
|
||||||
category: "Cloud Services",
|
|
||||||
desc: "Interface de gestion cloud pour équipes distribuées.",
|
|
||||||
img: "https://images.unsplash.com/photo-1551434678-e076c223a692?q=80&w=2070&auto=format&fit=crop",
|
|
||||||
offset: true,
|
|
||||||
},
|
|
||||||
].map((project, idx) => (
|
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className={`group relative overflow-hidden rounded-[3rem] aspect-[4/3] cursor-pointer shadow-2xl ${project.offset ? "md:translate-y-20" : ""
|
className={`group relative overflow-hidden rounded-[3rem] aspect-[4/3] cursor-pointer shadow-2xl ${project.offset ? "md:translate-y-20" : ""
|
||||||
@ -265,19 +336,27 @@ export default function Home() {
|
|||||||
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
|
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 flex flex-col justify-end p-12">
|
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 flex flex-col justify-end p-12">
|
||||||
<span className="text-primary font-bold text-sm mb-3 uppercase tracking-widest">
|
<div className="flex flex-col">
|
||||||
{project.category}
|
<span className="text-primary font-bold text-xs uppercase tracking-widest mb-1">
|
||||||
|
{project.categories.join(" / ")}
|
||||||
</span>
|
</span>
|
||||||
<h4 className="text-white text-4xl font-bold mb-4">
|
<h3 className="text-white text-2xl font-black mb-4">
|
||||||
{project.title}
|
{project.title}
|
||||||
</h4>
|
</h3>
|
||||||
<p className="text-slate-300 text-lg mb-8 max-w-sm">
|
</div>
|
||||||
|
<p className="text-white/60 mb-8 text-sm leading-relaxed line-clamp-2">
|
||||||
{project.desc}
|
{project.desc}
|
||||||
</p>
|
</p>
|
||||||
<button className="w-fit text-white flex items-center gap-2 font-bold group/btn text-lg">
|
<button
|
||||||
View Case Study{" "}
|
onClick={() => {
|
||||||
<span className="material-symbols-outlined group-hover/btn:translate-x-2 transition-transform">
|
setSelectedProject(project);
|
||||||
arrow_right_alt
|
setCurrentImageIndex(0);
|
||||||
|
}}
|
||||||
|
className="group/btn flex items-center gap-2 text-white font-bold text-sm tracking-widest uppercase"
|
||||||
|
>
|
||||||
|
Voir plus
|
||||||
|
<span className="material-symbols-outlined text-sm group-hover/btn:translate-x-1 transition-transform">
|
||||||
|
arrow_forward
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -285,91 +364,177 @@ export default function Home() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-48 text-center">
|
{(() => {
|
||||||
<button className="px-12 py-5 border-2 border-primary text-primary hover:bg-primary hover:text-white transition-all duration-300 font-bold rounded-2xl text-xl shadow-xl shadow-primary/10">
|
const extraProjects = allProjects.slice(4, 12); // Adjusted slice for extra projects
|
||||||
See More Excellence
|
return (
|
||||||
|
<div className="relative mt-32 flex flex-col items-center">
|
||||||
|
{/* Liquid Reveal Projects Container */}
|
||||||
|
<div
|
||||||
|
className={`relative w-full transition-all duration-[2000ms] ease-[cubic-bezier(0.85,0,0.15,1)] origin-top overflow-hidden
|
||||||
|
${showAllProjects ? "max-h-[5000px] opacity-100 pb-64" : "max-h-0 opacity-0"}`}
|
||||||
|
>
|
||||||
|
{/* Branded Expansion Background */}
|
||||||
|
<div className="absolute inset-0 -mx-[50vw] left-1/2 w-[200vw] h-full bg-zinc-50 dark:bg-white/5 pointer-events-none" />
|
||||||
|
<div className="absolute inset-0 -mx-[50vw] left-1/2 w-[200vw] h-full pointer-events-none opacity-50"
|
||||||
|
style={{ background: 'radial-gradient(circle at 50% 0%, var(--primary), transparent 70%)' }} />
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 relative z-10 container mx-auto px-6 pt-32">
|
||||||
|
{extraProjects.map((project, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx + 4}
|
||||||
|
className={`group relative overflow-hidden rounded-[3rem] aspect-[4/3] cursor-pointer shadow-2xl ${project.offset ? "md:translate-y-20" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={project.img}
|
||||||
|
alt={project.title}
|
||||||
|
className="w-full h-full object-cover transition-transform duration-1000 group-hover:scale-110"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500 flex flex-col justify-end p-12">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-primary font-bold text-xs uppercase tracking-widest mb-1">
|
||||||
|
{project.categories.join(" / ")}
|
||||||
|
</span>
|
||||||
|
<h3 className="text-white text-2xl font-black mb-4">
|
||||||
|
{project.title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-white/60 mb-8 text-sm leading-relaxed line-clamp-2">
|
||||||
|
{project.desc}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedProject(project);
|
||||||
|
setCurrentImageIndex(0);
|
||||||
|
}}
|
||||||
|
className="group/btn flex items-center gap-2 text-white font-bold text-sm tracking-widest uppercase"
|
||||||
|
>
|
||||||
|
Voir plus
|
||||||
|
<span className="material-symbols-outlined text-sm group-hover/btn:translate-x-1 transition-transform">
|
||||||
|
arrow_forward
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Contact Form Section */}
|
{/* Surface & Button (Shown when not expanded) */}
|
||||||
<section className="bg-white dark:bg-background-dark py-32">
|
{!showAllProjects && (
|
||||||
|
<div className="w-full flex flex-col items-center">
|
||||||
|
{/* The Liquid Base - High Visibility Curve */}
|
||||||
|
<div className="w-screen -mx-[50vw] left-1/2 relative h-80 overflow-visible pointer-events-none -mb-40">
|
||||||
|
{/* Deep Curved Glow */}
|
||||||
|
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-screen h-[400px] bg-primary/20 blur-[120px] rounded-full opacity-60" />
|
||||||
|
<svg viewBox="0 0 1440 200" preserveAspectRatio="none" className="w-full h-40 absolute top-0 fill-primary/10 stroke-primary/20 stroke-2">
|
||||||
|
<path d="M0,200 Q720,0 1440,200 Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => setShowAllProjects(true)}
|
||||||
|
className="group relative inline-flex items-center gap-8 px-14 py-7 bg-[#0A0F1C] text-white rounded-[4rem] font-black text-lg tracking-[0.2em] uppercase transition-all duration-500 hover:scale-105 hover:shadow-[0_20px_60px_-15px_rgba(30,174,188,0.5)] z-20"
|
||||||
|
>
|
||||||
|
<div className="flex flex-col items-start text-left">
|
||||||
|
<span className="text-[10px] text-primary mb-1 tracking-[0.4em]">Découverte</span>
|
||||||
|
<span className="relative z-10">Découvrir plus de projets</span>
|
||||||
|
</div>
|
||||||
|
<div className="relative z-10 w-14 h-14 rounded-2xl bg-gradient-to-br from-primary to-primary-hover flex items-center justify-center group-hover:translate-y-2 transition-all duration-500 shadow-lg shadow-primary/20">
|
||||||
|
<span className="material-symbols-outlined text-white text-3xl font-bold">expand_more</span>
|
||||||
|
</div>
|
||||||
|
<div className="absolute inset-x-0 -bottom-1 h-2 bg-gradient-to-r from-transparent via-primary to-transparent opacity-0 group-hover:opacity-100 transition-opacity blur-md" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
{/* Contact Form Section - REVISED LAYOUT to match Image */}
|
||||||
|
<section className="bg-white dark:bg-background-dark py-32" id="contact">
|
||||||
<div className="container mx-auto px-6">
|
<div className="container mx-auto px-6">
|
||||||
<div className="max-w-6xl mx-auto bg-slate-50 dark:bg-white/5 rounded-[4rem] overflow-hidden shadow-2xl flex flex-col md:row border border-slate-200/50 dark:border-white/5">
|
<div className="max-w-6xl mx-auto bg-white dark:bg-zinc-900 rounded-[4rem] overflow-hidden shadow-[0_50px_100px_-20px_rgba(0,0,0,0.15)] flex flex-col md:flex-row border border-slate-100 dark:border-white/5">
|
||||||
<div className="md:w-2/5 bg-primary p-16 text-white flex flex-col justify-between relative overflow-hidden">
|
{/* Sidebar (Teal) */}
|
||||||
<div className="relative z-10">
|
<div className="md:w-[40%] bg-primary p-16 text-white flex flex-col relative overflow-hidden">
|
||||||
<h4 className="text-4xl font-black mb-8 leading-tight">
|
<div className="relative z-10 flex-grow">
|
||||||
Let's build the future together
|
<h4 className="text-5xl font-black mb-10 leading-tight">
|
||||||
|
Let's talk about the future
|
||||||
</h4>
|
</h4>
|
||||||
<p className="opacity-90 text-xl leading-relaxed font-light">
|
<p className="opacity-90 text-xl leading-relaxed mb-12 font-light max-w-xs">
|
||||||
Prêt à donner vie à votre vision ? Notre équipe est prête à
|
Prêt à donner vie à votre vision ? Notre équipe est prête à
|
||||||
relever vos défis les plus complexes.
|
relever vos défis les plus complexes.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-8 relative z-10 pt-12">
|
<div className="space-y-10 relative z-10 pt-12 border-t border-white/20">
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="size-12 bg-white/10 rounded-xl flex items-center justify-center backdrop-blur-md">
|
<div className="size-12 bg-white/10 rounded-xl flex items-center justify-center">
|
||||||
<span className="material-symbols-outlined">mail</span>
|
<span className="material-symbols-outlined text-white">mail</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-lg font-medium">hello@unov.tech</span>
|
<span className="text-lg font-medium">contact@unov.be</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-6">
|
<div className="flex items-center gap-6">
|
||||||
<div className="size-12 bg-white/10 rounded-xl flex items-center justify-center backdrop-blur-md">
|
<div className="size-12 bg-white/10 rounded-xl flex items-center justify-center">
|
||||||
<span className="material-symbols-outlined">location_on</span>
|
<span className="material-symbols-outlined text-white">location_on</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-lg font-medium">Paris, France</span>
|
<span className="text-lg font-medium">Vedrin, Belgique</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Decorative circle */}
|
|
||||||
<div className="absolute -bottom-24 -right-24 size-64 bg-white/10 rounded-full blur-3xl"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="md:w-3/5 p-16 bg-white dark:bg-zinc-900/50">
|
{/* Form Area (White/Right) */}
|
||||||
<form className="space-y-8">
|
<div className="md:w-[60%] p-16 bg-[#F9FAFB] dark:bg-zinc-800/20">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
<form className="space-y-12">
|
||||||
<div className="space-y-3">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||||
<label className="text-xs font-bold uppercase tracking-widest opacity-60 ml-2">
|
<div className="space-y-4">
|
||||||
Nom complet
|
<label className="text-xs font-black uppercase tracking-widest text-[#2D3748] dark:text-white/60">
|
||||||
|
Nom
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
className="w-full bg-slate-100 dark:bg-white/5 border border-transparent focus:border-primary focus:bg-white dark:focus:bg-zinc-800 focus:ring-4 focus:ring-primary/10 rounded-2xl p-5 transition-all outline-none"
|
className="w-full bg-white dark:bg-white/5 border-none rounded-2xl p-5 shadow-sm focus:ring-4 focus:ring-primary/10 transition-all outline-none text-[#1A202C] dark:text-white"
|
||||||
placeholder="John Doe"
|
placeholder="John Doe"
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-4">
|
||||||
<label className="text-xs font-bold uppercase tracking-widest opacity-60 ml-2">
|
<label className="text-xs font-black uppercase tracking-widest text-[#2D3748] dark:text-white/60">
|
||||||
Email professionnel
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
className="w-full bg-slate-100 dark:bg-white/5 border border-transparent focus:border-primary focus:bg-white dark:focus:bg-zinc-800 focus:ring-4 focus:ring-primary/10 rounded-2xl p-5 transition-all outline-none"
|
className="w-full bg-white dark:bg-white/5 border-none rounded-2xl p-5 shadow-sm focus:ring-4 focus:ring-primary/10 transition-all outline-none text-[#1A202C] dark:text-white"
|
||||||
placeholder="john@example.com"
|
placeholder="john@example.com"
|
||||||
type="email"
|
type="email"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-4">
|
||||||
<label className="text-xs font-bold uppercase tracking-widest opacity-60 ml-2">
|
<label className="text-xs font-black uppercase tracking-widest text-[#2D3748] dark:text-white/60">
|
||||||
Service souhaité
|
Service
|
||||||
</label>
|
</label>
|
||||||
<select className="w-full bg-slate-100 dark:bg-white/5 border border-transparent focus:border-primary focus:bg-white dark:focus:bg-zinc-800 focus:ring-4 focus:ring-primary/10 rounded-2xl p-5 transition-all outline-none appearance-none cursor-pointer">
|
<div className="relative">
|
||||||
<option>Software Development</option>
|
<select className="w-full bg-white dark:bg-white/5 border-none rounded-2xl p-5 shadow-sm focus:ring-4 focus:ring-primary/10 transition-all outline-none appearance-none cursor-pointer text-[#1A202C] dark:text-white font-medium">
|
||||||
<option>AI Integration</option>
|
<option className="text-[#1A202C] bg-white">Software Development</option>
|
||||||
<option>3D Modeling & Printing</option>
|
<option className="text-[#1A202C] bg-white">AI Integration</option>
|
||||||
<option>Autre</option>
|
<option className="text-[#1A202C] bg-white">3D Modeling & Printing</option>
|
||||||
|
<option className="text-[#1A202C] bg-white">Autre</option>
|
||||||
</select>
|
</select>
|
||||||
|
<span className="material-symbols-outlined absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none text-slate-400">
|
||||||
|
expand_more
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-3">
|
</div>
|
||||||
<label className="text-xs font-bold uppercase tracking-widest opacity-60 ml-2">
|
<div className="space-y-4">
|
||||||
Votre Message
|
<label className="text-xs font-black uppercase tracking-widest text-[#2D3748] dark:text-white/60">
|
||||||
|
Message
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full bg-slate-100 dark:bg-white/5 border border-transparent focus:border-primary focus:bg-white dark:focus:bg-zinc-800 focus:ring-4 focus:ring-primary/10 rounded-2xl p-5 transition-all outline-none resize-none"
|
className="w-full bg-white dark:bg-white/5 border-none rounded-2xl p-5 shadow-sm focus:ring-4 focus:ring-primary/10 transition-all outline-none resize-none text-[#1A202C] dark:text-white"
|
||||||
placeholder="Parlez-nous de votre projet..."
|
placeholder="Tell us about your project..."
|
||||||
rows={5}
|
rows={6}
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button className="w-full bg-primary text-white py-6 rounded-2xl font-black text-xl shadow-2xl shadow-primary/20 hover:brightness-110 active:scale-[0.98] transition-all">
|
<button className="w-full bg-primary text-white py-6 rounded-2xl font-black text-xl shadow-xl shadow-primary/20 hover:brightness-110 active:scale-[0.98] transition-all">
|
||||||
Envoyer la demande
|
Envoyer la demande
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@ -378,30 +543,29 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer - DARK for logo contrast */}
|
||||||
<footer className="py-20 border-t border-slate-200 dark:border-white/5 bg-background-light dark:bg-background-dark">
|
<footer className="py-24 bg-[#0A0F1C] text-white/90">
|
||||||
<div className="container mx-auto px-6">
|
<div className="container mx-auto px-6">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-center gap-12">
|
<div className="flex flex-col md:flex-row justify-between items-center gap-16">
|
||||||
<div className="flex flex-col items-center md:items-start gap-4">
|
<div className="flex flex-col items-center md:items-start gap-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3 underline-offset-4">
|
||||||
<Image
|
<Image
|
||||||
src="/unov.png"
|
src={resolveImage("{{DATA:IMAGE:IMAGE_2}}")}
|
||||||
alt="UNOV Logo"
|
alt="UNOV Logo"
|
||||||
width={100}
|
width={120}
|
||||||
height={30}
|
height={35}
|
||||||
className="h-6 w-auto object-contain"
|
className="h-8 w-auto object-contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-slate-500 text-sm max-w-xs text-center md:text-left">
|
<p className="text-slate-400 text-sm max-w-xs text-center md:text-left leading-relaxed">
|
||||||
Precision technology agency focusing on high-end software and AI
|
Agence technologique de précision spécialisée dans les logiciels haut de gamme et les solutions d'IA. La perfection dans chaque ligne de code.
|
||||||
solutions.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center gap-6">
|
<div className="flex flex-col items-center md:items-end gap-8">
|
||||||
<div className="flex gap-8">
|
<div className="flex gap-10">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
className="text-slate-400 hover:text-primary transition-all hover:scale-110"
|
className="text-slate-400 hover:text-primary transition-all hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-3xl">
|
<span className="material-symbols-outlined text-3xl">
|
||||||
language
|
language
|
||||||
@ -409,7 +573,7 @@ export default function Home() {
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
className="text-slate-400 hover:text-primary transition-all hover:scale-110"
|
className="text-slate-400 hover:text-primary transition-all hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-3xl">
|
<span className="material-symbols-outlined text-3xl">
|
||||||
public
|
public
|
||||||
@ -417,20 +581,112 @@ export default function Home() {
|
|||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
className="text-slate-400 hover:text-primary transition-all hover:scale-110"
|
className="text-slate-400 hover:text-primary transition-all hover:-translate-y-1"
|
||||||
>
|
>
|
||||||
<span className="material-symbols-outlined text-3xl">
|
<span className="material-symbols-outlined text-3xl">
|
||||||
groups
|
groups
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-slate-500 text-sm font-medium">
|
<div className="text-slate-500 text-xs font-bold uppercase tracking-widest">
|
||||||
© {new Date().getFullYear()} UNOV. Perfection in every line.
|
© {new Date().getFullYear()} UNOV. Tous droits réservés.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
{/* Project Modal */}
|
||||||
|
{
|
||||||
|
selectedProject && (
|
||||||
|
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 md:p-8">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-slate-900/60 backdrop-blur-sm"
|
||||||
|
onClick={() => setSelectedProject(null)}
|
||||||
|
/>
|
||||||
|
<div className="relative bg-white dark:bg-slate-900 w-full max-w-4xl max-h-[90vh] overflow-y-auto rounded-3xl shadow-2xl flex flex-col md:flex-row overflow-hidden border border-slate-200 dark:border-slate-800 animate-in fade-in zoom-in duration-300">
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedProject(null)}
|
||||||
|
className="absolute top-6 right-6 z-20 bg-white/10 backdrop-blur-md text-slate-900 dark:text-white w-10 h-10 rounded-full flex items-center justify-center hover:bg-white/20 transition-all"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-xl">close</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="md:w-1/2 relative h-[400px] md:h-auto overflow-hidden group/modal">
|
||||||
|
<Image
|
||||||
|
src={selectedProject.images[currentImageIndex]}
|
||||||
|
alt={selectedProject.title}
|
||||||
|
fill
|
||||||
|
className="object-cover transition-all duration-700"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-slate-900/60 to-transparent pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Gallery Controls */}
|
||||||
|
{selectedProject.images && selectedProject.images.length > 1 && (
|
||||||
|
<>
|
||||||
|
<div className="absolute inset-x-0 bottom-8 flex justify-center gap-3 z-10">
|
||||||
|
{selectedProject.images.map((_: any, i: number) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
onClick={() => setCurrentImageIndex(i)}
|
||||||
|
className={`w-2.5 h-2.5 rounded-full transition-all ${i === currentImageIndex ? "bg-primary w-8" : "bg-white/40 hover:bg-white/60"}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentImageIndex((prev) => (prev > 0 ? prev - 1 : selectedProject.images.length - 1))}
|
||||||
|
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/10 backdrop-blur-md flex items-center justify-center text-white opacity-0 group-hover/modal:opacity-100 transition-all hover:bg-white/20"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined">chevron_left</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentImageIndex((prev) => (prev < selectedProject.images.length - 1 ? prev + 1 : 0))}
|
||||||
|
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/10 backdrop-blur-md flex items-center justify-center text-white opacity-0 group-hover/modal:opacity-100 transition-all hover:bg-white/20"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined">chevron_right</span>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:w-1/2 p-8 md:p-12 flex flex-col justify-center">
|
||||||
|
<span className="text-primary font-bold uppercase tracking-[0.2em] text-xs mb-4 block">
|
||||||
|
{selectedProject.categories.join(" / ")}
|
||||||
|
</span>
|
||||||
|
<h2 className="text-3xl md:text-5xl font-black mb-8 leading-tight text-slate-900 dark:text-white">
|
||||||
|
{selectedProject.title}
|
||||||
|
</h2>
|
||||||
|
<div className="w-12 h-1 bg-primary mb-8" />
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 text-lg leading-relaxed mb-10">
|
||||||
|
{selectedProject.fullDesc || selectedProject.desc}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center mt-auto">
|
||||||
|
{selectedProject.link && (
|
||||||
|
<a
|
||||||
|
href={selectedProject.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary font-bold flex items-center gap-2 group/link"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined">language</span>
|
||||||
|
<span className="border-b-2 border-transparent group-hover/link:border-primary transition-all pb-0.5">Visiter le site</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-end flex-grow">
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedProject(null)}
|
||||||
|
className="bg-primary hover:bg-primary-hover text-white px-10 py-4 rounded-xl font-bold transition-all w-fit shadow-xl"
|
||||||
|
>
|
||||||
|
Fermer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
302
src/app/portfolio/page.tsx
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Image from "next/image";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { resolveImage } from "@/data/stitch-images";
|
||||||
|
import { useState, useMemo } from "react";
|
||||||
|
|
||||||
|
export default function PortfolioPage() {
|
||||||
|
const [activeCategory, setActiveCategory] = useState("All");
|
||||||
|
const [selectedProject, setSelectedProject] = useState<any>(null);
|
||||||
|
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||||||
|
|
||||||
|
const allProjects = [
|
||||||
|
{
|
||||||
|
title: "MyInfoMate",
|
||||||
|
categories: ["Web", "Mobile"],
|
||||||
|
desc: "Solution SaaS pour la digitalisation des musées et sites culturels.",
|
||||||
|
img: resolveImage("PORTFOLIO_MYINFOMATE"),
|
||||||
|
fullDesc: "MyInfoMate est une plateforme innovante permettant aux institutions culturelles de créer, gérer et diffuser du contenu interactif sans connaissances techniques. Cartes interactives, guides multimédias et jeux de piste sont gérés via un CMS intuitif centralisé.",
|
||||||
|
link: "https://myinfomate.be",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_MYINFOMATE"),
|
||||||
|
"https://images.unsplash.com/photo-1542744173-8e7e53415bb0?q=80&w=2070",
|
||||||
|
"https://images.unsplash.com/photo-1551434678-e076c223a692?q=80&w=2070"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Office du Tourisme de Namur",
|
||||||
|
categories: ["Web", "Mobile"],
|
||||||
|
desc: "Interface tactile interactive sur grande tablette pour l'accueil touristique.",
|
||||||
|
img: resolveImage("PORTFOLIO_NAMUR"),
|
||||||
|
fullDesc: "Déploiement de la technologie MyInfoMate sur les bornes interactives de l'Office du Tourisme de Namur. Cette installation permet aux visiteurs d'explorer la ville de manière tactile, avec des points d'intérêt géo-localisés et une planification d'itinéraire simplifiée.",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_NAMUR"),
|
||||||
|
"https://images.unsplash.com/photo-1517048676732-d65bc937f952?q=80&w=2070",
|
||||||
|
"https://images.unsplash.com/photo-1531403001884-48a690c749ea?q=80&w=2070"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Carnaval de Marche",
|
||||||
|
categories: ["Impression 3D"],
|
||||||
|
desc: "Design et production de boucles d'oreilles personnalisées pour l'événement.",
|
||||||
|
img: resolveImage("PORTFOLIO_MARCHE"),
|
||||||
|
fullDesc: "Nous avons designé, imprimé et assemblé des boucles d'oreilles pour le Carnaval de Marche. Plus d'une centaine de paires ont été produites, démontrant notre capacité de prototypage et de production en petite série via l'impression 3D.",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_MARCHE"),
|
||||||
|
"https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=1964",
|
||||||
|
"https://images.unsplash.com/photo-1562408590-e32931084e23?q=80&w=2070"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Fort de Saint-Héribert",
|
||||||
|
categories: ["Mobile", "Embedded", "Web"],
|
||||||
|
desc: "Application mobile immersive pour la visite du fort via QR codes et localisation. Finalisée en mai 2023.",
|
||||||
|
img: resolveImage("PORTFOLIO_HERIBERT"),
|
||||||
|
fullDesc: "Notre réalisation pour le Fort de Saint-Héribert consiste en une application mobile dédiée à la visite du fort. Cette application offre une expérience immersive, où le contenu peut être consulté soit en scannant un QR code, soit en activant l'affichage de contenu basé sur la localisation du visiteur. La détection de proximité permet une interaction fluide avec le patrimoine.",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_HERIBERT"),
|
||||||
|
"https://images.unsplash.com/photo-1533106418989-88406c7cc8ca?q=80&w=2070",
|
||||||
|
"https://images.unsplash.com/photo-1496307653780-42ee777d4833?q=80&w=2070"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Musée de la fraise",
|
||||||
|
categories: ["Desktop", "Mobile"],
|
||||||
|
desc: "Tablettes interactives et gestionnaire de contenu pour enrichir l'expérience musée.",
|
||||||
|
img: resolveImage("PORTFOLIO_FRAISE"),
|
||||||
|
fullDesc: "Le projet pour le musée de la fraise de Wépion avait pour objectif d'intégrer des tablettes interactives. Nous avons créé un gestionnaire dédié (MyMuseum) permettant au personnel de personnaliser le contenu, ainsi que l'application mobile chargée sur les tablettes pour la consultation des visiteurs.",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_FRAISE"),
|
||||||
|
"https://images.unsplash.com/photo-1581291518062-c07a09ea0937?q=80&w=2070",
|
||||||
|
"https://images.unsplash.com/photo-1558655146-d09347e92766?q=80&w=1964"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Houblionnière de la Chistrée",
|
||||||
|
categories: ["Web"],
|
||||||
|
desc: "Site vitrine et boutique en ligne pour investir dans une coopérative locale.",
|
||||||
|
img: resolveImage("PORTFOLIO_CHISTREE"),
|
||||||
|
fullDesc: "La coopérative souhaitait un site internet vitrine permettant aux visiteurs d'investir dans ce beau projet via une petite boutique en ligne intégrée. Une solution Web complète alliant esthétique et fonctionnalité e-commerce.",
|
||||||
|
images: [
|
||||||
|
resolveImage("PORTFOLIO_CHISTREE"),
|
||||||
|
"https://images.unsplash.com/photo-1517245386807-bb43f82c33c4?q=80&w=2070",
|
||||||
|
"https://images.unsplash.com/photo-1460925895917-afdab827c52f?q=80&w=2015"
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const filteredProjects = useMemo(() => {
|
||||||
|
if (activeCategory === "All") return allProjects;
|
||||||
|
return allProjects.filter(p => p.categories.includes(activeCategory));
|
||||||
|
}, [activeCategory, allProjects]);
|
||||||
|
|
||||||
|
const categories = ["All", "Desktop", "Embedded", "Impression 3D", "Mobile", "Web"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-slate-50 dark:bg-slate-950 font-sans flex flex-col">
|
||||||
|
<nav className="fixed top-0 w-full z-50 bg-[#0A0F1C] py-6 shadow-xl shadow-black/20">
|
||||||
|
<div className="max-w-7xl mx-auto px-6 flex items-center justify-between">
|
||||||
|
<Link href="/" className="flex items-center gap-3">
|
||||||
|
<Image
|
||||||
|
src={resolveImage("{{DATA:IMAGE:IMAGE_2}}")}
|
||||||
|
alt="UNOV Logo"
|
||||||
|
width={120}
|
||||||
|
height={40}
|
||||||
|
className="h-8 w-auto object-contain"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<Link href="/" className="text-sm font-black uppercase tracking-widest text-primary hover:text-primary-light transition-all flex items-center gap-2">
|
||||||
|
<span className="material-symbols-outlined text-sm">arrow_back</span>
|
||||||
|
Retour à l'accueil
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className="pt-32 pb-20 px-6 max-w-7xl mx-auto flex-grow w-full">
|
||||||
|
<header className="mb-20">
|
||||||
|
<h1 className="text-5xl md:text-7xl font-black mb-8 text-slate-900 dark:text-white">
|
||||||
|
Nos <span className="text-primary italic">Réalisations</span>
|
||||||
|
</h1>
|
||||||
|
<div className="flex flex-wrap gap-4">
|
||||||
|
{categories.map((cat) => (
|
||||||
|
<button
|
||||||
|
key={cat}
|
||||||
|
onClick={() => setActiveCategory(cat)}
|
||||||
|
className={`px-8 py-3 rounded-2xl text-sm font-black tracking-widest uppercase transition-all ${cat === activeCategory ? "bg-primary text-white shadow-lg shadow-primary/30 scale-105" : "bg-white dark:bg-slate-900 text-slate-500 hover:bg-slate-50 dark:hover:bg-slate-800 hover:scale-105"}`}
|
||||||
|
>
|
||||||
|
{cat}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-12">
|
||||||
|
{filteredProjects.length > 0 ? (
|
||||||
|
filteredProjects.map((p, i) => (
|
||||||
|
<div key={i} className="group bg-white dark:bg-slate-900 rounded-[3rem] overflow-hidden shadow-2xl border border-slate-100 dark:border-slate-800 hover:scale-[1.02] transition-all duration-500">
|
||||||
|
<div className="relative h-72 overflow-hidden">
|
||||||
|
<Image src={p.img} alt={p.title} fill className="object-cover transition-transform duration-700 group-hover:scale-110" />
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-slate-900/40 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||||
|
</div>
|
||||||
|
<div className="p-12">
|
||||||
|
<span className="text-primary font-black text-xs uppercase tracking-[0.2em] mb-4 block">
|
||||||
|
{p.categories.join(" / ")}
|
||||||
|
</span>
|
||||||
|
<h2 className="text-3xl font-black mb-6 text-slate-900 dark:text-white leading-tight">{p.title}</h2>
|
||||||
|
<p className="text-slate-500 dark:text-slate-400 mb-10 leading-relaxed text-sm">{p.desc}</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedProject(p);
|
||||||
|
setCurrentImageIndex(0);
|
||||||
|
}}
|
||||||
|
className="text-slate-900 dark:text-white font-black text-xs uppercase tracking-widest flex items-center gap-2 group/btn"
|
||||||
|
>
|
||||||
|
Voir plus
|
||||||
|
<span className="material-symbols-outlined text-sm transition-transform group-hover/btn:translate-x-1">arrow_forward</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="col-span-full py-20 text-center">
|
||||||
|
<span className="material-symbols-outlined text-6xl text-slate-300 mb-6">inventory_2</span>
|
||||||
|
<p className="text-slate-400 font-medium">Aucun projet trouvé dans cette catégorie pour le moment.</p>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveCategory("All")}
|
||||||
|
className="mt-8 text-primary font-bold hover:underline"
|
||||||
|
>
|
||||||
|
Voir tous les projets
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* Project Modal */}
|
||||||
|
{selectedProject && (
|
||||||
|
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 md:p-8">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-slate-900/60 backdrop-blur-sm"
|
||||||
|
onClick={() => setSelectedProject(null)}
|
||||||
|
/>
|
||||||
|
<div className="relative bg-white dark:bg-slate-900 w-full max-w-4xl max-h-[90vh] overflow-y-auto rounded-3xl shadow-2xl flex flex-col md:flex-row overflow-hidden border border-slate-200 dark:border-slate-800 animate-in fade-in zoom-in duration-300">
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedProject(null)}
|
||||||
|
className="absolute top-6 right-6 z-20 bg-white/10 backdrop-blur-md text-slate-900 dark:text-white w-10 h-10 rounded-full flex items-center justify-center hover:bg-white/20 transition-all"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined text-xl">close</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="md:w-1/2 relative h-[400px] md:h-auto overflow-hidden group/modal">
|
||||||
|
<Image
|
||||||
|
src={selectedProject.images[currentImageIndex]}
|
||||||
|
alt={selectedProject.title}
|
||||||
|
fill
|
||||||
|
className="object-cover transition-all duration-700"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-slate-900/60 to-transparent pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Gallery Controls */}
|
||||||
|
{selectedProject.images && selectedProject.images.length > 1 && (
|
||||||
|
<>
|
||||||
|
<div className="absolute inset-x-0 bottom-8 flex justify-center gap-3 z-10">
|
||||||
|
{selectedProject.images.map((_: any, i: number) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
onClick={() => setCurrentImageIndex(i)}
|
||||||
|
className={`w-2.5 h-2.5 rounded-full transition-all ${i === currentImageIndex ? "bg-primary w-8" : "bg-white/40 hover:bg-white/60"}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentImageIndex((prev) => (prev > 0 ? prev - 1 : selectedProject.images.length - 1))}
|
||||||
|
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/10 backdrop-blur-md flex items-center justify-center text-white opacity-0 group-hover/modal:opacity-100 transition-all hover:bg-white/20"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined">chevron_left</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setCurrentImageIndex((prev) => (prev < selectedProject.images.length - 1 ? prev + 1 : 0))}
|
||||||
|
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/10 backdrop-blur-md flex items-center justify-center text-white opacity-0 group-hover/modal:opacity-100 transition-all hover:bg-white/20"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined">chevron_right</span>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="md:w-1/2 p-8 md:p-12 flex flex-col justify-center">
|
||||||
|
<span className="text-primary font-bold uppercase tracking-[0.2em] text-xs mb-4 block">
|
||||||
|
{selectedProject.categories.join(" / ")}
|
||||||
|
</span>
|
||||||
|
<h2 className="text-3xl md:text-5xl font-black mb-8 leading-tight text-slate-900 dark:text-white">
|
||||||
|
{selectedProject.title}
|
||||||
|
</h2>
|
||||||
|
<div className="w-12 h-1 bg-primary mb-8" />
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 text-lg leading-relaxed mb-10">
|
||||||
|
{selectedProject.fullDesc || selectedProject.desc}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center mt-auto">
|
||||||
|
{selectedProject.link && (
|
||||||
|
<a
|
||||||
|
href={selectedProject.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary font-bold flex items-center gap-2 group/link"
|
||||||
|
>
|
||||||
|
<span className="material-symbols-outlined">language</span>
|
||||||
|
<span className="border-b-2 border-transparent group-hover/link:border-primary transition-all pb-0.5">Visiter le site</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-end flex-grow">
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedProject(null)}
|
||||||
|
className="bg-primary hover:bg-primary-hover text-white px-10 py-4 rounded-xl font-bold transition-all w-fit shadow-xl"
|
||||||
|
>
|
||||||
|
Fermer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<footer className="py-24 bg-[#0A0F1C] text-white/90">
|
||||||
|
<div className="max-w-7xl mx-auto px-6">
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-center gap-16">
|
||||||
|
<div className="flex flex-col items-center md:items-start gap-6">
|
||||||
|
<Image
|
||||||
|
src={resolveImage("{{DATA:IMAGE:IMAGE_2}}")}
|
||||||
|
alt="UNOV Logo"
|
||||||
|
width={120}
|
||||||
|
height={35}
|
||||||
|
className="h-8 w-auto object-contain"
|
||||||
|
/>
|
||||||
|
<p className="text-slate-400 text-sm max-w-xs text-center md:text-left leading-relaxed">
|
||||||
|
Agence technologique de précision spécialisée dans les logiciels haut de gamme et les solutions d'IA. La perfection dans chaque ligne de code.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center md:items-end gap-8">
|
||||||
|
<div className="flex gap-10">
|
||||||
|
<a href="#" className="text-slate-400 hover:text-primary transition-all hover:-translate-y-1">
|
||||||
|
<span className="material-symbols-outlined text-3xl">language</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" className="text-slate-400 hover:text-primary transition-all hover:-translate-y-1">
|
||||||
|
<span className="material-symbols-outlined text-3xl">public</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" className="text-slate-400 hover:text-primary transition-all hover:-translate-y-1">
|
||||||
|
<span className="material-symbols-outlined text-3xl">groups</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="text-slate-500 text-xs font-bold uppercase tracking-widest">
|
||||||
|
© {new Date().getFullYear()} UNOV. Tous droits réservés.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
25
src/data/stitch-images.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Mapping object for Stitch image placeholders.
|
||||||
|
* Centralized management of landing page assets.
|
||||||
|
*/
|
||||||
|
export const STITCH_IMAGES: Record<string, string> = {
|
||||||
|
"{{DATA:IMAGE:IMAGE_2}}": "/unov.png",
|
||||||
|
"{{DATA:IMAGE:IMAGE_6}}": "/desk_04.png",
|
||||||
|
"SERVICE_SOFTWARE": "https://images.unsplash.com/photo-1517694712202-14dd9538aa97?q=80&w=2070&auto=format&fit=crop",
|
||||||
|
"SERVICE_AI": "/images/service_ai_v2.png",
|
||||||
|
"SERVICE_3D": "https://images.unsplash.com/photo-1581092160562-40aa08e78837?q=80&w=2070&auto=format&fit=crop",
|
||||||
|
"PORTFOLIO_HERIBERT": "https://unov.be/wp-content/uploads/2023/06/Screenshot_8.jpg",
|
||||||
|
"PORTFOLIO_MARCHE": "https://unov.be/wp-content/uploads/2022/11/Screenshot_1-370x370.jpg",
|
||||||
|
"PORTFOLIO_CHISTREE": "https://unov.be/wp-content/uploads/2022/04/houblon_0-370x370.jpg",
|
||||||
|
"PORTFOLIO_FRAISE": "https://unov.be/wp-content/uploads/2022/04/mdlf_3-370x370.jpg",
|
||||||
|
"PORTFOLIO_MYINFOMATE": "/images/myinfomate_logo.png",
|
||||||
|
"PORTFOLIO_NAMUR": "/images/portfolio_namur.png",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to resolve a Stitch placeholder.
|
||||||
|
* If the placeholder is not found, it returns the placeholder itself.
|
||||||
|
*/
|
||||||
|
export function resolveImage(key: string): string {
|
||||||
|
return STITCH_IMAGES[key] || key;
|
||||||
|
}
|
||||||