From 1598c8d3b026725e717f4905b1a9b29cf545a0ae Mon Sep 17 00:00:00 2001 From: Alejandro Repetto Date: Thu, 16 Apr 2026 00:01:12 -0300 Subject: [PATCH 1/8] Aggressive aesthetic redesign: indigo accent, project thumbnails, premium polish - Introduce indigo brand color (oklch 0.52 0.22 263 light / 0.68 0.18 263 dark) replacing achromatic primary across all tokens, buttons, badges, focus rings - Add project thumbnail images to card grid (16:9, hover zoom, gradient overlay) - Upgrade hero: edge-to-edge image, ambient glow ring, dual CTA (projects + resume), emerald pulse indicator on status card, dot-grid background, primary-colored role - Add eyebrow label pattern to SectionHeader (all major sections now use it) - Skills cards: per-category colored top border (indigo/violet/cyan/amber) + matching badge hover states - About section: accent-line decorated subheadings, improved spacing rhythm - Contact section: add proper SectionHeader with eyebrow - Footer: mono name/brand prominence - Button: active:scale-[0.98], hover:shadow-md on primary, border-primary on outline hover - Nav + footer links: hover to primary instead of foreground - Awards + project cards: hover:-translate-y-1 for elevation feel - CSS: scroll-reveal utility (view-timeline, reduced-motion aware), gradient-text utility, fade-in-up keyframes Co-Authored-By: Claude Sonnet 4.6 --- app/globals.css | 62 ++- components/footer.tsx | 9 +- components/layout/section-header.tsx | 34 +- components/navigation.tsx | 4 +- components/sections/about-section.tsx | 112 ++--- components/sections/awards-section.tsx | 3 +- components/sections/contact-section.tsx | 9 +- components/sections/hero-section.tsx | 292 +++++++------ components/sections/projects-section.tsx | 501 ++++++++++++----------- components/sections/skills-section.tsx | 264 ++++++------ components/ui/button.tsx | 8 +- 11 files changed, 721 insertions(+), 577 deletions(-) diff --git a/app/globals.css b/app/globals.css index 0e9e370..018469c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -10,7 +10,7 @@ --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); + --primary: oklch(0.52 0.22 263); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.97 0 0); --secondary-foreground: oklch(0.205 0 0); @@ -22,7 +22,7 @@ --destructive-foreground: oklch(0.577 0.245 27.325); --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); + --ring: oklch(0.52 0.22 263); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); @@ -46,8 +46,8 @@ --card-foreground: oklch(0.985 0 0); --popover: oklch(0.145 0 0); --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.985 0 0); - --primary-foreground: oklch(0.205 0 0); + --primary: oklch(0.68 0.18 263); + --primary-foreground: oklch(0.145 0 0); --secondary: oklch(0.269 0 0); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.269 0 0); @@ -58,7 +58,7 @@ --destructive-foreground: oklch(0.637 0.237 25.331); --border: oklch(0.269 0 0); --input: oklch(0.269 0 0); - --ring: oklch(0.439 0 0); + --ring: oklch(0.68 0.18 263); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); @@ -222,6 +222,58 @@ body { } } +/* ── Custom animations ─────────────────────────────── */ +@keyframes fade-in-up { + from { + opacity: 0; + transform: translateY(18px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Card entrance on scroll — progressive enhancement */ +@supports (animation-timeline: view()) { + @media (prefers-reduced-motion: no-preference) { + .scroll-reveal { + animation: fade-in-up 0.55s cubic-bezier(0.16, 1, 0.3, 1) both; + animation-timeline: view(); + animation-range: entry 0% entry 25%; + } + } +} + +/* Utility: gradient text (primary → violet) */ +.gradient-text { + background: linear-gradient( + 135deg, + oklch(0.52 0.22 263) 0%, + oklch(0.55 0.25 295) 100% + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.dark .gradient-text { + background: linear-gradient( + 135deg, + oklch(0.68 0.18 263) 0%, + oklch(0.72 0.22 295) 100% + ); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* ── Print styles ──────────────────────────────────── */ /* Print styles */ @media print { nav, diff --git a/components/footer.tsx b/components/footer.tsx index 04d70bd..f058588 100644 --- a/components/footer.tsx +++ b/components/footer.tsx @@ -9,7 +9,8 @@ export function Footer() {
-

© {currentYear} Alejandro Repetto. All rights reserved.

+

Alejandro Repetto

+

© {currentYear} · All rights reserved.

@@ -17,7 +18,7 @@ export function Footer() { href="https://github.com/Repetto-A" target="_blank" rel="noopener noreferrer" - className="text-muted-foreground hover:text-foreground transition-colors" + className="text-muted-foreground hover:text-primary transition-colors duration-150" aria-label="Visit GitHub profile" > @@ -28,7 +29,7 @@ export function Footer() { href="https://www.linkedin.com/in/alejandro-repetto" target="_blank" rel="noopener noreferrer" - className="text-muted-foreground hover:text-foreground transition-colors" + className="text-muted-foreground hover:text-primary transition-colors duration-150" aria-label="Visit LinkedIn profile" > @@ -37,7 +38,7 @@ export function Footer() { diff --git a/components/layout/section-header.tsx b/components/layout/section-header.tsx index a740ccb..2819558 100644 --- a/components/layout/section-header.tsx +++ b/components/layout/section-header.tsx @@ -7,15 +7,39 @@ interface SectionHeaderProps { children?: ReactNode className?: string align?: "left" | "center" + eyebrow?: string } -export function SectionHeader({ title, description, children, className, align = "center" }: SectionHeaderProps) { - const alignClass = align === "center" ? "text-center items-center" : "text-left items-start" +export function SectionHeader({ + title, + description, + children, + className, + align = "center", + eyebrow, +}: SectionHeaderProps) { + const alignClass = align === "center" ? "text-center" : "text-left" + const eyebrowAlign = align === "center" ? "justify-center" : "justify-start" return ( -
-

{title}

- {description &&

{description}

} +
+ {eyebrow && ( +
+
+ + {eyebrow} + +
+
+ )} +

+ {title} +

+ {description && ( +

+ {description} +

+ )} {children}
) diff --git a/components/navigation.tsx b/components/navigation.tsx index 549d46c..ad48081 100644 --- a/components/navigation.tsx +++ b/components/navigation.tsx @@ -43,7 +43,7 @@ export function Navigation() { {item.label} @@ -75,7 +75,7 @@ export function Navigation() { setIsOpen(false)} > {item.label} diff --git a/components/sections/about-section.tsx b/components/sections/about-section.tsx index 7bf376e..a362ce3 100644 --- a/components/sections/about-section.tsx +++ b/components/sections/about-section.tsx @@ -1,53 +1,59 @@ -"use client" - -import { Container } from "@/components/layout/container" -import { Section } from "@/components/layout/section" -import { SectionHeader } from "@/components/layout/section-header" -import { useTranslations, getTranslation } from "@/lib/i18n-context" - -export function AboutSection() { - const [translations] = useTranslations() - - return ( -
- -
- - -
- {/* Background */} -
-

- {getTranslation(translations, "about.bio.background.title")} -

-
- {getTranslation(translations, "about.bio.background.text") - .split("\n\n") - .map((paragraph, index) => ( -

{paragraph}

- ))} -
-
- - {/* Approach */} -
-

- {getTranslation(translations, "about.bio.approach.title")} -

-
- {getTranslation(translations, "about.bio.approach.text") - .split("\n\n") - .map((paragraph, index) => ( -

{paragraph}

- ))} -
-
-
-
-
-
- ) -} +"use client" + +import { Container } from "@/components/layout/container" +import { Section } from "@/components/layout/section" +import { SectionHeader } from "@/components/layout/section-header" +import { useTranslations, getTranslation } from "@/lib/i18n-context" + +export function AboutSection() { + const [translations] = useTranslations() + + return ( +
+ +
+ + +
+ {/* Background */} +
+
+
+

+ {getTranslation(translations, "about.bio.background.title")} +

+
+
+ {getTranslation(translations, "about.bio.background.text") + .split("\n\n") + .map((paragraph, index) => ( +

{paragraph}

+ ))} +
+
+ + {/* Approach */} +
+
+
+

+ {getTranslation(translations, "about.bio.approach.title")} +

+
+
+ {getTranslation(translations, "about.bio.approach.text") + .split("\n\n") + .map((paragraph, index) => ( +

{paragraph}

+ ))} +
+
+
+
+ +
+ ) +} diff --git a/components/sections/awards-section.tsx b/components/sections/awards-section.tsx index 4b8fa2a..90a87f6 100644 --- a/components/sections/awards-section.tsx +++ b/components/sections/awards-section.tsx @@ -43,7 +43,7 @@ function AwardCard({ const showSeeMore = hasMatchingProject(award.projectId) return ( - +
@@ -157,6 +157,7 @@ export function AwardsSection() {
diff --git a/components/sections/contact-section.tsx b/components/sections/contact-section.tsx index 89d55d8..07fa8b4 100644 --- a/components/sections/contact-section.tsx +++ b/components/sections/contact-section.tsx @@ -21,6 +21,7 @@ import { } from "lucide-react" import Link from "next/link" import { useTranslations, getTranslation, getResumeUrl } from "@/lib/i18n-context" +import { SectionHeader } from "@/components/layout/section-header" interface FormData { name: string @@ -89,6 +90,10 @@ export function ContactSection() { return (
+
{/* Contact Information */}
@@ -122,10 +127,10 @@ export function ContactSection() { {getTranslation(translations, "contact.methods.email")}
- contact@repetto-a.com + contact@repetto-a.com
diff --git a/components/sections/hero-section.tsx b/components/sections/hero-section.tsx index e7e7c35..fd63f2a 100644 --- a/components/sections/hero-section.tsx +++ b/components/sections/hero-section.tsx @@ -1,130 +1,162 @@ -"use client" - -import { Button } from "@/components/ui/button" -import { ArrowDown, ExternalLink } from "lucide-react" -import Link from "next/link" -import Image from "next/image" -import { useTranslations, getTranslation } from "@/lib/i18n-context" -import { useScrollContext } from "@/lib/scroll-context" -import { cn } from "@/lib/utils" - -export function HeroSection() { - const [translations] = useTranslations() - const { registerHeroTitleRef, isFloatingTitleActive, fontsReady } = useScrollContext() - - return ( -
-
-
- {/* Left Column - Text Content */} -
-
-

- {getTranslation(translations, "hero.name")} -

-

- {getTranslation(translations, "hero.title")} -

-

- {(() => { - const desc = getTranslation(translations, "hero.description") - const [main, awards] = desc.split("\n") - return ( - <> - {main} - {awards && ( - <> -
- - {awards} - - - )} - - ) - })()} -

-
- - {/* CTA */} -
- -
-
- - {/* Right Column - Profile Image */} -
-
-
-
- Alejandro Repetto - Systems Engineer specializing in AI/ML and automation -
-
- - {/* Floating Elements */} -
-
- {getTranslation(translations, "hero.status.currently")} -
-
- {getTranslation(translations, "hero.status.building")} -
-
- -
-
- {getTranslation(translations, "hero.status.focus")} -
-
- {getTranslation(translations, "hero.status.automation")} -
-
-
-
-
- - {/* Scroll Indicator */} -
- -
- - {getTranslation(translations, "hero.scroll")} - - -
- -
-
-
- ) -} +"use client" + +import { Button } from "@/components/ui/button" +import { ArrowDown, ExternalLink } from "lucide-react" +import Link from "next/link" +import Image from "next/image" +import { useTranslations, getTranslation, getResumeUrl } from "@/lib/i18n-context" +import { useScrollContext } from "@/lib/scroll-context" +import { cn } from "@/lib/utils" + +export function HeroSection() { + const [translations, locale] = useTranslations() + const { registerHeroTitleRef, isFloatingTitleActive, fontsReady } = useScrollContext() + const resumeUrl = getResumeUrl(locale) + + return ( +
+ {/* Subtle dot-grid background */} +
+ {/* Top-right ambient glow */} +
+ +
+
+ {/* Left Column - Text Content */} +
+
+

+ {getTranslation(translations, "hero.name")} +

+ +

+ {getTranslation(translations, "hero.title")} +

+ +

+ {(() => { + const desc = getTranslation(translations, "hero.description") + const [main, awards] = desc.split("\n") + return ( + <> + {main} + {awards && ( + <> +
+ + {awards} + + + )} + + ) + })()} +

+
+ + {/* CTAs */} +
+ + +
+
+ + {/* Right Column - Profile Image */} +
+
+ {/* Ambient glow behind image */} +
+ + {/* Image container — edge-to-edge, clean */} +
+ Alejandro Repetto - Systems Engineer specializing in AI/ML and automation + {/* Subtle gradient overlay */} +
+
+ + {/* Floating card — top right */} +
+
+ + + {getTranslation(translations, "hero.status.currently")} + +
+
+ {getTranslation(translations, "hero.status.building")} +
+
+ + {/* Floating card — bottom left */} +
+
+ {getTranslation(translations, "hero.status.focus")} +
+
+ {getTranslation(translations, "hero.status.automation")} +
+
+
+
+
+ + {/* Scroll Indicator */} +
+ +
+ + {getTranslation(translations, "hero.scroll")} + + +
+ +
+
+
+ ) +} diff --git a/components/sections/projects-section.tsx b/components/sections/projects-section.tsx index 7c42c55..5b763ff 100644 --- a/components/sections/projects-section.tsx +++ b/components/sections/projects-section.tsx @@ -1,243 +1,258 @@ -"use client" - -import { useState, useEffect, useCallback, useRef } from "react" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" -import { ProjectDetailModal } from "@/components/project-detail-modal" -import { Container } from "@/components/layout/container" -import { Section } from "@/components/layout/section" -import { SectionHeader } from "@/components/layout/section-header" -import { ExternalLink, Github, Code, Zap, Calendar, Eye } from "lucide-react" -import Link from "next/link" -import projectsData from "@/content/projects.json" -import { useTranslations, getTranslation } from "@/lib/i18n-context" - -const categoryIcons = { - "Web Application": Code, - "Business Automation": Zap, - "Management System": Calendar, - "AI/ML Research": Eye, - "Blockchain/DeFi": ExternalLink, - "Blockchain/AI": ExternalLink, - "Healthcare System": Calendar, - "Game / AgTech": Eye, -} - -interface Project { - id: string - locales: { - [key: string]: { - title: string - short: string - description: string - features: string[] - } - } - status: string - techStack: string[] - githubUrl: string - demoUrl: string | null - images: string[] - category: keyof typeof categoryIcons -} - -export function ProjectsSection() { - const [translations, locale] = useTranslations() - const [selectedProject, setSelectedProject] = useState(null) - const [modalOpen, setModalOpen] = useState(false) - const projectsRef = useRef>(new Map()) - - const statusConfig = getStatusConfig(translations) - - // Build project map for event-based lookup - const projects = projectsData.projects.map((projectData): Project => ({ - ...projectData, - status: projectData.status in statusConfig ? projectData.status : "in-progress", - category: (projectData.category in categoryIcons - ? projectData.category - : "Web Application") as keyof typeof categoryIcons, - })) - - // Keep map in sync - projectsRef.current.clear() - for (const p of projects) projectsRef.current.set(p.id, p) - - const handleProjectModalOpen = useCallback((project: Project) => { - setSelectedProject(project) - setModalOpen(true) - }, []) - - const handleModalClose = useCallback(() => { - setModalOpen(false) - setSelectedProject(null) - }, []) - - // Listen for "open-project" events dispatched by awards section - useEffect(() => { - const handler = (e: Event) => { - const projectId = (e as CustomEvent).detail - const project = projectsRef.current.get(projectId) - if (project) { - // Scroll the card into view, then open modal - const card = document.getElementById(`project-${projectId}`) - if (card) { - card.scrollIntoView({ behavior: "smooth", block: "center" }) - } - // Small delay so the scroll is visible before modal opens - setTimeout(() => { - setSelectedProject(project) - setModalOpen(true) - }, 400) - } - } - window.addEventListener("open-project", handler) - return () => window.removeEventListener("open-project", handler) - }, []) - - return ( -
- - - -
- {projects.map((project) => { - const IconComponent = categoryIcons[project.category] || Code - const statusStyle = statusConfig[project.status as keyof typeof statusConfig] || statusConfig["in-progress"] - const projectContent = project.locales[locale] || project.locales["en"] - - return ( - - -
-
- - - {statusStyle.label} - -
- - {getTranslation(translations, `projects.categories.${project.category}`) || project.category} - -
- - - - -
- - -

- {projectContent.short} -

- -
- {project.techStack.slice(0, 3).map((tech) => ( - - {tech} - - ))} - {project.techStack.length > 3 && ( - - +{project.techStack.length - 3} - - )} -
- -
-
- {project.githubUrl && ( - - - GitHub - - )} - {project.demoUrl && ( - - - Demo - - )} -
- - -
-
-
- ) - })} -
- -
-
-

- {getTranslation(translations, "projects.collaboration.title")} -

-

- {getTranslation(translations, "projects.collaboration.description")} -

- -
-
-
- - -
- ) -} - -function getStatusConfig(translations: any) { - return { - "in-progress": { - label: getTranslation(translations, "projects.status.in-progress"), - color: "bg-yellow-500/10 text-yellow-500 border-yellow-500/20", - }, - production: { - label: getTranslation(translations, "projects.status.production"), - color: "bg-green-500/10 text-green-500 border-green-500/20", - }, - prototype: { - label: getTranslation(translations, "projects.status.prototype"), - color: "bg-purple-500/10 text-purple-500 border-purple-500/20", - }, - "regional-winner-global-nominee": { - label: getTranslation(translations, "projects.status.regional-winner-global-nominee"), - color: "bg-blue-500/10 text-blue-500 border-blue-500/20", - }, - } -} +"use client" + +import { useState, useEffect, useCallback, useRef } from "react" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { ProjectDetailModal } from "@/components/project-detail-modal" +import { Container } from "@/components/layout/container" +import { Section } from "@/components/layout/section" +import { SectionHeader } from "@/components/layout/section-header" +import { ExternalLink, Github, Code, Zap, Calendar, Eye } from "lucide-react" +import Link from "next/link" +import Image from "next/image" +import projectsData from "@/content/projects.json" +import { useTranslations, getTranslation } from "@/lib/i18n-context" + +const categoryIcons = { + "Web Application": Code, + "Business Automation": Zap, + "Management System": Calendar, + "AI/ML Research": Eye, + "Blockchain/DeFi": ExternalLink, + "Blockchain/AI": ExternalLink, + "Healthcare System": Calendar, + "Game / AgTech": Eye, +} + +interface Project { + id: string + locales: { + [key: string]: { + title: string + short: string + description: string + features: string[] + } + } + status: string + techStack: string[] + githubUrl: string + demoUrl: string | null + images: string[] + category: keyof typeof categoryIcons +} + +export function ProjectsSection() { + const [translations, locale] = useTranslations() + const [selectedProject, setSelectedProject] = useState(null) + const [modalOpen, setModalOpen] = useState(false) + const projectsRef = useRef>(new Map()) + + const statusConfig = getStatusConfig(translations) + + const projects = projectsData.projects.map((projectData): Project => ({ + ...projectData, + status: projectData.status in statusConfig ? projectData.status : "in-progress", + category: (projectData.category in categoryIcons + ? projectData.category + : "Web Application") as keyof typeof categoryIcons, + })) + + projectsRef.current.clear() + for (const p of projects) projectsRef.current.set(p.id, p) + + const handleProjectModalOpen = useCallback((project: Project) => { + setSelectedProject(project) + setModalOpen(true) + }, []) + + const handleModalClose = useCallback(() => { + setModalOpen(false) + setSelectedProject(null) + }, []) + + useEffect(() => { + const handler = (e: Event) => { + const projectId = (e as CustomEvent).detail + const project = projectsRef.current.get(projectId) + if (project) { + const card = document.getElementById(`project-${projectId}`) + if (card) { + card.scrollIntoView({ behavior: "smooth", block: "center" }) + } + setTimeout(() => { + setSelectedProject(project) + setModalOpen(true) + }, 400) + } + } + window.addEventListener("open-project", handler) + return () => window.removeEventListener("open-project", handler) + }, []) + + return ( +
+ + + +
+ {projects.map((project) => { + const IconComponent = categoryIcons[project.category] || Code + const statusStyle = statusConfig[project.status as keyof typeof statusConfig] || statusConfig["in-progress"] + const projectContent = project.locales[locale] || project.locales["en"] + + return ( + + {/* Project thumbnail — -mt-6 to break out of Card's py-6 top padding */} + {project.images?.[0] && ( +
+ {projectContent.title} + {/* Hover overlay */} +
+
+ )} + + +
+
+ + + {statusStyle.label} + +
+ + {getTranslation(translations, `projects.categories.${project.category}`) || project.category} + +
+ + + + +
+ + +

+ {projectContent.short} +

+ +
+ {project.techStack.slice(0, 3).map((tech) => ( + + {tech} + + ))} + {project.techStack.length > 3 && ( + + +{project.techStack.length - 3} + + )} +
+ +
+
+ {project.githubUrl && ( + + + GitHub + + )} + {project.demoUrl && ( + + + Demo + + )} +
+ + +
+
+ + ) + })} +
+ + {/* Collaboration CTA */} +
+
+
+

+ {getTranslation(translations, "projects.collaboration.title")} +

+

+ {getTranslation(translations, "projects.collaboration.description")} +

+ +
+
+ + + +
+ ) +} + +function getStatusConfig(translations: any) { + return { + "in-progress": { + label: getTranslation(translations, "projects.status.in-progress"), + color: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20", + }, + production: { + label: getTranslation(translations, "projects.status.production"), + color: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/20", + }, + prototype: { + label: getTranslation(translations, "projects.status.prototype"), + color: "bg-violet-500/10 text-violet-600 dark:text-violet-400 border-violet-500/20", + }, + "regional-winner-global-nominee": { + label: getTranslation(translations, "projects.status.regional-winner-global-nominee"), + color: "bg-primary/10 text-primary border-primary/20", + }, + } +} diff --git a/components/sections/skills-section.tsx b/components/sections/skills-section.tsx index 8a6b8db..88c9363 100644 --- a/components/sections/skills-section.tsx +++ b/components/sections/skills-section.tsx @@ -1,128 +1,136 @@ -"use client" - -import { Badge } from "@/components/ui/badge" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" -import { Container } from "@/components/layout/container" -import { Section } from "@/components/layout/section" -import { SectionHeader } from "@/components/layout/section-header" -import { Grid } from "@/components/layout/grid" -import { useTranslations, getTranslation } from "@/lib/i18n-context" - -export function SkillsSection() { - const [translations] = useTranslations() - - const skillCategories = [ - { - titleKey: "skills.categories.languages", - titleDefault: "Languages & Frameworks", - skills: [ - "Python", - "C", - "C++", - "Java", - "TypeScript", - "JavaScript", - "Django", - "Django Rest Framework", - "FastAPI", - "Flask", - "Express", - "React", - "Next.js", - "HTML5", - "CSS3", - "TailwindCSS", - ], - }, - { - titleKey: "skills.categories.frameworks", - titleDefault: "AI/ML & Data Science", - skills: [ - "TensorFlow", - "PyTorch", - "Scikit-learn", - "Langchain", - "Langgraph", - "NumPy", - "Pandas", - "Matplotlib", - "OpenCV", - "Natural Language Processing", - "Computer Vision", - "Data Analysis & Visualization", - ], - }, - { - titleKey: "skills.categories.databases", - titleDefault: "Databases & Infrastructure", - skills: [ - "MySQL", - "PostgreSQL", - "SQLite", - "MongoDB", - "Redis", - "Docker", - "Terraform", - "AWS", - "Vercel", - "Nginx", - "Linux", - "Git", - ], - }, - { - titleKey: "skills.categories.tools", - titleDefault: "Automation & Integration", - skills: [ - "REST APIs", - "GraphQL", - "Webhook Integration", - "Selenium", - "Social Media Automation", - "Telegram and Whatsapp Bots", - "Email Automation", - "AFIP Connection", - "Document Processing", - "AI Chatbots", - "AI Agents", - "Workflow Automations", - ], - }, - ] - - return ( -
- - - - - {skillCategories.map((category, index) => ( - - - - {getTranslation(translations, category.titleKey)} - - - -
- {category.skills.map((skill, skillIndex) => ( - - {skill} - - ))} -
-
-
- ))} -
-
-
- ) -} +"use client" + +import { Badge } from "@/components/ui/badge" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Container } from "@/components/layout/container" +import { Section } from "@/components/layout/section" +import { SectionHeader } from "@/components/layout/section-header" +import { Grid } from "@/components/layout/grid" +import { useTranslations, getTranslation } from "@/lib/i18n-context" + +const skillCategories = [ + { + titleKey: "skills.categories.languages", + topBorder: "border-t-primary", + badgeHover: "hover:bg-primary/10 hover:text-primary hover:border-primary/40", + skills: [ + "Python", + "C", + "C++", + "Java", + "TypeScript", + "JavaScript", + "Django", + "Django Rest Framework", + "FastAPI", + "Flask", + "Express", + "React", + "Next.js", + "HTML5", + "CSS3", + "TailwindCSS", + ], + }, + { + titleKey: "skills.categories.frameworks", + topBorder: "border-t-violet-500", + badgeHover: "hover:bg-violet-500/10 hover:text-violet-600 dark:hover:text-violet-400 hover:border-violet-500/40", + skills: [ + "TensorFlow", + "PyTorch", + "Scikit-learn", + "Langchain", + "Langgraph", + "NumPy", + "Pandas", + "Matplotlib", + "OpenCV", + "Natural Language Processing", + "Computer Vision", + "Data Analysis & Visualization", + ], + }, + { + titleKey: "skills.categories.databases", + topBorder: "border-t-cyan-500", + badgeHover: "hover:bg-cyan-500/10 hover:text-cyan-600 dark:hover:text-cyan-400 hover:border-cyan-500/40", + skills: [ + "MySQL", + "PostgreSQL", + "SQLite", + "MongoDB", + "Redis", + "Docker", + "Terraform", + "AWS", + "Vercel", + "Nginx", + "Linux", + "Git", + ], + }, + { + titleKey: "skills.categories.tools", + topBorder: "border-t-amber-500", + badgeHover: "hover:bg-amber-500/10 hover:text-amber-600 dark:hover:text-amber-400 hover:border-amber-500/40", + skills: [ + "REST APIs", + "GraphQL", + "Webhook Integration", + "Selenium", + "Social Media Automation", + "Telegram and Whatsapp Bots", + "Email Automation", + "AFIP Connection", + "Document Processing", + "AI Chatbots", + "AI Agents", + "Workflow Automations", + ], + }, +] + +export function SkillsSection() { + const [translations] = useTranslations() + + return ( +
+ + + + + {skillCategories.map((category, index) => ( + + + + {getTranslation(translations, category.titleKey)} + + + +
+ {category.skills.map((skill, skillIndex) => ( + + {skill} + + ))} +
+
+
+ ))} +
+
+
+ ) +} diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 729bf6a..7bf58f5 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -10,12 +10,12 @@ interface ButtonProps extends React.ComponentProps<"button"> { const getButtonClasses = (variant = "default", size = "default") => { const baseClasses = - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 outline-none focus-visible:ring-2 focus-visible:ring-offset-2" + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all duration-150 disabled:pointer-events-none disabled:opacity-50 outline-none focus-visible:ring-2 focus-visible:ring-offset-2 active:scale-[0.98]" const variantClasses = { - default: "bg-primary text-primary-foreground shadow hover:bg-primary/90", + default: "bg-primary text-primary-foreground shadow hover:bg-primary/90 hover:shadow-md", destructive: "bg-destructive text-white shadow hover:bg-destructive/90", - outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", + outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground hover:border-primary/40", secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", @@ -24,7 +24,7 @@ const getButtonClasses = (variant = "default", size = "default") => { const sizeClasses = { default: "h-9 px-4 py-2", sm: "h-8 rounded-md px-3", - lg: "h-10 rounded-md px-6", + lg: "h-10 rounded-md px-8", icon: "h-9 w-9", } From bbd92b862ce799155f031cecb53e88e9d38d0487 Mon Sep 17 00:00:00 2001 From: Alejandro Repetto Date: Thu, 16 Apr 2026 01:19:40 -0300 Subject: [PATCH 2/8] Second-pass redesign: B&W palette, typography polish, image carousel, bug fixes - Revert indigo accent to achromatic B&W palette (near-black light / near-white dark) - Wire Inter + JetBrains Mono fonts to Tailwind (were loaded but never mapped) - Fix broken oklch CSS in scrollbar and skip-link (rgb() wrapper invalid with oklch) - Add body letter-spacing (-0.011em) and section header tight tracking (-0.035em) - Replace project modal 2-col image grid with proper carousel + thumbnail strip - Fix infinite loop in ImageLightbox (unstable analytics object + resetTransform) - Add DialogTitle/Description to lightbox, remove double close button - Add staggered scroll-reveal animations for grid sections (CSS-only) - Vary section spacing rhythm (compact about, relaxed awards/contact) - Add editorial section dividers between Skills/Awards and Projects/Contact - Migrate contact section from raw
to Section/Container components - Replace hero bouncing chevron with minimal line+dot scroll indicator - Remove colored skill category borders, uniform B&W treatment - Hero H1 whitespace-nowrap to prevent name wrapping - Delete stale styles/globals.css duplicate, remove debug console.log Co-Authored-By: Claude Sonnet 4.6 --- app/globals.css | 52 +- app/page.tsx | 6 + components/layout/section-header.tsx | 5 +- components/project-detail-modal.tsx | 463 ++++++++------- components/sections/about-section.tsx | 6 +- components/sections/awards-section.tsx | 336 ++++++----- components/sections/contact-section.tsx | 704 +++++++++++------------ components/sections/hero-section.tsx | 28 +- components/sections/projects-section.tsx | 35 +- components/sections/skills-section.tsx | 12 +- components/ui/image-lightbox.tsx | 134 +++-- styles/globals.css | 125 ---- 12 files changed, 953 insertions(+), 953 deletions(-) delete mode 100644 styles/globals.css diff --git a/app/globals.css b/app/globals.css index 018469c..1d8bc15 100644 --- a/app/globals.css +++ b/app/globals.css @@ -10,7 +10,7 @@ --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.52 0.22 263); + --primary: oklch(0.145 0 0); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.97 0 0); --secondary-foreground: oklch(0.205 0 0); @@ -22,7 +22,7 @@ --destructive-foreground: oklch(0.577 0.245 27.325); --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); - --ring: oklch(0.52 0.22 263); + --ring: oklch(0.145 0 0); --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); @@ -42,11 +42,11 @@ .dark { --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); - --card: oklch(0.145 0 0); + --card: oklch(0.16 0 0); --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.145 0 0); + --popover: oklch(0.16 0 0); --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.68 0.18 263); + --primary: oklch(0.922 0 0); --primary-foreground: oklch(0.145 0 0); --secondary: oklch(0.269 0 0); --secondary-foreground: oklch(0.985 0 0); @@ -58,7 +58,7 @@ --destructive-foreground: oklch(0.637 0.237 25.331); --border: oklch(0.269 0 0); --input: oklch(0.269 0 0); - --ring: oklch(0.68 0.18 263); + --ring: oklch(0.922 0 0); --chart-1: oklch(0.488 0.243 264.376); --chart-2: oklch(0.696 0.17 162.48); --chart-3: oklch(0.769 0.188 70.08); @@ -111,6 +111,8 @@ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + --font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif; + --font-mono: var(--font-jetbrains-mono), ui-monospace, monospace; } html { @@ -136,16 +138,16 @@ body { } ::-webkit-scrollbar-track { - background: rgb(var(--background)); + background: var(--background); } ::-webkit-scrollbar-thumb { - background: rgb(var(--muted)); + background: var(--muted); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { - background: rgb(var(--muted-foreground)); + background: var(--muted-foreground); } @layer base { @@ -155,6 +157,8 @@ body { body { @apply bg-background text-foreground; + letter-spacing: -0.011em; + line-height: 1.6; } /* Ensure tap targets are at least 44x44px on touch devices */ @@ -199,8 +203,8 @@ body { left: -9999px; z-index: 999; padding: 1rem 1.5rem; - background-color: rgb(var(--background)); - color: rgb(var(--foreground)); + background-color: var(--background); + color: var(--foreground); text-decoration: none; border-radius: 0.5rem; } @@ -250,6 +254,32 @@ body { } } +/* Staggered scroll-reveal variants for grid cards */ +@supports (animation-timeline: view()) { + @media (prefers-reduced-motion: no-preference) { + .scroll-reveal-stagger-1 { + animation: fade-in-up 0.55s cubic-bezier(0.16, 1, 0.3, 1) both; + animation-timeline: view(); + animation-range: entry 0% entry 25%; + } + .scroll-reveal-stagger-2 { + animation: fade-in-up 0.55s cubic-bezier(0.16, 1, 0.3, 1) both; + animation-timeline: view(); + animation-range: entry 3% entry 28%; + } + .scroll-reveal-stagger-3 { + animation: fade-in-up 0.55s cubic-bezier(0.16, 1, 0.3, 1) both; + animation-timeline: view(); + animation-range: entry 6% entry 31%; + } + .scroll-reveal-stagger-4 { + animation: fade-in-up 0.55s cubic-bezier(0.16, 1, 0.3, 1) both; + animation-timeline: view(); + animation-range: entry 9% entry 34%; + } + } +} + /* Utility: gradient text (primary → violet) */ .gradient-text { background: linear-gradient( diff --git a/app/page.tsx b/app/page.tsx index e28448e..763eb0a 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -23,8 +23,14 @@ export default function Home() { +
+
+
+
+
+