Design Lounge
Install

Nº 326 of 520

Outline name marquee

Four rows of huge outlined client names drift in alternating directions. Hover or arrow to a name to fill it and caption the project; a ring badge spins.

Preview

Open full size
1280 × 800 · 10.3 KB · 191 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 "Outline name marquee" (piece logos-outline-name-marquee) 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

189 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

Studied from moremedia.at: the “our clients” wall where the names themselves are the logos, set huge in outline type and drifting in rows that alternate direction, plus the slowly rotating circular-text badge used as a link. This rebuild is for Ombra, a fictional design studio, and flips it to a warm plum-black page with cream outlines. Hovering a name fills it solid apricot, pauses its row, and a caption under the rows says what the studio made for that client and since when. The detail worth copying is that the wall stops being a brag and becomes an index: each name answers “what did you do for them?” without leaving the section.

Reference behaviour

  1. First frame: eyebrow “SELECTED CLIENTS · 2014–2026”, a 52px serif heading “Some of the people we have made things with” (the two words in apricot italic), and a 15px note on the right.
  2. Four 92px rows of 70px outlined names (Hanken Grotesk 800, 1px cream stroke at 62%, transparent fill) separated by 10px ring dots with 34px margins on each side.
  3. Rows 1 and 3 drift left; rows 2 and 4 drift right. Durations 64s, 76s, 58s, 70s, linear, infinite.
  4. “Ivelle Ferries” (row 2) starts filled apricot, and the caption reads “Ivelle Ferries — Timetables, booking and onboard signs” with “Since 2021 · row 2 of 4”.
  5. Hover any name: the previous fill clears, this name (and its clone) fills apricot over 200ms, the caption updates. The hovered row pauses in place; other rows keep moving.
  6. Leave the row: it resumes from the same offset. The last name stays filled.
  7. Tab focuses a row (2px apricot outline inset). Focus pauses it and selects its first name unless one in that row is already selected. ←/→ step through that row’s names (wrapping), Home/End jump.
  8. Bottom right: a 124px ring badge “ALL WORK · ALL WORK · ALL WORK ·” rotating once every 22s around a ↗ arrow. Hover/focus turns the arrow 45° and apricot; the ring text turns apricot on hover.
  9. Reduced motion: rows and badge stop; the clone set hides; names still fill and caption on hover and arrows.

Structure

1280 × 800
┌────────────────────────────────────────────────────────────────────────┐
│ SELECTED CLIENTS · 2014–2026                       64 companies, …     │ pad 44/64
│ Some of the people we have                         (15px, 30ch)        │
│ made things with           (52px serif)                                │
│                                                                        │
│ …mmer Opera  ○  Pellucid Optics  ○  Wendle Da…          ← 64s          │ 92
│ ○ [Ivelle Ferries]  ○  Mossgiel Hotels  ○  Oberla…       → 76s          │ 92
│ …he & Linden  ○  Ostwick Coffee  ○  Pelham Cli…          ← 58s          │ 92
│ … ○  The Glass Archive  ○  Stillwell Trust  ○  Oak…      → 70s          │ 92
│ ────────────────────────────────────────────────────────────────────── │ 1px
│ ● NOW SHOWING                                              ╭ALL WORK╮  │
│ Ivelle Ferries — Timetables, booking and onboard signs     │   ↗    │  │ 124
│ Since 2021 · row 2 of 4                                    ╰────────╯  │
└────────────────────────────────────────────────────────────────────────┘
  • section[aria-labelledby] > .top (eyebrow, h2, note), .rows, .bottom, and a visually hidden ul.sr client list.
  • Each .row is div[role=group][tabindex=0] with an aria-label naming its number and direction. Inside: .track > two .sets (both aria-hidden; the hidden list carries the names).
  • Each .set: span.name[data-k="row-index"] + span.dot, repeated.
  • .bottom: .now[aria-live=polite] (key, value, year line) and a.badge holding a ring SVG with textPath and an arrow SVG.

Tokens

:root {
  --bg: #191517;                          /* warm plum-black */
  --cream: #eadfcb;                       /* headings, caption, ring text */
  --cream-2: #b3a895;                     /* eyebrow, note, year line */
  --stroke: rgba(234, 223, 203, 0.62);    /* outline names, dots */
  --line: rgba(234, 223, 203, 0.14);      /* rule above caption */
  --apricot: #ff9f6b;                     /* filled name, italic words, focus */

  --sans: "Hanken Grotesk", system-ui, sans-serif;
  --serif: "Instrument Serif", Georgia, serif;

  --row-h: 92px;
  --name: 70px;
  --dot: 10px;
  --dot-gap: 34px;
  --badge: 124px;
  --pad-x: 64px;

  --d1: 64s; --d2: 76s; --d3: 58s; --d4: 70s;
  --spin: 22s;
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  --expo: cubic-bezier(0.16, 1, 0.3, 1);
}

Typography

RoleFamilySizeWeightLine-heightTrackingNotes
Eyebrow / caption keyHanken Grotesk11px60010.18emUPPER
HeadingInstrument Serif52px4001.02−0.01emmax 20ch, 2 lines; accent words italic
NoteHanken Grotesk15px400 (lead 600)1.50max 30ch
Client nameHanken Grotesk70px8001−0.035emoutline only, 1px stroke
CaptionInstrument Serif28px4001.20name roman, project italic
Year lineHanken Grotesk14px4001.50—
Ring textHanken Grotesk10.5px60010.32emUPPER, on a 48px-radius path

Motion

ThingTriggerPropertyFrom → ToDurationEasingReduced motion
Row 1 / 3 trackloadtranslateX0 → −50%64s / 58slinear, infinitestatic, clone hidden
Row 2 / 4 trackloadtranslateX−50% → 0 (reverse)76s / 70slinear, infinitestatic, clone hidden
Trackrow hover / focusanimation-play-staterunning → paused0——
Nameselectcolor, stroke colourtransparent / cream 62% → apricot200msstandardinstant
Ringloadrotate0 → 360°22slinear, infinitestatic
Badge arrowhover / focusrotate, colour0 → 45°, cream → apricot350msexpo outinstant

The four durations are deliberately unrelated so the rows never line up into a grid.

States

  • Name rest: outline only.
  • Name selected: solid apricot fill and stroke. Exactly one name (plus its clone) is selected at any time.
  • Row hover / focus: paused. Focus-visible: 2px apricot outline, inset 2px.
  • Badge rest: spinning ring, cream arrow. Hover: arrow 45° apricot, ring text apricot. Focus-visible: 2px apricot ring, 2px offset, round.
  • Caption always shows the selected client; it is never empty.

Accessibility

  • The moving sets are aria-hidden. A visually hidden ul lists every client as “Name: project, since year”, so screen readers get a calm list, not a ticker.
  • Rows are focusable groups with labels such as “Client row 2 of 4, scrolling right. Arrow keys read names.” Focus pauses the row (:focus and :focus-within).
  • Keys on a focused row: ← previous, → next (wrap), Home first, End last.
  • The caption is aria-live="polite", so stepping names announces them.
  • The badge is a link with aria-label="See all work"; both SVGs are aria-hidden.
  • Contrast: #EADFCB on #191517 ≈ 14:1; #B3A895 ≈ 7.8:1; #FF9F6B ≈ 8.9:1. Outlined names are large display type; the hidden list and caption carry the same information at full contrast.
  • Motion never exceeds one row-width per minute and stops on hover, focus, or reduced motion.

Responsive rules

  • ≥ 1280: as specified.
  • 1024–1100: horizontal padding 32px; heading 44px.
  • 768–1023: same; keep four rows.
  • < 760: top block stacks (heading 36px, note below), rows 64px with 46px names and 20px dot gaps, caption 22px, badge 96px.
  • Reduced motion: each row shows its first names from the left edge and clips at the right. Do not wrap names onto a second line.
  • At 375px: no horizontal scroll (rows clip with overflow: hidden).

Acceptance checklist

Always

  • Client names are set in type, outlined, very large — no logo images.
  • Four rows, alternating direction, each with its own unrelated duration.
  • One name is selected at load and the caption already describes it.
  • Hovering a name fills it and its clone and updates the caption; the hovered row pauses and resumes from the same offset.
  • Rows are keyboard focusable; arrows step through names; the caption is a polite live region.
  • The moving sets are aria-hidden and a hidden list carries the names once.
  • Circular text badge links onward and rotates slowly; it stops under reduced motion.
  • Reduced motion stops all drifting and hides the clone set.
  • One accent colour for fill, accent words and focus.
  • No horizontal scroll at 375px.

This demo

  • 24 fictional clients, six per row; row 2 starts with “Ivelle Ferries” filled.
  • Durations 64s, 76s, 58s, 70s; ring 22s.
  • Names 70px Hanken Grotesk 800 with a 1px rgba(234,223,203,.62) stroke on #191517.
  • Fill colour #FF9F6B; heading and caption in Instrument Serif.

Implementation notes

Outline type and fill. Use a transparent fill with -webkit-text-stroke; transition both colour properties. Stroke at 1px — 2px turns 70px type into a blob.

.name { font: 800 70px/1 var(--sans); letter-spacing: -.035em; white-space: nowrap;
  color: transparent; -webkit-text-stroke: 1px var(--stroke);
  transition: color .2s var(--ease), -webkit-text-stroke-color .2s var(--ease); }
.name.on { color: var(--apricot); -webkit-text-stroke-color: var(--apricot); }

Closed loop, reversed rows. Two identical sets in one track, animate to −50%. Reverse rows with animation-direction: reverse rather than a second keyframe, and pause with animation-play-state so leaving the row does not snap it.

.track { display: flex; width: max-content; animation: marq var(--d) linear infinite; }
.row.rev .track { animation-direction: reverse; }
.row:hover .track, .row:focus .track { animation-play-state: paused; }
@keyframes marq { to { transform: translateX(-50%); } }

Select by key, not by element. Every name carries data-k="row-index", so one call fills the original and its clone, whichever the pointer happened to touch:

function show(r, i) {
  document.querySelectorAll('.name.on').forEach(e => e.classList.remove('on'));
  document.querySelectorAll(`.name[data-k="${r}-${i}"]`).forEach(e => e.classList.add('on'));
  // write caption: name roman, " — " + project italic; "Since " + year
}

Use one delegated mouseover on each track instead of a listener per name.

Ring text. One circle path, one textPath; repeat the phrase three times so it closes the circle at 10.5px with 0.32em tracking on a 48px radius. Rotate the SVG, not the arrow.

Common mistakes: solid grey names (loses the outline idea); all rows the same direction and speed; filling the name only in one of the two copies, so it flickers as the loop wraps; restarting the animation on mouseleave; reading the ticker to screen readers twice; making every name a tab stop (24+ tab stops for a logo wall).

Details

Palette
Type
Hanken Grotesk · Instrument Serif
Motion
Rich motion
Build
One session
Designed bySusan AcharyaKathmandu

Studied

Moremedia

Set the services as tall labels. Open one, and keep the rest as type.

All sources