Photography — Landing Editorial
A gallery-dark landing page for an editorial and fashion photographer, built around a full-bleed hero, oversized Fraunces headlines and thin uppercase labels. Filterable tearsheet grid pulls from real magazine-style credits, a marquee of client logos scrolls, and a masthead nav shrinks on scroll. Visitors can filter published work, open a lightbox reader, copy the studio email and submit a commission inquiry with inline validation and toast feedback.
MCP
Code
:root {
--ink: #0b0b0c;
--bg: #101012;
--surface: #17171b;
--surface-2: #1e1e23;
--paper: #f6f4ef;
--sand: #c9b79c;
--sand-d: #a8916f;
--muted: #9a9aa2;
--line: rgba(255, 255, 255, 0.10);
--line-2: rgba(255, 255, 255, 0.18);
--white: #fff;
--r-sm: 6px;
--r-md: 12px;
--r-lg: 18px;
--shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
background: var(--bg);
color: var(--paper);
font-family: "Inter", system-ui, -apple-system, sans-serif;
font-size: 16px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
}
img {
max-width: 100%;
display: block;
}
h1,
h2,
h3 {
font-family: "Fraunces", Georgia, serif;
font-weight: 600;
line-height: 1.02;
margin: 0;
letter-spacing: -0.01em;
}
em {
font-style: italic;
}
a {
color: inherit;
}
.skip-link {
position: absolute;
left: -999px;
top: 0;
z-index: 200;
background: var(--sand);
color: var(--ink);
padding: 10px 16px;
border-radius: var(--r-sm);
font-weight: 600;
}
.skip-link:focus {
left: 16px;
top: 16px;
}
:focus-visible {
outline: 2px solid var(--sand);
outline-offset: 3px;
}
.kicker {
font-size: 12px;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--sand);
margin: 0 0 18px;
font-weight: 600;
}
/* ---------- Buttons ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: inherit;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
text-decoration: none;
padding: 13px 24px;
border-radius: 999px;
border: 1px solid transparent;
cursor: pointer;
transition: transform 0.18s ease, background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.btn:active {
transform: translateY(1px) scale(0.99);
}
.btn--solid {
background: var(--paper);
color: var(--ink);
}
.btn--solid:hover {
background: var(--sand);
}
.btn--ghost {
background: transparent;
color: var(--paper);
border-color: var(--line-2);
}
.btn--ghost:hover {
border-color: var(--sand);
color: var(--sand);
}
/* ---------- Masthead ---------- */
.masthead {
position: sticky;
top: 0;
z-index: 100;
backdrop-filter: blur(14px);
background: rgba(16, 16, 18, 0.55);
border-bottom: 1px solid transparent;
transition: background 0.3s ease, border-color 0.3s ease, padding 0.3s ease;
}
.masthead.is-scrolled {
background: rgba(16, 16, 18, 0.9);
border-bottom-color: var(--line);
}
.masthead__inner {
max-width: 1200px;
margin: 0 auto;
padding: 20px 32px;
display: flex;
align-items: center;
gap: 24px;
transition: padding 0.3s ease;
}
.masthead.is-scrolled .masthead__inner {
padding: 12px 32px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
text-decoration: none;
margin-right: auto;
}
.brand__mark {
display: inline-grid;
place-items: center;
width: 38px;
height: 38px;
border-radius: var(--r-sm);
background: var(--sand);
color: var(--ink);
font-family: "Fraunces", serif;
font-weight: 600;
font-size: 16px;
}
.brand__name {
font-family: "Fraunces", serif;
font-size: 18px;
letter-spacing: 0.01em;
}
.nav {
display: flex;
gap: 28px;
}
.nav a {
text-decoration: none;
font-size: 12px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
font-weight: 500;
position: relative;
padding: 4px 0;
transition: color 0.2s ease;
}
.nav a::after {
content: "";
position: absolute;
left: 0;
bottom: 0;
height: 1px;
width: 0;
background: var(--sand);
transition: width 0.25s ease;
}
.nav a:hover {
color: var(--paper);
}
.nav a:hover::after {
width: 100%;
}
/* ---------- Hero ---------- */
.hero {
position: relative;
min-height: 92vh;
display: flex;
align-items: flex-end;
overflow: hidden;
padding: 0 32px 72px;
}
.hero__media {
position: absolute;
inset: 0;
background:
radial-gradient(120% 90% at 70% 10%, rgba(201, 183, 156, 0.28), transparent 55%),
linear-gradient(200deg, #2a2622, #121214 60%),
url("https://images.unsplash.com/photo-1483985988355-763728e1935b?auto=format&fit=crop&w=1600&q=80");
background-size: cover;
background-position: center 30%;
filter: grayscale(0.25) contrast(1.05);
transform: scale(1.04);
will-change: transform;
}
.hero__scrim {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(11, 11, 12, 0.35) 0%, rgba(11, 11, 12, 0.15) 40%, rgba(11, 11, 12, 0.92) 100%);
}
.hero__content {
position: relative;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}
.hero__title {
font-size: clamp(3rem, 9vw, 7rem);
margin: 0 0 24px;
letter-spacing: -0.02em;
}
.hero__lede {
max-width: 46ch;
color: rgba(246, 244, 239, 0.86);
font-size: 1.05rem;
margin: 0 0 32px;
}
.hero__actions {
display: flex;
gap: 14px;
flex-wrap: wrap;
margin-bottom: 44px;
}
.hero__stats {
display: flex;
gap: 48px;
margin: 0;
padding-top: 28px;
border-top: 1px solid var(--line);
flex-wrap: wrap;
}
.hero__stats div {
display: flex;
flex-direction: column-reverse;
}
.hero__stats dt {
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
}
.hero__stats dd {
margin: 0 0 4px;
font-family: "Fraunces", serif;
font-size: 2rem;
color: var(--sand);
}
.hero__scroll {
position: absolute;
right: 32px;
bottom: 72px;
writing-mode: vertical-rl;
font-size: 11px;
letter-spacing: 0.3em;
text-transform: uppercase;
color: var(--muted);
animation: bob 2.4s ease-in-out infinite;
}
@keyframes bob {
0%, 100% { transform: translateY(0); opacity: 0.5; }
50% { transform: translateY(10px); opacity: 1; }
}
/* ---------- Marquee ---------- */
.marquee {
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
background: var(--surface);
overflow: hidden;
padding: 22px 0;
}
.marquee__track {
display: flex;
gap: 64px;
white-space: nowrap;
width: max-content;
animation: scroll 34s linear infinite;
font-family: "Fraunces", serif;
font-size: 1.4rem;
color: rgba(246, 244, 239, 0.4);
}
.marquee:hover .marquee__track {
animation-play-state: paused;
}
.marquee__track span {
transition: color 0.2s ease;
}
.marquee__track span:hover {
color: var(--sand);
}
@keyframes scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
/* ---------- Sections ---------- */
.section-head {
max-width: 1200px;
margin: 0 auto;
padding: 96px 32px 40px;
}
.section-title {
font-size: clamp(2rem, 5vw, 3.4rem);
}
.work {
padding-bottom: 40px;
}
.filters {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 32px;
}
.chip {
font-family: inherit;
font-size: 12px;
letter-spacing: 0.12em;
text-transform: uppercase;
font-weight: 600;
color: var(--muted);
background: transparent;
border: 1px solid var(--line-2);
border-radius: 999px;
padding: 9px 18px;
cursor: pointer;
transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease;
}
.chip:hover {
color: var(--paper);
border-color: var(--sand-d);
}
.chip.is-active {
background: var(--paper);
color: var(--ink);
border-color: var(--paper);
}
/* ---------- Grid ---------- */
.grid {
max-width: 1200px;
margin: 0 auto;
padding: 0 32px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.card {
position: relative;
border-radius: var(--r-md);
overflow: hidden;
background: var(--surface-2);
border: 1px solid var(--line);
cursor: pointer;
aspect-ratio: 3 / 4;
transition: transform 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
animation: rise 0.4s ease both;
}
.card:hover {
transform: translateY(-6px);
border-color: var(--sand-d);
box-shadow: var(--shadow);
}
.card:focus-visible {
outline-offset: 4px;
}
.card.is-tall {
aspect-ratio: 3 / 5;
}
.card__img {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
filter: grayscale(0.3) contrast(1.03);
transition: transform 0.6s ease, filter 0.4s ease;
}
.card:hover .card__img {
transform: scale(1.06);
filter: grayscale(0) contrast(1.06);
}
.card__scrim {
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(11, 11, 12, 0) 40%, rgba(11, 11, 12, 0.9) 100%);
}
.card__body {
position: absolute;
left: 0;
right: 0;
bottom: 0;
padding: 20px;
}
.card__tag {
display: inline-block;
font-size: 10px;
letter-spacing: 0.16em;
text-transform: uppercase;
font-weight: 700;
color: var(--ink);
background: var(--sand);
padding: 4px 10px;
border-radius: 999px;
margin-bottom: 12px;
}
.card__title {
font-size: 1.35rem;
margin: 0 0 6px;
}
.card__credit {
font-size: 12px;
letter-spacing: 0.05em;
color: var(--muted);
}
.card__issue {
position: absolute;
top: 16px;
right: 16px;
font-size: 10px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: rgba(246, 244, 239, 0.85);
background: rgba(11, 11, 12, 0.5);
backdrop-filter: blur(4px);
border: 1px solid var(--line-2);
padding: 5px 10px;
border-radius: 999px;
}
@keyframes rise {
from { opacity: 0; transform: translateY(18px); }
to { opacity: 1; transform: translateY(0); }
}
.grid__empty {
max-width: 1200px;
margin: 0 auto;
padding: 40px 32px 0;
color: var(--muted);
font-family: "Fraunces", serif;
font-size: 1.3rem;
}
/* ---------- Featured ---------- */
.featured {
max-width: 1200px;
margin: 96px auto 0;
padding: 0 32px;
display: grid;
grid-template-columns: 1.1fr 0.9fr;
gap: 48px;
align-items: center;
}
.featured__media {
border-radius: var(--r-lg);
min-height: 560px;
background:
linear-gradient(150deg, rgba(201, 183, 156, 0.14), transparent 60%),
url("https://images.unsplash.com/photo-1490481651871-ab68de25d43d?auto=format&fit=crop&w=1000&q=80");
background-size: cover;
background-position: center;
filter: grayscale(0.2) contrast(1.05);
border: 1px solid var(--line);
}
.featured__title {
font-size: clamp(1.8rem, 4vw, 2.8rem);
margin: 0 0 28px;
}
.pull {
margin: 0 0 28px;
padding-left: 22px;
border-left: 2px solid var(--sand);
font-family: "Fraunces", serif;
font-size: 1.3rem;
font-style: italic;
color: rgba(246, 244, 239, 0.9);
}
.pull cite {
display: block;
margin-top: 14px;
font-family: "Inter", sans-serif;
font-size: 12px;
letter-spacing: 0.12em;
text-transform: uppercase;
font-style: normal;
color: var(--muted);
}
.featured__text {
color: rgba(246, 244, 239, 0.78);
margin: 0 0 28px;
}
/* ---------- About ---------- */
.about {
border-top: 1px solid var(--line);
margin-top: 110px;
background: var(--surface);
}
.about__inner {
max-width: 1200px;
margin: 0 auto;
padding: 96px 32px;
}
.about__text {
max-width: 60ch;
color: rgba(246, 244, 239, 0.82);
font-size: 1.1rem;
margin: 0 0 48px;
}
.about__list {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.about__list li {
padding: 28px 24px;
border: 1px solid var(--line);
border-radius: var(--r-md);
background: var(--surface-2);
transition: border-color 0.2s ease, transform 0.2s ease;
}
.about__list li:hover {
border-color: var(--sand-d);
transform: translateY(-4px);
}
.about__list strong {
display: block;
font-family: "Fraunces", serif;
font-weight: 600;
font-size: 1.3rem;
margin-bottom: 8px;
color: var(--sand);
}
.about__list span {
color: var(--muted);
font-size: 0.95rem;
}
/* ---------- Contact ---------- */
.contact {
max-width: 1200px;
margin: 0 auto;
padding: 96px 32px 40px;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 56px;
align-items: start;
}
.contact__text {
color: rgba(246, 244, 239, 0.8);
max-width: 42ch;
margin: 0 0 32px;
}
.email-copy {
display: flex;
align-items: center;
gap: 14px;
width: 100%;
text-align: left;
background: var(--surface-2);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
padding: 16px 18px;
cursor: pointer;
font-family: inherit;
color: var(--paper);
transition: border-color 0.2s ease, background 0.2s ease;
}
.email-copy:hover {
border-color: var(--sand);
}
.email-copy__label {
font-size: 10px;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--muted);
}
.email-copy__value {
font-family: "Fraunces", serif;
font-size: 1.05rem;
margin-right: auto;
}
.email-copy__hint {
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--sand);
border: 1px solid var(--line-2);
padding: 5px 10px;
border-radius: 999px;
}
.contact__meta {
list-style: none;
margin: 28px 0 0;
padding: 0;
color: var(--muted);
font-size: 0.9rem;
}
.contact__meta li {
padding: 6px 0;
border-bottom: 1px solid var(--line);
letter-spacing: 0.03em;
}
/* ---------- Form ---------- */
.form {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 32px;
}
.field {
margin-bottom: 20px;
}
.field label {
display: block;
font-size: 11px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 8px;
font-weight: 600;
}
.field input,
.field select,
.field textarea {
width: 100%;
font-family: inherit;
font-size: 15px;
color: var(--paper);
background: var(--bg);
border: 1px solid var(--line-2);
border-radius: var(--r-sm);
padding: 13px 14px;
transition: border-color 0.2s ease, background 0.2s ease;
}
.field textarea {
resize: vertical;
min-height: 96px;
}
.field input::placeholder,
.field textarea::placeholder {
color: #6a6a72;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
border-color: var(--sand);
background: var(--surface-2);
outline: none;
}
.field.has-error input,
.field.has-error textarea {
border-color: #e0745f;
}
.field__error {
margin: 6px 0 0;
font-size: 12px;
color: #e59c8c;
min-height: 0;
}
.form__submit {
width: 100%;
margin-top: 6px;
}
/* ---------- Footer ---------- */
.foot {
border-top: 1px solid var(--line);
margin-top: 96px;
background: var(--surface);
}
.foot__inner {
max-width: 1200px;
margin: 0 auto;
padding: 40px 32px;
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
}
.foot__inner p {
margin: 0;
color: var(--muted);
font-size: 0.85rem;
margin-right: auto;
}
.foot__nav {
display: flex;
gap: 22px;
}
.foot__nav a {
text-decoration: none;
font-size: 11px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
transition: color 0.2s ease;
}
.foot__nav a:hover {
color: var(--sand);
}
/* ---------- Lightbox ---------- */
.lightbox {
position: fixed;
inset: 0;
z-index: 300;
background: rgba(6, 6, 8, 0.94);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
padding: 32px;
animation: fade 0.25s ease;
}
@keyframes fade {
from { opacity: 0; }
to { opacity: 1; }
}
.lightbox__figure {
margin: 0;
max-width: 720px;
width: 100%;
}
.lightbox__img {
width: 100%;
aspect-ratio: 3 / 4;
max-height: 76vh;
background-size: cover;
background-position: center;
border-radius: var(--r-md);
border: 1px solid var(--line-2);
}
.lightbox__cap {
display: flex;
flex-direction: column;
gap: 6px;
padding-top: 18px;
text-align: center;
}
.lightbox__title {
font-family: "Fraunces", serif;
font-size: 1.4rem;
}
.lightbox__credit {
font-size: 12px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--muted);
}
.lightbox__close {
position: absolute;
top: 22px;
right: 26px;
width: 46px;
height: 46px;
border-radius: 50%;
border: 1px solid var(--line-2);
background: rgba(30, 30, 35, 0.7);
color: var(--paper);
font-size: 26px;
line-height: 1;
cursor: pointer;
transition: border-color 0.2s ease, transform 0.2s ease;
}
.lightbox__close:hover {
border-color: var(--sand);
transform: rotate(90deg);
}
.lightbox__nav {
width: 52px;
height: 52px;
flex: none;
border-radius: 50%;
border: 1px solid var(--line-2);
background: rgba(30, 30, 35, 0.7);
color: var(--paper);
font-size: 28px;
line-height: 1;
cursor: pointer;
transition: border-color 0.2s ease, background 0.2s ease;
}
.lightbox__nav:hover {
border-color: var(--sand);
background: var(--surface-2);
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 32px;
transform: translateX(-50%) translateY(20px);
z-index: 400;
background: var(--paper);
color: var(--ink);
font-size: 14px;
font-weight: 600;
padding: 13px 22px;
border-radius: 999px;
box-shadow: var(--shadow);
opacity: 0;
transition: opacity 0.25s ease, transform 0.25s ease;
pointer-events: none;
}
.toast.is-visible {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
body.no-scroll {
overflow: hidden;
}
/* ---------- Responsive ---------- */
@media (max-width: 900px) {
.grid { grid-template-columns: repeat(2, 1fr); }
.featured { grid-template-columns: 1fr; }
.featured__media { min-height: 360px; }
.contact { grid-template-columns: 1fr; }
.about__list { grid-template-columns: 1fr; }
.nav { display: none; }
}
@media (max-width: 520px) {
.masthead__inner { padding: 14px 18px; gap: 12px; }
.masthead__cta { display: none; }
.brand__name { display: none; }
.hero { padding: 0 18px 48px; min-height: 88vh; }
.hero__stats { gap: 28px; }
.hero__stats dd { font-size: 1.5rem; }
.hero__scroll { display: none; }
.marquee__track { font-size: 1.1rem; gap: 40px; }
.section-head { padding: 64px 18px 28px; }
.grid { grid-template-columns: 1fr; padding: 0 18px; }
.featured { padding: 0 18px; margin-top: 64px; }
.about__inner { padding: 64px 18px; }
.contact { padding: 64px 18px 24px; gap: 36px; }
.form { padding: 22px; }
.foot__inner { padding: 28px 18px; }
.lightbox { padding: 16px; gap: 6px; }
.lightbox__nav { width: 42px; height: 42px; font-size: 22px; }
.lightbox__close { top: 14px; right: 14px; }
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.05ms !important;
scroll-behavior: auto !important;
}
}(function () {
"use strict";
/* ---------- Toast helper ---------- */
var toastEl = document.getElementById("toast");
var toastTimer;
function toast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.hidden = false;
// force reflow so the transition runs
void toastEl.offsetWidth;
toastEl.classList.add("is-visible");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-visible");
setTimeout(function () { toastEl.hidden = true; }, 260);
}, 2600);
}
/* ---------- Data: tearsheets ---------- */
var TEARSHEETS = [
{
cat: "editorial",
tag: "Editorial",
title: "The Long Winter",
credit: "Vestige Quarterly · Styling Marco Feld",
issue: "Issue 214",
img: "https://images.unsplash.com/photo-1509631179647-0177331693ae?auto=format&fit=crop&w=900&q=80",
},
{
cat: "cover",
tag: "Cover",
title: "Salt Season",
credit: "NÉ Magazine · Talent Aria Vos",
issue: "May 2026",
img: "https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=900&q=80",
tall: true,
},
{
cat: "beauty",
tag: "Beauty",
title: "Second Skin",
credit: "ODE Paris · MUA Ines Kort",
issue: "Digital",
img: "https://images.unsplash.com/photo-1487412720507-e7ab37603c6f?auto=format&fit=crop&w=900&q=80",
},
{
cat: "campaign",
tag: "Campaign",
title: "Field & Kort SS26",
credit: "Lookbook · Art Dir. R. Sol",
issue: "Campaign",
img: "https://images.unsplash.com/photo-1515886657613-9f3515b0c78f?auto=format&fit=crop&w=900&q=80",
},
{
cat: "editorial",
tag: "Editorial",
title: "Blue Hour Girls",
credit: "PARALLAX · Set Nord Studio",
issue: "Issue 08",
img: "https://images.unsplash.com/photo-1496747611176-843222e1e57c?auto=format&fit=crop&w=900&q=80",
tall: true,
},
{
cat: "beauty",
tag: "Beauty",
title: "Gloss & Grain",
credit: "Muse Editorial · Hair T. Lund",
issue: "Digital",
img: "https://images.unsplash.com/photo-1503342217505-b0a15ec3261c?auto=format&fit=crop&w=900&q=80",
},
{
cat: "cover",
tag: "Cover",
title: "Aperture Nord",
credit: "Aperture Nord · Talent K. Møller",
issue: "Winter 26",
img: "https://images.unsplash.com/photo-1529626455594-4ff0802cfb7e?auto=format&fit=crop&w=900&q=80",
},
{
cat: "campaign",
tag: "Campaign",
title: "Vestige Atelier",
credit: "Campaign · Prod. Salt & Iron",
issue: "Campaign",
img: "https://images.unsplash.com/photo-1492707892479-7bc8d5a4ee93?auto=format&fit=crop&w=900&q=80",
},
{
cat: "editorial",
tag: "Editorial",
title: "Paper Weather",
credit: "PARALLAX · Styling M. Feld",
issue: "Issue 09",
img: "https://images.unsplash.com/photo-1502823403499-6ccfcf4fb453?auto=format&fit=crop&w=900&q=80",
},
];
/* ---------- Render grid ---------- */
var grid = document.getElementById("grid");
var emptyEl = document.getElementById("grid-empty");
var currentFilter = "all";
var visible = [];
function renderGrid() {
if (!grid) return;
grid.innerHTML = "";
visible = TEARSHEETS.filter(function (t) {
return currentFilter === "all" || t.cat === currentFilter;
});
if (visible.length === 0) {
if (emptyEl) emptyEl.hidden = false;
return;
}
if (emptyEl) emptyEl.hidden = true;
visible.forEach(function (t, i) {
var card = document.createElement("button");
card.type = "button";
card.className = "card" + (t.tall ? " is-tall" : "");
card.style.animationDelay = (i * 45) + "ms";
card.setAttribute("aria-label", "Open tearsheet: " + t.title + ", " + t.credit);
card.dataset.index = String(i);
card.innerHTML =
'<div class="card__img" style="background-image:url(' + t.img + ')"></div>' +
'<div class="card__scrim"></div>' +
'<span class="card__issue">' + t.issue + "</span>" +
'<div class="card__body">' +
'<span class="card__tag">' + t.tag + "</span>" +
'<h3 class="card__title">' + t.title + "</h3>" +
'<p class="card__credit">' + t.credit + "</p>" +
"</div>";
card.addEventListener("click", function () {
openLightbox(parseInt(card.dataset.index, 10));
});
grid.appendChild(card);
});
}
/* ---------- Filters ---------- */
var chips = Array.prototype.slice.call(document.querySelectorAll(".chip"));
chips.forEach(function (chip) {
chip.addEventListener("click", function () {
chips.forEach(function (c) {
c.classList.remove("is-active");
c.setAttribute("aria-selected", "false");
});
chip.classList.add("is-active");
chip.setAttribute("aria-selected", "true");
currentFilter = chip.dataset.filter;
renderGrid();
});
});
/* ---------- Lightbox ---------- */
var lb = document.getElementById("lightbox");
var lbImg = document.getElementById("lb-img");
var lbTitle = lb ? lb.querySelector(".lightbox__title") : null;
var lbCredit = lb ? lb.querySelector(".lightbox__credit") : null;
var lbIndex = 0;
var lastFocused = null;
function openLightbox(i) {
if (!lb) return;
lbIndex = i;
lastFocused = document.activeElement;
updateLightbox();
lb.hidden = false;
document.body.classList.add("no-scroll");
document.getElementById("lb-close").focus();
}
function updateLightbox() {
var t = visible[lbIndex];
if (!t) return;
lbImg.style.backgroundImage = "url(" + t.img + ")";
lbImg.setAttribute("aria-label", t.title + " — " + t.credit);
lbTitle.textContent = t.title;
lbCredit.textContent = t.credit + " · " + t.issue;
}
function closeLightbox() {
if (!lb) return;
lb.hidden = true;
document.body.classList.remove("no-scroll");
if (lastFocused && lastFocused.focus) lastFocused.focus();
}
function step(dir) {
lbIndex = (lbIndex + dir + visible.length) % visible.length;
updateLightbox();
}
if (lb) {
document.getElementById("lb-close").addEventListener("click", closeLightbox);
document.getElementById("lb-prev").addEventListener("click", function () { step(-1); });
document.getElementById("lb-next").addEventListener("click", function () { step(1); });
lb.addEventListener("click", function (e) {
if (e.target === lb) closeLightbox();
});
document.addEventListener("keydown", function (e) {
if (lb.hidden) return;
if (e.key === "Escape") closeLightbox();
else if (e.key === "ArrowLeft") step(-1);
else if (e.key === "ArrowRight") step(1);
});
}
/* ---------- Masthead scroll state ---------- */
var masthead = document.getElementById("masthead");
var heroMedia = document.querySelector(".hero__media");
function onScroll() {
var y = window.pageYOffset;
if (masthead) masthead.classList.toggle("is-scrolled", y > 40);
if (heroMedia && y < window.innerHeight) {
heroMedia.style.transform = "scale(1.04) translateY(" + (y * 0.15) + "px)";
}
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
/* ---------- Copy email ---------- */
var emailBtn = document.getElementById("email-copy");
if (emailBtn) {
emailBtn.addEventListener("click", function () {
var email = "[email protected]";
function done() { toast("Studio email copied to clipboard"); }
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(email).then(done).catch(fallback);
} else {
fallback();
}
function fallback() {
var ta = document.createElement("textarea");
ta.value = email;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.select();
try { document.execCommand("copy"); done(); } catch (err) { toast("Copy: " + email); }
document.body.removeChild(ta);
}
});
}
/* ---------- Form validation ---------- */
var form = document.getElementById("inquiry-form");
function setError(name, msg) {
var field = form.querySelector('[name="' + name + '"]');
var wrap = field.closest(".field");
var errEl = form.querySelector('.field__error[data-for="' + name + '"]');
if (msg) {
wrap.classList.add("has-error");
field.setAttribute("aria-invalid", "true");
errEl.textContent = msg;
} else {
wrap.classList.remove("has-error");
field.removeAttribute("aria-invalid");
errEl.textContent = "";
}
return !msg;
}
if (form) {
form.addEventListener("submit", function (e) {
e.preventDefault();
var data = new FormData(form);
var name = (data.get("name") || "").trim();
var email = (data.get("email") || "").trim();
var message = (data.get("message") || "").trim();
var emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var ok = true;
ok = setError("name", name.length < 2 ? "Please tell me your name." : "") && ok;
ok = setError("email", !emailRe.test(email) ? "Enter a valid email address." : "") && ok;
ok = setError("message", message.length < 12 ? "A few more words about the project, please." : "") && ok;
if (!ok) {
var firstErr = form.querySelector(".has-error input, .has-error textarea");
if (firstErr) firstErr.focus();
toast("Please fix the highlighted fields");
return;
}
var btn = form.querySelector(".form__submit");
btn.disabled = true;
btn.textContent = "Sending…";
setTimeout(function () {
form.reset();
btn.disabled = false;
btn.textContent = "Send inquiry";
toast("Thanks, " + name.split(" ")[0] + " — inquiry received. I'll reply within two days.");
}, 800);
});
// clear error on input
["name", "email", "message"].forEach(function (n) {
var f = form.querySelector('[name="' + n + '"]');
if (f) f.addEventListener("input", function () { setError(n, ""); });
});
}
/* ---------- Init ---------- */
renderGrid();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Lena Marchetti — Editorial & Fashion Photographer</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@400;600&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#work">Skip to work</a>
<!-- Masthead -->
<header class="masthead" id="masthead">
<div class="masthead__inner">
<a class="brand" href="#top" aria-label="Lena Marchetti, home">
<span class="brand__mark">LM</span>
<span class="brand__name">Lena Marchetti</span>
</a>
<nav class="nav" aria-label="Primary">
<a href="#work">Work</a>
<a href="#featured">Featured</a>
<a href="#about">Studio</a>
<a href="#contact">Contact</a>
</nav>
<a class="btn btn--ghost masthead__cta" href="#contact">Commission</a>
</div>
</header>
<main id="top">
<!-- Hero -->
<section class="hero" aria-labelledby="hero-title">
<div class="hero__media" role="img" aria-label="Editorial fashion portrait in cool light"></div>
<div class="hero__scrim"></div>
<div class="hero__content">
<p class="kicker">Editorial · Fashion · Beauty — Based in Milan</p>
<h1 id="hero-title" class="hero__title">Images that<br /><em>hold a room</em>.</h1>
<p class="hero__lede">Lena Marchetti photographs fashion stories, covers and beauty campaigns for print and screen. Fifteen years of tearsheets, one obsessive eye for light.</p>
<div class="hero__actions">
<a class="btn btn--solid" href="#work">View the work</a>
<a class="btn btn--ghost" href="#featured">Latest cover</a>
</div>
<dl class="hero__stats">
<div><dt>Covers shot</dt><dd>42</dd></div>
<div><dt>Titles worked</dt><dd>28</dd></div>
<div><dt>Since</dt><dd>2011</dd></div>
</dl>
</div>
<div class="hero__scroll" aria-hidden="true">Scroll</div>
</section>
<!-- Publication marquee -->
<section class="marquee" aria-label="Published in">
<div class="marquee__track">
<span>NÉ Magazine</span><span>PARALLAX</span><span>Vestige Quarterly</span><span>ODE Paris</span><span>Salt & Iron</span><span>Muse Editorial</span><span>Aperture Nord</span>
<span aria-hidden="true">NÉ Magazine</span><span aria-hidden="true">PARALLAX</span><span aria-hidden="true">Vestige Quarterly</span><span aria-hidden="true">ODE Paris</span><span aria-hidden="true">Salt & Iron</span><span aria-hidden="true">Muse Editorial</span><span aria-hidden="true">Aperture Nord</span>
</div>
</section>
<!-- Work / tearsheets -->
<section class="work" id="work" aria-labelledby="work-title">
<div class="section-head">
<p class="kicker">Selected Tearsheets</p>
<h2 id="work-title" class="section-title">Published stories</h2>
<div class="filters" role="tablist" aria-label="Filter work by category">
<button class="chip is-active" role="tab" aria-selected="true" data-filter="all">All</button>
<button class="chip" role="tab" aria-selected="false" data-filter="editorial">Editorial</button>
<button class="chip" role="tab" aria-selected="false" data-filter="cover">Cover</button>
<button class="chip" role="tab" aria-selected="false" data-filter="beauty">Beauty</button>
<button class="chip" role="tab" aria-selected="false" data-filter="campaign">Campaign</button>
</div>
</div>
<div class="grid" id="grid">
<!-- cards injected by script.js -->
</div>
<p class="grid__empty" id="grid-empty" hidden>No tearsheets in this category yet.</p>
</section>
<!-- Featured -->
<section class="featured" id="featured" aria-labelledby="featured-title">
<div class="featured__media" role="img" aria-label="Featured cover story photograph"></div>
<div class="featured__body">
<p class="kicker">Cover Story · Issue 214</p>
<h2 id="featured-title" class="featured__title">The Long Winter, for <em>Vestige Quarterly</em></h2>
<blockquote class="pull">
“Lena shoots like the light might leave the room forever. Every frame feels like the last good minute of a day.”
<cite>— Odette Rousseau, Photo Director</cite>
</blockquote>
<p class="featured__text">A twelve-page fashion story shot across two mornings in the Dolomites: raw wool, breath in cold air, and a single continuous run of natural light. Styling by Marco Feld, hair by Ines Kort.</p>
<a class="btn btn--solid" href="#contact">Request the full story</a>
</div>
</section>
<!-- About -->
<section class="about" id="about" aria-labelledby="about-title">
<div class="about__inner">
<p class="kicker">The Studio</p>
<h2 id="about-title" class="section-title">A small studio, a long lens on style</h2>
<p class="about__text">Studio Marchetti works with magazines, fashion houses and beauty brands on editorial and campaign photography. We keep crews small, days long and post honest. From casting to final grade, the same eye stays on the picture the whole way through.</p>
<ul class="about__list">
<li><strong>Editorial</strong><span>Fashion & beauty stories for print and digital titles.</span></li>
<li><strong>Campaign</strong><span>Lookbooks and brand campaigns, stills and motion stills.</span></li>
<li><strong>Covers</strong><span>Portrait covers with talent and celebrity crews.</span></li>
</ul>
</div>
</section>
<!-- Contact -->
<section class="contact" id="contact" aria-labelledby="contact-title">
<div class="contact__intro">
<p class="kicker">Commissions</p>
<h2 id="contact-title" class="section-title">Let’s make a story</h2>
<p class="contact__text">Booking editorial and campaign work for late 2026. Share the title, the concept and your window — I reply to every genuine inquiry within two days.</p>
<button class="email-copy" id="email-copy" type="button" aria-label="Copy studio email address">
<span class="email-copy__label">Studio</span>
<span class="email-copy__value">[email protected]</span>
<span class="email-copy__hint" aria-hidden="true">Copy</span>
</button>
<ul class="contact__meta">
<li>Milan · Available worldwide</li>
<li>Representation — Field & Kort Agency</li>
</ul>
</div>
<form class="form" id="inquiry-form" novalidate>
<div class="field">
<label for="f-name">Name</label>
<input id="f-name" name="name" type="text" autocomplete="name" placeholder="Your name" required />
<p class="field__error" data-for="name" aria-live="polite"></p>
</div>
<div class="field">
<label for="f-email">Email</label>
<input id="f-email" name="email" type="email" autocomplete="email" placeholder="[email protected]" required />
<p class="field__error" data-for="email" aria-live="polite"></p>
</div>
<div class="field">
<label for="f-type">Project type</label>
<select id="f-type" name="type">
<option value="editorial">Editorial story</option>
<option value="cover">Cover shoot</option>
<option value="beauty">Beauty</option>
<option value="campaign">Campaign</option>
</select>
</div>
<div class="field">
<label for="f-message">Brief</label>
<textarea id="f-message" name="message" rows="4" placeholder="Concept, title, dates, budget range…" required></textarea>
<p class="field__error" data-for="message" aria-live="polite"></p>
</div>
<button class="btn btn--solid form__submit" type="submit">Send inquiry</button>
</form>
</section>
</main>
<footer class="foot">
<div class="foot__inner">
<span class="brand__mark">LM</span>
<p>© 2026 Studio Lena Marchetti. Fictional showcase, MIT licensed.</p>
<nav class="foot__nav" aria-label="Footer">
<a href="#work">Work</a>
<a href="#about">Studio</a>
<a href="#contact">Contact</a>
</nav>
</div>
</footer>
<!-- Lightbox -->
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Tearsheet viewer" hidden>
<button class="lightbox__close" id="lb-close" aria-label="Close viewer">×</button>
<button class="lightbox__nav lightbox__nav--prev" id="lb-prev" aria-label="Previous tearsheet">‹</button>
<figure class="lightbox__figure">
<div class="lightbox__img" id="lb-img" role="img" aria-label="Tearsheet"></div>
<figcaption class="lightbox__cap" id="lb-cap">
<span class="lightbox__title"></span>
<span class="lightbox__credit"></span>
</figcaption>
</figure>
<button class="lightbox__nav lightbox__nav--next" id="lb-next" aria-label="Next tearsheet">›</button>
</div>
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<script src="script.js"></script>
</body>
</html>An editorial photographer landing page laid out like the opening spread of a fashion magazine. A full-bleed hero pairs an oversized Fraunces headline with a thin uppercase kicker and a set of running stats, while a sticky masthead nav condenses as you scroll past the fold. Below it, a scrolling marquee of fictional publication logos gives the page a portfolio-of-record feel.
The core of the page is a tearsheet grid of published stories, each with a magazine credit, issue tag and category. Filter chips switch between Editorial, Beauty, Cover and Campaign work with a smooth re-flow, and clicking any tearsheet opens a full-screen lightbox reader with keyboard navigation and a caption panel. A featured story block breaks the grid with an asymmetric two-column layout and pull-quote typography.
The contact section closes the page with a copy-to-clipboard studio email and a commission inquiry form that validates name, email and message inline, surfacing errors with accessible messaging and confirming submission through a small toast. Every control has hover, active and visible focus states, and the whole layout collapses gracefully to a single column below 520px.