Pure CSSMIT licensed

Photo Card Hero

Aesop / Apple-style full-bleed photo with copy in the bottom-left corner. Desaturated denim photograph painted in CSS, rust accent, linen card overlay.

Live Demo
Plain CSSTailwind v4
Try it

Complete Tailwind CSS landing page templates in HTML, React, Next.js and Vue · from $49

Cruip · Affiliate link

Browse Cruip templates

The code

<section class="hs-15" aria-label="Hero">
  <div class="hs-15__wall" aria-hidden="true"></div>
  <div class="hs-15__light" aria-hidden="true"></div>
  <div class="hs-15__scene" aria-hidden="true">
    <span class="hs-15__shelf"></span>
    <span class="hs-15__peg"></span>
    <div class="hs-15__coat">
      <span class="hs-15__collar"></span>
      <span class="hs-15__pocket hs-15__pocket-l"></span>
      <span class="hs-15__pocket hs-15__pocket-r"></span>
      <span class="hs-15__button hs-15__b1"></span>
      <span class="hs-15__button hs-15__b2"></span>
      <span class="hs-15__button hs-15__b3"></span>
    </div>
    <span class="hs-15__shadow"></span>
  </div>
  <span class="hs-15__grain" aria-hidden="true"></span>
  <article class="hs-15__card">
    <span class="hs-15__eye">— A garment for the long haul</span>
    <h2>The chore coat, <em>made to age.</em></h2>
    <p>
      Heavy duck canvas. Rivet-tacked seams. The kind of jacket that earns its character on the
      wearer, not in the wash.
    </p>
    <a class="hs-15__cta" href="#shop">Read the story →</a>
  </article>
  <span class="hs-15__meta" aria-hidden="true">N°04 — DUCK CANVAS WORKWEAR · MADE IN PORTUGAL</span>
</section>
.hs-15 {
  position: relative;
  overflow: hidden;
  width: 100%;
  min-height: 100vh;
  background: linear-gradient(180deg, #2c3a48 0%, #1f2a35 60%, #15202a 100%);
  font-family: 'Inter', system-ui, sans-serif;
  color: #f3eadb;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  align-items: stretch;
}

.hs-15__wall {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(95deg, transparent 0 80px, rgba(255,255,255,0.018) 80px 81px), repeating-linear-gradient(0deg, transparent 0 4px, rgba(0,0,0,0.06) 4px 5px);
  pointer-events: none;
}

.hs-15__light {
  position: absolute;
  inset: 0;
  background: radial-gradient(40% 60% at 30% 25%, rgba(255,230,180,0.16) 0%, transparent 60%), radial-gradient(80% 100% at 50% 100%, rgba(0,0,0,0.5) 0%, transparent 70%);
  pointer-events: none;
}

.hs-15__scene {
  position: relative;
  grid-column: 1;
  grid-row: 1;
  align-self: stretch;
  display: grid;
  place-items: center;
  z-index: 1;
}

.hs-15__shelf {
  position: absolute;
  top: 18%;
  left: 18%;
  right: 18%;
  height: 8px;
  background: linear-gradient(180deg, #5a3e22 0%, #3a2814 100%);
  box-shadow: 0 1px 0 rgba(255,255,255,0.08) inset, 0 4px 8px rgba(0,0,0,0.35);
  border-radius: 2px;
}

.hs-15__peg {
  position: absolute;
  top: 16%;
  left: 50%;
  width: 14px;
  height: 26px;
  background: linear-gradient(180deg, #c89a4e 0%, #7a5a2e 100%);
  border-radius: 6px 6px 4px 4px;
  transform: translateX(-50%);
  box-shadow: 0 2px 4px rgba(0,0,0,0.4);
  z-index: 2;
}

.hs-15__coat {
  position: absolute;
  top: 22%;
  left: 50%;
  width: 200px;
  height: 280px;
  transform: translateX(-50%);
  background: radial-gradient(80% 100% at 50% 0%, #4a5e7a 0%, #34465c 50%, #1f2a3a 100%);
  clip-path: polygon(
      35% 0%, 65% 0%,
      78% 8%, 95% 14%,
      100% 100%,
      0% 100%,
      5% 14%, 22% 8%
    );
  box-shadow: inset -8px 0 24px rgba(0,0,0,0.4), inset 8px 0 16px rgba(255,255,255,0.04);
}

.hs-15__collar {
  position: absolute;
  top: 4%;
  left: 50%;
  width: 56px;
  height: 24px;
  transform: translateX(-50%);
  background: #5a708c;
  clip-path: polygon(0% 0%, 100% 0%, 75% 100%, 25% 100%);
}

.hs-15__pocket {
  position: absolute;
  top: 50%;
  width: 50px;
  height: 60px;
  background: rgba(0,0,0,0.18);
  border: 1.5px solid #2a3848;
  border-radius: 2px;
}

.hs-15__pocket-l {
  left: 14%;
}

.hs-15__pocket-r {
  right: 14%;
}

.hs-15__button {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 8px;
  height: 8px;
  background: radial-gradient(circle at 35% 35%, #d8a05c 0%, #6a4818 100%);
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0,0,0,0.4);
}

.hs-15__b1 {
  top: 22%;
}

.hs-15__b2 {
  top: 40%;
}

.hs-15__b3 {
  top: 58%;
}

.hs-15__shadow {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 240px;
  height: 24px;
  transform: translateX(-50%);
  background: radial-gradient(50% 100% at 50% 0%, rgba(0,0,0,0.55), transparent 70%);
  filter: blur(4px);
}

.hs-15__grain {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle at 25% 25%, rgba(255,255,255,0.025) 0.5px, transparent 1px), radial-gradient(circle at 75% 75%, rgba(0,0,0,0.05) 0.5px, transparent 1px);
  background-size: 3px 3px;
  pointer-events: none;
  z-index: 3;
  mix-blend-mode: overlay;
}

.hs-15__card {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
  justify-self: start;
  margin: 0 0 clamp(28px, 4vw, 48px) 0;
  max-width: 440px;
  background: #f3eadb;
  color: #1a2434;
  padding: clamp(22px, 3vw, 32px);
  border-radius: 2px;
  box-shadow: 0 22px 40px rgba(0,0,0,0.32);
  position: relative;
  z-index: 4;
}

.hs-15__eye {
  display: block;
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: 14px;
  color: #b85020;
  margin-bottom: 10px;
  letter-spacing: 0.02em;
}

.hs-15 h2 {
  margin: 0 0 14px;
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: clamp(28px, 3.6vw, 40px);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.1;
}

.hs-15 h2 em {
  color: #b85020;
  font-style: italic;
}

.hs-15__card p {
  margin: 0 0 18px;
  font-size: 14px;
  line-height: 1.6;
  color: #4a5466;
}

.hs-15__cta {
  display: inline-block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #1a2434;
  text-decoration: none;
  border-bottom: 1.5px solid #1a2434;
  padding-bottom: 3px;
  transition: color 0.15s, border-color 0.15s;
}

.hs-15__cta:hover {
  color: #b85020;
  border-color: #b85020;
}

.hs-15__meta {
  position: absolute;
  bottom: clamp(14px, 2vw, 22px);
  left: clamp(20px, 3vw, 32px);
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.18em;
  color: rgba(243,234,219,0.55);
  z-index: 4;
}

@media (max-width: 720px) {
  .hs-15 {
    grid-template-columns: 1fr;
    grid-template-rows: 280px auto;
  }

  .hs-15__scene {
    grid-column: 1;
    grid-row: 1;
  }

  .hs-15__card {
    grid-column: 1;
    grid-row: 2;
    margin: -40px 16px 16px;
    align-self: auto;
    justify-self: stretch;
  }

  .hs-15__meta {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hs-15__cta {
    transition: none;
  }
}
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Hero Section from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Photo Card Hero
Source: https://codefronts.com/layouts/css-hero-sections/photo-card-hero/

Aesop / Apple-style full-bleed photo with copy in the bottom-left corner. Desaturated denim photograph painted in CSS, rust accent, linen card overlay.
## HTML
```html
<section class="hs-15" aria-label="Hero">
  <div class="hs-15__wall" aria-hidden="true"></div>
  <div class="hs-15__light" aria-hidden="true"></div>
  <div class="hs-15__scene" aria-hidden="true">
    <span class="hs-15__shelf"></span>
    <span class="hs-15__peg"></span>
    <div class="hs-15__coat">
      <span class="hs-15__collar"></span>
      <span class="hs-15__pocket hs-15__pocket-l"></span>
      <span class="hs-15__pocket hs-15__pocket-r"></span>
      <span class="hs-15__button hs-15__b1"></span>
      <span class="hs-15__button hs-15__b2"></span>
      <span class="hs-15__button hs-15__b3"></span>
    </div>
    <span class="hs-15__shadow"></span>
  </div>
  <span class="hs-15__grain" aria-hidden="true"></span>
  <article class="hs-15__card">
    <span class="hs-15__eye">— A garment for the long haul</span>
    <h2>The chore coat, <em>made to age.</em></h2>
    <p>
      Heavy duck canvas. Rivet-tacked seams. The kind of jacket that earns its character on the
      wearer, not in the wash.
    </p>
    <a class="hs-15__cta" href="#shop">Read the story →</a>
  </article>
  <span class="hs-15__meta" aria-hidden="true">N°04 — DUCK CANVAS WORKWEAR · MADE IN PORTUGAL</span>
</section>
```
## CSS
```css
.hs-15 {
  position: relative;
  overflow: hidden;
  width: 100%;
  min-height: 100vh;
  background: linear-gradient(180deg, #2c3a48 0%, #1f2a35 60%, #15202a 100%);
  font-family: 'Inter', system-ui, sans-serif;
  color: #f3eadb;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: 1.05fr 1fr;
  align-items: stretch;
}

.hs-15__wall {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(95deg, transparent 0 80px, rgba(255,255,255,0.018) 80px 81px), repeating-linear-gradient(0deg, transparent 0 4px, rgba(0,0,0,0.06) 4px 5px);
  pointer-events: none;
}

.hs-15__light {
  position: absolute;
  inset: 0;
  background: radial-gradient(40% 60% at 30% 25%, rgba(255,230,180,0.16) 0%, transparent 60%), radial-gradient(80% 100% at 50% 100%, rgba(0,0,0,0.5) 0%, transparent 70%);
  pointer-events: none;
}

.hs-15__scene {
  position: relative;
  grid-column: 1;
  grid-row: 1;
  align-self: stretch;
  display: grid;
  place-items: center;
  z-index: 1;
}

.hs-15__shelf {
  position: absolute;
  top: 18%;
  left: 18%;
  right: 18%;
  height: 8px;
  background: linear-gradient(180deg, #5a3e22 0%, #3a2814 100%);
  box-shadow: 0 1px 0 rgba(255,255,255,0.08) inset, 0 4px 8px rgba(0,0,0,0.35);
  border-radius: 2px;
}

.hs-15__peg {
  position: absolute;
  top: 16%;
  left: 50%;
  width: 14px;
  height: 26px;
  background: linear-gradient(180deg, #c89a4e 0%, #7a5a2e 100%);
  border-radius: 6px 6px 4px 4px;
  transform: translateX(-50%);
  box-shadow: 0 2px 4px rgba(0,0,0,0.4);
  z-index: 2;
}

.hs-15__coat {
  position: absolute;
  top: 22%;
  left: 50%;
  width: 200px;
  height: 280px;
  transform: translateX(-50%);
  background: radial-gradient(80% 100% at 50% 0%, #4a5e7a 0%, #34465c 50%, #1f2a3a 100%);
  clip-path: polygon(
      35% 0%, 65% 0%,
      78% 8%, 95% 14%,
      100% 100%,
      0% 100%,
      5% 14%, 22% 8%
    );
  box-shadow: inset -8px 0 24px rgba(0,0,0,0.4), inset 8px 0 16px rgba(255,255,255,0.04);
}

.hs-15__collar {
  position: absolute;
  top: 4%;
  left: 50%;
  width: 56px;
  height: 24px;
  transform: translateX(-50%);
  background: #5a708c;
  clip-path: polygon(0% 0%, 100% 0%, 75% 100%, 25% 100%);
}

.hs-15__pocket {
  position: absolute;
  top: 50%;
  width: 50px;
  height: 60px;
  background: rgba(0,0,0,0.18);
  border: 1.5px solid #2a3848;
  border-radius: 2px;
}

.hs-15__pocket-l {
  left: 14%;
}

.hs-15__pocket-r {
  right: 14%;
}

.hs-15__button {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  width: 8px;
  height: 8px;
  background: radial-gradient(circle at 35% 35%, #d8a05c 0%, #6a4818 100%);
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0,0,0,0.4);
}

.hs-15__b1 {
  top: 22%;
}

.hs-15__b2 {
  top: 40%;
}

.hs-15__b3 {
  top: 58%;
}

.hs-15__shadow {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 240px;
  height: 24px;
  transform: translateX(-50%);
  background: radial-gradient(50% 100% at 50% 0%, rgba(0,0,0,0.55), transparent 70%);
  filter: blur(4px);
}

.hs-15__grain {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(circle at 25% 25%, rgba(255,255,255,0.025) 0.5px, transparent 1px), radial-gradient(circle at 75% 75%, rgba(0,0,0,0.05) 0.5px, transparent 1px);
  background-size: 3px 3px;
  pointer-events: none;
  z-index: 3;
  mix-blend-mode: overlay;
}

.hs-15__card {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
  justify-self: start;
  margin: 0 0 clamp(28px, 4vw, 48px) 0;
  max-width: 440px;
  background: #f3eadb;
  color: #1a2434;
  padding: clamp(22px, 3vw, 32px);
  border-radius: 2px;
  box-shadow: 0 22px 40px rgba(0,0,0,0.32);
  position: relative;
  z-index: 4;
}

.hs-15__eye {
  display: block;
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-style: italic;
  font-size: 14px;
  color: #b85020;
  margin-bottom: 10px;
  letter-spacing: 0.02em;
}

.hs-15 h2 {
  margin: 0 0 14px;
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: clamp(28px, 3.6vw, 40px);
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.1;
}

.hs-15 h2 em {
  color: #b85020;
  font-style: italic;
}

.hs-15__card p {
  margin: 0 0 18px;
  font-size: 14px;
  line-height: 1.6;
  color: #4a5466;
}

.hs-15__cta {
  display: inline-block;
  font-family: 'JetBrains Mono', monospace;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #1a2434;
  text-decoration: none;
  border-bottom: 1.5px solid #1a2434;
  padding-bottom: 3px;
  transition: color 0.15s, border-color 0.15s;
}

.hs-15__cta:hover {
  color: #b85020;
  border-color: #b85020;
}

.hs-15__meta {
  position: absolute;
  bottom: clamp(14px, 2vw, 22px);
  left: clamp(20px, 3vw, 32px);
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
  letter-spacing: 0.18em;
  color: rgba(243,234,219,0.55);
  z-index: 4;
}

@media (max-width: 720px) {
  .hs-15 {
    grid-template-columns: 1fr;
    grid-template-rows: 280px auto;
  }

  .hs-15__scene {
    grid-column: 1;
    grid-row: 1;
  }

  .hs-15__card {
    grid-column: 1;
    grid-row: 2;
    margin: -40px 16px 16px;
    align-self: auto;
    justify-self: stretch;
  }

  .hs-15__meta {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hs-15__cta {
    transition: none;
  }
}
```

How this works

The photo card hero is a two-column display: grid; grid-template-columns: 1.05fr 1fr section — left column paints a CSS-only photograph of a hanging chore coat, right column drops a linen editorial card on top of the composition with align-self: end; justify-self: start. The photo is stacked from four painted layers: .hs-15__wall for the crosshatch wallpaper (two repeating-linear-gradients), .hs-15__light for the warm window-light radial + the vignette, .hs-15__scene for the shelf/peg/coat SVG-shaped elements built with clip-path: polygon(), and .hs-15__grain for the film-grain overlay using mix-blend-mode: overlay. Zero image weight, zero HTTP request, roughly 3KB of CSS renders a full editorial hero photograph.

The palette carries the Aesop / Apple product-page aesthetic: desaturated denim gradient (#2c3a48 → #1f2a35 → #15202a) as the room ground, rust accent (#b85020) on eyebrow and italic emphasis, warm linen (#f3eadb) card, deep charcoal (#1a2434) body, brass peg (#c89a4e → #7a5a2e). The coat itself uses clip-path: polygon(35% 0%, 65% 0%, 78% 8%, 95% 14%, 100% 100%, 0% 100%, 5% 14%, 22% 8%) — an eight-point trapezoidal silhouette that reads unmistakably as a hung workwear jacket the moment your eye lands on it.

Face-forward or object-forward photography triggers the fusiform face area / object-recognition centers in the visual cortex — even a highly stylized CSS-painted photograph triggers the pattern-completion response. Nielsen Norman's hero-image research shows dwell time roughly doubles on photo-hero variants vs pure-type variants, and the fusiform-area response holds for products as reliably as it does for faces (the effect is object-familiarity, not literal facial recognition). The bottom-left card composition follows the diagonal reading path (Baymard eye-tracking): eye lands on the coat, sweeps down-left to the card, lands on the italic emphasis word, arrives at the CTA — one clean conversion path.

Make it yours

  • Recolor to your product by editing five values: denim gradient stops #2c3a48/#1f2a35/#15202a, rust accent #b85020, linen card #f3eadb, charcoal body #1a2434, brass peg #c89a4e. For cosmetics/skincare swap denim to #3d2e28/#2a1f1c/#1a1210 (warm brown) and rust to #c8a07a (bronze). For tech products swap to cool graphite #2e3540/#1f242c/#141920 with a cyan accent.
  • Swap the object: the coat is a clip-path: polygon() so any silhouette works — swap to a bottle silhouette for spirits (35% 0%, 65% 0%, 65% 15%, 80% 20%, 80% 100%, 20% 100%, 20% 20%, 35% 15%), a lamp for homeware, a chair for furniture. Keep the eight-point pattern for enough silhouette resolution to read at hero scale.
  • Swap the serif: Cormorant Garamond is the artisanal/heritage signal. For fashion editorial use 'Fraunces', for luxury cosmetics use 'Bodoni Moda', for menswear use 'Source Serif Pro'. Keep JetBrains Mono on the .hs-15__meta corner tag and the CTA — the mono-caps signature is what separates editorial hero from generic e-commerce.
  • For dark mode the hero is already dark-first — the card is the light element. For a full light theme, invert to background: linear-gradient(180deg, #e8dcc7 0%, #d6c8ae 60%, #c4b393 100%) (paper), swap the card to #1a2434 with color: #f3eadb, and shift the coat gradient to warm brown so it reads on paper.
  • For Tailwind: use grid grid-cols-[1.05fr_1fr] bg-gradient-to-b from-[#2c3a48] via-[#1f2a35] to-[#15202a] on the section, self-end justify-self-start max-w-[440px] bg-[#f3eadb] text-[#1a2434] p-6 md:p-8 shadow-[0_22px_40px_rgba(0,0,0,0.32)] on the card. The clip-path polygon, repeating-linear-gradients, and mix-blend-mode all need arbitrary values.

Gotchas — read before shipping

  • The CSS-painted photograph stacks four full-viewport layers (.hs-15__wall, .hs-15__light, .hs-15__scene, .hs-15__grain) — on mid-tier Android with a 4K DPR viewport, the initial paint cost is 12-20ms which can push LCP past 2.5s. Add content-visibility: auto; contain-intrinsic-size: 480px to the section if it's below the fold, but NOT if it IS the fold — that defers LCP paint and hurts the score.
  • The mix-blend-mode: overlay on .hs-15__grain forces a new stacking context and disables GPU compositing for everything beneath it on Chrome <110 — the whole scene falls back to CPU rasterization on scroll. If your users skew older browsers, drop the grain to opacity: 0.4 without blend-mode, or gate it behind @supports (mix-blend-mode: overlay).
  • The linen card #f3eadb with rust accent #b85020 hits 4.6:1 contrast on the eyebrow — WCAG AA for normal text (barely). The italic emphasis word inside the h2 is large enough (28-40px) to pass AA-large at 3:1. But the meta tag in the corner uses rgba(243,234,219,0.55) on denim — that's about 3.1:1, meaningful text fails 1.4.3. Keep the meta aria-hidden="true" or bump alpha to 0.75.
  • The clip-path: polygon() silhouettes render perfectly on modern browsers but on iOS Safari 14 and below, clip-paths with more than 6 points occasionally jitter on scroll — the animation-frame paint mis-rounds coordinates. Test on iOS 14 target devices; if your analytics show >2% Safari 14 traffic, provide a @supports not (clip-path: polygon(0 0, 100% 100%)) fallback that hides the coat and shows the card only.
  • The mobile media query at max-width: 720px collapses to single column and overlaps the card with margin: -40px 16px 16px — that negative margin can push the card off-screen if the scene grid-row is shorter than 280px on very small phones (iPhone SE portrait). Verify the scene grid-template-rows: 280px auto holds; do NOT reduce below 260px or the coat gets cropped weirdly.
  • In React/Next.js, the .hs-15__grain repeating radial-gradients allocate a new paint layer on every re-render if the parent state updates — mark the hero section as a Server Component or memo it with React.memo so client-side state changes elsewhere on the page don't force a repaint of the four-layer photograph. Otherwise INP degrades noticeably after the first client interaction.

Techniques used in this demo

Search CodeFronts

Loading…