Style guide

Last updated

Style guide 1.0

The system behind aitormurillo.com. Every value is read from the code as of 1 Oct 2026 (frontend/, Nuxt 4 + Tailwind v4).

Audience: designers and front-end developers. Each rule gives the design value first and, where one exists, the token, utility or Vue component that implements it, in Mute.

Principles

One size, one weight.

Hierarchy comes from position on the grid, case and colour, never from scale.

Text is the interface.

Every control is a word: no buttons with fills or borders, no icons. The only glyphs are + (rotated 45° for Close), ×, ↗, ↑, ⌘K.

Light, with colour from the work.

White ground, black type, one grey, one blue, one hairline grey. All other colour comes from project media.

Everything starts on a column edge.

The header’s five blocks are the master columns for the whole site.

Motion has two curves.

Reveal, for masks and slides; settle, for entrances and zoom.

Hover is an enhancement.

Hover effects exist only on devices that can hover; keyboard focus mirrors them where they carry meaning.

Type

Aitor Murillo

Geist Mono Regular, 12/16, everywhere. Hierarchy comes from position, case and colour.

ABCDEFGHIJKLMNOPQRSTUVWXYZ
abcdefghijklmnopqrstuvwxyz
0123456789 àáçèéíïòóúüñ ·,.:;!?¿¡()“”‘’
Ŀŀ ı Œœ € ™ + × ↗ ↑ ← → ⌘K

Running text · CommonTextContent

Every control is a word. Paragraphs are 16px apart and the measure is four columns, about 45–60 characters.

  • Bullet lists use a disc,
  • with a 2ch indent.

Inline links are always underlined, because blue alone is too close to black.

Family

Geist Mono, regular, self-hosted and subset (Latin, Latin-1, Ŀŀ ıŒœ €™ and arrows)

--font-primary · /fonts/GeistMono-Regular.woff2, preload, font-display: swap

Size / line height

12px / 16px, everywhere

--text-base / --text-base--line-height

Weight / tracking

400 / 0

Wrapping

text-wrap: pretty on everything

body

Fallback

ui-monospace, monospace (also covers glyphs outside the subset, e.g. Japanese)

--font-primary

UPPERCASE

The logo, project names (always: list, cards, caption, project title on mobile), language codes, the error page.

Sentence case

Everything else: header copy, body, labels, credits, controls.

Line breaks

Kept where a CMS field allows them: tagline, address, stack.

whitespace-pre-line

Truncation

SINGLE LINE AND AN ELLIPSIS FOR LIST NAMES

List-row names and credits on desktop, card names. The credit revealed on card hover wraps in full.

Colour

Ground
#FFFFFF
--color-white
On black 21:1

Ink
#000000
--color-black
On white 21:1

Mute
#767676
--color-gray
4.54:1 on white, 4.6:1 on black (AA)

Link / active
#0000EE
--color-blue
9.4:1 on white, 2.2:1 on black: not for text on black

Hairline
#E6E6E6
--color-gray-light
Rules and media frames, never text

Active

About

Blue: current main-nav item, active language, active word in the archive’s view toggle. On the home the active language stays white.

Inactive

ES

Mute: inactive languages, inactive view word, other pages in the mobile menu. Opacity is never used for inactive states.

Inactive → hover

Goes to the surrounding text colour: black on white, white on the home. Text buttons go Mute → black.

Link hover

Black → blue, 150ms default or 200ms where set.

Dim

Context

Opacity 0.2, only to push context back: non-hovered list rows; the project page and header block 2 behind More projects.

Missing media

A solid black block. Every media frame has a black ground.

Selection

Selected

Blue background, white text.

Grounds

Home: black. Every other page: white. The ground changes colour during the page transition, never by a jump.

Over imagery

Home only. Top gradient 180px, rgba(0,0,0,.32) → transparent. Bottom gradient 200px, transparent → rgba(0,0,0,.36). Labels on media elsewhere use a white block behind black text, never a scrim.

Grid, spacing and layout

12 columns on desktop, 6 below 768px (Tailwind’s md). 20px side margin and 10px gutter at every size. Nested layouts reuse the parent’s tracks with grid-cols-subgrid, so a nested column always lands on a page column.

container + main-grid · main-gap

1
2
3
4
5
6
7
8
9
10
11
12

1 · Logo
1–2

2 · Per-page copy
3–6, the only block that changes

3 · Main nav
7–8

4 · Secondary nav, languages
9–10

5 · Clock
11–12, right

Spacing, in multiples of the 4px unit

4

Horizontal padding of a white label on media

px-1

8

Vertical padding of table and list rows

py-2

10

Gutter; label inset on cards; gallery gap; mobile row padding top

2.5

16

Paragraph gap; gap between banner rows

4

20

Side margin; header padding and row gap; fixed elements’ distance from the edge

5

24

Gap between groups in the project text column

6

32

Footer row gap on mobile

8

40

Gap between groups in the mobile menu; project text to its URL

10

48

Section gap on mobile (About); title-to-body in legal and project

12

96

Section gap on desktop (About); footer row gap on desktop

24

120

Space under the content before the footer

pb-30

Where content starts (all pages but the home)

Desktop

32vh

Mobile

172px (the archive’s two-row header + 48px)

--page-content-top. pt-page-content subtracts the measured --header-height. The project’s fixed desktop column uses --page-content-top directly. This page starts at 32vh.

Layers

50

Cookie banner

10

Header; More projects button and carousel; home caption

5

Home gradients

3

Hovered list row, above the preview

2

List preview layer; current home slide

1

Previous home slide during a wipe; the shown list preview

Components

Text link

ArchiveAbout

Black → blue on hover. Current page blue. External links open in a new tab; in footer menus they carry ↗ on the right.

CommonLink · CommonHeaderNavLink

Inline link

Blue and always underlined in running text. Standalone blue links (the project URL, “View programme ↗”) underline on hover only.

CommonContentLink

Standalone link

Underline on hover only.

Text button

Mute → black on hover, 200ms. The inactive view word, “× Clear” and “Clear search”.

CommonTextButton

Close

+ rotated 45°, then “Close”. Black, hover blue. Project, mobile menu, More projects, cookie settings.

CommonCloseLabel

Language switcher

Uppercase, separated by a space. Active blue (white on the home), inactive Mute, hover to the surrounding colour. Each link carries lang and hreflang.

I18nSwitcher

Credits

Design by Aitor Murillo / Collab with Studio Norte

On the project page, “in Collaboration with Y” on its own line. Collaborators with a website link to it (Mute → blue, new tab). Categories read “Type, Sector”. Empty fields are omitted, never replaced by a dash.

ProjectCollaborators

Table row

1px hairline on top, 8px vertical padding. About (services, collaborators, teaching) and the footer menus. Every row on this page is one.

Archive controls, list rows and grid cards · interactive. Hover a row or a card; try the search (⌘K).

Aitor Murillo
+ Menu

Full archive, 8 projects.

Change view from grid to

  • Archive
  • About
Contactenesca

Archive controls

Count announced via aria-live. View toggle: current view blue, the other a text button; default grid. Search: borderless, max 320px, blue caret, no focus outline; ⌘K hint becomes Esc while focused, × Clear with a query. Live, accent-insensitive, every word must match, across name, year, design, collaboration, type and sector. Search and view last for the visit and aren’t in the URL.

ArchiveControls

List row

Year 1–2 · NAME 3–6 · Design by 7–9 · Type, Sector 10–12. Mobile: year col 1, the rest stacked on 2–6, 10px top and 12px bottom padding. On hover or focus the other rows dim to 0.2 (250ms), the row drops its rule and its words get white blocks, and the first gallery image shows fixed in the viewport centre on cols 4–9, uncropped.

ArchiveProjectList

Project card

The project’s gallery frame: 4:3 on black, the first gallery image at 66% width and its own ratio over a blurred copy. NAME on a white label 10px from the bottom-left. 4 columns wide on desktop (3 per row), full width on mobile. At rest a grayscale image on a gray-light field; hover or focus fades the blurred copy in and brings the colour back with the zoom; colour and blur on touch. The More projects carousel uses the same card. On hover or focus the image zooms to 1.06 over a still blurred copy (500ms, settle) and the label grows to reveal the credit (450ms, settle). Siblings don’t dim. Mobile shows the NAME label only.

ArchiveProjectGrid · ProjectCard

Cookie banner · interactive. Fixed to the bottom on the site; shown in place here.

Cookies

Copy from Site Global → Cookie Banner Text, with a link to the cookie policy.

Cookie banner

Full width with the 20px margins, white, hairline on top, 20px vertical padding, no shadow. The analytics switch is Off by default; the current word is black. “Settings” becomes “× Close”, “Accept” becomes “Save preferences” once the switch changes. Never taller than the screen: only the settings list scrolls, without a visible scrollbar. Mobile: label, text, then the three actions on two columns each.

CommonCookieBanner

Footer

Closes Archive, About and Legal; never on the Home or a Project. The footer of this page is the specimen. Mobile: everything stacked on 1–6, the two menus read as one table, 32px between rows, “Last updated” 1–4 and “Top ↑” 5–6.

CommonFooter · CommonLastUpdated

Pages

Home

Full screen, no page scroll, black ground, one featured project per gesture. Media at full width of cols 3–10 on desktop, 1–6 on mobile, at its own ratio (never cropped). Behind it a full-bleed blurred copy (120%, blur 60px, brightness 0.8). Caption 20px from the bottom, white.

HomeFeaturedProjects · HomeProjectCaption · CommonMediaBlur

Archive

Controls in header block 2; the grid (default) or the list. Empty search: “No projects match “query”.” in Mute, then “Clear search”.

Project

Desktop holds still. Text on 3–5: description; credits, categories (links, one per line) and stack in Mute; groups 24px apart; URL, without a label, pinned to the bottom. Gallery on 7–12, an infinite vertical loop, wheel anywhere scrolls it. Year in Mute under the NAME. Mobile: one column, NAME and year on top, gallery below without the loop. Close returns to the Archive with its search and view.

ProjectGalleryFrame

More projects

“+ More projects” fixed 20px from the bottom-left (white block on mobile), “+ Close” while open. Carousel 52px from the bottom: 3 cards visible on desktop, 2 on mobile, infinite loop from the next project. Page and block 2 dim to 0.2. Closes with Close, Esc or a click on the dimmed page.

ProjectMoreProjects

About

Mute label on 1–2, content beside; sections 96px apart (48px on mobile). Intro 3–6, portrait 4:3 on 9–12. Services rows alternate 4 and 6 columns wide. Collaboration: every collaborator, alphabetical, down 4 columns, with a two-digit count in Mute, linking to the filtered Archive; those without projects in Mute, not links. Biography 3–6. Teaching: a definition table, then “View programme ↗”. Contact 3–6, address in Mute on 9–10.

Legal

Running text on 3–6: paragraphs, bullet lists and inline links only. Desktop: title and “Last updated” in header block 2. Mobile: they open the content, text 48px below.

Error

No header or footer. Centred, uppercase. The link stays in the visitor’s language. Other errors: “Sorry, something went wrong. Please try again later.”

404
The page could not be found.

Go to the homepage

Media

Home frame · the media’s own ratio
8 of 12 cols / full width

List preview · the gallery frame
6 of 12 cols; the media 2/3 of that

Gallery frame · 4:3, media at its own ratio (never cropped), 66% wide, over a blurred copy (40px)
2/3 of 6 cols / 2/3 of the width; also the project card in the archive grid and More projects, 4 of 12 cols. A video sits on the gray field instead.

About portrait · 4:3
4 of 12 cols / full width

Cover

None: the first gallery item stands in for it on the home, the archive list and grid, More projects, and as the share image when the project’s SEO has none.

Images

Sanity CDN, srcset 320–2400px, never wider than the original. Each use declares its width in sizes. Lazy by default; the home’s first slide and a project’s first image load with high priority.

CommonMediaImage

Video

Muted, looping, inline, no controls, object-fit: cover, same frame as the image. The image underneath is the poster and carries the alt text; the video is hidden from assistive tech. Cards play only while in view.

CommonMediaLoop · VideoLoop

Missing media

Leaves the frame’s black ground.

Share image

1200×630, cropped around the CMS hotspot.

Motion

Reveal
--ease-reveal
cubic-bezier(0.76, 0, 0.24, 1)
Masks (wipes) and slides


1200ms wipe bottom → top + zoom 1.14 → 1 over 1800ms

2026
ATLAS EDITORIAL
Design by Aitor Murillo

Settle
--ease-settle
cubic-bezier(0.2, 0.7, 0.2, 1)
Entrances, zoom, label reveals


Home caption: rise 8px and fade in over 600ms

Durations

--page-transition-duration 300ms: page fade and every colour that changes with it. Tailwind default 150ms, cubic-bezier(0.4, 0, 0.2, 1), for link colour without an explicit duration.

Masks

clip-path: inset() wipes. clip-to-top → clip-none wipes downward from the top edge; clip-to-bottom → clip-none wipes upward from the bottom edge.

utils/wipe.js · wipeIn, wipeOut, zoomIn

Page change

Outgoing page fades out 300ms, then the new one fades in 300ms

Header ground, type colour and body ground fade with the fade-out. The header’s height is held while a page leaves.

Home slide

Clip wipe 1200ms reveal; media zooms 1.14 → 1 over 1800ms; blurred field crossfades 1400ms

Forward wipes bottom → top, back top → bottom. Caption out 180ms ease-in; new one rises 8px and fades in 600ms settle, from 450ms. Input locked during the wipe.

Archive entrance

Items rise and fade in turn (settle). List 650ms, 30ms step, 8px. Grid 900ms, 60ms step, 16px

The first screen in CSS from first paint and on each view change; the rest rises as it scrolls into view, each batch in turn. Delay capped at 1000ms per batch. Filtering doesn’t replay it.

Page entrance

Blocks rise and fade in turn (settle). 900ms, 60ms step, 16px, as the Archive’s grid

About, Legal and the Style guide, each heading and block of text; the About portrait keeps its own wipe. First screen and scroll as in the Archive.

List preview

In: wipe 620ms reveal + zoom 1.1 → 1 over 1000ms. Out: wipe top → bottom 520ms

Down the list wipes up, up wipes down. 110ms dwell; one wipe at a time. Undecoded images wait up to 400ms. Row dim 250ms.

Card hover

Media 1 → 1.06, 500ms settle; credit row 0fr → 1fr, 450ms settle

Frame size never changes.

More projects

Cards rise from below the screen, 800ms cubic-bezier(.7,0,.2,1), 70ms step

Close falls in reverse at 35ms. First 8 cards stagger. Page dim 500ms.

About portrait

Wipe bottom → top 1200ms reveal + zoom 1.14 → 1 over 1800ms

Once the image has loaded or failed.

Mobile menu

Panel wipes top → bottom 700ms reveal; reversed on close

The blur behind fades over the same 700ms.

Cookie banner

Wipes up from the bottom edge 700ms reveal; back down on dismiss

Also on first appearance.

Cookie settings

Row grows 0fr → 1fr while the list wipes top → bottom, 700ms reveal

The rest of the banner rises with it.

Colour hovers

150ms default; 200ms on text buttons and footer rows

Reduced motion

With prefers-reduced-motion: reduce, every wipe, zoom, stagger, rise, carousel animation and smooth scroll is skipped: things appear and disappear in place. Colour fades remain.

Interaction

Home

One project per gesture: wheel or vertical swipe past 30px (gesture ends after 160ms idle), or ↑ ↓ ← →.

Archive

⌘K / Ctrl+K focuses the search (not past an open dialog); Esc clears it and leaves.

Project

The wheel anywhere scrolls the gallery (desktop). Esc closes More projects, then the project.

More projects

Vertical wheel scrolls the carousel sideways. Focus moves to the first real card on open and back to the toggle on close.

Touch / no hover

No list dimming or preview, no card zoom or credit reveal.

URLs

/, /archive/, /about/, /archive/{slug}/, /legal/{slug}/, localised under /es/ and /ca/ (e.g. /es/archivo/). Search and view aren’t in the URL; Back works.

Cross-links

A category on a project, or a collaborator on About, opens the Archive with the search set to it.

Accessibility

Contrast

All text meets WCAG AA (Mute 4.54:1 on white, 4.6:1 on black). Blue is never text on black. The hairline grey is never text.

Links

Inline links are underlined; colour is never the only cue inside running text.

Semantics

nav for menus, dialog for the mobile menu, time with datetime for years and dates, dl for teaching, role="switch" for cookie toggles.

Headings

One h1 per page: the site name on the home, visually hidden where the header already shows the title.

Hidden but present

Folded or off-screen UI is inert (closed cookie settings, closed carousel). Decorative media is alt="" or aria-hidden. Hidden home captions remain readable.

Alt text

From the CMS; otherwise the project’s title in galleries and on the home, the name on the About portrait, and the file name as a last resort.

Focus

Mobile menu focuses Close; cookie settings the first switch (without scrolling); More projects its first card; Back to top the first link.

Live regions

The archive count is announced as the search changes.

Keyboard parity

List previews and card reveals react to focus as to hover.

Language

<html lang> follows the locale; language links carry lang and hreflang.

Open item

There is no visible focus ring: :focus-visible uses a transparent outline, which forced-colors mode still draws.

Not yet built

Open item

There is no “skip to content” link.

Not yet built

Implementation

Tokens and utilities

--font-primary

font

Geist Mono, ui-monospace, monospace

--text-base, --text-base--line-height

type

12px / 16px

--color-black · white · gray · gray-light · blue

colour

#000 · #FFF · #767676 · #E6E6E6 · #0000EE

--ease-reveal, --ease-settle

easing

cubic-bezier(0.76,0,0.24,1) · cubic-bezier(0.2,0.7,0.2,1)

--page-transition-duration

duration

300ms

--page-content-top

layout

172px / 32vh

--header-height

layout (runtime)

Measured header height

--spacing-content-top

spacing

24vh, mobile menu content offset

container

utility

Max-width 100%, 20px side padding

main-grid

utility

6 / 12 columns, 10px gap

main-gap

utility

10px gap

pt-page-content

utility

Content top minus header height

clip-to-top, clip-to-bottom, clip-none

utility

Mask states

stagger-rise

utility

Children rise in turn (--stagger-*; --index or their place among siblings; data-own-entrance opts out)

scrollbar-none

utility

Scrolls without a visible bar

scripted:

variant

Applies only when JavaScript runs

can-hover:

variant

Applies only on devices that can hover