feat: Enhance layout and styling with new fonts and components

- Integrated Space Grotesk font alongside Inter in layout.
- Added FloatingSocials component for social media links.
- Updated Footer with a new background color.
- Modified Header to improve spacing and added LanguageSwitcher.
- Refactored LanguageSwitcher to use a dropdown for language selection.
- Updated ProjectCard to include images and improved layout.
- Revamped About section to include categorized skills with animations.
- Enhanced Contact section with animations and improved form styling.
- Updated Hero section with type animation for dynamic text display.
- Refactored Projects section to include animations for project cards.
- Removed Skills section as it was integrated into the About section.
- Updated global styles for light and dark themes, including new animations.
- Updated translations for new skills and hero section text.
This commit is contained in:
2025-06-10 00:27:48 -03:00
parent 762ce1fb59
commit fe8bbf05f6
22 changed files with 1075 additions and 165 deletions

View File

@@ -1,4 +1,4 @@
import { Inter } from "next/font/google";
import { Inter, Space_Grotesk } from "next/font/google";
import Header from "@/app/components/Header"; // Usando o Header que forneci
import Footer from "@/app/components/Footer";
import "@/app/globals.css"; // Importando o CSS global
@@ -11,8 +11,18 @@ import {getTranslations, setRequestLocale} from 'next-intl/server';
import { ReactNode } from "react";
import { routing } from "@/i18n/routing";
import { notFound } from "next/navigation";
import FloatingSocials from "@/app/components/FloatingSocials";
const inter = Inter({ subsets: ["latin"] });
const inter = Inter({
subsets: ["latin"],
variable: '--font-inter',
});
const space_grotesk = Space_Grotesk({
subsets: ["latin"],
weight: ['300', '400', '500', '700'],
variable: '--font-space-grotesk',
});
type Props = {
children: ReactNode;
@@ -42,13 +52,13 @@ export default async function RootLayout({children, params}: Props) {
setRequestLocale(locale);
return (
<html lang={locale} className="scroll-smooth">
<html lang={locale} className={`${inter.variable} ${space_grotesk.variable} scroll-smooth`}>
<body className={`${inter.className} bg-background text-text-primary`}>
<ThemeProvider>
<NextIntlClientProvider locale={locale}>
<FloatingSocials />
<Header />
<main className="flex flex-col items-center">
<main className="flex flex-col">
{children}
</main>
<Footer />

View File

@@ -1,6 +1,5 @@
import Hero from "@/app/components/sections/Hero";
import About from "@/app/components/sections/About";
import Skills from "@/app/components/sections/Skills";
import Projects from "@/app/components/sections/Projects";
import Contact from "@/app/components/sections/Contact";
import { Locale } from "next-intl";
@@ -10,19 +9,14 @@ import { setRequestLocale } from "next-intl/server";
export default function Home({params}: {
params: Promise<{locale: Locale}>;
}) {
const {locale} = use(params);
// Enable static rendering
setRequestLocale(locale);
setRequestLocale(use(params).locale);
return (
<div className="container mx-auto px-4">
<>
<Hero />
<About />
<Skills />
<Projects />
<Contact />
</div>
</>
);
}

View File

@@ -0,0 +1,30 @@
import Link from 'next/link';
import { FaGithub, FaLinkedin } from 'react-icons/fa';
import { HiOutlineMail } from 'react-icons/hi';
const socialLinks = [
{ href: 'mailto:contato@joaoloureiro.dev.br', icon: <HiOutlineMail/>, label: 'Email' },
{ href: process.env.NEXT_PUBLIC_GITHUB_URL || '#', icon: <FaGithub/>, label: 'GitHub' },
{ href: process.env.NEXT_PUBLIC_LINKEDIN_URL || '#', icon: <FaLinkedin/>, label: 'LinkedIn' },
];
export default function FloatingSocials() {
return (
<div className="hidden md:block fixed left-0 top-1/2 -translate-y-1/2 z-30">
<div className="flex flex-col items-center space-y-1 bg-[var(--color-card)]/50 border border-[var(--color-border)] p-2 rounded-r-lg backdrop-blur-sm">
{socialLinks.map(link => (
<Link
key={link.label}
href={link.href}
target="_blank"
rel="noopener noreferrer"
aria-label={link.label}
className="p-2 text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] hover:bg-[var(--color-background)] rounded-md transition-colors"
>
<div className="h-6 w-6">{link.icon}</div>
</Link>
))}
</div>
</div>
);
}

View File

@@ -9,7 +9,7 @@ export default function Footer() {
const linkedinUrl = process.env.NEXT_PUBLIC_LINKEDIN_URL || '#';
return (
<footer className="border-t border-[var(--color-border)] py-8 text-[var(--color-text-secondary)]">
<footer className="border-t bg-black border-[var(--color-border)] py-8 text-[var(--color-text-secondary)]">
<div className="container mx-auto flex flex-col md:flex-row justify-between items-center gap-4 px-4 sm:px-6 lg:px-8">
<p className="text-sm">&copy; {new Date().getFullYear()} João Loureiro. {t('copyright')}</p>
<div className="flex items-center space-x-4">

View File

@@ -11,24 +11,25 @@ export default function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header className="sticky top-0 z-50 border-b border-[var(--color-border)] bg-[var(--color-background)]/90 backdrop-blur-sm">
<header className="sticky top-0 z-50 bg-[var(--color-background)]/90 backdrop-blur-sm">
<nav className="container mx-auto flex items-center justify-between py-4 px-4 sm:px-6 lg:px-8">
<Link href="/">
<Image src="/logo.png" alt="João Loureiro Logo" width={40} height={40} priority />
</Link>
<div className="hidden md:flex items-center space-x-6 text-[var(--color-text-secondary)]">
<div className="hidden md:flex items-center space-x-12 text-[var(--color-text-secondary)]">
<Link href="#about" className="hover:text-[var(--color-primary)] transition-colors">{t('about')}</Link>
<Link href="#skills" className="hover:text-[var(--color-primary)] transition-colors">{t('tech')}</Link>
<Link href="#projects" className="hover:text-[var(--color-primary)] transition-colors">{t('projects')}</Link>
<Link href="#contact" className="hover:text-[var(--color-primary)] transition-colors">{t('contact')}</Link>
</div>
<div className="hidden md:flex items-center space-x-6 text-[var(--color-text-secondary)]">
<LanguageSwitcher />
{/* Theme Toggle */}
<button onClick={toggleTheme} className="p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700">
{theme === 'dark'? <SunIcon className="h-6 w-6 text-yellow-400" /> : <MoonIcon className="h-6 w-6 text-gray-600" />}
</button>
<LanguageSwitcher />
</div>
</nav>
</header>

View File

@@ -2,31 +2,70 @@
import { useLocale } from 'next-intl';
import { usePathname, useRouter } from 'next/navigation';
import { useTransition } from 'react';
import { useState, useTransition, useRef, useEffect } from 'react';
import { FaChevronDown } from 'react-icons/fa6';
export default function LanguageSwitcher() {
const [isPending, startTransition] = useTransition();
const router = useRouter();
const pathname = usePathname().replaceAll(/^\/(pt|en)/g, '');
const locale = useLocale();
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const onSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const nextLocale = e.target.value;
const onSelectChange = (nextLocale: string) => {
startTransition(() => {
router.replace(`/${nextLocale}${pathname}`);
});
setIsOpen(false);
};
useEffect(() => {
const handleOutsideClick = (event: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleOutsideClick);
return () => document.removeEventListener('mousedown', handleOutsideClick);
}, []);
const languages: { [key: string]: { flag: string; label: string } } = {
en: { flag: '🇺🇸', label: 'English' },
pt: { flag: '🇧🇷', label: 'Português' },
};
return (
<select
defaultValue={locale}
onChange={onSelectChange}
disabled={isPending}
className="bg-[var(--color-card)] text-[var(--color-text-secondary)] border border-[var(--color-border)] rounded-md focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]"
>
<option value="pt">🇧🇷 Português</option>
<option value="en">🇺🇸 English</option>
</select>
<div className="relative" ref={containerRef}>
<button
type="button"
disabled={isPending}
onClick={() => setIsOpen(!isOpen)}
className="flex items-center justify-between w-full px-3 py-2 text-sm font-medium text-[var(--color-text-secondary)] bg-[var(--color-card)] border border-[var(--color-border)] rounded-md hover:bg-[var(--color-border)]/50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-[var(--color-background)] focus:ring-[var(--color-primary)] transition-colors"
>
<span>{languages[locale].flag} {languages[locale].label}</span>
<FaChevronDown className={`ml-2 h-4 w-4 transition-transform ${isOpen ? 'transform rotate-180' : ''}`} />
</button>
{isOpen && (
<div className="absolute right-0 mt-2 w-40 bg-[var(--color-card)] border border-[var(--color-border)] rounded-md shadow-lg z-10 lang-switcher-options">
<ul className="py-1">
{Object.keys(languages).map((langCode) => (
<li key={langCode}>
<button
onClick={() => onSelectChange(langCode)}
className="flex items-center w-full px-4 py-2 text-sm text-left text-[var(--color-text-secondary)] hover:bg-[var(--color-border)]/50"
>
<span className="mr-2">{languages[langCode].flag}</span>
{languages[langCode].label}
</button>
</li>
))}
</ul>
</div>
)}
</div>
);
}

View File

@@ -1,36 +1,44 @@
import Link from 'next/link';
import {useTranslations} from 'next-intl';
import { FaGithub, FaArrowUpRightFromSquare } from 'react-icons/fa6';
import Image from 'next/image';
type ProjectCardProps = {
title: string;
description: string;
tech: string[];
imageUrl: string;
};
export default function ProjectCard({ title, description, tech }: ProjectCardProps) {
export default function ProjectCard({ title, description, tech, imageUrl }: ProjectCardProps) {
const t = useTranslations('projects');
return (
<div className="bg-[var(--color-card)] rounded-lg p-6 flex flex-col border border-[var(--color-border)] hover:border-[var(--color-primary)]/50 transition-colors duration-300">
<h3 className="text-[var(--color-text-primary)] mb-2">{title}</h3>
<p className="text-[var(--color-text-secondary)] mb-4 flex-grow">{description}</p>
<div className="flex flex-wrap gap-2 mb-4">
{tech.map(t => (
<span key={t} className="bg-[var(--color-border)]/50 text-[var(--color-text-secondary)] text-xs font-semibold px-2.5 py-1 rounded-full">
{t}
</span>
))}
// Added 'h-full' to make all cards in a row the same height
<div className="h-full bg-[var(--color-background)] rounded-lg overflow-hidden flex flex-col border border-[var(--color-border)] hover:border-[var(--color-primary)]/50 transition-all duration-300 hover:shadow-xl hover:-translate-y-1">
<div className="relative w-full h-48">
<Image src={imageUrl} alt={title} layout="fill" objectFit="cover" />
</div>
<div className="flex items-center space-x-8 mt-auto pt-4 border-t border-[var(--color-border)]">
<Link href="#" target="_blank" className="flex gap-2 items-center text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors">
{t('live_link')}
<FaArrowUpRightFromSquare size={12} />
</Link>
<Link href="#" target="_blank" className="flex gap-2 items-center text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors">
<FaGithub size={20} />
{t('repo_link')}
</Link>
<div className="p-6 flex flex-col flex-grow">
<h3 className="text-[var(--color-text-primary)] mb-2">{title}</h3>
<p className="text-[var(--color-text-secondary)] mb-4 flex-grow">{description}</p>
<div className="flex flex-wrap gap-2 mb-4">
{tech.map(t => (
<span key={t} className="bg-[var(--color-border)]/50 text-[var(--color-text-secondary)] text-xs font-semibold px-2.5 py-1 rounded-full">
{t}
</span>
))}
</div>
<div className="flex items-center space-x-8 mt-auto pt-4 border-t border-[var(--color-border)]">
<Link href="#" target="_blank" className="flex gap-2 items-center text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors">
{t('live_link')}
<FaArrowUpRightFromSquare size={12} />
</Link>
<Link href="#" target="_blank" className="flex gap-2 items-center text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-primary)] transition-colors">
<FaGithub size={20} />
{t('repo_link')}
</Link>
</div>
</div>
</div>
);

View File

@@ -1,16 +1,72 @@
'use client';
import {useTranslations} from 'next-intl';
import { motion } from 'framer-motion';
export default function About() {
const t = useTranslations('about');
// Updated skills based on your professional experience
const skillsData = {
backend: [".NET (Framework, Core, 6+)", "C#", "Node.js", "Entity Framework"],
frontend: ["Angular", "TypeScript", "Next.js", "React", "HTML5 & CSS3"],
databases: ["Oracle", "PostgreSQL", "Elasticsearch", "SQL"],
cloud: ["Azure", "Docker", "RabbitMQ", "Git", "Kibana"]
};
const SkillPill = ({ skill }: { skill: string }) => (
<div className="bg-[var(--color-background)] text-[var(--color-text-secondary)] border border-[var(--color-border)] rounded-full px-4 py-2 text-sm font-medium">
{skill}
</div>
);
export default function AboutAndSkills() {
const tAbout = useTranslations('about');
const tSkills = useTranslations('skills');
return (
<section id="about" className="container mx-auto py-24 md:py-32 lg:py-48">
<h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--color-text-primary)] mb-12">{t('title')}</h2>
<div className="max-w-3xl mx-auto text-[var(--color-text-secondary)] space-y-6 text-lg text-left md:text-justify">
<p>{t('paragraph1')}</p>
<p>{t('paragraph2')}</p>
<p>{t('paragraph3')}</p>
<motion.section
id="about"
className="container mx-auto py-12 md:py-20"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, ease: "easeOut" }}
>
<h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--color-text-primary)] heading-underline">{tAbout('title')}</h2>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-16 items-start">
{/* About Me Column */}
<div className="space-y-4 text-lg text-left text-[var(--color-text-secondary)]">
<p>{tAbout('paragraph1')}</p>
<p>{tAbout('paragraph2')}</p>
<p>{tAbout('paragraph3')}</p>
</div>
{/* Skills Column - Updated with new categories */}
<div className="space-y-6">
<div>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">{tSkills('backend')}</h3>
<div className="flex flex-wrap gap-3">
{skillsData.backend.map(skill => <SkillPill key={skill} skill={skill} />)}
</div>
</div>
<div>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">{tSkills('frontend')}</h3>
<div className="flex flex-wrap gap-3">
{skillsData.frontend.map(skill => <SkillPill key={skill} skill={skill} />)}
</div>
</div>
<div>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">{tSkills('databases')}</h3>
<div className="flex flex-wrap gap-3">
{skillsData.databases.map(skill => <SkillPill key={skill} skill={skill} />)}
</div>
</div>
<div>
<h3 className="text-xl font-semibold text-[var(--color-text-primary)] mb-3">{tSkills('cloud')}</h3>
<div className="flex flex-wrap gap-3">
{skillsData.cloud.map(skill => <SkillPill key={skill} skill={skill} />)}
</div>
</div>
</div>
</div>
</section>
</motion.section>
);
}

View File

@@ -1,5 +1,5 @@
'use client';
import { motion } from 'framer-motion';
import {useTranslations} from 'next-intl';
import { FormEvent, useState } from 'react';
import toast from 'react-hot-toast';
@@ -72,33 +72,40 @@ export default function Contact() {
};
return (
<section id="contact" className="container mx-auto py-24 md:py-32 lg:py-48">
<motion.section
id="contact"
className="container mx-auto py-12 md:py-20"
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, ease: "easeOut" }}
>
<div className="text-center max-w-2xl mx-auto">
<h2 className="text-3xl md:text-4xl font-bold text-[var(--color-text-primary)] mb-4">{t('title')}</h2>
<h2 className="text-3xl md:text-4xl font-bold text-[var(--color-text-primary)] heading-underline">{t('title')}</h2>
<p className="text-[var(--color-text-secondary)] mb-10">{t('subtitle')}</p>
</div>
{/* Centered Form */}
<form onSubmit={handleSubmit} className="max-w-xl mx-auto">
<div className="mb-4">
<label htmlFor="name" className="block mb-2 text-sm font-medium text-[var(--color-text-secondary)]">{t('form_name')}</label>
<input type="text" name="name" id="name" value={formData.name} onChange={handleChange} required
className="bg-[var(--color-card)] border border-[var(--color-border)] text-[var(--color-text-primary)] text-sm rounded-lg focus:ring-[var(--color-primary)] focus:border-[var(--color-primary)] block w-full p-2.5" />
className="bg-[var(--color-card)] border border-[var(--color-border)] text-[var(--color-text-primary)] text-sm rounded-lg focus:ring-[var(--color-primary)] focus:border-[var(--color-primary)] block w-full p-2.5" />
</div>
<div className="mb-4">
<label htmlFor="email" className="block mb-2 text-sm font-medium text-[var(--color-text-secondary)]">{t('form_email')}</label>
<input type="email" name="email" id="email" value={formData.email} onChange={handleChange} required
className="bg-[var(--color-card)] border border-[var(--color-border)] text-[var(--color-text-primary)] text-sm rounded-lg focus:ring-[var(--color-primary)] focus:border-[var(--color-primary)] block w-full p-2.5" />
className="bg-[var(--color-card)] border border-[var(--color-border)] text-[var(--color-text-primary)] text-sm rounded-lg focus:ring-[var(--color-primary)] focus:border-[var(--color-primary)] block w-full p-2.5" />
</div>
<div className="mb-6">
<label htmlFor="message" className="block mb-2 text-sm font-medium text-[var(--color-text-secondary)]">{t('form_message')}</label>
<textarea name="message" id="message" rows={5} value={formData.message} onChange={handleChange} required
className="bg-[var(--color-card)] border border-[var(--color-border)] text-[var(--color-text-primary)] text-sm rounded-lg focus:ring-[var(--color-primary)] focus:border-[var(--color-primary)] block w-full p-2.5"></textarea>
</div>
<div className="text-center">
<button type="submit" className="bg-[var(--color-primary)] hover:bg-[var(--color-primary)]/90 text-white font-bold py-3 px-8 rounded-full transition-colors" disabled={isSubmitting}>
{isSubmitting ? t('status_sending') : t('submit_button')}
</button>
</div>
<button type="submit" className="bg-[var(--color-primary)] hover:bg-[var(--color-primary)]/90 text-white font-bold py-3 px-8 rounded-full transition-colors w-full" disabled={isSubmitting}>
{isSubmitting ? t('status_sending') : t('submit_button')}
</button>
</form>
</section>
</motion.section>
);
}

View File

@@ -1,20 +1,42 @@
'use client';
import {useTranslations} from 'next-intl';
import Link from 'next/link';
import { TypeAnimation } from 'react-type-animation';
export default function Hero() {
const t = useTranslations('hero');
return (
<section id="home" className="container mx-auto text-center py-24 md:py-32 lg:py-48">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-[var(--color-text-primary)]">
{t('greeting')} <span className="block text-[var(--color-primary)]">{t('title')}</span>
</h1>
<p className="text-lg md:text-xl text-[var(--color-text-secondary)] max-w-2xl mx-auto mt-6 mb-8">
{t('subtitle')}
</p>
<Link href="#contact" className="bg-[var(--color-primary)] hover:bg-[var(--color-primary)]/90 text-white font-bold py-3 px-8 rounded-full transition-colors">
{t('cta_button')}
</Link>
<section id="home" className="relative w-full flex flex-col items-center justify-center min-h-[calc(100vh-var(--header-height))]">
<div className="container mx-auto px-4 text-center flex flex-col items-center">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-white">
{t('greeting')} <span className="text-[var(--color-primary)]">João Loureiro</span>
</h1>
<TypeAnimation
sequence={[
t('sequence_1'),
1500,
t('sequence_2'),
1500,
t('sequence_3'),
1500,
]}
wrapper="p"
speed={50}
className="text-xl md:text-2xl lg:text-3xl font-medium text-slate-300 max-w-3xl mx-auto mt-6 mb-8"
repeat={Infinity}
/>
<p className="text-lg md:text-xl text-slate-200 max-w-2xl mx-auto mt-6 mb-8">
{t('subtitle')}
</p>
<Link href="#contact" className="bg-[var(--color-primary)] hover:bg-[var(--color-primary)]/90 text-white font-bold py-3 px-8 rounded-full transition-colors">
{t('cta_button')}
</Link>
</div>
<div className="absolute bottom-12">
<div className="mouse"></div>
</div>
</section>
);
}

View File

@@ -1,29 +1,60 @@
'use client';
import {useTranslations} from 'next-intl';
import ProjectCard from '../ProjectCard';
import { motion } from 'framer-motion';
// Você pode popular isso com seus dados reais
const projectsData = [
{ id: 1, tech: ["Next.js", "Stripe", "Tailwind CSS", "Prisma"] },
{ id: 2, tech: ["Socket.IO", "Node.js", "React", "Express"] },
{ id: 3, tech: ["Next.js", "MDX", "Tailwind CSS", "Vercel"] }
{ id: 1, tech: ["Next.js", "Stripe", "Tailwind CSS", "Prisma"], imageUrl: "/project1.jpg" },
{ id: 2, tech: ["Socket.IO", "Node.js", "React", "Express"], imageUrl: "/project2.jpg" },
{ id: 3, tech: ["Next.js", "MDX", "Tailwind CSS", "Vercel"], imageUrl: "/project3.jpg" }
];
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.2, delayChildren: 0.1 }
}
};
const cardVariants = {
hidden: { y: 20, opacity: 0 },
visible: { y: 0, opacity: 1 }
};
export default function Projects() {
const t = useTranslations('projects');
return (
<section id="projects" className="container mx-auto py-24 md:py-32 lg:py-48">
<h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--color-text-primary)] mb-12">{t('title')}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{projectsData.map(project => (
<ProjectCard
key={project.id}
title={t(`project_${project.id}_title`)}
description={t(`project_${project.id}_description`)}
tech={project.tech}
/>
))}
<motion.section
id="projects"
className="bg-[var(--color-card)] py-12 md:py-20"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
<div className="container mx-auto px-4">
<h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--color-text-primary)] heading-underline">{t('title')}</h2>
<motion.div
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
{projectsData.map(project => (
<motion.div key={project.id} variants={cardVariants}>
<ProjectCard
title={t(`project_${project.id}_title`)}
description={t(`project_${project.id}_description`)}
tech={project.tech}
imageUrl={project.imageUrl}
/>
</motion.div>
))}
</motion.div>
</div>
</section>
</motion.section>
);
}

View File

@@ -1,37 +0,0 @@
import {useTranslations} from 'next-intl';
const skillsData = {
languages: ["JavaScript (ES6+)", "TypeScript", "HTML5", "CSS3", "Python"],
frameworks: ["React", "Next.js", "Node.js", "Express", "Tailwind CSS", "tRPC"],
tools: ["Git", "GitHub", "Docker", "VS Code", "Figma", "Postman", "Vercel"],
databases: ["PostgreSQL", "MongoDB", "Redis", "Prisma ORM"]
};
const SkillCategory = ({ title, skills }: { title: string, skills: string[] }) => (
<div className="bg-[var(--color-card)] p-6 rounded-lg border border-[var(--color-border)]">
<h3 className="text-[var(--color-primary)] mb-4">{title}</h3>
<div className="flex flex-wrap gap-2">
{skills.map(skill => (
<span key={skill} className="bg-[var(--color-border)]/50 text-[var(--color-text-secondary)] px-3 py-1 rounded-full text-sm font-medium">
{skill}
</span>
))}
</div>
</div>
);
export default function Skills() {
const t = useTranslations('skills');
return (
<section id="skills" className="container mx-auto py-24 md:py-32 lg:py-48">
<h2 className="text-3xl md:text-4xl font-bold text-center text-[var(--color-text-primary)] mb-12">{t('title')}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<SkillCategory title={t('languages')} skills={skillsData.languages} />
<SkillCategory title={t('frameworks')} skills={skillsData.frameworks} />
<SkillCategory title={t('tools')} skills={skillsData.tools} />
<SkillCategory title={t('databases')} skills={skillsData.databases} />
</div>
</section>
);
}

View File

@@ -1,29 +1,142 @@
@import "tailwindcss";
@import "tailwindcss";
@tailwind utilities;
@theme {
--color-background: #161616;
--color-card: #121212;
--color-primary: #636B97;
--color-text-primary: #FFFFFF;
--color-text-secondary: #EAE3DB;
--color-border: #27272A;
/* --- Indigo Dream | Light Theme (Default) --- */
:root {
--color-background: #ffffff;
--color-card: #f7f7f7;
--color-primary: #3b82f6; /* A vibrant, modern blue */
--color-text-primary: #111827;
--color-text-secondary: #6b7280;
--color-border: #e5e7eb;
--header-height: 4.6rem;
}
/* --- Modern Gradient | Dark Theme --- */
.dark {
--color-background: #030712; /* A very deep, neutral blue-gray */
--color-card: #111827;
--color-primary: #3b82f6; /* Blue remains vibrant on the dark background */
--color-text-primary: #f9fafb;
--color-text-secondary: #9ca3af;
--color-border: #374151;
}
body {
color: var(--color-text-primary);
/* Apply the IBM Plex Sans font variable to the body */
font-family: var(--font-quantico);
background-color: var(--color-background);
font-family: Inter, sans-serif; /* Adicione sua fonte preferida */
color: var(--color-text-primary);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background-color 0.3s ease, color 0.3s ease;
}
h1, h2, h3, h4, h5, h6 {
/* Apply the Poppins font variable to all headings */
font-family: var(--font-quantico), sans-serif;
font-weight: 700;
line-height: 1.2;
}
h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
h3 { font-size: 1.75rem; }
h3 { font-size: 1.75rem; }
/* --- Hero Section Styling --- */
#home {
/* Re-added your background image underneath the gradient */
background:
linear-gradient(125deg, rgba(5, 7, 21, 0.85) 0%, rgba(23, 33, 84, 0.85) 50%, rgba(67, 61, 125, 0.85) 100%),
url("/pattern-randomized (3).svg");
background-position: center;
background-size: cover;
background-blend-mode: normal;
}
/* Adds a subtle glow to your name in the hero section */
.hero-name-accent {
color: var(--color-primary);
text-shadow: 0 0 12px rgba(var(--color-primary-rgb), 0.5);
}
/* Adds a subtle shadow to hero text for better readability on the gradient */
#home h1, #home p {
text-shadow: 0px 4px 8px rgba(0, 0, 0, 0.7);
}
/* --- Scroll Mouse Animation --- */
.mouse {
width: 25px;
height: 40px;
/* Always white to be visible on the dark hero gradient */
border: 2px solid #ffffff;
border-radius: 60px;
position: relative;
overflow: hidden;
}
.mouse::before {
content: '';
width: 5px;
height: 5px;
position: absolute;
top: 7px;
left: 50%;
transform: translateX(-50%);
background-color: #ffffff;
border-radius: 50%;
opacity: 1;
animation: wheel 1.5s infinite;
-webkit-animation: wheel 1.5s infinite;
}
@keyframes wheel {
to {
opacity: 0;
top: 27px;
}
}
@-webkit-keyframes wheel {
to {
opacity: 0;
top: 27px;
}
}
/* --- Language Switcher Dropdown --- */
.lang-switcher-options {
transform-origin: top right;
animation: scale-in 0.1s ease-out forwards;
}
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* For the accent line under headings */
.heading-underline {
position: relative;
padding-bottom: 1.5rem;
margin-bottom: 3rem !important; /* Ensure spacing */
}
.heading-underline::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 5rem;
height: 4px;
background-color: var(--color-primary);
border-radius: 9999px;
}

View File

@@ -15,8 +15,11 @@
"contact": "Contact"
},
"hero": {
"greeting": "Hello, I'm João Loureiro.",
"greeting": "Hello, I'm",
"title": "Software Developer & Web Enthusiast",
"sequence_1": "Full-Stack Software Developer",
"sequence_2": ".NET & Cloud Enthusiast",
"sequence_3": "Passionate about Technology",
"subtitle": "I build modern, responsive, and user-friendly web applications from front-end to back-end.",
"cta_button": "See My Work"
},
@@ -27,11 +30,11 @@
"paragraph3": "When I'm not at my keyboard, I enjoy exploring the outdoors, reading about new tech trends, and contributing to open-source projects. I believe the best products are built at the intersection of great technology and human-centric design."
},
"skills": {
"title": "My Tech Stack",
"languages": "Languages",
"frameworks": "Frameworks & Libraries",
"tools": "Developer Tools",
"databases": "Databases"
"title": "Minhas Tecnologias",
"backend": "Backend & Linguagens",
"frontend": "Frontend",
"databases": "Bancos de Dados & Dados",
"cloud": "Cloud & DevOps"
},
"projects": {
"title": "Projects I've Built",

View File

@@ -15,8 +15,11 @@
"contact": "Contato"
},
"hero": {
"greeting": "Olá, eu sou o João Loureiro.",
"greeting": "Olá, eu sou o",
"title": "Desenvolvedor de Software & Entusiasta Web",
"sequence_1": "Desenvolvedor de Software Full-Stack",
"sequence_2": "Entusiasta em .NET & Cloud",
"sequence_3": "Apaixonado por Tecnologia",
"subtitle": "Eu construo aplicações web modernas, responsivas e fáceis de usar, do front-end ao back-end.",
"cta_button": "Veja Meus Projetos"
},
@@ -27,11 +30,11 @@
"paragraph3": "Quando não estou no meu teclado, gosto de explorar a natureza, ler sobre novas tendências tecnológicas e contribuir para projetos de código aberto. Acredito que os melhores produtos são construídos na interseção de ótima tecnologia e design centrado no ser humano."
},
"skills": {
"title": "Minhas Tecnologias",
"languages": "Linguagens",
"frameworks": "Frameworks & Bibliotecas",
"tools": "Ferramentas de Dev",
"databases": "Bancos de Dados"
"title": "My Tech Stack",
"backend": "Backend & Languages",
"frontend": "Frontend",
"databases": "Databases & Data",
"cloud": "Cloud & DevOps"
},
"projects": {
"title": "Projetos que Construí",