Overview
The Layout.astro component is the main layout wrapper for all pages in the portfolio. It provides the HTML structure, includes global styles, handles dark mode initialization, and implements the scroll-reveal animation system.
Location: src/layouts/Layout.astro:1
Features
Dark mode initialization - Prevents flash of unstyled content (FOUC)
Scroll-reveal animations - IntersectionObserver-based animations
SEO optimization - Meta tags and structured data (JSON-LD)
Performance optimization - Critical CSS inlining, font preloading
Responsive design - Mobile-first approach with custom scrollbar
i18n support - Language detection from URL
Props
Page title displayed in the browser tab
Usage
---
import Layout from '@/layouts/Layout.astro' ;
---
< Layout title = "Kevin Palma - Portfolio" >
< section >
<!-- Your page content -->
</ section >
</ Layout >
HTML Structure
The Layout component provides a complete HTML document structure:
< ! doctype html >
< html lang = { lang } >
< head >
<!-- Meta tags, fonts, critical CSS -->
</ head >
< body >
< Navbar />
< main >
< slot /> <!-- Your page content goes here -->
</ main >
< Footer />
<!-- Dark mode and scroll-reveal scripts -->
</ body >
</ html >
Key Components
1. Head Section
The <head> includes:
< meta charset = "UTF-8" />
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" />
< meta name = "description" content = "Kevin Palma - Azure Cloud Engineer | Portfolio" />
< link rel = "icon" type = "image/svg+xml" href = "/favicon.svg" />
Font Loading:
Uses preload with onload fallback for optimal performance:
< link rel = "preload"
href = "https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
as = "style"
onload = " this . onload = null ; this . rel = 'stylesheet'" />
2. Critical CSS
Inline critical CSS eliminates render-blocking:
/* Critical CSS — inlined to eliminate render-blocking */
* , * ::before , * ::after {
box-sizing : border-box ;
border-width : 0 ;
border-style : solid ;
}
html {
scroll-behavior : smooth ;
scroll-padding-top : 5 rem ;
}
.dark body , html .dark body {
background-color : #020617 ;
color : #f1f5f9 ;
}
.reveal {
opacity : 0 ;
transform : translateY ( 24 px );
transition : opacity .7 s cubic-bezier ( .16 , 1 , .3 , 1 ),
transform .7 s cubic-bezier ( .16 , 1 , .3 , 1 );
transition-delay : var ( --reveal-delay , 0 ms );
}
.reveal.revealed {
opacity : 1 ;
transform : translateY ( 0 );
}
3. Structured Data (JSON-LD)
SEO-friendly structured data for search engines:
{
"@context" : "https://schema.org" ,
"@type" : "Person" ,
"name" : "Kevin Maximiliano Palma Romero" ,
"jobTitle" : "Azure Cloud Engineer" ,
"worksFor" : { "@type" : "Organization" , "name" : "Readymind" },
"hasCredential" : [
{ "@type" : "EducationalOccupationalCredential" , "name" : "AZ-104 Azure Administrator" },
{ "@type" : "EducationalOccupationalCredential" , "name" : "AZ-700 Azure Network Engineer" }
]
}
Dark Mode Initialization
The dark mode script runs before first paint to prevent FOUC:
// Dark mode initialization - runs before paint to avoid flash
( function () {
const theme = localStorage . getItem ( 'theme' );
if ( theme === 'dark' || ( ! theme && window . matchMedia ( '(prefers-color-scheme: dark)' ). matches )) {
document . documentElement . classList . add ( 'dark' );
} else {
document . documentElement . classList . remove ( 'dark' );
}
})();
How it works:
Reads saved theme from localStorage
Falls back to system preference if no saved theme
Applies dark class to <html> element before rendering
Uses inline script to execute before CSS loads
The Layout implements a performant scroll-reveal system using IntersectionObserver:
// Scroll-reveal animations using IntersectionObserver
const observer = new IntersectionObserver (( entries ) => {
entries . forEach (( entry ) => {
if ( entry . isIntersecting ) {
entry . target . classList . add ( 'revealed' );
observer . unobserve ( entry . target );
}
});
}, { threshold: 0.1 , rootMargin: '0px 0px -40px 0px' });
function observeElements () {
document . querySelectorAll ( '.reveal' ). forEach (( el ) => observer . observe ( el ));
}
Using scroll-reveal in components:
< section class = "reveal" style = "--reveal-delay: 0ms" >
< h2 > This fades in when scrolled into view </ h2 >
</ section >
< div class = "reveal" style = "--reveal-delay: 100ms" >
< p > This appears 100ms after the section </ p >
</ div >
The --reveal-delay CSS variable allows staggered animations. Increment by 50-100ms for each element.
Global styles include a custom scrollbar themed to match the design:
::-webkit-scrollbar {
width : 8 px ;
}
::-webkit-scrollbar-track {
background : transparent ;
}
::-webkit-scrollbar-thumb {
background : rgba ( 59 , 130 , 246 , 0.3 );
border-radius : 4 px ;
}
::-webkit-scrollbar-thumb:hover {
background : rgba ( 59 , 130 , 246 , 0.5 );
}
Critical CSS inlining - Eliminates render-blocking CSS
Font preloading - rel="preload" with async loading
Inline scripts - Dark mode and animations don’t block rendering
Smooth scroll - Native CSS scroll-behavior: smooth
Scroll padding - scroll-padding-top: 5rem accounts for fixed navbar
Contain styles - contain: style on body improves paint performance
Accessibility
Language attribute - <html lang={lang}> set based on route
Semantic HTML - <main> for primary content
Viewport meta - Responsive scaling
Smooth scroll - Keyboard navigation friendly
System preferences - Respects prefers-color-scheme
Example: Complete Page
---
import Layout from '../../layouts/Layout.astro' ;
import Hero from '../../components/Hero.astro' ;
import About from '../../components/About.astro' ;
import Projects from '../../components/Projects.astro' ;
import Skills from '../../components/Skills.astro' ;
import Contact from '../../components/Contact.astro' ;
---
< Layout title = "Kevin Palma - Azure Cloud Engineer" >
< Hero />
< About />
< Projects />
< Skills />
< Contact />
</ Layout >
Customization
Edit line 20:
< meta name = "description" content = "Your custom description" />
Modify Dark Mode Colors
Edit line 31:
.dark body , html .dark body {
background-color : #your-dark-bg;
color : #your-dark-text;
}
Modify the transition duration in line 36:
.reveal {
transition : opacity 1 s cubic-bezier ( .16 , 1 , .3 , 1 ), /* Change from .7s to 1s */
transform 1 s cubic-bezier ( .16 , 1 , .3 , 1 );
}
Add Custom Global Styles
Add styles in the <style is:global> block at the bottom of the file.
Navbar Fixed navigation bar included in Layout
Footer Footer component included in Layout
Theme Toggle Dark mode toggle that works with Layout
Language Selector Language switcher that updates lang attribute
Technical Details
Import path: @/layouts/Layout.astro or ../layouts/Layout.astro
File size: ~3.7 KB (with inline scripts and styles)
Dependencies: Navbar, Footer, i18n utils
Browser support: Modern browsers (IntersectionObserver, CSS variables)