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
3 changes: 1 addition & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,14 @@ src/
│ └── sitemap.ts # SEO sitemap
├── components/
│ ├── ui/ # Pure presentational (Button, Badge, Card, Modal)
│ ├── ui/ # Pure presentational (Button, Badge, Avatar, ImageCarousel)
│ │ └── *.tsx # No data fetching, minimal logic
│ └── common/ # Shared layout components (Navbar, Footer, Container)
│ └── *.tsx
├── features/ # Domain features (encapsulated behavior + UI)
│ ├── landing/ # Hero section components
│ ├── language/ # Language switch logic + UI
│ ├── constructionNotice/ # Scroll guard + modal
│ └── projects/ # Project cards, detail views
├── i18n/ # next-intl configuration
Expand Down
3 changes: 2 additions & 1 deletion src/app/es/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { Metadata } from 'next'
import { JsonLd, LocalizedShell } from '@/components/common'
import { HomePageClient } from '@/features/landing'
import { SIDEBAR_CONTENT_OFFSET_CLASS } from '@/features/morphNav/layout'
import { buildHomeJsonLd, buildHomeMetadata } from '@/lib/seo'

export const metadata: Metadata = buildHomeMetadata('es')

export default function SpanishHome() {
return (
<LocalizedShell locale="es">
<LocalizedShell locale="es" footerClassName={SIDEBAR_CONTENT_OFFSET_CLASS}>
<JsonLd data={buildHomeJsonLd('es')} />
<HomePageClient />
</LocalizedShell>
Expand Down
7 changes: 7 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
@import "tailwindcss";

@theme {
/* Extra breakpoint between small phones (320-419px) and large phones (420-639px). */
--breakpoint-xs: 26.25rem;
}

/* CSS Variables for theming */
:root {
--bg: #040304;
Expand Down Expand Up @@ -29,6 +34,8 @@
html {
color-scheme: dark;
scroll-behavior: smooth;
/* Keep hash targets clear of the fixed 4rem header. */
scroll-padding-top: 5rem;
}

body {
Expand Down
10 changes: 8 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Metadata } from 'next'
import type { Metadata, Viewport } from 'next'
import './globals.css'
import { SITE } from '@/lib/constants'
import { OG_IMAGE } from '@/lib/seo'
Expand Down Expand Up @@ -58,6 +58,13 @@ export const metadata: Metadata = {
},
}

export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
viewportFit: 'cover',
themeColor: '#040304',
}

export default function RootLayout({
children,
}: Readonly<{
Expand All @@ -66,7 +73,6 @@ export default function RootLayout({
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta name="theme-color" content="#040304" />
<link
rel="preload"
as="image"
Expand Down
3 changes: 2 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { Metadata } from 'next'
import { JsonLd, LocalizedShell } from '@/components/common'
import { HomePageClient } from '@/features/landing'
import { SIDEBAR_CONTENT_OFFSET_CLASS } from '@/features/morphNav/layout'
import { buildHomeJsonLd, buildHomeMetadata } from '@/lib/seo'

export const metadata: Metadata = buildHomeMetadata('en')

export default function Home() {
return (
<LocalizedShell locale="en">
<LocalizedShell locale="en" footerClassName={SIDEBAR_CONTENT_OFFSET_CLASS}>
<JsonLd data={buildHomeJsonLd('en')} />
<HomePageClient />
</LocalizedShell>
Expand Down
19 changes: 12 additions & 7 deletions src/components/common/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import { Container, EmailIcon, ExternalLinkIcon, GitHubIcon, LinkedInIcon } from
import { LINKS, SITE, getCvUrl } from '@/lib/constants'
import { useEmailCopyFeedback } from '@/lib/hooks/useEmailCopyFeedback'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'

export function Footer() {
export interface FooterProps {
className?: string
}

export function Footer({ className }: FooterProps = {}) {
const t = useTranslations('footer')
const tNav = useTranslations('nav')
const { locale } = useLocale()
Expand Down Expand Up @@ -37,7 +42,7 @@ export function Footer() {
]

return (
<footer className="relative overflow-hidden border-t border-[var(--border)] bg-[var(--card)]/30">
<footer className={cn('relative overflow-hidden border-t border-[var(--border)] bg-[var(--card)]/30', className)}>
<div className="absolute inset-0 pointer-events-none">
<div className="absolute bottom-0 left-1/2 h-[14rem] w-[min(42rem,90vw)] -translate-x-1/2 rounded-full bg-[var(--accent)]/4 blur-[120px]" />
</div>
Expand All @@ -56,12 +61,12 @@ export function Footer() {
<h3 className="mb-4 text-xs font-semibold uppercase tracking-wider text-[var(--fg-muted)]/60">
{t('sections.navigation')}
</h3>
<ul className="grid grid-cols-2 gap-x-5 gap-y-2.5">
<ul className="grid grid-cols-1 gap-x-5 gap-y-1 xs:grid-cols-2">
{navLinks.map((link) => (
<li key={link.href}>
<a
href={link.href}
className="inline-flex min-h-6 items-center text-sm text-[var(--fg-muted)] transition-colors duration-200 hover:text-[var(--fg)]"
className="inline-flex min-h-9 items-center py-1 text-sm text-[var(--fg-muted)] transition-colors duration-200 hover:text-[var(--fg)]"
>
{link.label}
</a>
Expand All @@ -74,14 +79,14 @@ export function Footer() {
<h3 className="mb-4 text-xs font-semibold uppercase tracking-wider text-[var(--fg-muted)]/60">
{t('sections.resources')}
</h3>
<ul className="space-y-2.5">
<ul className="space-y-1">
{resourceLinks.map((link) => (
<li key={link.href}>
<a
href={link.href}
target={link.external ? '_blank' : undefined}
rel={link.external ? 'noopener noreferrer' : undefined}
className="inline-flex min-h-6 max-w-full items-center gap-1.5 text-sm text-[var(--fg-muted)] transition-colors duration-200 hover:text-[var(--fg)]"
className="inline-flex min-h-9 max-w-full items-center gap-1.5 py-1 text-sm text-[var(--fg-muted)] transition-colors duration-200 hover:text-[var(--fg)]"
>
<span className="min-w-0 truncate">{link.label}</span>
{link.external && (
Expand All @@ -105,7 +110,7 @@ export function Footer() {
target={link.icon !== 'email' ? '_blank' : undefined}
rel={link.icon !== 'email' ? 'noopener noreferrer' : undefined}
onClick={link.icon === 'email' ? copyEmail : undefined}
className="flex h-10 w-10 items-center justify-center rounded-xl border border-[var(--border)] bg-[var(--card)] text-[var(--fg-muted)] transition-colors duration-200 hover:border-[var(--accent)]/40 hover:text-[var(--fg)]"
className="flex size-11 items-center justify-center rounded-xl border border-[var(--border)] bg-[var(--card)] text-[var(--fg-muted)] transition-colors duration-200 hover:border-[var(--accent)]/40 hover:text-[var(--fg)]"
aria-label={link.label}
whileHover={{ y: -2 }}
whileTap={{ scale: 0.95 }}
Expand Down
11 changes: 9 additions & 2 deletions src/components/common/LocalizedShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface LocalizedShellProps {
children: ReactNode
locale: Locale
showFooter?: boolean
/** Extra classes for the footer, e.g. the sidebar offset on the home page. */
footerClassName?: string
}

function SkipToContentLink() {
Expand All @@ -21,15 +23,20 @@ function SkipToContentLink() {
)
}

export function LocalizedShell({ children, locale, showFooter = true }: LocalizedShellProps) {
export function LocalizedShell({
children,
locale,
showFooter = true,
footerClassName,
}: LocalizedShellProps) {
return (
<I18nProvider locale={locale}>
<LocalePreferenceGate />
<SkipToContentLink />
<main id="main-content">
{children}
</main>
{showFooter && <Footer />}
{showFooter && <Footer className={footerClassName} />}
<BrowserTranslateHint />
</I18nProvider>
)
Expand Down
6 changes: 3 additions & 3 deletions src/components/ui/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export function Badge({ children, variant = 'default', size = 'sm', className }:
const baseStyles = 'inline-flex items-center font-medium transition-all duration-200'

const variants = {
default: 'bg-card text-fg-muted border border-border',
accent: 'bg-accent/20 text-accent border border-accent/30',
outline: 'bg-transparent text-fg-muted border border-border hover:border-accent/50'
default: 'border border-[var(--border)] bg-[var(--card)] text-[var(--fg-muted)]',
accent: 'border border-[var(--accent)]/30 bg-[var(--accent)]/20 text-[var(--accent)]',
outline: 'border border-[var(--border)] bg-transparent text-[var(--fg-muted)] hover:border-[var(--accent)]/50'
}

const sizes = {
Expand Down
Loading
Loading