01 , foundations and brand

Foundations

The parts everything else is made of: the mark, the colour, the type, the space, the motion and the pattern. UNIÓN, Beckenham Place Park, Friday 9 July 2027. Every value on this page is read live from css/tokens.css, so what you see here is what a component gets.

F1 The idea of the identity "What am I actually looking at?" from the source mockups
One

The U and the N are joined. The wordmark is drawn as one continuous inflated stroke, so the first and last letters close the word into a loop rather than ending it.

Two

The Ó is a character. The accented O steps out of the word, puts on headphones and dances. It is the only figure in the system.

Three

The U, the I and the N, cut from the wordmark and stacked, make the soundwave. It is drawn from the same letterforms as everything else, so it is the icon, and it also works as a meter, a pin, a bullet and an equaliser.

Anatomy, tokens, behaviour, a11y, markup

Anatomy

Three observations, not three claims. Each one is shown with the real asset beside it, so a reader can check the statement against the drawing.

Tokens used

  • .t-micro for the numbering
  • --s6 grid gap

When NOT to use

This is documentation, not a component. Nothing on a live surface says what the identity means; it just is it.

F2 The marks and their grounds "Which lockup goes on this colour?" from the source mockups

Every real mark, on the ground it is drawn for

Horizontal primary. Black on cream, coral or yellow. The default.
Portrait stack, black on pink. The poster lockup.
Portrait stack, cream on coral.
Portrait stack on black, recoloured cream. Dark grounds only.
Outline cut. A drawn line, for a single-colour print or an emboss.
The sticker. A die-cut object, not a lockup. PNG only.

Clear space , one cap height of the Ó on every side, and nothing inside it


Minimum sizes, shown at their real size

Horizontal, 120px. Screen floor.
Stack, 74px wide. Screen floor.
UNIÓN London sticker, black on cream Sticker, 150px. Below this LONDON closes up.
Misuse. Each one is shown wrong, on purpose.
Never stretched. Width is a drawn thing, not a scale handle.
UNIÓN London sticker, black on yellow
The sticker is not the mark. It is a printed object with its own cut edge.
UNIÓN outlined cream and pink lockup
The outlined cream and pink cut is a sticker treatment, never the lockup.
UNION
Never without the accent. UNIÓN, every time, everywhere.
Never the mascot on black. Its body is that colour and it disappears.
Anatomy, tokens, behaviour, a11y, markup

Anatomy

Two lockups (horizontal and portrait stack), one outline cut, one sticker. The stack exists in four ground pairings; the horizontal in one, recoloured by the ground.

Ground pairings

  • Black mark on cream, pink, coral or yellow
  • Cream mark on black, coral or pink
  • Never a cream or yellow mark on cream, coral or pink

Asset trap, read this

The four UNION_STICKER-*.svg files and UNION_LOGO-horizontal-cream-pink-outline.svg are BROKEN: the stickers carry a hole through the field and the outlined cut is cropped. They have been deleted from assets/svg/. Use the 4x PNGs in assets/img/brand/ through an <img> with width, height and alt. <u-svg> logs an error if you ask it for one.

Accessibility

A mark that carries meaning gets real alt text naming the festival. A mark that repeats a heading beside it is decorative and gets alt="". <u-svg> marks its own SVG aria-hidden.

When NOT to use

Never set the wordmark in Archivo. It is a drawing, not type; there is no font that produces it. Where the word must be live text, it is set in the display voice and is not the mark.

Copy this

<u-svg src="UNION_LOGO-horizontal-primary"></u-svg>
<u-svg src="UNION_LOGO-stack-portrait-black" fill="var(--cream)"></u-svg>

<!-- the sticker and the outlined cut are PNGs, never <u-svg> -->
<img class="d-sticker" style="--mark:240px"
     src="assets/img/brand/UNION_STICKER-union-london-black-on-pink@4x.png"
     width="1125" height="654" alt="UNIÓN London sticker">
F3 The soundwave icon "Is that a logo or an icon?" from the source mockups

Three paths, three bars. The system's only icon, because it is the only one the identity actually draws. Never stretched to fit a space: it repeats at its native proportion instead.

120px, the poster scale
64px, in a control
40px, the floor
22px, illegible. Do not.
Stretched. Do not.

The four supplied colourways

Anatomy, tokens, behaviour, a11y, markup

Anatomy

Three separate paths, so each bar can be animated independently. That is what makes it a working equaliser rather than a picture of one.

Behaviour

As a meter or equaliser it is the .d-wave device, documented on 02 The seven devices. Verb: BOUNCE.

When NOT to use

Below 40px, and never stretched on either axis. Where a small mark is needed, repeat the glyph at native proportion or use a drawn rule instead.

Copy this

<u-svg src="icon:black"></u-svg>
<u-svg src="icon:black" fill="var(--cream)"></u-svg>
F4 The soundwave, moving "Is this thing live?"

The soundwave's animation is part of the identity, not a decoration applied to it. The icon is three drawn paths, the U, the I and the N cut and set side by side at three different heights, so each is a squiggle rather than a bar. Measured off the artwork: the strokes sit at x 0, 110 and 168 in a 327 by 198 box, which is why each one pivots on its OWN CENTRE and not on a common floor. Scaling them from the bottom flattens the drawing into a smear. The glyph's overall box is never scaled, X is left alone, and the amplitudes stay small, so it breathes rather than stretches. Five modes, one API: UNION.wave(el, mode).

intro

The three bars stand up one after another, on the bounce ease. The logo's load animation.

eq

A continuous equaliser. Each bar scales on Y from its own baseline at its own tempo, squashy rather than linear, so the three never lock into one pulse. Runs only while something is genuinely playing.

pulse

A slow live-light breath, for an alert bar. Says "this is live" without asking to be watched.

meter

The bars fill to a value from 0 to 1 and hold, reading at slightly different heights the way a real level meter does. UNION.wave(el, 'meter', {value:.7})

rest

Everything stops at a readable level, never flat: a flat wave reads as broken. This is also what every mode becomes under prefers-reduced-motion.


The 40px floor still holds when it moves. Below it the three bars merge and the motion reads as a flicker.

120px
64px
40px, the floor
Anatomy, tokens, behaviour, a11y, markup

The API

UNION.wave(el, mode, opts) where el is the <u-svg> and mode is one of 'intro' 'eq' 'pulse' 'meter' 'rest'. It returns the GSAP timeline, and calling it again on the same element replaces whatever was running. UNION.wave.stop(el) kills it.

How it transforms

Each path gets transform-box:fill-box and transform-origin:50% 50%, so it pivots on its own centre inside its own fill box. The paths are sorted left to right by bounding box first, because paint order is not reading order and a stagger should run across the glyph the way an eye does.

Only scaleY moves, and only between about.58 and 1.3. Rest is exactly 1, the glyph as drawn. Past roughly 1.3 the strokes stop reading as the mark.

Reduced motion

Every mode resolves to rest: the bars sit at a readable, uneven level and nothing loops.

In the chrome

The small soundwave beside the sidebar wordmark runs intro on load and eq while the brand block is hovered, so the documentation itself uses the identity's own animation.

When NOT to use

Below 40px. On anything with no level to report: eq on a static page is the icon pretending. Use rest and let it simply be the mark.

Copy this

<u-svg id="wave" src="icon:black"></u-svg>

UNION.wave(wave, 'intro');                  // the load animation
UNION.wave(wave, 'eq');                     // playing
UNION.wave(wave, 'pulse');                  // a live alert
UNION.wave(wave, 'meter', {value:.7});      // a level, 0..1
UNION.wave(wave, 'rest');                   // stop, at a readable level
UNION.wave.stop(wave);
F5 The mascot, the Ó "Who is the little dancing figure?" from the source mockups

Large or not at all

It is a character with a body and a gesture, not a glyph. At icon size the headphones close up and the accent reads as a smudge, so the system gives it a floor of 150px and no ceiling. One per surface.

Floor, 150px
Dancing (verb: bounce)
Its grounds. It never sits on black.
Do
Pink, coral or cream. Not yellow either: the headphones are yellow and vanish. Its body is the warm near-black, so a light ground is what makes it pop.
Do not
Never on black. It IS that colour, so it vanishes into it and only the headphones and the accent survive.
Anatomy, tokens, behaviour, a11y, markup

Tokens used

  • .d-mascot with --sm or --lg
  • --dur-bounce / --ease-verb-bounce

Behaviour

Verb: BOUNCE, because it dances. .is-dancing loops; it stops under prefers-reduced-motion and it stands still.

Its grounds

Pink, coral, cream. Never black, never yellow (the headphones are yellow and vanish). Its body is the warm near-black, so on a black ground it disappears and all that is left is a pair of headphones and a floating accent. There is deliberately no dark-ground modifier on .d-mascot.

When NOT to use

Never on black. Never on a form field, never as a bullet, never repeated across a grid, never below 150px. It is the one place the brand is allowed to be charming, so spending it on decoration wastes it.

Copy this

<u-svg class="d-mascot" src="UNION_MASCOT-o-acute-dancing-headphones"></u-svg>
F6 Colour, stages and the contrast matrix "Can I read this in a field at six in the evening?" from the source mockups
Pink--pinkThe primary. The call to action, the label bar, Main Stage.
Coral--coralThe Grove. The second accent, never the CTA.
Yellow--yellowThe Corner, and every welfare and safety surface.
Cream--creamThe ground. Never white.
Black--blackThe ink and the slab. Never #000; it is a warm brown-black.

The stage system. [data-stage] sets --stage for everything inside it.

Main StagePink. --stage-main
The GroveCoral. --stage-grove
The CornerYellow. --stage-corner

Measured contrast, computed in the browser from the tokens. Three verdicts: any text, display only, never. Only the never pairs are struck through.

Text on groundSampleRatioBody 17pxDisplay 44px+Verdict
Anatomy, tokens, behaviour, a11y, markup

The rule, in three verdicts

  • Any text. Black on cream, pink, coral or yellow. Cream on black.
  • Display only. Cream on pink (3.16:1) and cream on coral (2.27:1), which are brand pairs used on the stack logo, the tote and the stickers. Also pink or yellow on black. These carry marks, .t-mega, .t-display, .t-head and button labels at 18px bold and up. Never .t-body, .t-small or .t-micro.
  • Never. Yellow or cream on cream. Yellow on pink or coral. Pink on coral.

What "display only" actually forbids

Information someone NEEDS never goes on a display pair: set times, prices, directions, gate numbers, welfare and safety. Those are always black on a light ground, or cream on black. The display pairs are for the mark and the shout, which is what they do beautifully in the mockups.

Tokens: --on-pink-display and --on-coral-display, both cream, so a component states that it knows it is using a display pair.

How this table is built

JavaScript reads the five colour tokens off the computed style, converts to relative luminance and computes the WCAG ratio. Nothing is typed in, so the table cannot drift from tokens.css.

Hairlines

A hairline may use black at 25% alpha (--hairline). That is a rule weight, not a sixth colour, and it never carries text.

When NOT to use

No tints, no shades, no sixth colour, no gradient between two of them. If a surface needs another value, it needs a different composition.

F7 Type, every voice "Which size is this line?" from the source mockups

Archivo variable, one family, two axes. Width is an axis (font-stretch), never a separate family: there is no "Archivo Expanded" in this system. Latin-ext is loaded so the Ó is real.

UNIÓN
.t-mega 900 · width 62% · caps
clamp(72px,14vw,220px) · lh.82
The one-word statement. One per surface, or none.
Music. People.
Together.
.t-display 900 · width 62% · caps
clamp(44px,9vw,150px) · lh.85
The poster headline. The workhorse of every hero.
Beckenham Place Park
.t-head 900 · width 62% · caps
clamp(26px,3.4vw,52px) · lh.9
Section headings and slab titles.
Doors 2:30pm
.t-title 900 · width 62% · caps · 22px · lh 1
A component's own title. Fixed, not fluid.
Set times
.t-wide 800 · width 125% · caps
clamp(28px,5vw,84px)
The one WIDE voice, from the SET TIMES poster. Used where the headline must fill a measure rather than shout.
Music · people · culture · together
.t-micro 700 · 11px · tracking.18em · caps
The poster's small label. Corner marks, captions, field labels.

A festival in south London parkland, ninety-six acres of it, for 11,500 people across three stages. Doors 2:30pm, curfew 10:30pm. Lineup to confirm.

.t-body / .t-small 400 · 17px / 1.55 · max 66ch
.t-small 14px
The only two sizes body text comes in.

The Ó, at every weight the system uses

Ó Ó Ó Ó Ó

The 10x ratio. Display to body, at the same scale the posters run it.

9 July

Beckenham Place Park, London. Doors 2:30pm, curfew 10:30pm. 11,500 capacity. Three stages.

Anatomy, tokens, behaviour, a11y, markup

Tokens used

  • --font, --wdth-condensed, --wdth-wide
  • --size-mega through --size-small
  • --track-micro

The ratio

The posters run roughly ten times the body size for the headline. A page where the headline is only two or three times the body has drifted into a UI kit, whatever the colours say.

Accessibility

Every voice is set in rem-safe clamps and none is below 11px. All-caps display text gets its real casing in the markup where it is read out, so a screen reader does not spell it.

When NOT to use

Never .t-mega twice on one surface. Never .t-wide for a paragraph. Never a script or handwriting font: the aside is a drawing.

Copy this

<h1 class="t-display">Music. People. Together.</h1>
<span class="t-micro">Music · people · culture · together</span>
<p class="t-body">…</p>
F8 Space, shape, rules and focus "How much room does this get?" from the source mockups

Space , nine steps. Nothing between them.

s1 · 4
s2 · 8
s3 · 12
s4 · 16
s5 · 24
s6 · 32
s7 · 48
s8 · 72
s9 · 112

Shape , three radii, and each one belongs to a named kind of object.

0 · slab, label, totem bar, stub
10px · reserved, unused
round · ribbon cap, sticker, the one pill

Rules and hairlines

Rule, 2px
Slab, 3px
Hairline

Focus , 3px solid, offset 3px. Yellow on a dark ground.

Anatomy, tokens, behaviour, a11y, markup

Tokens used

  • --s1 to --s9, --gutter
  • --radius-slab / --radius-card / --radius-round
  • --rule, --rule-heavy, --hairline
  • --focus-width, --focus-offset, --tap-min

Breakpoints

390 phone · 768 tablet · 1100 the sidebar threshold · 1440 the desktop shot width. They live as comments in tokens.css, because a custom property cannot be used inside a media query.

When NOT to use

No uniform radius across everything: that is the single loudest AI tell, and the one the mockups never commit. A square thing stays square.

F9 Motion, the eight verbs "Why did that move?"

One verb per component, chosen because the object DOES that. A stub tears because a stub tears. Bars bounce because they are a meter. Nothing fades in on scroll just to arrive. Press Replay on any of them.

Bounce

The object arrives and settles on its own springiness. --dur-bounce 420ms · --ease-bounce

Squash
Press

The bubble lettering's own compression, scaleY.6 to 1. The brand's core gesture. --dur-squash 360ms

Tear
Ticket Stub

Rotate and translate ALONG the perforation, the way a stub comes off. --dur-tear 520ms

Stamp
Sold out

Scale 1.6 to 1 with a rotation and an overshoot, like a rubber stamp landing. --dur-stamp 260ms

Draw

Reveal ALONG the length, the way a stroke is drawn. --dur-draw 700ms · --ease-out

Flood
Tag The slab

Colour spreads OUT from the tag that names it, by clip-path. --dur-flood 380ms

Drop
Lineup Set times Tickets

From above, on a bounce, the way the signage flags fall in. --dur-drop 480ms

Swing
Main stage →

Rotate on a FIXING, elastic, the way a fingerpost blade hangs. --dur-swing 900ms

Anatomy, tokens, behaviour, a11y, markup

The API

Every verb is UNION.motion.<verb>(element, options). Each reads its duration and easing from tokens.css, returns the GSAP tween so it can be chained, and honours prefers-reduced-motion by jumping to the final state with no motion.

Reduced motion

Every component shows its final state and any auto-running loop stops: marquee, equaliser, river drift, the mascot's dance. Marquees otherwise run slow, 90 to 120 seconds a loop (--dur-marquee).

When NOT to use

Never two verbs on one component. Never a fade as the mechanism: a fade is what you reach for when you have not decided what the thing does.

Copy this

UNION.motion.tear(stub);
UNION.motion.drop(flags, {stagger:.06});
UNION.motion.flood(slabFill, {from:'left'});
UNION.motion.draw(pathEl);            // an SVG path draws along its length
UNION.motion.swing(blade, {origin:'50% 0%'});
F10 Pattern, a work in progress "Why does this festival look like this?" from the source mockups

This is not solved. The pattern work across the project is still falling short of the mockups, and what follows is offered as observation rather than law. It is judged by eye against the source mockups and by measuring, not by rule. Wide drawn pattern artwork may be supplied, which is why the .pattern-slot below takes its image from a variable: new artwork drops in without a rebuild.

In the meantime: use pattern sparingly. A component that works without it goes without it. Most sections on these two pages carry none at all, on purpose.

The preferred treatment , the river seen through a window that fades at its edges (the waiting room). This is the approved treatment. Prefer a fade into the ground over any hard pattern edge.

The strokes overlap into one mass along the track, each at its own scale and angle, the ground breaking through between them. The window is taller than the tallest stroke, so nothing is cut top or bottom, and both ends dissolve. Nothing stops on a straight line.


What has been measured so far. Observations, beside what the mockups actually do. Where our render and the mockup disagree, the mockup wins.

Observed , a band
Strokes stepped tight down one diagonal, overlapping, ground breaking through between them, ends running off the edge. This is the closest our generated river gets to the poster.
The mockup it is measured against UNIÓN poster mockup, pattern band across the foot
The drawn band. Note how much fatter and more continuous the mass is, and how little of it there is relative to the page.
Observed , spread too evenly
The same strokes spread across the whole box. No ground breaks through, so it stops being a drawn thing and becomes a texture. This is the failure mode our generator falls into most easily.
Observed , a hard cut
Strokes ending on a straight line inside the frame. This is what the fading window above exists to avoid.

Measured so far, all of it provisional: a river runs 12 to 28 strokes, each 9 to 14% of its host's width, stepped tight down one diagonal. Ink budget somewhere around 7 to 20% of a composition. A ribbon bleeds off an edge or ends in its own round cap. Pattern does not sit behind live text. The three UNION_PATTERN tiles are not seamless, so they are not tiled. Pattern all over is fine as the SKIN of a physical object, which is where the mockups use it most: the cup, the wristband, the lanyard, the flag.


The ribbon library. Each stroke at its own proportion, on the ground it is drawn for. The p3 cream strokes (06, 07, 10, 15, 16) are for dark grounds only.

Anatomy, tokens, behaviour, a11y, markup

.pattern-slot , the swappable band

One element whose pattern comes from either a CSS variable or a UNION.river() call, with the edge fade applied as a mask. When wide drawn artwork is supplied it drops straight in:

  • --pattern-image any image, repeated along X at its own height
  • --pattern-height the band's height, default 150px (measured on K4)
  • --pattern-fade how far the fade reaches in from each edge, default 10%
  • --y fades on the Y axis instead, for a tall band
  • .pattern-slot__run the K4 stroke run, for the generated form

Because the slot owns the fade, a lane never has to decide how a pattern band ends. That was the decision most often got wrong.

The river API

UNION.river(host, opts) generates a river. It is not markup, so it cannot be pasted in as a decorative block. Each stroke is a .rv-s carrying --i and --n for staggered motion.

Tokens (provisional)

  • --ink-budget-min 7% / --ink-budget-max 20%
  • --river-stroke-min 9% / --river-stroke-max 14% of the HOST's width
  • --river-count-min 12 / --river-count-max 28

These record what has been measured, not a settled standard.

Measured notes

  • size is a fraction of the HOST, not the page. Set it per breakpoint.
  • A river needs WIDTH. Wide and shallow reads as a river; narrow and tall reads as a blob, whatever the count.
  • On a dark ground the same settings read thinner: raise count, tighten spread.
  • fade over a dark ground reads as a grey glow, a banned tell. Prefer bleeding off the host edge.

The one place it goes all over

As the SKIN of a physical object: a cup, a wristband, a lanyard, a bag, a bottle, a flag. That is a thing wearing the pattern, not a sample of it, and it is where the mockups use pattern most confidently. See the applications gallery below.

Still weak

The generated river reads thinner and more scattered than the drawn bands, which are a fatter, more continuous mass. Held next to the mockup sheets the difference is obvious. Until that closes, prefer the fading window, use less than feels right, and leave a section without pattern rather than give it a weak one.

Copy this

<!-- drawn artwork drops straight in -->
<div class="pattern-slot" style="--pattern-image:url(assets/img/band.png)"></div>

<!-- or the generated form, the K4 run inside the same fading window -->
<div class="pattern-slot">
  <span class="pattern-slot__run" aria-hidden="true">
    <u-svg src="ribbon:p3-15" fill="var(--pink)"></u-svg>
    <u-svg src="ribbon:p3-06" fill="var(--coral)"></u-svg>
  </span>
</div>

UNION.river(host, {
  from:[-.08,.86], to:[1.08,.14],   // go past 0/1 so the ends bleed off
  count:18, size:.12, spread:.06,   // size is a fraction of the HOST's width
  dark:false, seed:7
});
F11 Voice and placeholders "What do we say when nothing is confirmed?" from the source mockups

The register

Plain, warm, declarative. British English. The festival talks like someone who has run one before, not like a brand launching one.

No em dashes, anywhere, in copy or in code comments. Commas, semicolons, full stops and brackets do the work.

Never

No hype, no "curated", no "elevated", no "iconic", no empty intensifiers, nothing that reads as a press release. No exclamation marks on a practical instruction.

Care surfaces

Welfare, lost child, first aid and accessibility drop the brand voice entirely. Black on yellow, numbered steps, the phone number last and biggest. Plain sentences. The brand steps back because being found matters more than sounding like us.


The only placeholders. Nothing is invented: no artist, no price, no sponsor, no phone number, no URL.

ARTIST NAME HERE HEADLINER HERE to confirm £ to confirm

The fixed facts. These are the only facts the system knows.

  • UNIÓN, accent on the Ó, always
  • Friday 9 July 2027
  • Beckenham Place Park, London
  • 11,500 capacity
  • Main Stage, The Grove, The Corner
  • Doors 2:30pm, curfew 10:30pm
  • Rail: Beckenham Hill, Beckenham Junction
Anatomy, tokens, behaviour, a11y, markup

Why placeholders are visible

A placeholder is set in the same type as the real thing would be, at the same size, so a layout can be judged before the lineup exists. It is never greeked and never hidden.

When NOT to use

Never a plausible-looking fake. A made-up artist name in a mockup gets quoted back as real within a week.

F12 Applications "What does the whole thing look like in the world?" the 30 mockups

The thirty source mockups, the source of taste for the whole system. Where the system and a mockup disagree, the mockup wins. Each sits at its own ratio and is never cropped. Note where the pattern runs all over: it is always the skin of a physical object.

Anatomy, tokens, behaviour, a11y, markup

Anatomy

A CSS-column masonry. Columns, not a grid, because a grid would force a row height and therefore a crop. Four columns at 1440, three at 1240, two at 820, one at 460.

The rule

A reference is NEVER cropped. Only our own photography may be cropped, and there is no photography in this pack.

Accessibility

Each image carries alt text naming what the mockup shows, and intrinsic width and height so the column does not reflow as images load.