Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default defineConfig({
},

use: {
baseURL: 'http://localhost:3000/',
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000/',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
Expand Down
19 changes: 0 additions & 19 deletions src/app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,10 @@
'use client';

import { useState, useEffect } from 'react';
import Image from 'next/image';
import AboutSections from '../components/AboutSections/AboutSections';
import LinkButton from '../components/Button/LinkButton';
import LoadingSpinner from '../components/Loading/Loading';

export default function About() {
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
const timer = setTimeout(() => {
setIsLoading(false);
}, 1000); // Simulate a loading time, adjust as needed
return () => clearTimeout(timer);
}, []);

if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<LoadingSpinner />
</div>
);
}

return (
<main
className="flex flex-col items-center justify-center min-h-[85vh] p-8"
Expand Down
13 changes: 6 additions & 7 deletions src/app/components/AboutSections/AboutSections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,13 @@ export default function AboutSections() {
{sections.map((section, index) => (
<section
key={index}
className="w-full pb-4 border-b border-yellow-500 cursor-pointer last:border-none px-0 mx-0"
className="w-full pb-4 border-b border-yellow-500 last:border-none px-0 mx-0"
data-testid={`section-${index}`}
onClick={() => toggleSection(index)}
>
<header
className="flex justify-between items-center"
role="button"
tabIndex={0}
<button
type="button"
className="flex w-full justify-between items-center text-left"
onClick={() => toggleSection(index)}
aria-expanded={openSection === index}
aria-controls={`section-content-${index}`}
data-testid={`section-toggle-${index}`}
Expand All @@ -59,7 +58,7 @@ export default function AboutSections() {
>
</span>
</header>
</button>
{openSection === index && (
<div
className="mt-2 text-gray-300 text-base sm:text-lg"
Expand Down
41 changes: 26 additions & 15 deletions src/app/components/Button/LinkButton.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,44 @@
import Link from 'next/link';
import { ButtonHTMLAttributes } from 'react';
import { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react';

interface LinkButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
interface BaseLinkButtonProps {
text: string;
href?: string;
extraClasses?: string;
className?: string;
}

export default function LinkButton({
text,
href,
extraClasses = '',
...props
}: LinkButtonProps) {
type LinkButtonAsLinkProps = BaseLinkButtonProps &
Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> & {
href: string;
};

type LinkButtonAsButtonProps = BaseLinkButtonProps &
ButtonHTMLAttributes<HTMLButtonElement> & {
href?: undefined;
};

type LinkButtonProps = LinkButtonAsLinkProps | LinkButtonAsButtonProps;

export default function LinkButton(props: LinkButtonProps) {
const { text, extraClasses = '', className } = props;
const baseClasses =
'border border-yellow-500 text-yellow-500 rounded-full px-8 py-4 text-lg transition-transform hover:scale-110';
const combinedClasses = `${baseClasses} ${extraClasses} ${className ?? ''}`.trim();

if ('href' in props && props.href) {
const { href, ...linkProps } = props as LinkButtonAsLinkProps;

if (href) {
return (
<Link href={href}>
<button className={`${baseClasses} ${extraClasses}`} {...props}>
{text}
</button>
<Link href={href} className={combinedClasses} {...linkProps}>
{text}
</Link>
);
}

const { type = 'button', ...buttonProps } = props as LinkButtonAsButtonProps;

return (
<button className={`${baseClasses} ${extraClasses}`} {...props}>
<button className={combinedClasses} type={type} {...buttonProps}>
{text}
</button>
);
Expand Down
6 changes: 4 additions & 2 deletions src/app/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export default function Footer() {
const currentYear = new Date().getFullYear();

return (
<footer
className="w-full flex flex-col items-center justify-start gap-6 p-4 border-t border-white/20"
className="w-full flex flex-col items-center justify-start gap-6 bg-black p-4 border-t border-white/20"
data-testid="footer-container"
>
<div
Expand Down Expand Up @@ -62,7 +64,7 @@ export default function Footer() {
className="text-sm uppercase text-center px-4"
data-testid="footer-copyright"
>
© 2024 Bruno Machado.
© {currentYear} Bruno Machado.
</p>
</footer>
);
Expand Down
1 change: 1 addition & 0 deletions src/app/components/Header/DesktopMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const menuItems = [
{ name: 'Resume', href: '/resume' },
{ name: 'Skills', href: '/skills' },
{ name: 'Projects', href: '/projects' },
{ name: 'Talks', href: '/talks' },
{ name: 'Posts', href: '/posts' },
{ name: 'Contacts', href: '/contacts' },
];
Expand Down
2 changes: 1 addition & 1 deletion src/app/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Menu from './Menu';

export default function Header() {
return (
<header className="flex justify-between items-center px-6 py-6 sm:px-24">
<header className="sticky top-0 z-50 flex justify-between items-center bg-black px-6 py-6 sm:px-24">
<Logo />
<Menu />
</header>
Expand Down
6 changes: 4 additions & 2 deletions src/app/components/Header/MenuItem.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Link from 'next/link';

interface MenuItemProps {
name: string;
href: string;
Expand All @@ -14,7 +16,7 @@ export const MenuItem: React.FC<MenuItemProps> = ({
dataTestId,
}) => {
return (
<a
<Link
href={href}
aria-label={`Navigate to ${name}`}
aria-current={isActive ? 'page' : undefined}
Expand All @@ -25,6 +27,6 @@ export const MenuItem: React.FC<MenuItemProps> = ({
onClick={onClick}
>
{name}
</a>
</Link>
);
};
3 changes: 2 additions & 1 deletion src/app/components/Header/MobileMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const menuItems = [
{ name: 'Resume', href: '/resume' },
{ name: 'Skills', href: '/skills' },
{ name: 'Projects', href: '/projects' },
{ name: 'Talks', href: '/talks' },
{ name: 'Posts', href: '/posts' },
{ name: 'Contacts', href: '/contacts' },
];
Expand All @@ -22,7 +23,7 @@ export default function MobileMenu({ closeMenu }: MobileMenuProps) {

return (
<nav
className="absolute top-16 left-0 w-full h-full p-6 flex flex-col items-start gap-4 lg:hidden bg-background"
className="fixed inset-0 z-[60] overflow-y-auto bg-black p-6 pt-24 flex flex-col items-start gap-4 lg:hidden"
aria-label="Mobile Navigation"
>
{menuItems.map((item) => (
Expand Down
1 change: 1 addition & 0 deletions src/app/content/experiences.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const EXPERIENCES = [
'https://res.cloudinary.com/dtglidvcw/image/upload/v1735601960/Portifolio/Avios/avios1.jpg',
'https://res.cloudinary.com/dtglidvcw/image/upload/v1735601960/Portifolio/Avios/avios2.jpg',
'https://res.cloudinary.com/dtglidvcw/image/upload/v1735601960/Portifolio/Avios/avios3.jpg',
'https://res.cloudinary.com/dtglidvcw/image/upload/v1777031448/Portifolio/Avios/1F6EC3B4-ACF8-4288-BDCE-67F1BF884082_1_105_c_mqjqqi.jpg',
],
},
{
Expand Down
44 changes: 44 additions & 0 deletions src/app/content/projects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,57 @@ export interface Project {
description: string;
style: ProjectStyle;
logo: string;
logoClassName?: string;
sections: {
title: string;
content: string | JSX.Element;
}[];
}

export const PROJECTS: Project[] = [
{
title: 'Adventurers Guild',
description:
'A fantasy-themed API portal inspired by Dungeons & Dragons, designed as an interactive codex with guides, documentation, and immersive UI for exploring RPG resources and character systems.',
style: 'AdventurersGuild',
logo: 'https://res.cloudinary.com/dtglidvcw/image/upload/v1776961236/adventurers/ChatGPT_Image_26_de_mar._de_2026_10_47_27_v6yuq8.png',
logoClassName: 'scale-150 sm:scale-[1.85]',
sections: [
{
title: 'Overview',
content:
'Adventurers Guild is an API portal with an immersive frontend inspired by tabletop RPGs and Dungeons & Dragons, presenting technical resources through a worldbuilding-oriented experience.',
},
{
title: 'Experience Direction',
content:
'The documentation is treated like an illustrated codex, guiding users through attributes, skills, classes, species, spells, equipment, and character systems in a more exploratory way.',
},
{
title: 'Visual Style',
content:
'The interface leans into a fantasy codex and medieval grimoire aesthetic, using parchment surfaces, leather tones, ornamental framing, and warm contrast to reinforce the setting.',
},
{
title: 'Current Status',
content:
'More details, links, and final visual assets will be added later as the project evolves.',
},
{
title: 'Visit the Project',
content: (
<a
href="https://adventurers-guild-api.vercel.app/"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
Adventurers Guild Website
</a>
),
},
],
},
{
title: 'BugBuster',
description:
Expand Down
Loading
Loading