--- version: alpha name: wavelen-design description: "A near-black instrument panel on a #0a0e12 canvas with #eef2f5 text, teal (#2dd4c8) for progress and identity and amber (#ffb020) for position in time. Space Mono carries every headline, number and timestamp, Inter carries prose and control labels. The page is laid out around a scrubbable waveform strip docked to the viewport floor: the strip is the episode navigator, and everything above it is a readout of where the strip is standing." --- ## Brand Positioning Wavelen is a four-show podcast network for people who already listen to podcasts and are tired of being onboarded onto one. It exists because a podcast network's front page is almost always the same page: a centred photo of a microphone, a headline nobody wrote on purpose, three platform badges, and a carousel of episode cards that tell you a title and a date and nothing about the thing itself. Wavelen's bet is that the most honest object a network can put on a page is the audio, drawn. So the whole current feed, six episodes end to end, is rendered as one continuous waveform and docked across the bottom of the screen, and dragging it is how you move between episodes. Every decision below serves that bet. The strip is the structure: it decides where the headline sits, how tall the first screen is, and what the rest of the page is for. Nothing on either page is decorative, and nothing claims to do something it does not do. There is no play button, because there is no audio, and a network this fussy about corrections does not get to fake a transport control. colors: accent: "#2dd4c8" accent-hot: "#5fe6dc" accent-alt: "#ffb020" ink: "#eef2f5" ink-muted: "#93a4b1" canvas: "#0a0e12" surface: "#10161c" surface-raised: "#182028" border: "#1e2830" border-strong: "#2b3a45" border-ui: "#5d7484" focus-ring: "#eef2f5" ## Logo / Wordmark System The mark is the instrument, at 20 by 16. Six amplitude bars around a horizontal centre axis, cut by one amber vertical rule at the 48% mark. The three bars behind the rule are teal, the three ahead of it are `--border-ui`. It is not a microphone, a headphone cup, a sound-wave arc, or a play triangle. It is the scrubber in miniature, with the same colour contract: teal behind the playhead, grey ahead of it, amber for the playhead itself. ```svg ``` - **Lockup**: mark, then a `0.55rem` gap, then "Wavelen" in Space Mono 700 at `1.05rem`, `-0.02em` tracking, sentence case. The mark is always left of the wordmark. Never stacked, never reversed, never centred over the word. - **Minimum size**: mark not smaller than `18px` by `14px`. Below that the 1.5px playhead and the 2px bars merge. Wordmark not smaller than `0.9rem`. - **Clearspace**: empty margin equal to the mark's own height (16px at default size) on all four sides. - **Don't**: don't recolour the mark. Don't put the playhead in the middle of the six bars, its 48% offset is the whole point, a centred rule reads as a symmetrical logo instead of a position. Don't flip the teal and grey groups, teal is always the passed side and always on the left. Don't animate the mark's playhead, the mark is a still frame of an instrument, not a second instrument. Don't set the wordmark in Inter. ## Color System ### Roles | Role | Token | Value | Used for | |---|---|---|---| | Canvas | `--bg` | `#0a0e12` | Page background, scrubber track, cells, table body rows | | Surface | `--panel` | `#10161c` | Nav, the strip, chapter panel, notice, follow form, footer, empty state | | Surface raised | `--raised` | `#182028` | Filter segment hover | | Border | `--border` | `#1e2830` | Card edges, row rules, dividers | | Border strong | `--border-strong` | `#2b3a45` | Chapter hairlines inside the wave, disabled control edge, inactive chapter rule | | Border UI | `--border-ui` | `#5d7484` | Every interactive boundary: scrubber frame, secondary button, input, filter frame, and the unplayed waveform itself | | Ink | `--ink` | `#eef2f5` | Headlines, body copy, episode titles, focus ring | | Ink muted | `--dim` | `#93a4b1` | Labels, meta lines, helper text, durations, footer lists | | Progress accent | `--accent` | `#2dd4c8` | Played waveform, current segment, show names, primary button, transcript-ready status | | Progress accent hot | `--accent-hot` | `#5fe6dc` | Primary button hover only | | Position accent | `--accent-alt` | `#ffb020` | The playhead, the two clocks, the standing-notice rule, an episode still asking for attention, a form error | The two accents split by job, and the split is printed on the page in the "Reading the strip" section, so it has to be true. **Teal is progress and identity**: what the playhead has passed, which show you are on, the one primary action. **Amber is position in time and the attention that goes with it**: the playhead, the feed clock, the chapter clock, "New this week", "Correction attached", and a field that needs correcting. A static list of keyboard bindings is neither, so those are `--ink`, not amber. There is deliberately no green and no red: "transcript ready" and "transcript in progress" are a word and a shape, not a traffic light, and an error is amber because on this page amber already means "look here now." ### Contrast (computed from the hex values above via WCAG relative luminance) | Pairing | Ratio | Passes | |---|---|---| | `--ink` on `--bg` (headlines, body copy) | 17.2:1 | AAA | | `--ink` on `--panel` (deck copy inside a panel, footer) | 16.2:1 | AAA | | `--ink` on `--raised` (hovered filter segment) | 14.6:1 | AAA | | `--dim` on `--bg` (meta lines, chapter rows, row notes) | 7.5:1 | AAA | | `--dim` on `--panel` (labels, hints, footer lists) | 7.1:1 | AAA | | `--dim` on `--raised` (hovered filter label) | 6.4:1 | AA | | `--accent` on `--bg` (show names, segment numbers) | 10.5:1 | AAA | | `--accent` on `--panel` (current segment, success helper) | 9.9:1 | AAA | | `--accent-alt` on `--bg` (chapter clock, status words) | 10.6:1 | AAA | | `--accent-alt` on `--panel` (feed clock, error helper) | 10.0:1 | AAA | | `--bg` label on `--accent` fill (primary button, selected filter) | 10.5:1 | AAA | | `--bg` label on `--accent-hot` fill (primary button hover) | 12.8:1 | AAA | | `--dim` label on `--bg` (disabled control) | 7.5:1 | AAA | | `--border-ui` control edge on `--bg` (non-text) | 4.0:1 | AA (1.4.11) | | `--border-ui` control edge on `--panel` (non-text) | 3.7:1 | AA (1.4.11) | | `--ink` focus ring on `--bg` (non-text) | 17.2:1 | AA (1.4.11) | Two failures are load-bearing and are the reason for two specific rules: **`--ink` on `--accent` measures 1.6:1.** Anything filled with teal labels itself in `--bg`, never in ink and never in white. **`--accent-alt` on `--accent` measures 1.01:1.** Teal and amber sit at almost exactly the same relative luminance, which is a hue-only difference and effectively invisible to a large number of people. This has two consequences that the whole system obeys. First, **the focus ring is `--ink`, not either accent**, at `2px` with `2px` offset, so it always lands on canvas or panel and reads at 14:1 or better no matter which control it surrounds. Second, **the playhead carries a 1px `--bg` halo** (`box-shadow: 0 0 0 1px var(--bg)`). Without it, the most important mark on the instrument disappears at the exact moment it matters, when it crosses the teal it is meant to be leading. `--border` at 1.3:1 and `--border-strong` at 1.7:1 are decorative only: card edges, row rules, and chapter hairlines drawn on top of a waveform, never a control boundary. Anything a person can click, type in, or drag gets `--border-ui` at 4.0:1. That split is why there are three border tokens rather than one. typography: display: fontFamily: Space Mono fontWeight: 700 body: fontFamily: Inter fontWeight: 400 ## Type Scale | Token | Size | Weight | Family | Line-height | Letter-spacing | Used for | |---|---|---|---|---|---|---| | `--text-episode` | `clamp(1.9rem, 4.4vw, 3.3rem)` | 700 | Space Mono | 1.06 | -0.035em | The episode under the playhead | | `--text-page-h1` | `clamp(2rem, 5vw, 3.2rem)` | 700 | Space Mono | default | -0.04em | Second-page H1 | | `--text-h2` | `clamp(1.3rem, 2.6vw, 2rem)` | 700 | Space Mono | default | -0.03em | Section headings | | `--text-standing` | `clamp(1.05rem, 1.8vw, 1.35rem)` | 400 | Space Mono | 1.4 | -0.015em | The homepage H1, the standing line | | `--text-clock` | `1.15rem` | 400 | Space Mono | 1 | 0 | The feed clock | | `--text-clock-sm` | `1.05rem` | 400 | Space Mono | 1 | 0 | Chapter clock, episode durations | | `--text-h3` | `1.08rem` | 700 | Space Mono | default | -0.02em | Episode titles in a list | | `--text-body` | `1rem` | 400 | Inter | 1.65 | 0 | Paragraph copy | | `--text-small` | `0.88rem` | 400 | Inter | 1.6 | 0 | Cell copy, footer links, chapter rows | | `--text-meta` | `0.82rem` | 400 | Inter | 1.55 | 0 | Host and date lines, hints, status line | | `--text-status` | `0.8rem` | 600 | Inter | 1 | 0.04em | Status words | | `--text-control` | `0.75rem` | 600 | Inter | 1 | 0.09em | Button labels, uppercase | | `--text-label` | `0.74rem` | 600 | Inter | 1 | 0.15em | Section kickers, form labels, footer headings, segment numbers | | `--text-seg` | `0.7rem` to `0.72rem` | 400 to 600 | mixed | 1 | 0 to 0.07em | The four labels inside a scrubber segment | Rule for Space Mono versus Inter: **Space Mono carries anything that is a title or a measurement**, because a monospace face makes every timestamp on the page align down a column without a single tabular-nums declaration, and because an episode title set in a typewriter face reads as a transcript rather than as marketing. **Inter carries anything a person reads as a sentence or clicks as a control.** Space Mono has exactly two weights here, 400 for numbers and the standing line, 700 for titles, and no italic, so hierarchy inside a heading comes from size and colour rather than a fake bold. **Nothing functional is set below 11px.** The smallest type in the system is `0.7rem` (11.2px), and it appears only inside the scrubber's segment labels, where four facts have to fit into a column that can be 120px wide. Every one of those facts also appears at `--text-small` or larger in the feed list below, in plain markup, at all times. ## Spacing Scale Base unit `8px`. Tokens in use: `--space-1: .25rem`, `--space-2: .5rem`, `--space-3: 1rem`, `--space-4: 1.5rem`, `--space-6: 2rem`, `--space-8: 3rem`, `--space-12: 4rem`. Four structural constants sit outside the scale because they are measured, not spaced: - `--nav-h: 60px`, the single nav line. - `--strip-h: 184px` (`168px` below 760px), the docked scrubber. The document reserves exactly this much `padding-bottom`, so nothing on either page ever ends up underneath it. - `--label-h: 36px` (`26px` below 760px), the label band inside a segment. The playhead starts below it, so a vertical amber rule never cuts through a line of text. - The deck's first screen is `calc(100dvh - var(--nav-h) - var(--strip-h))`. The strip does not sit under the fold, it defines where the fold is. ## Shape `--radius: 3px` on buttons, inputs, cards, the chapter panel, and the scrubber frame. Small enough to read as machined rather than friendly. Three things are deliberately square: the waveform bars (butt caps, `vector-effect: non-scaling-stroke`, so they hold 2px at every segment width), the strip itself, which is full bleed edge to edge and has no radius to have, and the standing-policy notice on the archive, which is a plate bolted to the wall rather than another card in the list. Depth is a 1px hairline and a step from `--bg` to `--panel` to `--raised`. No shadows, no glows, no glass, and no gradients anywhere except the 135 degree hatch that marks a control out of service. ## Iconography No icon library and no pictograms. The only non-text marks in the system are structural, and every one of them is a reading of the same data: - the logo mark, which is the scrubber in miniature, - the waveform itself, one `` of vertical line commands per episode, drawn twice by `` and clipped to the played fraction, - the amber playhead: a 2px rule with an 8px crossbar cap, both haloed in `--bg`, - the chapter hairlines inside a segment, and the brighter full-height episode dividers between segments, - a 7px dot before every status word, filled for a live state, outlined for a pending one, hatched for a pulled one, - the 135 degree hatch on a disabled control, - one `←` in the "Gallery" utility link. If this system ever needs true iconography, the fallback is a 2px stroked geometric set with butt caps, matching the waveform bars. It has not been needed. ## Motion Principles There is one scripted interaction on the homepage, and it is the product. **The scrubber.** A full-bleed strip is fixed to the bottom of the viewport for the whole page. It holds six segments laid end to end, and each segment's `flex-grow` is that episode's length in seconds, so **segment width is duration**: Ground Truth 07 takes 22% of the strip because it is 1:12:04 of the feed's 5:28:15. Inside each segment, one `` of `M{x} {y1}V{y2}` commands draws that recording's amplitude envelope, sampled once every few seconds. The path is drawn twice by ``: once in `--border-ui` for the whole episode, and once in `--accent` clipped by an SVG `clipPath` rect whose width is set **in user units by script, with no transition**, so the teal edge tracks the pointer exactly rather than easing behind it. Faint `--border-strong` hairlines mark chapter starts at their real fractional offsets. The playhead is a `translateX` on the compositor, `calc(var(--pos) * 100%)`, with its travel inset by its own 2px width so it is fully drawn at 0:00 and at the end of the feed instead of being half clipped by the track edge. Everything above the strip is a readout of it. Moving the playhead rewrites the show name, episode number, date, title, host line and description in the deck; re-seats the highlight in the chapter panel; advances both clocks; and repoints the deck's one forward link at the archive row for the episode you are actually standing on. The swap is instant and uncrossfaded, on purpose: this is a direct response to a drag, and a fade would lag the pointer. **The reveal.** Each segment's own title and length start at `opacity: 0` and land over 280ms when the playhead first reaches that segment. The reveal is **latched**: an episode that has been reached stays named. Two rules keep this honest. The segment's number and show name are always visible, so the strip is legible at rest and no segment is ever a blank slot. And **nothing is only in the strip**: every title, host, date, duration and status is in the feed list below in plain markup at all times, which is what makes an exotic navigator defensible rather than reckless. **Input.** A mouse commits on press, because clicking a timeline means "go there". A finger does not: the strip is docked across the bottom of a phone, which is exactly where a thumb rests, so a touch has to travel 8px horizontally before it counts as a scrub, and a touch that never travels commits on release as a tap. `touch-action: pan-y` lets a vertical swipe scroll the page through the strip. Pointer moves are coalesced into one `requestAnimationFrame`. Everything else, buttons, filter segments, chapter rows, nav links, is a `:hover` / `:active` / `:focus-visible` colour transition at 140 to 160ms. There is no scroll-linked animation, no parallax, no auto-advancing anything, and no `@keyframes` on either page. Under `prefers-reduced-motion: reduce`, every transition on both pages resolves to `0s` and smooth scrolling is switched off. The scrubber still works exactly as it does otherwise, because it was never animating: it responds to input, which is not motion in the sense the media query means. A reduced-motion visitor loses the 280ms label fade and nothing else. ## Voice & Tone Conversational, curious, a little irreverent, and never corporate-podcast-network stiff. Wavelen talks like a producer explaining a decision, not like a network announcing a slate. In practice: - **Empty state**: "Nothing under that show yet. That show has episodes on the way but none published. Clear the filter to see the rest of the archive.", not "No results found" - **Corrections policy**: "When we get something wrong we say so in the next episode of the same show and add a note to the original. We do not quietly re-upload a fixed file.", not "We are committed to accuracy" - **Newsletter pitch**: "New episode, what is in it, and how long it runs. No trailers, no sponsor reads in the email, no second list you did not ask for.", not "Join our community" - **Unsubscribe promise**: "Unsubscribe link at the top, not buried at the bottom." - **Form error**: "That address will not reach you. Check it and send again.", not "Please enter a valid email address" - **A pulled episode**: "Pulled for a re-edit", with "back once the re-edit is signed off" on the row, not a silently missing entry The tell that a sentence is off-brand: it is a promise with no mechanism attached. Wavelen states the thing and then states how it works, in the same breath. Episode titles follow the same rule and are written to be the shortest true description of the tape ("Twelve minutes of nothing, on purpose", "We shipped it wrong on purpose"). ## Accessibility Baseline - **Focus ring**: `2px solid var(--ink)` at `2px` offset via `:focus-visible` on every link, button, input and on the scrubber, never removed. Ink rather than either accent, because teal and amber measure 1.01:1 against each other and a ring in one vanishes on the other. The archive's filter segments sit flush inside a shared frame, so they swap the outline for an `inset 0 0 0 2px var(--ink)` box-shadow, which a neighbouring segment cannot clip. - **The scrubber is a real slider.** `role="slider"`, `tabindex="0"`, `aria-label`, `aria-valuemin` / `aria-valuemax` in seconds, a live `aria-valuenow`, and an `aria-valuetext` that reads "Hold Music, episode 42, Nobody agrees on the pause. A call centre in Leeds, 22:24 of 47:35." `aria-valuetext` is rewritten only when the episode or chapter actually changes, not on every frame of a drag, so a screen reader gets a reading rather than a firehose. `aria-describedby` points at the visible hint line. - **Keyboard**: Left and Right step episode to episode, Up and Down move a minute inside the current episode, Home and End jump to the newest and the oldest. Left goes to the current episode's own start before it goes to the previous episode, so a back press is never a surprise jump out of where you are. Right at the end of the feed pins at the end, it never seeks backwards. All four bindings are printed on the page in the "Reading the strip" section. - **The strip is not a control until script says so.** `role`, `tabindex` and the value attributes are added by JavaScript, and the hint line is rewritten by it. A no-script visitor gets a fully drawn strip with every label and both clocks, and is never handed a focusable control that does nothing. - **Two skip links.** The first goes to the current episode, the second goes to the scrubber. The scrubber is the page's main control but is visually last, so its DOM position matches its visual position and a second skip link closes the gap rather than inverting the reading order. - **Nothing is announced twice.** The chapter list is rebuilt only when the episode changes; moving between chapters toggles one class, so a reader's cursor is never torn out of a list that is only changing which row is current. The follow form's helper is not a live region: on error, focus returns to the field and `aria-describedby` reads the helper; on success, focus moves to the helper itself before anything is switched off, and the field goes `readonly` rather than `disabled` so focus is never dumped to the document root. The archive's filter status is `role="status"` and is deliberately not written on load, so nothing announces a count nobody asked for. - **The playhead is `aria-hidden`.** It is a graphic that changes continuously. Nothing is communicated by it alone: its position is in the slider's `aria-valuetext`, in both clocks, and in the deck. - **Colour is never the only signal.** Every status pairs its dot with a word, and the three dots differ in shape as well as colour: filled, outlined, hatched. A disabled control keeps its label at 7.5:1 on a 135 degree hatch rather than fading under an opacity. A form error changes the border, sets `aria-invalid`, and replaces the helper with a sentence. - **Tap targets**: 44px minimum on every nav link, button, filter segment and input, 38px on the tight in-row variant, which sits in a row with generous vertical padding. - **Structure**: one `

` per page, no skipped heading levels, `nav` / `main` / `footer` landmarks plus labelled `section` regions on both pages, `aria-current="page"` on the active nav link, `aria-pressed` on every filter segment, and a distinct accessible name on each of the six otherwise-identical "Cue in the strip" links. - **The visual hierarchy is deliberately inverted from the semantic one.** The homepage `h1` is the standing line, "Four shows, one feed, drawn as one waveform. Scrub it to move," at `1.35rem`. The episode title is an `h2` at `3.3rem`. The biggest text on this page is by design a value the scrubber changes, and an `h1` must not be that. - **No horizontal scroll at any width** from 1440px down to 320px, verified on both pages. ## Components ### Navigation Single line, `60px`, `--panel` with a `--border` bottom hairline, sticky. Left: a "Gallery" utility link and the mark plus wordmark lockup. Right: three links and exactly one primary button. Below 940px the in-page anchors collapse but the cross-page link stays, because it is the only thing in the nav a phone cannot reach any other way. No hamburger, a two-page network has nothing worth collapsing. ### Buttons Three variants, `3px` radius, 44px minimum height: - **Primary**: `--accent` fill, `--bg` label, `--accent-hot` on hover. Once per view, in the nav. - **Secondary**: transparent, `1px solid --border-ui`, ink label, edge and label both going teal on hover. Every in-page action. - **Ghost**: text only, `--dim` label with a `--border-ui` underline rule, going ink plus a teal rule on hover. Tertiary jumps. All three define `:hover`, `:active` (`translateY(1px)`), `:disabled` (a `--dim` label at 7.5:1 on a 135 degree hatch, plus `pointer-events: none`), and `:focus-visible`. The disabled rule is **last in the cascade on purpose**: a variant's `:hover` must never be able to repaint a control that cannot be used. A `btn--tight` size modifier at 38px exists for list rows. ### The scrubber (signature) Documented in full under Motion Principles. Structurally: a `--panel` bar fixed to the viewport floor, holding a head row (section label, hint line, and the feed clock in amber Space Mono) and the track. The track is a `--border-ui` framed row of six proportional segments, each with a two-line label band above its waveform, plus the playhead. Below 760px the segment labels drop to their episode numbers, and the head grows a line naming the episode the playhead is standing on, so the phone loses the label layout but not the information. ### Deck The first screen. Two columns: the identity block (standing line, show and episode and date, the episode title, the host line, the description, and two actions) and the chapter panel. Single column below 940px. The one forward action follows the playhead: it always points at the archive row for the current episode, and the archive points back with an `#ep-n` hash the homepage reads on load, so the position a visitor found is never discarded by a link. ### Chapter panel A `--panel` card listing the current episode's chapters with their in-episode timestamps and a live position clock. The current row takes a teal left rule and ink text. It is deliberately **not** clickable: the strip is the navigator, and a second competing navigator for the same data would dilute exactly that. Every chapter it lists is a hairline on the strip. ### Feed list Six rows, the same six the strip is drawn from, in the same order, left to right becoming top to bottom. Row: index, show name, episode title, host and date meta, duration, status, and one secondary "Cue in the strip" button that sets the playhead and moves focus to it. Without script those buttons are plain anchors to the strip, which is where they point anyway. This list is the reason the strip's reveal is safe: everything the scrubber can tell you is written out here, permanently. ### Reading the strip A four-cell hairline grid stating what each dimension of the instrument means: width is length, height is loudness, hairlines are chapters, teal is progress and amber is position. Plus a keyboard block listing all four bindings. It sits **above** the feed list, because an instrument should be explained before its inventory. This section is the reason the playhead can be `aria-hidden` and the reason the colour contract is falsifiable, so its claims are held to being true. ### Episode row (second page) The waveform component off the hero: a 132px static sparkline of that recording's own envelope, the show and episode number, the title, host and date, duration, status, and an action. The sparkline strokes `--border-ui` and goes teal on row hover or focus-within. Below 760px the row becomes one column and the sparkline is capped at 220px rather than stretched full width, because 22 bars across a phone reads as a dashed rule, not a recording. ### Show filter (second page) Five real buttons in one hard `--border-ui` frame, one border between segments, no gaps and no pills. Selected segment is a teal fill with a `--bg` label and `aria-pressed="true"`. Every change writes a count into the status line beneath it. Full width below 760px. ### Status states - **New this week**: filled amber dot plus the words. - **Correction attached**: filled amber dot, used when an episode carries a published correction. - **Transcript ready**: filled teal dot. - **Transcript in progress**: outlined dim dot. - **Pulled for a re-edit**: hatched dim square, the row's title and duration drop to `--dim`, and its action becomes a real `disabled` button labelled "Unavailable". A pulled episode still counts as an episode in the archive's tally but is subtracted from the hours of tape, because it is not tape anyone can hear. - **Empty**: a `--panel` block with a real heading and a sentence saying what to do next. - **Notice**: a square `--panel` plate with a 2px amber top rule, carrying the corrections policy. ### Follow form (second page) Label above control. The input shares the `--panel` page surface with a `--border-ui` edge, `3px` radius, 44px height, and a `--raised` fill plus a teal border on focus. The helper sits below at a reserved minimum height so validating never shifts the layout. An empty field and a malformed address get different sentences, because "you left it blank" and "you mistyped it" are different mistakes. Focus and announcement handling is described under Accessibility Baseline. ### Footer Four columns: brand block with an honest note that the network is fictional, the four show names as plain text rather than links to pages that do not exist, a "Getting it" column with the feed address and the transcript and corrections promises, and an Elsewhere list linking to the other page, the gallery, and this file. A bottom bar carries the tagline. No invented legal text. ## Do's and Don'ts ### Do - Let the strip decide the layout. It defines where the fold is, and everything above it is a readout of where it is standing. - Make a dimension mean something. Segment width is duration because duration is a fact worth seeing. - Write out, permanently and in plain markup, everything an exotic navigator can reveal. - Use `--bg` as the label colour on any `--accent` fill, and `--ink` for the focus ring. - Halo the playhead. Two accents at the same luminance need a value break between them, not a hue break. - Give a chapter hairline and an episode divider different colours and different heights. Two meanings must not be the same 1px. - Make a control follow the interaction. If the scrubber found an episode, the link out has to carry that episode. - Give a touch a deadzone before it commits, on any control docked where a thumb rests. - Add `role` and `tabindex` from script, so a no-script visitor is never handed a dead control. - Keep every functional size at 11px or above, including inside the instrument. ### Don't - Don't put three "listen on" platform badges in a centred hero, or anywhere above the fold. Distribution lives as one line of text in the footer. - Don't lead with a featured-episode card carousel, or any auto-advancing content. - Don't reach for a purple or pink gradient, or any gradient. The only gradient in this system is the out-of-service hatch. - Don't draw a microphone, a headphone cup, a sound-wave arc, or a play triangle. The waveform is the iconography. - Don't autoplay anything. Nothing on either page plays. - Don't use a third accent, and don't use either of the two decoratively. Teal is progress and identity, amber is position and attention, and a static list of key bindings is neither. - Don't put ink or white on a teal fill, it measures 1.6:1. - Don't use amber for a focus ring or a border on teal, it measures 1.01:1. - Don't animate the waveform fill. It tracks a pointer, and a transition on it is a lie about where your finger is. - Don't make the chapter list clickable. One navigator per page. - Don't ship a link that discards the position the visitor worked to find. - Don't count a pulled episode as hours of tape. - Don't set Space Mono as body copy, or Inter as a headline. ### Deliberate exceptions Three flags from the impeccable detector are consciously overruled, on this project's established precedent that a system's own rules win where they conflict with a generic pattern. **`overused-font`, on Inter.** Inter is named as this system's body face in its `systems.json` entry, which is the brand fact the whole page is built from, and it carries body copy only: the display voice, every headline, number, timestamp and the wordmark, is Space Mono. Changing the face to satisfy the rule would mean changing the brief. **`ai-color-palette`, "cyan neon text on dark background", 14 instances on the homepage.** The palette is prescribed verbatim by this system's `systems.json` entry. The rule targets the cyan-on-near-black cliché as decoration, and the answer here is the never-list itself: every teal instance on both pages is functional (the played waveform, the current segment, a show's identity, the one primary action, a transcript state), it measures 10.5:1, and the page prints its own colour contract in the "Reading the strip" section so the claim can be checked. The rule also fires once per instance rather than once per token, so a single custom property used 14 times inflates the count 14 times. **`kicker-above-heading` and `hero-eyebrow-chip`.** The tracked-caps `--text-label` line above a section heading is an established convention across this whole gallery, not a decorative pill: it names the section's subject where the heading names its claim. Kept for consistency with the other 24 systems. Two further flags are kept with reasons rather than overruled outright: `repeating-stripes-gradient` fires on the disabled-control hatch, which is a functional affordance and the alternative would be a low-opacity fade that drops a label below 3:1; and `text-overflow` on `span.seg__name` fires on deliberate `text-overflow: ellipsis` inside a fixed-width segment label, where the full string is present at full size in the deck directly above and in the feed list below. **A product change proposed by the critique and declined:** adding a play control at the playhead. There is no audio on this site and none is coming, so a transport control would be a fake affordance on a page whose own voice is built on saying how things actually work. The real problem behind that note, that every "open an episode" path discarded the scrubber's position, was fixed instead: the deck's action follows the playhead into the archive, and the archive links back with an `#ep-n` hash the homepage reads on load. ## Known Constraints - Two routes exist (the feed, the archive). There is no per-episode page, no show page, no transcript, no search, and no authenticated state. The four shows in the footer are plain text rather than links, because linking to pages that do not exist would be the same lie as a play button. - **Nothing plays.** The waveform is real code drawn from a fixed amplitude table generated by a seeded envelope function, not from decoded audio. It is honest about a shape and a duration, and it is not a recording. The "Getting it" footer column names an RSS address that does not resolve. - The strip holds the six most recent episodes, so the four older rows on the archive have no strip position. Rather than give them a link that lands somewhere else, they carry the words "Older than the current strip" and no action. That is the honest state, and it is also what makes the strip and the archive legibly different things. - The archive's show filter and the follow form both need script. Without it, all ten rows are visible, which is the "All" state and the correct default, and the five filter segments are inert. The homepage has no such gap: its strip, deck, chapter list and feed list are all fully present without script, and the strip is not exposed as a control at all until script has run. - The archive's empty state is written and styled but currently unreachable, because all four shows have at least one published episode. It is kept because a network with a fifth show would reach it immediately, and an empty state written after the fact is always worse. - The follow form changes nothing beyond the current tab, and both pages' status counts are computed from the markup rather than a backend. - Responsive behaviour is three breakpoints (940px, 760px, 460px) plus fluid type. There is no tablet-specific layout. The strip keeps its horizontal orientation at every width rather than flipping vertical, because a vertical strip would stop being the floor the page stands on. At 390px the strip is roughly 55 seconds per pixel and the shortest segment is about 36px, which is under the 44px target minimum: the phone answer to that is the "Cue in the strip" buttons in the feed list, which set the playhead exactly, and the head line that names the episode you land on. ## Process Built through the impeccable-audited anti-slop workflow: never-list written before any HTML, page structure derived from the signature interaction rather than a template, `/impeccable audit` and `/impeccable critique` run against the built page before this system was considered done.