Design Lounge
Install

Nº 355 of 520

Photo album with page turns

A black-paged family album with photo corners and white-pen captions. Leaves turn over the spine in 3D on click or the arrow keys.

Preview

Open full size
1280 × 800 · 20.8 KB · 217 lines

Use it

Free to use · credit appreciated

1 Once, in your project

npx skills add SusanAcharya/Design-Lounge

2 Then ask

Using the Design Lounge skill, build "Photo album with page turns" (piece gallery-photo-album) in my stack. Match its brief; restyle it onto my product's theme.

No skill? Paste the brief instead. It carries the numbers, tokens and behaviour.

Brief

219 lines

Build brief for a coding agent. Rebuild this piece in the reader’s stack. If they haven’t said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don’t “improve” them.

What it is

A family photo album from summer 1986, open on a bottle-green felt table. The album has a brown leather cover and black paper pages. Prints have a 7px cream border, sit at small angles, and are held by four cream photo corners. Captions are handwritten in white gel pen (Caveat). The leaves turn over the spine in real 3D: each leaf is a two-faced card that rotates −180° around its left edge. The detail worth copying is the photo corner: one pseudo-element with four diagonal gradients, so any print gets corners without extra markup.

Every photo is an illustrated SVG scene (canoe on a dock, cabin under pines, birthday cake, station wagon, dog, rope swing, campfire, watermelon picnic, fireflies in a jar). No image files.

Reference behaviour

  1. First frame: the album is open at pages 2–3 (the first leaf is already turned). Header counter reads “Pages 2–3 of 8”.
  2. Pressing → (anywhere), the Next button, or clicking anywhere on the right page turns the top right leaf over to the left in 900ms. The counter updates as the turn starts.
  3. Pressing ←, the Previous button, or clicking the left page turns the top left leaf back.
  4. A shade gradient sweeps across the turning leaf: opacity 0 → 1 at the midpoint → 0.
  5. Turns can be queued quickly; each turning leaf goes on top while it moves (z-index 100+), then settles into the stack.
  6. At the start (title page and page 1) Previous is disabled; at the end (page 8 and the end page) Next is disabled. The page clicks follow the same rule.
  7. The left inside cover is a title page: “Kettle Pond”, a rule, “Cabin 4 · July 19 – August 24, 1986”, “The Okafor-Lindqvist family”. The right inside back cover reads “Same time next year.”
  8. Reduced motion: the turn is instant (1ms) and the shade does not animate.

Structure

1280 × 800, column flex
┌───────────────────────────────────────────────────────────────┐
│ ALBUM NO. 3 · SUMMER 1986                                     │ header max 1040, padding 26 24 0
│ Kettle Pond (Lora italic 30)                 Pages 2–3 of 8   │
│  ┌ cover, inset −12 −14, leather ──────────────────────────┐  │
│  │┌ left page 480×600 ─────────┐┌ right page 480×600 ──────┐│  │
│  ││  [print −3°]   note+arrow  ││   [print 2°, 320 wide]   ││  │ book 960×600
│  ││  caption                   ││   caption + small line   ││  │ scaled by --s
│  ││        [print 2°]          ││     [print −3.5°]        ││  │
│  ││        caption       2     ││     caption        3     ││  │
│  │└────────────────────────────┘└──────────────────────────┘│  │
│  └───────────────────────────── ribbon ───────────────────────┘
│                (‹)   ← → turn the page   (›)                  │ nav, padding 34 0 20
└───────────────────────────────────────────────────────────────┘
  • header: kicker p, the only h1, and a counter p with aria-live="polite".
  • .fit reserves 960×600 × --s of layout; .book inside is 960×600 with transform: scale(var(--s)), origin top left, perspective: 2400px.
  • Two static .pages: left inside cover, right inside back cover.
  • Four .leaf elements stacked on the right half (left:480px), each with section.face.front (odd page) and section.face.back (even page, pre-rotated 180°).
  • Each photo is a figure.ph with a .print holding the SVG, and a figcaption. Optional .note handwritten asides with a drawn arrow.
  • Two transparent button.hit areas over the left and right page (tabindex="-1", the visible buttons are the keyboard path).
  • nav.controls labelled “Album pages” with Previous and Next icon buttons.

Tokens

:root {
  --bg: #26332b;        /* felt table */
  --bg-2: #1e2922;      /* table edge */
  --cover: #5a2f24;     /* leather */
  --page: #1c1c1a;      /* black album paper */
  --ink: #efe8d8;       /* white gel pen, primary text */
  --ink-2: #b9b19f;     /* second caption line, notes */
  --ink-3: #8a8578;     /* page numbers, hint */
  --corner: #d3c8b0;    /* photo corners */
  --print: #f2ecdf;     /* print border */
  --ribbon: #c8962f;    /* bookmark, focus */
  --hand: "Caveat", cursive;
  --serif: "Lora", Georgia, serif;
  --turn: 900ms;
  --sheet: cubic-bezier(.32,.72,0,1);
  --ease: cubic-bezier(.2,.7,.2,1);
  --page-w: 480px;
  --page-h: 600px;
  --corner-size: 26px;
}
  • Table: radial-gradient(90% 70% at 50% 45%, #2e3d33, #26332b 55%, #1e2922).
  • Cover: horizontal gradient #4c271e → #5a2f24 8% … #3a1d16 50% (spine) … #5a2f24 92% → #4c271e, radius 6px, shadow 0 30px 60px -20px #000c, 0 4px 10px #0006.
  • Paper grain: two dot grids (#ffffff06 at 5px, #00000030 at 7px offset 2px 3px).
  • Gutter: 40px gradient from #000a at the spine edge of each page.
  • Print shadow: 0 2px 3px #0009, 0 8px 16px -6px #0008. Photos get sepia(.22) saturate(.86) contrast(.96) to read as 1980s prints.

Typography

RoleFamilySize / lineWeightTrackingColour
KickerLora12px4000.16em, upper--ink-2
Title h1Lora italic30px / 1.15000--ink
CounterLora14px4000--ink-2
CaptionCaveat25px / 1.055000--ink
Caption second lineCaveat19px5000--ink-2
NoteCaveat22px / 1.15000--ink-2
Title pageCaveat58px / 16000--ink
Page numberLora12px4000.1em--ink-3

Captions counter-rotate at −1.4× the print angle, so the handwriting is not parallel to the print.

Motion

ThingTriggerPropertyFrom → toDurationEasingReduced motion
Leaf turn forwardnexttransformrotateY(0) → rotateY(-180deg)900ms--sheet1ms
Leaf turn backprevtransformrotateY(-180deg) → rotateY(0)900ms--sheet1ms
Leaf shadeduring turnopacity0 → 1 (50%) → 0900ms--sheetnone
Turning z-indexturn start/endz-index100+n while moving, stack value afterinstant—same
Buttonshoverbackground, border#00000026 → #efe8d814160ms--easenone

States

  • Hit areas: pointer cursor. Disabled at the ends (default cursor, no click).
  • Buttons: resting 1px #efe8d833 border, 44px circle. Hover lightens. Disabled at 35% opacity.
  • Focus-visible: 2px --ribbon outline, offset 3px.
  • Faces not in the open spread are aria-hidden="true".
  • No loading, empty or error state; the album always has 8 pages.

Accessibility

  • .book has aria-roledescription="photo album" and a label; each face is a section labelled “Page N”.
  • Photos are figure + figcaption; the caption is the accessible text. SVGs are decorative.
  • ← and → are global shortcuts; Previous/Next buttons are reachable by Tab, 44px.
  • The counter is aria-live="polite" and announces the new pages at the start of each turn.
  • Hidden faces are aria-hidden so the reader only hears the open spread.
  • Contrast: #efe8d8 on #1c1c1a ≈ 14:1. #b9b19f ≈ 8:1. #8a8578 page numbers ≈ 4.6:1.

Responsive rules

  • ≥1280: scale 1 at 800 high.
  • The book scales as a unit: --s = min(1, (innerWidth − 48) / 988, (innerHeight − 214) / 624), floor 0.3. The .fit box reserves the scaled size so nothing overlaps.
  • 1024 and 768: same spread, scaled down.
  • <640: the spread still shows at about 0.33. In a product, switch to one page at a time below 640 (each face full width, the turn becomes a horizontal slide) rather than going smaller.
  • Never let the book cause a horizontal scrollbar.

Acceptance checklist

Always

  • Each leaf is one element with two backface-hidden faces; the back face is pre-rotated 180°.
  • The turn rotates around the spine (transform-origin: 0 50%), with perspective on the parent.
  • A turning leaf is on top for the whole turn and the stack is correct afterwards.
  • ← / → keys, the buttons, and clicking a page all turn pages.
  • Previous is disabled on the first spread and Next on the last.
  • Every print has four photo corners from a single pseudo-element.
  • Captions are handwritten and sit under their print, never over another print or the page number.
  • Reduced motion makes the turn instant.
  • No image URLs; pictures are inline SVG.

This demo

  • Opens on pages 2–3: “Nell turns 7!” and “Watermelon contest” left, “The Wagon. 212,000 miles” and “Biscuit, refusing to swim” right.
  • Four leaves, eight pages, plus title page and end page.
  • Kicker “Album no. 3 · Summer 1986”, title “Kettle Pond”.
  • Turn is 900ms on cubic-bezier(.32,.72,0,1).
  • Corners #d3c8b0, 26px; print border 7px #f2ecdf.

Implementation notes

The leaf. Faces are absolutely stacked; only the leaf rotates.

.book { perspective: 2400px; }
.leaf { position: absolute; left: 480px; width: 480px; height: 600px;
  transform-style: preserve-3d; transform-origin: 0 50%;
  transition: transform var(--turn) var(--sheet); }
.leaf.turned { transform: rotateY(-180deg); }
.face { position: absolute; inset: 0; backface-visibility: hidden; }
.face.back { transform: rotateY(180deg); }

Z-order. Unturned leaves stack first-on-top; turned leaves stack last-on-top. While turning, lift the leaf above both.

function z() {
  leaves.forEach((l, i) => {
    if (!l.classList.contains('flipping')) l.style.zIndex = i < spread ? i + 1 : N * 2 - i;
  });
}
function go(d) {
  const t = spread + d; if (t < 0 || t > N) return;
  const leaf = leaves[d > 0 ? spread : spread - 1];
  spread = t;
  leaf.classList.add('flipping'); leaf.style.zIndex = 100 + (++busy);
  leaf.classList.toggle('turned', d > 0);
  setTimeout(() => { leaf.classList.remove('flipping'); busy--; z(); }, 920);
}

Photo corners. One pseudo-element, inset −6px, four triangles.

.print::after { content: ""; position: absolute; inset: -6px; --c: var(--corner);
  background:
    linear-gradient(135deg, var(--c) 0 50%, #0000 50%) top left / 26px 26px no-repeat,
    linear-gradient(225deg, var(--c) 0 50%, #0000 50%) top right / 26px 26px no-repeat,
    linear-gradient(45deg,  var(--c) 0 50%, #0000 50%) bottom left / 26px 26px no-repeat,
    linear-gradient(315deg, var(--c) 0 50%, #0000 50%) bottom right / 26px 26px no-repeat;
  filter: drop-shadow(0 1px 1px #0007); }

Common mistakes:

  • Applying the initial “turned” class from JS after first paint, so the album plays a turn on load. Put it in the markup.
  • Swapping z-index at the end of the turn only; the leaf slides under its neighbour halfway through.
  • overflow:hidden on the leaf; it flattens preserve-3d. Put it on the faces.
  • Rotating the captions with the print. They are written on the page, not on the photo.
  • White album pages. This album is black paper; the white pen only works on black.
  • Scaling the book with width percentages; scale the whole 960×600 unit so the 3D maths stays in pixels.

Rebuild order:

  1. Table, header, cover and two static pages.
  2. One leaf with two faces; get the turn right.
  3. Prints, corners, captions, notes on all eight faces.
  4. Stack order, queueing, end-of-album limits.
  5. Keyboard, counter, aria-hidden faces.
  6. Scale to fit.

Details

Palette
Type
Caveat · Lora
Motion
Rich motion
Build
Multi-step
Designed bySusan AcharyaKathmandu

Studied

Icon Museum

Let the icons be the only colour. Give each one a shelf, a lift on hover, and a quiet label.

All sources

On the shelves