New
Paper PortfolioPortfolios
A calm, light one-page engineer portfolio: an airy hero, a quick-facts strip, an about section with a workspace photo, grouped skills, selected work, an experience timeline and a contact close. Every word lives in one content file, and the headshot ships background-free so it sits on any page colour.
import type { Metadata } from "next"
import { SiteFooter } from "./components/site-footer"
import { SiteHeader } from "./components/site-header"
import { profile } from "./data/content"
import "./daylight.css"
export const metadata: Metadata = {
title: {
default: `${profile.name} ${profile.surname} — ${profile.role}`,
template: `%s — ${profile.name} ${profile.surname}`,
},
description: `Portfolio of ${profile.name} ${profile.surname}, a ${profile.role.toLowerCase()} based in ${profile.location}.`,
}
/**
* The template shell.
*
* A nested layout, so it renders no `<html>` or `<body>` — the app's root
* layout owns those. `.dlSite` is the single wrapper every style in
* `daylight.css` hangs off, which is what keeps this sheet from touching
* anything else in the app.
*
* The `<noscript>` block matters: scroll reveals start at `opacity: 0` in CSS
* so they cannot flash in before hydration, which means without JavaScript they
* would never appear at all. This turns them on for that reader instead of
* showing them an empty page.
*/
export default function DaylightLayout({ children }: { children: React.ReactNode }) {
return (
<div className="dlSite">
<noscript>
<style
dangerouslySetInnerHTML={{
__html: ".dlReveal{opacity:1;transform:none}",
}}
/>
</noscript>
<SiteHeader />
<main>{children}</main>
<SiteFooter />
</div>
)
}/*
* daylight — a bright, professional developer portfolio.
*
* Nothing here can leak. Every class carries a `dl` prefix and every
* bare-element rule is scoped under `.dlSite`, so the whole sheet is inert
* outside its own wrapper. The two things most people will change live at the
* top: the palette and the type scale. Nothing below hard-codes a value that is
* a variable up here.
*/
.dlSite {
/* Palette. One calm accent blue on a soft blue-grey ground. */
--bg: #eef2f7;
--bg-tint: #e3e9f2;
--card: #ffffff;
--ink: #1e293b;
--ink-soft: #475569;
--ink-mute: #64748b;
--ink-faint: #94a3b8;
--line: #dbe2ec;
--line-soft: #e8edf4;
--accent: #2563eb;
--accent-soft: #dbe6ff;
--accent-ink: #1d4ed8;
/* Type scale. Clamp keeps the hero confident on desktop and calm on phones. */
--display: clamp(2.5rem, 6vw, 4.25rem);
--heading: clamp(1.75rem, 3.2vw, 2.6rem);
--subhead: clamp(1.25rem, 2vw, 1.6rem);
--body: clamp(1rem, 1.05vw, 1.125rem);
/* Rhythm. */
--gutter: clamp(1.25rem, 5vw, 4.5rem);
--section: clamp(4.5rem, 9vw, 8rem);
--radius: 16px;
--radius-sm: 12px;
--shadow: 0 1px 2px rgba(30, 41, 59, 0.04), 0 12px 32px -12px rgba(30, 41, 59, 0.14);
--shadow-lift: 0 2px 4px rgba(30, 41, 59, 0.05), 0 22px 48px -16px rgba(30, 41, 59, 0.22);
--font-body: -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
position: relative;
min-height: 100vh;
background: var(--bg);
color: var(--ink);
font-family: var(--font-body);
font-size: var(--body);
line-height: 1.65;
isolation: isolate;
overflow-x: hidden;
}
.dlSite *,
.dlSite *::before,
.dlSite *::after { box-sizing: border-box; }
/* :where() adds no specificity, so any class rule below still wins. */
.dlSite :where(a) { color: inherit; text-decoration: none; }
.dlSite :where(button, input, textarea, select) { font: inherit; color: inherit; }
.dlSite :where(img) { display: block; max-width: 100%; }
.dlSite ::selection { background: var(--accent-soft); color: var(--accent-ink); }
.dlSite :focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
border-radius: 4px;
}
/* ---------------------------------------------------------------- primitives */
.dlWrap {
width: 100%;
max-width: 72rem;
margin: 0 auto;
padding-inline: var(--gutter);
}
.dlSection { padding-block: var(--section); }
.dlSectionTight { padding-top: 0; }
.dlLabel {
margin: 0 0 0.75rem;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--accent);
}
.dlHeading {
margin: 0;
font-size: var(--heading);
font-weight: 700;
line-height: 1.1;
letter-spacing: -0.02em;
text-wrap: balance;
}
.dlLede {
max-width: 42rem;
margin: 1.25rem 0 0;
font-size: var(--subhead);
line-height: 1.55;
color: var(--ink-soft);
text-wrap: pretty;
}
.dlSectionHead { margin-bottom: clamp(2rem, 4vw, 3.25rem); }
/* -------------------------------------------------------------------- buttons */
.dlBtn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.8125rem 1.5rem;
border: 1px solid var(--line);
border-radius: 999px;
background: var(--card);
color: var(--ink);
font-size: 0.9375rem;
font-weight: 600;
cursor: pointer;
box-shadow: var(--shadow);
transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 0.25s ease, border-color 0.25s ease, background-color 0.25s ease, color 0.25s ease;
}
.dlBtn:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); border-color: var(--line-soft); }
.dlBtnSolid {
background: var(--accent);
border-color: var(--accent);
color: #fff;
box-shadow: 0 8px 20px -8px rgba(37, 99, 235, 0.6);
}
.dlBtnSolid:hover { background: var(--accent-ink); border-color: var(--accent-ink); }
.dlBtn > .dlArrow { transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1); }
.dlBtn:hover > .dlArrow { transform: translateX(3px); }
/* --------------------------------------------------------------------- header */
.dlHeader {
position: sticky;
top: 0;
z-index: 20;
background: color-mix(in oklab, var(--bg) 82%, transparent);
backdrop-filter: blur(12px);
border-bottom: 1px solid var(--line-soft);
}
.dlHeaderRow {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1.5rem;
height: 4.25rem;
}
.dlBrand {
display: inline-flex;
align-items: center;
gap: 0.625rem;
font-weight: 700;
font-size: 1.0625rem;
letter-spacing: -0.01em;
}
.dlBrandMark {
display: grid;
place-items: center;
width: 2rem;
height: 2rem;
border-radius: 8px;
background: var(--accent);
color: #fff;
font-size: 0.9375rem;
font-weight: 700;
}
.dlBrandRole {
font-size: 0.75rem;
font-weight: 500;
color: var(--ink-mute);
}
.dlNav { display: none; align-items: center; gap: 1.75rem; }
.dlNavLink {
position: relative;
font-size: 0.9375rem;
font-weight: 500;
color: var(--ink-soft);
transition: color 0.2s ease;
}
.dlNavLink:hover { color: var(--ink); }
.dlNavLink::after {
content: "";
position: absolute;
left: 0;
bottom: -0.35rem;
width: 100%;
height: 2px;
border-radius: 2px;
background: var(--accent);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.dlNavLink:hover::after { transform: scaleX(1); }
/* Mobile menu is a native <details> — no state, no focus trap to get wrong. */
.dlMenu { position: relative; }
.dlMenuSummary {
display: inline-flex;
align-items: center;
padding: 0.5rem 0.875rem;
border: 1px solid var(--line);
border-radius: 999px;
background: var(--card);
font-size: 0.8125rem;
font-weight: 600;
color: var(--ink-soft);
cursor: pointer;
list-style: none;
box-shadow: var(--shadow);
}
.dlMenuSummary::-webkit-details-marker { display: none; }
.dlMenu[open] .dlMenuSummary { color: var(--ink); border-color: var(--accent); }
.dlMenuPanel {
position: absolute;
right: 0;
top: calc(100% + 0.6rem);
z-index: 30;
display: grid;
gap: 0.125rem;
min-width: 11rem;
padding: 0.5rem;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
background: var(--card);
box-shadow: var(--shadow-lift);
}
.dlMenuLink {
padding: 0.5rem 0.75rem;
border-radius: 8px;
font-size: 0.9375rem;
color: var(--ink-soft);
}
.dlMenuLink:hover { background: var(--bg-tint); color: var(--ink); }
@media (min-width: 52rem) {
.dlNav { display: flex; }
.dlHeaderRow { height: 5rem; }
.dlMenu { display: none; }
}
/* ----------------------------------------------------------------------- hero */
.dlHero { padding-top: clamp(3rem, 7vw, 5.5rem); padding-bottom: clamp(2.5rem, 5vw, 4rem); }
.dlHeroGrid { display: grid; gap: clamp(2.5rem, 5vw, 4rem); align-items: center; }
.dlHeroEyebrow {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.875rem;
border-radius: 999px;
background: var(--accent-soft);
color: var(--accent-ink);
font-size: 0.8125rem;
font-weight: 600;
}
.dlHeroEyebrow::before {
content: "";
width: 0.5rem;
height: 0.5rem;
border-radius: 999px;
background: var(--accent);
}
.dlHeroGreeting {
margin: 1.5rem 0 0;
font-size: var(--subhead);
font-weight: 600;
color: var(--ink-soft);
}
.dlHeroTitle {
margin: 0.5rem 0 0;
font-size: var(--display);
font-weight: 800;
line-height: 1.05;
letter-spacing: -0.03em;
text-wrap: balance;
}
.dlHeroActions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 2rem; }
/* The portrait sits in a soft rounded card with a gradient halo behind it. */
.dlHeroArt { position: relative; justify-self: center; width: 100%; max-width: 24rem; }
.dlHeroArt::before {
content: "";
position: absolute;
inset: -8%;
border-radius: 50%;
background: radial-gradient(circle at 50% 40%, var(--accent-soft), transparent 70%);
z-index: -1;
}
.dlHeroPortrait {
width: 100%;
height: auto;
aspect-ratio: 1 / 1;
object-fit: cover;
border-radius: 24px;
border: 6px solid var(--card);
box-shadow: var(--shadow-lift);
}
@media (min-width: 56rem) {
.dlHeroGrid { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); }
.dlHeroArt { justify-self: end; }
}
/* ---------------------------------------------------------------------- stats */
.dlStats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1px;
background: var(--line);
border: 1px solid var(--line);
border-radius: var(--radius);
overflow: hidden;
box-shadow: var(--shadow);
}
.dlStat { padding: clamp(1.25rem, 3vw, 2rem); background: var(--card); text-align: center; }
.dlStatValue {
display: block;
font-size: clamp(1.75rem, 3.5vw, 2.75rem);
font-weight: 800;
letter-spacing: -0.03em;
color: var(--accent);
font-variant-numeric: tabular-nums;
}
.dlStatLabel { display: block; margin-top: 0.25rem; font-size: 0.875rem; color: var(--ink-mute); }
@media (min-width: 44rem) { .dlStats { grid-template-columns: repeat(4, 1fr); } }
/* ---------------------------------------------------------------------- about */
.dlAbout { display: grid; gap: clamp(2rem, 5vw, 3.5rem); align-items: center; }
.dlAboutBody { display: grid; gap: 1.25rem; }
.dlAboutBody p { margin: 0; color: var(--ink-soft); text-wrap: pretty; }
.dlAboutBody p:first-of-type { color: var(--ink); font-size: var(--subhead); line-height: 1.5; }
.dlAboutFigure { position: relative; margin: 0; }
.dlAboutImage {
width: 100%;
height: auto;
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: var(--radius);
box-shadow: var(--shadow-lift);
}
.dlAboutCaption {
margin: 0;
position: absolute;
left: 1rem;
bottom: 1rem;
padding: 0.5rem 0.875rem;
border-radius: 999px;
background: color-mix(in oklab, var(--card) 90%, transparent);
backdrop-filter: blur(6px);
font-size: 0.8125rem;
font-weight: 500;
color: var(--ink-soft);
box-shadow: var(--shadow);
}
@media (min-width: 56rem) { .dlAbout { grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr); } }
/* --------------------------------------------------------------------- skills */
.dlSkills { display: grid; gap: 1.25rem; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); }
.dlSkillGroup {
padding: clamp(1.25rem, 2.5vw, 1.75rem);
border: 1px solid var(--line);
border-radius: var(--radius);
background: var(--card);
box-shadow: var(--shadow);
transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.25s ease;
}
.dlSkillGroup:hover { transform: translateY(-3px); box-shadow: var(--shadow-lift); }
.dlSkillTitle {
margin: 0 0 1rem;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--ink-mute);
}
.dlChips { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.dlChip {
padding: 0.375rem 0.75rem;
border-radius: 999px;
background: var(--bg-tint);
border: 1px solid transparent;
font-size: 0.875rem;
font-weight: 500;
color: var(--ink-soft);
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.dlSkillGroup:hover .dlChip { background: var(--accent-soft); color: var(--accent-ink); }
/* -------------------------------------------------------------------- projects */
.dlProjects { display: grid; gap: 1.5rem; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); }
.dlCard {
display: flex;
flex-direction: column;
overflow: hidden;
border: 1px solid var(--line);
border-radius: var(--radius);
background: var(--card);
box-shadow: var(--shadow);
transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.3s ease;
}
.dlCard:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); }
/* The thumbnail: either the workspace photo or a soft gradient mock. */
.dlCardThumb { position: relative; aspect-ratio: 16 / 10; overflow: hidden; }
.dlCardThumb img { width: 100%; height: 100%; object-fit: cover; }
.dlCardThumb::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(120deg, rgba(255, 255, 255, 0.35), transparent 55%);
mix-blend-mode: screen;
}
.dlCardThumb[data-accent="aurora"] { background: linear-gradient(135deg, #60a5fa, #a78bfa 55%, #f0abfc); }
.dlCardThumb[data-accent="meadow"] { background: linear-gradient(135deg, #34d399, #22d3ee 55%, #60a5fa); }
.dlCardThumb[data-accent="dusk"] { background: linear-gradient(135deg, #f59e0b, #fb7185 55%, #a855f7); }
.dlCardMark {
position: absolute;
left: 1rem;
bottom: 1rem;
font-size: 1.5rem;
font-weight: 800;
color: #fff;
letter-spacing: -0.02em;
text-shadow: 0 2px 8px rgba(30, 41, 59, 0.35);
}
.dlCardBody { display: flex; flex-direction: column; gap: 0.875rem; padding: 1.5rem; flex: 1; }
.dlCardTitle { margin: 0; font-size: 1.25rem; font-weight: 700; letter-spacing: -0.01em; }
.dlCardBlurb { margin: 0; font-size: 0.9375rem; color: var(--ink-soft); text-wrap: pretty; }
.dlCardTags { display: flex; flex-wrap: wrap; gap: 0.4rem; margin-top: auto; }
.dlTag {
padding: 0.25rem 0.625rem;
border-radius: 6px;
background: var(--bg-tint);
font-size: 0.75rem;
font-weight: 600;
color: var(--ink-mute);
}
.dlCardLinks { display: flex; flex-wrap: wrap; gap: 1rem; padding-top: 0.25rem; }
.dlCardLink {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-size: 0.875rem;
font-weight: 600;
color: var(--accent);
}
.dlCardLink:hover { color: var(--accent-ink); }
.dlCardLink > span { transition: transform 0.2s ease; }
.dlCardLink:hover > span { transform: translateX(2px); }
/* ------------------------------------------------------------------ experience */
.dlTimeline { display: grid; gap: 0; }
.dlBeat {
display: grid;
gap: 0.375rem;
padding-block: clamp(1.5rem, 3vw, 2rem);
border-top: 1px solid var(--line);
}
.dlBeat:first-child { border-top: 0; }
.dlBeatPeriod {
font-size: 0.8125rem;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--accent);
font-variant-numeric: tabular-nums;
}
.dlBeatTitle { margin: 0; font-size: 1.1875rem; font-weight: 700; }
.dlBeatCompany { font-size: 0.9375rem; font-weight: 500; color: var(--ink-mute); }
.dlBeatBody { margin: 0.25rem 0 0; max-width: 44rem; color: var(--ink-soft); text-wrap: pretty; }
@media (min-width: 48rem) {
.dlBeat { grid-template-columns: 10rem minmax(0, 1fr); gap: 2rem; align-items: start; }
.dlBeatPeriod { padding-top: 0.15rem; }
}
/* -------------------------------------------------------------------- contact */
.dlContact {
display: grid;
gap: 1.75rem;
justify-items: center;
text-align: center;
padding: clamp(2.5rem, 6vw, 4.5rem) clamp(1.5rem, 5vw, 3rem);
border-radius: var(--radius);
background: linear-gradient(160deg, var(--card), var(--bg-tint));
border: 1px solid var(--line);
box-shadow: var(--shadow);
}
.dlContact .dlLede { margin-inline: auto; text-align: center; }
.dlContactActions { display: flex; flex-wrap: wrap; gap: 1.5rem; justify-content: center; align-items: center; }
.dlContactLink { font-size: 0.9375rem; font-weight: 600; color: var(--ink-soft); transition: color 0.2s ease; }
.dlContactLink:hover { color: var(--accent); }
/* --------------------------------------------------------------------- footer */
.dlFooter { border-top: 1px solid var(--line); padding-block: clamp(2.5rem, 5vw, 3.5rem); }
.dlFooterRow {
display: flex;
flex-wrap: wrap;
gap: 1.5rem 2rem;
align-items: center;
justify-content: space-between;
}
.dlFooterBlurb { margin: 0; max-width: 30rem; font-size: 0.9375rem; color: var(--ink-mute); text-wrap: pretty; }
.dlFooterMeta { display: grid; gap: 0.5rem; font-size: 0.8125rem; color: var(--ink-faint); }
.dlFooterMeta a { color: var(--ink-mute); transition: color 0.2s ease; }
.dlFooterMeta a:hover { color: var(--accent); }
/* --------------------------------------------------------------- scroll reveal */
.dlReveal {
opacity: 0;
transform: translate3d(0, 1.25rem, 0);
transition:
opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1),
transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
transition-delay: var(--dl-delay, 0ms);
}
.dlReveal[data-shown="true"] { opacity: 1; transform: translate3d(0, 0, 0); }
@media (prefers-reduced-motion: reduce) {
.dlReveal,
.dlReveal[data-shown="true"] { opacity: 1; transform: none; transition: none; }
.dlSite *,
.dlSite *::before,
.dlSite *::after {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}import { Reveal } from "./components/reveal"
import { about, contact, experience, hero, profile, projects, skills, stats } from "./data/content"
/**
* The daylight portfolio: one long, bright scroll.
*
* Hero, a quick-facts strip, about, skills, featured work, an experience
* timeline, and a closing contact card. Every string comes from
* `data/content.ts`; the section ids match the header anchors.
*/
export default function DaylightHome() {
return (
<>
{/* ------------------------------------------------------------- hero */}
<section className="dlWrap dlHero">
<div className="dlHeroGrid">
<div>
<p className="dlHeroGreeting">{hero.greeting}</p>
<h1 className="dlHeroTitle">{hero.headline}</h1>
<p className="dlLede">{hero.lede}</p>
<div className="dlHeroActions">
<a className="dlBtn dlBtnSolid" href={hero.primary.href}>
{hero.primary.label}
<span className="dlArrow" aria-hidden="true">
→
</span>
</a>
<a className="dlBtn" href={hero.secondary.href}>
{hero.secondary.label}
</a>
</div>
</div>
<div className="dlHeroArt">
<img
className="dlHeroPortrait"
src={profile.portrait.src}
width={profile.portrait.width}
height={profile.portrait.height}
alt={`${profile.name} ${profile.surname}, ${profile.role}`}
loading="eager"
fetchPriority="high"
decoding="async"
/>
</div>
</div>
</section>
{/* ------------------------------------------------------------ stats */}
<section className="dlWrap dlSection dlSectionTight">
<Reveal className="dlStats">
{stats.map((stat) => (
<div className="dlStat" key={stat.label}>
<span className="dlStatValue">{stat.value}</span>
<span className="dlStatLabel">{stat.label}</span>
</div>
))}
</Reveal>
</section>
{/* ------------------------------------------------------------ about */}
<section id="about" className="dlWrap dlSection dlSectionTight">
<Reveal className="dlAbout">
<div>
<p className="dlLabel">{about.label}</p>
<h2 className="dlHeading">{about.heading}</h2>
<div className="dlAboutBody" style={{ marginTop: "1.5rem" }}>
{about.body.map((paragraph) => (
<p key={paragraph}>{paragraph}</p>
))}
</div>
</div>
<figure className="dlAboutFigure">
<img
className="dlAboutImage"
src={about.image.src}
width={about.image.width}
height={about.image.height}
alt={about.image.alt}
loading="lazy"
decoding="async"
/>
<figcaption className="dlAboutCaption">{about.caption}</figcaption>
</figure>
</Reveal>
</section>
{/* ----------------------------------------------------------- skills */}
<section id="skills" className="dlWrap dlSection dlSectionTight">
<Reveal className="dlSectionHead">
<p className="dlLabel">{skills.label}</p>
<h2 className="dlHeading">{skills.heading}</h2>
<p className="dlLede">{skills.lede}</p>
</Reveal>
<div className="dlSkills">
{skills.groups.map((group, index) => (
<Reveal className="dlSkillGroup" key={group.title} delay={index * 70}>
<h3 className="dlSkillTitle">{group.title}</h3>
<div className="dlChips">
{group.items.map((item) => (
<span className="dlChip" key={item}>
{item}
</span>
))}
</div>
</Reveal>
))}
</div>
</section>
{/* --------------------------------------------------------- projects */}
<section id="work" className="dlWrap dlSection dlSectionTight">
<Reveal className="dlSectionHead">
<p className="dlLabel">{projects.label}</p>
<h2 className="dlHeading">{projects.heading}</h2>
</Reveal>
<div className="dlProjects">
{projects.items.map((project, index) => {
const image = "image" in project ? project.image : undefined
const accent = "accent" in project ? project.accent : undefined
return (
<Reveal className="dlCard" as="article" key={project.title} delay={index * 80}>
<div className="dlCardThumb" data-accent={accent}>
{image ? (
<img src={image.src} width={image.width} height={image.height} alt="" loading="lazy" decoding="async" />
) : (
<span className="dlCardMark" aria-hidden="true">
{project.title}
</span>
)}
</div>
<div className="dlCardBody">
<h3 className="dlCardTitle">{project.title}</h3>
<p className="dlCardBlurb">{project.blurb}</p>
<div className="dlCardTags">
{project.tags.map((tag) => (
<span className="dlTag" key={tag}>
{tag}
</span>
))}
</div>
<div className="dlCardLinks">
{project.links.map((link) => (
<a className="dlCardLink" href={link.href} key={link.href} target="_blank" rel="noreferrer">
{link.label}
<span aria-hidden="true">→</span>
</a>
))}
</div>
</div>
</Reveal>
)
})}
</div>
</section>
{/* ------------------------------------------------------- experience */}
<section id="experience" className="dlWrap dlSection dlSectionTight">
<Reveal className="dlSectionHead">
<p className="dlLabel">{experience.label}</p>
<h2 className="dlHeading">{experience.heading}</h2>
</Reveal>
<div className="dlTimeline">
{experience.roles.map((role, index) => (
<Reveal className="dlBeat" key={role.company} delay={index * 60}>
<span className="dlBeatPeriod">{role.period}</span>
<div>
<h3 className="dlBeatTitle">
{role.title} <span className="dlBeatCompany">· {role.company}</span>
</h3>
<p className="dlBeatBody">{role.body}</p>
</div>
</Reveal>
))}
</div>
</section>
{/* ---------------------------------------------------------- contact */}
<section id="contact" className="dlWrap dlSection dlSectionTight">
<Reveal className="dlContact">
<p className="dlLabel">{contact.label}</p>
<h2 className="dlHeading">{contact.heading}</h2>
<p className="dlLede">{contact.lede}</p>
<div className="dlContactActions">
<a className="dlBtn dlBtnSolid" href={contact.cta.href}>
{contact.cta.label}
<span className="dlArrow" aria-hidden="true">
→
</span>
</a>
<a className="dlContactLink" href={profile.github.href} target="_blank" rel="noreferrer">
{profile.github.label}
</a>
<a className="dlContactLink" href={profile.linkedin.href} target="_blank" rel="noreferrer">
{profile.linkedin.label}
</a>
</div>
</Reveal>
</section>
</>
)
}/**
* Everything the daylight portfolio says, in one file.
*
* The page and its components read from here and hold no copy of their own, so
* re-voicing the whole site for a different person is one file to edit and no
* JSX to touch. Swap the palette in `daylight.css` and the words here and
* nothing else needs to change.
*
* The persona is a fictional senior engineer. Every string is meant to sound
* like a real portfolio, not filler — replace it with yours.
*/
/**
* Where the template is mounted. The home link uses it; the in-page nav uses
* the `#anchor` hashes below, which do not move when `BASE` changes.
*/
export const BASE = "/daylight"
export const profile = {
name: "Adrian",
surname: "Vale",
/** Sits in the header beside the name and drives the page title. */
role: "Senior Software Engineer",
location: "Lisbon, Portugal",
email: "hello@example.com",
github: { label: "github.com/adrianvale", href: "https://github.com/adrianvale" },
linkedin: { label: "in/adrianvale", href: "https://www.linkedin.com/in/adrianvale" },
/** Friendly professional headshot, served from `public/daylight/`. */
portrait: { src: `${BASE}/portrait.png`, width: 1024, height: 1024 },
year: 2026,
} as const
/** In-page anchors. `href` is a hash so it works no matter where BASE points. */
export const nav = [
{ label: "About", href: "#about" },
{ label: "Skills", href: "#skills" },
{ label: "Work", href: "#work" },
{ label: "Experience", href: "#experience" },
{ label: "Contact", href: "#contact" },
] as const
/** The opening. Kept short — the whitespace does the rest of the work. */
export const hero = {
greeting: "Hi, I'm Adrian.",
headline: "I build calm, reliable software that people actually enjoy using.",
lede:
"Senior engineer with a decade of shipping web platforms end to end — from the data model to the last pixel. I care about clarity, performance, and interfaces that stay out of the way.",
primary: { label: "See my work", href: "#work" },
secondary: { label: "Get in touch", href: "#contact" },
} as const
/** The quick-facts strip under the hero. Four numbers, no more. */
export const stats = [
{ value: "10+", label: "Years shipping" },
{ value: "40+", label: "Projects delivered" },
{ value: "12", label: "Teams mentored" },
{ value: "99.9%", label: "Uptime maintained" },
] as const
/** The about section: a small heading and a couple of grounded paragraphs. */
export const about = {
label: "About",
heading: "A senior engineer who sweats the details so users never have to.",
body: [
"I've spent the last ten years building products across fintech, health, and developer tooling — usually as the person who owns a feature from the first sketch to the graphs the morning after launch. I like being close to the problem and close to the people it affects.",
"My happiest work sits at the seam between design and engineering: turning a fuzzy idea into something that loads fast, reads clearly, and holds up under real traffic. I write tests I'd trust at 3am, document the decisions that mattered, and leave codebases calmer than I found them.",
],
/** A bright, clean workspace photo, served from `public/daylight/`. */
image: { src: `${BASE}/workspace.png`, width: 1024, height: 1024, alt: "A bright, tidy desk with a laptop and notebook" },
/** The small caption pinned to the workspace image. */
caption: "Where the work happens — Lisbon, most mornings.",
} as const
/** Skills, grouped. Each group is a titled cluster of chips. */
export const skills = {
label: "Skills",
heading: "The tools I reach for.",
lede: "A working set built over years, not a wishlist. These are the things I use well and often.",
groups: [
{
title: "Languages",
items: ["TypeScript", "JavaScript", "Python", "Go", "SQL"],
},
{
title: "Frontend",
items: ["React", "Next.js", "Tailwind", "Design systems", "Accessibility"],
},
{
title: "Backend",
items: ["Node.js", "PostgreSQL", "GraphQL", "REST APIs", "Redis"],
},
{
title: "Platform",
items: ["AWS", "Docker", "CI/CD", "Observability", "Terraform"],
},
],
} as const
/** Featured projects. Three or four, each defensible. */
export const projects = {
label: "Selected work",
heading: "Projects I'm proud of.",
items: [
{
title: "Ledgerline",
blurb:
"A double-entry accounting engine for small studios. I designed the data model and the reconciliation flow, cutting month-end close from two days to twenty minutes.",
tags: ["Next.js", "PostgreSQL", "TypeScript"],
/** A soft gradient stands in for a screenshot — see `daylight.css`. */
accent: "aurora",
links: [
{ label: "Live site", href: "https://example.com/ledgerline" },
{ label: "Case study", href: "https://example.com/ledgerline/case-study" },
],
},
{
title: "Northwind Health",
blurb:
"A patient-facing scheduling app used by nine clinics. Built the availability engine and the accessible booking UI; it now handles 4,000 appointments a week without a hitch.",
tags: ["React", "Go", "Accessibility"],
accent: "meadow",
links: [{ label: "Read more", href: "https://example.com/northwind" }],
},
{
title: "Beacon CLI",
blurb:
"An open-source tool that turns flaky CI logs into a single readable timeline. Started as a weekend fix for my own frustration; now 3k stars and a dozen contributors.",
tags: ["Go", "Open source", "DX"],
accent: "dusk",
links: [
{ label: "GitHub", href: "https://github.com/adrianvale/beacon" },
{ label: "Docs", href: "https://example.com/beacon" },
],
},
{
title: "Studio Workspace",
blurb:
"A collaborative whiteboard for distributed design teams, rebuilt for speed. Rewrote the sync layer to CRDTs and brought p95 render latency under 40ms on 50-person boards.",
tags: ["TypeScript", "WebSockets", "Performance"],
/** This card uses the real workspace photo instead of a gradient. */
image: about.image,
links: [{ label: "Live site", href: "https://example.com/studio" }],
},
],
} as const
/** Experience timeline. Newest first. */
export const experience = {
label: "Experience",
heading: "Where I've been.",
roles: [
{
period: "2022 — Now",
title: "Staff Engineer",
company: "Meridian Labs",
body:
"Lead the platform team behind a payments product handling millions in monthly volume. Set the technical direction, mentor five engineers, and still ship the hard bits myself.",
},
{
period: "2019 — 2022",
title: "Senior Software Engineer",
company: "Northwind Health",
body:
"Owned the patient scheduling platform end to end. Rebuilt it for accessibility and scale, and grew the frontend guild from a habit into a practice.",
},
{
period: "2016 — 2019",
title: "Software Engineer",
company: "Fieldnote",
body:
"Second engineer at an early-stage startup. Wore every hat — shipped the first web app, the mobile client, and the on-call rotation that kept them running.",
},
{
period: "2014 — 2016",
title: "Frontend Developer",
company: "Studio Cobalt",
body:
"Built marketing sites and product prototypes for agency clients. Learned to make things fast, make them accessible, and make them on time.",
},
],
} as const
/** The closing call to action. */
export const contact = {
label: "Contact",
heading: "Let's build something worth using.",
lede:
"I'm open to freelance projects, fractional engineering leadership, and the occasional interesting conversation. The fastest way to reach me is email.",
cta: { label: "Say hello", href: "mailto:hello@example.com" },
} as const
export const footer = {
blurb:
"Adrian Vale — senior software engineer in Lisbon. This is a demo of the daylight portfolio template; every word on it lives in one file.",
builtWith: "Built with Next.js. Designed to stay out of the way.",
} as const"use client"
import Link from "next/link"
import { useRef } from "react"
import { BASE, nav, profile } from "../data/content"
/**
* The header, and the only client component in the chrome.
*
* The nav is a set of in-page anchors, so there is no active-route state to
* track. The mobile menu is a native `<details>` — nothing to get wrong about
* focus or aria-expanded — and the one bit of JS closes it after a link is
* tapped, since a hash jump does not remount the layout to close it for us.
*/
export function SiteHeader() {
const menu = useRef<HTMLDetailsElement>(null)
const close = () => {
if (menu.current) menu.current.open = false
}
return (
<header className="dlHeader">
<div className="dlWrap dlHeaderRow">
<Link className="dlBrand" href={BASE || "/"}>
<span className="dlBrandMark" aria-hidden="true">
{profile.name[0]}
{profile.surname[0]}
</span>
<span>
{profile.name} {profile.surname}
</span>
<span className="dlBrandRole">{profile.role}</span>
</Link>
<nav className="dlNav" aria-label="Primary">
{nav.map((link) => (
<a key={link.href} href={link.href} className="dlNavLink">
{link.label}
</a>
))}
</nav>
<details className="dlMenu" ref={menu}>
<summary className="dlMenuSummary">Menu</summary>
<nav className="dlMenuPanel" aria-label="Primary">
{nav.map((link) => (
<a key={link.href} href={link.href} className="dlMenuLink" onClick={close}>
{link.label}
</a>
))}
</nav>
</details>
</div>
</header>
)
}import { footer, profile } from "../data/content"
/**
* The footer. Server component — there is nothing here to hydrate. It closes
* the page quietly with a blurb, the contact routes, and a small colophon.
*/
export function SiteFooter() {
return (
<footer className="dlFooter">
<div className="dlWrap dlFooterRow">
<p className="dlFooterBlurb">{footer.blurb}</p>
<div className="dlFooterMeta">
<a href={`mailto:${profile.email}`}>{profile.email}</a>
<span>
<a href={profile.github.href} target="_blank" rel="noreferrer">
GitHub
</a>{" "}
·{" "}
<a href={profile.linkedin.href} target="_blank" rel="noreferrer">
LinkedIn
</a>
</span>
<span>
© {profile.year} {profile.name} {profile.surname} — {footer.builtWith}
</span>
</div>
</div>
</footer>
)
}"use client"
import { useEffect, useRef, useState } from "react"
type RevealProps = {
children: React.ReactNode
/** Merged after `dlReveal`, so this is where the layout class goes. */
className?: string
/** Stagger, in milliseconds. Feeds `--dl-delay` in the stylesheet. */
delay?: number
/** Render as something other than a `<div>` — e.g. "section", "li". */
as?: "div" | "section" | "li" | "article"
}
/**
* Fades and lifts its children in when they first scroll into view.
*
* It merges `className` onto the element it renders, so the revealed element
* *is* the layout element — `<Reveal className="dlCard">` is a grid child, not
* a wrapper that breaks the grid.
*
* Three details are deliberate:
*
* - The observer disconnects on the first intersection. Re-hiding content the
* reader has already passed is motion for its own sake, and a long page
* keeps zero observers alive by the time you reach the footer.
* - The hidden state lives in CSS, not here, so nothing flashes in before
* hydration and `prefers-reduced-motion` can switch it off without this
* component knowing.
* - If `IntersectionObserver` is missing, the content shows immediately.
* Failing open is the only acceptable direction for a fade-in.
*/
export function Reveal({ children, className, delay = 0, as = "div" }: RevealProps) {
const ref = useRef<HTMLDivElement>(null)
const [shown, setShown] = useState(false)
useEffect(() => {
const node = ref.current
if (!node || shown) return
if (typeof IntersectionObserver === "undefined") {
setShown(true)
return
}
const observer = new IntersectionObserver(
(entries) => {
if (!entries.some((entry) => entry.isIntersecting)) return
setShown(true)
observer.disconnect()
},
/* Held back from the bottom edge so it reads as a reveal, not a pop. */
{ rootMargin: "0px 0px -10% 0px", threshold: 0.05 },
)
observer.observe(node)
return () => observer.disconnect()
}, [shown])
const Tag = as
return (
<Tag
ref={ref as React.Ref<never>}
className={className ? `dlReveal ${className}` : "dlReveal"}
data-shown={shown ? "true" : "false"}
style={delay ? ({ "--dl-delay": `${delay}ms` } as React.CSSProperties) : undefined}
>
{children}
</Tag>
)
}