Design Lounge
Install

Nº 432 of 520

Sunrise horizon background

Hard-edged sky and sea bands recolour through dawn while a sun disc rises over the horizon, driven by scroll, a time slider, or a slow autoplay.

Preview

Open full size
1280 × 800 · 12.0 KB · 179 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 "Sunrise horizon background" (piece background-sunrise-horizon) 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

213 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 full-frame sunrise for an editorial hero, shown here behind the cover of “Eastlight Almanac”, a quarterly journal about first light on far coasts. The sky is nine flat horizontal bands with hard edges (no smooth gradient), the sea is six more, and a 144px sun disc rises from behind a 1px horizon rule at 62% height. Every band’s colour is interpolated from five dawn keyframes (05:30 → 08:00), so moving the clock recolours the whole print at once. Scrolling the page moves the clock forward; scrolling back rewinds it. The detail worth copying is the banding: stepped colour reads as a screen-print or a travel poster, which is why dark serif type sits directly on it with no scrim.

Reference behaviour

  1. Initial state: clock 06:12, the sun is half above the horizon at x = 72%, warm orange bands near the horizon, pale slate-blue at the top.
  2. Autoplay runs on load: the clock advances 3.2 minutes per second (06:12 → 08:00 in ~34s). Bands cool toward pale blue and cream, the sun climbs and turns from #F26B2A to #FDE3A0.
  3. At 08:00 autoplay stops by itself and the button shows play. Pressing play at 08:00 restarts from 05:40.
  4. The time slider (05:30–08:00, step 1 minute) sets the clock directly and live; the large readout (“06:12”) and aria-valuetext follow.
  5. Scrolling: each pixel of scroll adds 90 / viewportHeight minutes (one full screen = 90 minutes), clamped to 05:30–08:00. Scrolling up rewinds. Slider and readout update.
  6. The sun’s centre sits at horizonY − altitude × skyHeight × 0.62, where altitude = (minutes − 370) / 100; it is clipped by the sky container so it rises from behind the horizon.
  7. Below the horizon a “glint” column of eight horizontal strokes (3px, widths 100% → 16% of 220px, 9px gaps) mirrors the sun. Its opacity is clamp((altitude + .25) × 1.6, 0, 1); each stroke shimmers with scaleX(.7 ↔ 1.12) over 2.6–3.8s.
  8. The nav ink switches to cream #F7F1E6 when the top band’s luminance is below 0.2 (before ~05:45), and back to ink #221D18 above it. The copy column always stays ink because its bands never go below luminance 0.22.
  9. The page scrolls to an “In this issue” section on paper #F5EFE4 that slides over the fixed sky.
  10. The loop is cancelled while the tab is hidden; it resumes at the same clock.
  11. Under prefers-reduced-motion: reduce autoplay is off (button shows play), shimmer stops, and all transitions drop to 1ms. Scroll and slider still recolour the scene, because they are user-driven.

Structure

1280 × 800   .scene fixed (aria-hidden) · .page scrolls on top
┌──────────────────────────────────────────────────────────────────────┐
│ Eastlight Almanac (italic)           Issues  Shore notes  Stockists  Subscribe │ band 0 18%
│──────────────────────────────────────────────────────────────────────│ band 1 15%
│ NO. 14 · SPRING 2027                                                  │ band 2 13%
│ Every morning arrives                                                 │ band 3 11%
│ in layers.                92px serif                                  │ band 4 10%
│ A quarterly journal…  20px serif, max 500px                           │ band 5  9%
│ [ Read issue 14 → ]                                    ( sun 144 )    │ band 6–8 8%
│━━━━━━━━━━━━━━━━━━━━━━━━━━━━ horizon 62% ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━│
│                                                       ═══════         │ sea 14
│                                                        ═════          │ sea 15…20
│                                         ┌ SUNRISE 06:12 ──●──── Scroll to… (II) ┐ │
└──────────────────────────────────────────────────────────────────────┘
then: section.issue (paper, 1px top rule): IN THIS ISSUE · 3 columns
  • .scene (position: fixed; inset: 0; overflow: hidden; pointer-events: none; aria-hidden): .sky (flex column, height 62%, overflow: hidden) holding nine <i> bands and .sun; .sea (flex column, top 62% to bottom) holding six <i> bands and .glint; .horizon-rule.
  • Band heights are flex ratios: sky 18 15 13 11 10 9 8 8 8, sea 14 15 16 17 18 20.
  • .page: <header class="hero"> (100vh, min 620px) with <nav aria-label="Main"> and .copy (p.kicker, h1 with <em>, p.sub, a.btn); then <section class="issue" aria-labelledby> with three <article>s (<time>, <h3>, <p>).
  • .ctl (role="group" aria-label="Time of day"): <label for="time">, <output>, input[type=range], hint span, play/pause button.

Tokens

:root {
  --paper: #f5efe4;       /* issue section, html background */
  --paper-ink: #221d18;
  --paper-2: #5e554b;     /* secondary text on paper */
  --rule: #ddd2c1;        /* hairlines, panel border */
  --ink: #221d18;         /* copy on the sky */
  --ink-light: #f7f1e6;   /* nav ink before ~05:45 */
  --accent: #c4471f;      /* slider thumb, story times */
  --panel: #fbf7f0;
  --serif: "Newsreader", Georgia, serif;
  --sans: "Schibsted Grotesk", system-ui, sans-serif;
  --horizon: 62%;
  --sun-size: 144px;
  --sun-x: 72%;
  --fs-display: 92px;
  --fs-sub: 20px;
  --fs-story: 28px;
  --fs-clock: 28px;
  --pad-x: 64px;
  --r: 2px;               /* button, panel: nearly square, print-like */
  --ease: cubic-bezier(.2,.7,.2,1);
  --t: 180ms;
  --t-ink: 600ms;         /* nav ink swap */
  --minutes-per-second: 3.2;
  --minutes-per-screen: 90;
}

Dawn keyframes (minute of day → top, mid, low/horizon, sun, water):

Timemintopmidlowsunwater
05:30330#4F5C76#B9B3B9#EFB08C#E2552A#3E4860
05:52352#7487A3#DCC0B6#F6A876#EC5F27#56637C
06:10370#9DB0C6#EDCDB9#F9B071#F26B2A#6E8098
06:50410#BCCDDC#F3DFCB#FBD496#F8A04A#89A0B3
08:00480#D3E0EA#F0F0EA#F7E8C8#FDE3A0#A7BCCB

Typography

RoleFamilySizeWeightLine-heightTrackingCase
MastheadNewsreader italic24px4001−0.01emTitle
HeadlineNewsreader (opsz auto)92px400, em italic0.98−0.025emsentence
SubNewsreader20px4001.50sentence
KickerSchibsted Grotesk12px6001.5+0.18emUPPERCASE
Nav / buttonSchibsted Grotesk14px400 / 5001.50 / +0.02emsentence
Clock readoutNewsreader28px40010tabular numerals
Panel labelSchibsted Grotesk11px6001+0.14emUPPERCASE
Story titleNewsreader28px4001.15−0.01emsentence
Story timeSchibsted Grotesk13px50010tabular, accent

Motion

ElementTriggerPropertyFrom → ToDurationEasingReduced motion
Clockautoplay (rAF, 30fps cap)minutescurrent → 4803.2 min/slinear timeoff at load
Bandsclock changebackground (inline rgb)keyframe interpolationper framepiecewise linearinstant on input
Sunclock changetransform: translateYbelow → above horizonper framelinear in minutesinstant
Sun colourclock changebackground#E2552A → #FDE3A0per frame—instant
Glint strokesCSS looptransform: scaleX.7 ↔ 1.122.6 / 3.2 / 3.8s alternate--easenone
Nav inkluminance thresholdcolorink ↔ cream600ms--ease1ms
Buttonhoverbackground, colortransparent → ink fill180ms--ease1ms

Band colour rule: for sky band i of 9, p = sqrt(i / 8); colour = mix(top, mid, p / .6) when p < .6, else mix(mid, low, (p − .6) / .4). The square root pushes the light mid tone up behind the headline. Sea band j of 6: mix(mix(low, mid, .35), water, .25 + .75 × j/5).

States

  • Playing: pause bars, aria-pressed="false", label “Pause the morning”; glint shimmers.
  • Paused / ended: play triangle, aria-pressed="true", label “Play the morning”; body.paused stops the glint animation.
  • Early (before ~05:45): nav turns cream; copy stays ink.
  • Slider focus: 2px outline in currentColor, offset 3px; thumb is a 16px accent dot with a 3px panel-coloured ring.
  • Button hover: fills with ink, text goes paper.
  • Panel button hover: border --rule → --paper-ink.
  • Hidden tab: loop cancelled.

Accessibility

  • The scene is decorative (aria-hidden="true", pointer-events: none).
  • The slider is a native range labelled “Sunrise”; aria-valuetext is the formatted clock (“06:12”), so screen readers don’t announce “372”.
  • The output readout is visible; it is not a live region (the slider already announces).
  • The play/pause button names the action; it satisfies WCAG 2.2.2.
  • Scroll-to-scrub is a bonus, never the only way: the slider covers the full range by keyboard (arrows ±1 min, Page Up/Down ±10%).
  • Contrast: ink #221D18 on the darkest band behind the copy (band 1 at 05:30, about #9A9CAA) is 6.1:1; on the horizon peach it is 9:1. Cream nav on #4F5C76 is 6.0:1.
  • Tab order: masthead → 4 nav links → Read issue 14 → slider → play button → (scroll) section content.

Responsive rules

  • ≥ 1280: as specified.
  • 1024–1279: headline 80px; sun stays at 72%.
  • 768–1023: headline 68px; stories become 2 columns + 1.
  • < 760: nav links hide, padding 20px, headline 54px, sub 18px; sun shrinks to 96px at x = 78%; stories stack; the hint text hides and the panel spans the bottom edge (12px insets) with the slider flexing.
  • Minutes per screen stays 90 at every height, so a phone scrolls the same morning.

Acceptance checklist

Always

  • Sky and sea are flat bands with hard edges, recoloured from keyframes; no CSS gradient on them.
  • One time value drives bands, sun position, sun colour, glint and nav ink.
  • The sun is clipped by the horizon (overflow: hidden on the sky).
  • Scroll down advances, scroll up rewinds, clamped to the slider range.
  • The slider has aria-valuetext in hh:mm.
  • Autoplay stops at the end of its range and can be replayed.
  • Loop capped at 30fps and cancelled while hidden.
  • Reduced motion: no autoplay, no shimmer; scroll and slider still work.
  • Copy contrast ≥ 4.5:1 at every time in range.

This demo

  • Nine sky bands 18 15 13 11 10 9 8 8 8, six sea bands, horizon at 62%.
  • Opens at 06:12 with the sun half risen at x = 72%.
  • Clock range 05:30–08:00; autoplay 3.2 min/s; one screen of scroll = 90 minutes.
  • Headline “Every morning arrives in layers.” at 92px Newsreader.
  • Panel reads “SUNRISE 06:12” with a 180px slider and “Scroll to move the morning”.

Implementation notes

Keyframe interpolation. Find the bracketing pair, mix each channel:

function stops(m) {
  let i = 0; while (i < K.length - 2 && m > K[i + 1][0]) i++;
  const a = K[i], b = K[i + 1];
  const f = Math.max(0, Math.min(1, (m - a[0]) / (b[0] - a[0])));
  return [1, 2, 3, 4, 5].map(j => mix(hex(a[j]), hex(b[j]), f));   // top, mid, low, sun, water
}

Scroll as a delta, not a position. Mapping scrollY straight to time breaks the slider (the next scroll would snap back). Add the delta instead:

let lastY = scrollY;
addEventListener('scroll', () => {
  const d = scrollY - lastY; lastY = scrollY;
  minutes = Math.max(330, Math.min(480, minutes + d / innerHeight * 90));
  if (!raf) render();
}, { passive: true });

Sun rising from behind the horizon. Put the sun inside the sky container, which clips it:

.sky { position: absolute; inset: 0 0 auto 0; height: var(--horizon); overflow: hidden; display: flex; flex-direction: column; }
.sky i { flex: var(--h) 0 0; }
.sun { position: absolute; left: 72%; top: 0; width: 144px; height: 144px; margin: -72px 0 0 -72px; border-radius: 50%; }
/* JS: sun.style.transform = `translate3d(0, ${H - alt * H * .62}px, 0)` */

Common mistakes:

  • A smooth linear-gradient sky. It reads as a generic sunset wallpaper; the hard bands are the piece.
  • Letting the earliest keyframe go navy behind the headline, then needing a scrim. Keep the copy bands light and switch only the nav.
  • Driving the sun with CSS keyframes and the bands with JS; they drift apart. One clock.
  • Running the clock with Date.now() and no clamp; returning from a hidden tab jumps the sun.
  • Putting the scene inside the scrolling content; it must be position: fixed so the issue section slides over it.

Details

Palette
Type
Newsreader · Schibsted Grotesk
Motion
Subtle motion
Build
One session
Designed bySusan AcharyaKathmandu

Studied

Susan Acharya

Draw one scene and let it move a little. The words stay calm on top.

All sources