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
4 changes: 2 additions & 2 deletions public/favicons/site.webmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"purpose": "maskable"
}
],
"theme_color": "#403e3e",
"background_color": "#403e3e",
"theme_color": "#040304",
"background_color": "#040304",
"display": "standalone"
}
4 changes: 2 additions & 2 deletions src/app/es/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +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 { FLOATING_MENU_CLEARANCE_CLASS, 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" footerClassName={SIDEBAR_CONTENT_OFFSET_CLASS}>
<LocalizedShell locale="es" footerClassName={`${SIDEBAR_CONTENT_OFFSET_CLASS} ${FLOATING_MENU_CLEARANCE_CLASS}`}>
<JsonLd data={buildHomeJsonLd('es')} />
<HomePageClient />
</LocalizedShell>
Expand Down
275 changes: 71 additions & 204 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@
--breakpoint-xs: 26.25rem;
}

/*
* Page gutter (16/24/32px) that also clears landscape notches: the viewport uses
* viewport-fit=cover, so content would otherwise slide under the notch.
*/
@utility px-gutter {
padding-left: max(1rem, env(safe-area-inset-left));
padding-right: max(1rem, env(safe-area-inset-right));

@media (width >= 40rem) {
padding-left: max(1.5rem, env(safe-area-inset-left));
padding-right: max(1.5rem, env(safe-area-inset-right));
}

@media (width >= 64rem) {
padding-left: max(2rem, env(safe-area-inset-left));
padding-right: max(2rem, env(safe-area-inset-right));
}
}

/* CSS Variables for theming */
:root {
--bg: #040304;
Expand Down Expand Up @@ -43,7 +62,7 @@ body {
radial-gradient(circle at 70% 8%, rgba(166, 107, 87, 0.12), transparent 32rem),
linear-gradient(180deg, #060403 0%, var(--bg) 36%, #070504 100%);
color: var(--fg);
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
font-family: var(--font-inter), Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
line-height: 1.6;
letter-spacing: 0.01em;
position: relative;
Expand Down Expand Up @@ -96,11 +115,37 @@ body::before {
border-bottom: 1px solid;
}

/* Focus styles for accessibility */
*:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: 2px;
/*
* Base rules live in the base layer so Tailwind utilities can override them
* (e.g. `focus-visible:outline-none` + a ring, or an explicit image height).
*/
@layer base {
/* Default focus ring for anything that does not draw its own */
*:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}

::selection {
background-color: rgba(220, 162, 147, 0.3);
color: var(--fg);
}

img {
image-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
max-width: 100%;
height: auto;
}

/* Photos, portraits and carousel frames fill their aspect box */
img[class*="object-cover"],
img[class*="object-contain"] {
display: block;
width: 100%;
height: 100%;
}
}

/* Skip to content link */
Expand All @@ -122,91 +167,6 @@ body::before {
top: 6px;
}

/* Utility classes */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}

@media (min-width: 640px) {
.container {
padding: 0 2rem;
}
}

.section {
padding: 4rem 0;
}

@media (min-width: 768px) {
.section {
padding: 6rem 0;
}
}

/* Selection styling */
::selection {
background-color: rgba(220, 162, 147, 0.3);
color: var(--fg);
}

/* Image quality optimization - Prevent blurry images across entire project */
img {
/* High quality rendering for all resolutions */
image-rendering: -webkit-optimize-contrast;
image-rendering: auto;

/* Smooth font rendering */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;

/* Prevent layout shifts */
max-width: 100%;
height: auto;

/* Disable blur filter by default */
filter: none;
}

/* For images that need smooth scaling (photos, portraits, carousels) */
img[class*="object-cover"],
img[class*="object-contain"],
motion img,
[data-framer-component-type="motion.img"] img {
image-rendering: auto;

/* Ensure proper scaling */
display: block;
width: 100%;
height: 100%;

/* Smooth transitions - prevents flashing */
will-change: opacity;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}

/* Avatar images - extra sharp but smooth */
.avatar img,
[class*="Avatar"] img {
image-rendering: -webkit-optimize-contrast;
image-rendering: auto;
}

/* Carousel and gallery images - optimized for smooth viewing and transitions */
.carousel img,
.gallery img,
[class*="Carousel"] img,
[class*="Gallery"] img {
image-rendering: auto;
-webkit-font-smoothing: antialiased;
will-change: opacity;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}

.photo-render {
image-rendering: auto;
filter: none;
Expand All @@ -227,129 +187,36 @@ motion img,
}
}

/* Animation utility classes */
.transition-base {
transition: all var(--dur-base) var(--ease);
}

.transition-fast {
transition: all var(--dur-fast) var(--ease-out);
}

/* Hover effects for interactive elements */
.hover-lift {
transition: transform var(--dur-fast) var(--ease-out);
}

.hover-lift:hover {
transform: translateY(-2px);
}

/* Shine effect for avatar */
.shine-on-hover {
position: relative;
overflow: hidden;
}

.shine-on-hover::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(
45deg,
transparent 30%,
rgba(255, 255, 255, 0.1) 50%,
transparent 70%
);
transform: translateX(-100%);
transition: transform var(--dur-slow) var(--ease);
}

.shine-on-hover:hover::after {
transform: translateX(100%);
}

/* Reduce animations for accessibility */
@media (prefers-reduced-motion: reduce) {
.scroll-cue {
animation: none;
}

.transition-base,
.transition-fast,
.hover-lift,
.shine-on-hover::after {
transition: none;
}

.hover-lift:hover {
transform: none;
}

.shine-on-hover:hover::after {
transform: translateX(-100%);
}
}

/* Fluid container utilities */
.content-width {
max-width: 72rem;
}

@media (min-width: 1536px) {
.content-width {
max-width: 80rem;
/*
* Hero entrance: plain CSS so the first screen paints (and animates) before
* JavaScript hydrates. Stagger with an arbitrary `[animation-delay:…]` utility.
*/
@layer components {
.hero-enter {
animation: hero-enter 0.7s var(--ease-out) both;
}
}

@media (min-width: 1920px) {
.content-width {
max-width: 88rem;
@keyframes hero-enter {
from {
opacity: 0;
transform: translateY(14px);
}
}

@media (min-width: 2560px) {
.content-width {
max-width: 96rem;
to {
opacity: 1;
transform: none;
}
}

/* Fluid spacing utilities */
.section-y {
padding-block: clamp(2rem, 4vw + 1rem, 6rem);
}

.section-x {
padding-inline: clamp(1rem, 3vw, 3rem);
}

/* Header scroll effects */
.header {
transition: box-shadow 180ms var(--ease), backdrop-filter 180ms var(--ease);
}

.header.has-shadow {
box-shadow: 0 6px 20px -12px rgba(0, 0, 0, 0.45);
backdrop-filter: saturate(1.1) blur(4px);
}

/* Hero spotlight effect */
.hero-spotlight::before {
content: "";
position: absolute;
inset: -10% -10% -10% -10%;
background: radial-gradient(60% 60% at 50% 35%, rgba(220, 162, 147, 0.12), transparent 70%);
pointer-events: none;
z-index: -1;
}

/* Reduce motion accessibility */
/* Reduce animations for accessibility */
@media (prefers-reduced-motion: reduce) {
.header {
transition: none;
}

html {
scroll-behavior: auto;
}

.scroll-cue,
.hero-enter {
animation: none;
}
}
17 changes: 13 additions & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Metadata, Viewport } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { SITE } from '@/lib/constants'
import { OG_IMAGE } from '@/lib/seo'
Expand Down Expand Up @@ -58,6 +59,13 @@ export const metadata: Metadata = {
},
}

// Self-hosted at build time so every device renders the same typeface (no system-font fallback)
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})

export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
Expand All @@ -71,14 +79,15 @@ export default function RootLayout({
children: React.ReactNode
}>) {
return (
<html lang="en" suppressHydrationWarning>
<html lang="en" className={inter.variable} suppressHydrationWarning>
<head>
{/* The hero portrait (Avatar size="hero") is the largest image on the first screen */}
<link
rel="preload"
as="image"
imageSrcSet="/images/profile/hero-196.avif 1x, /images/profile/hero-196@2x.avif 2x"
imageSizes="196px"
href="/images/profile/hero-196.avif"
type="image/avif"
imageSrcSet="/images/profile/hero-320.avif 1x, /images/profile/hero-320@2x.avif 2x"
href="/images/profile/hero-320.avif"
/>
</head>
<body className="antialiased" suppressHydrationWarning>
Expand Down
Loading