The Meridian PostMaking of the first editionNotes from the composing room

How it was made

A browser typeset like a daily paper

The Meridian Post is a fictional first edition for a river city at the moment it chooses to uncover what it once buried. The interface treats each section as a sheet of newsprint, not a conventional scrolling product page.

The concept

A paper you turn through

The visual centre is the collision of precise civic reporting and imperfect paper. The masthead gets the ceremonial display face; the articles get a generous old-style serif, actual justified browser columns, narrow column rules and hyphenation. Each page gives its subject a different editorial composition: front-page lead, city docket, culture feature and a letters page.

The river drawings are inline SVG rather than image files. They use repeated hatch and stipple patterns, so the illustrations retain their engraved texture at every size and remain part of the document’s ink language.

Signature technique

Columns that carry the story — and jumps that land

These are real CSS newspaper columns, not a screenshot or a column-shaped grid. The browser balances long-form copy into a true multi-column region, justifies the measure, adds controlled hyphenation and physically draws the rule between columns. “Continued” links identify an actual destination in a different sheet; the script turns there, updates the URL fragment, then places focus on the continuation.

.copy-columns {
  column-count: 3;
  column-gap: 25px;
  column-rule: 1px solid rgba(23,21,18,.48);
  hyphens: auto;
  text-align: justify;
}

let exitTimer;
let settleTimer;
let transitionId = 0;
let pendingPageId;
let pendingTargetId;

function focusDestination(page, targetId) {
  const target = targetId && document.getElementById(targetId);
  if (target) {
    target.focus({ preventScroll: false });
    return;
  }
  page.querySelector(`[data-page="${page.id}"]`)?.focus({ preventScroll: true });
}

function cancelPendingTurn() {
  window.clearTimeout(exitTimer);
  window.clearTimeout(settleTimer);
  pages.forEach((page) => delete page.dataset.state);
}

function showPage(id, targetId) {
  const next = document.getElementById(id);
  if (!next || !current) return;

  const turn = ++transitionId;
  cancelPendingTurn();
  pendingPageId = id;
  pendingTargetId = targetId;
  if (next === current) {
    updateControls(current.id);
    pendingPageId = undefined;
    pendingTargetId = undefined;
    if (targetId) focusDestination(current, targetId);
    return;
  }
  updateControls(id);
  const outgoing = current;
  const completeTurn = () => {
    if (turn !== transitionId) return;
    outgoing.hidden = true;
    delete outgoing.dataset.state;
    next.hidden = false;
    current = next;

    const settle = () => {
      if (turn !== transitionId) return;
      delete next.dataset.state;
      pendingPageId = undefined;
      pendingTargetId = undefined;
      focusDestination(next, targetId);
    };
    if (reduceMotion.matches) return settle();
    next.dataset.state = 'entering';
    settleTimer = window.setTimeout(settle, 570);
  };
  if (reduceMotion.matches) return completeTurn();
  outgoing.dataset.state = 'exiting';
  exitTimer = window.setTimeout(completeTurn, 360);
}

The final reading moment is a small tide proof beneath the Arts feature. Its three radio choices do not swap between images: a 41-point trace recalculates the engraved current, its fill and three ripple lines from the selected water level. The state changes discretely, so it extends the paper’s reporting language without breaking the page-turn-only motion rule.

function traceCurrent(baseline, phase, amplitude = 6) {
  const samples = 40;
  let trace = '';
  for (let point = 0; point <= samples; point += 1) {
    const x = (680 / samples) * point;
    const y = baseline
      + Math.sin(point * .78 + phase) * amplitude
      + Math.sin(point * .23 - phase) * 2.4;
    trace += `${point ? ' L' : 'M'}${x.toFixed(1)} ${y.toFixed(1)}`;
  }
  return trace;
}

const baseline = 196 - reading.level * 23;
const currentLine = traceCurrent(baseline, reading.phase, 5.7 + reading.level * .7);
tideWaterLine.setAttribute('d', currentLine);
tideWaterFill.setAttribute('d', `${currentLine} L680 276 L0 276 Z`);

Motion rule: the only non-essential movement is a deliberate, left-bound page turn. There are no scroll-triggered reveals or drifting decorations; reduced-motion users see the next page immediately, while the tide proof remains a usable, instant state change.

Iteration notes

Three passes

Pass 1 — correctness

  • Replaced the single page-turn timer with cancellable exit and settle timers plus a transition token, preventing a rapid tab change from leaving stale animation state or moving focus to a hidden sheet.
  • Made continuation destinations programmatically focusable, and moved focus to the selected tab after ordinary section turns; the page-3 and page-4 jump lines now land on real, readable destinations.
  • Rebuilt the phone section controls as a two-by-two grid with 44px targets, removed their horizontal overflow, and simplified the narrow masthead so metadata cannot collide with the title.
  • Strengthened visible keyboard focus treatment, preserved reduced-motion’s instant turn behavior in both CSS and JavaScript, and added the required styled gallery and making-of link classes to this page.
  • Known limitation: the supplied web fonts remain network-hosted; the declared Georgia and Rockwell fallbacks preserve the hierarchy if a font request is unavailable, but cannot exactly match the intended faces.

Pass 2 — design depth

  • Weak moment: repeated lead-and-rail structure. The City Desk duplicated the front-page silhouette, so it is now a distinct civic ledger: an oversized full-width report set in four true newspaper columns above a three-part map, quotation and docket band.
  • Weak moment: headlines were bold but not composed. I introduced a deliberate spacing scale, tightened display tracking and leading, prevented key headline widows with controlled line breaks, and strengthened the contrast between deck, byline and display type.
  • Weak moment: the Arts engraving sat too politely inside its column. The tide-score plate now bleeds through the sheet’s right margin and carries an overlapping red plate label, extending the engraved-print language beyond a collection of contained illustrations.

Pass 3 — complexify

  • New reader’s proof: The Arts sheet now rewards a trip below the feature with three bridge soundings. Each choice recalculates a 41-point current, its hatch-filled water and ripple lines, then pairs the engraving with the choir’s actual field note for that tide.
  • Kept the motion grammar intact: I made the tide proof a deliberate, instant editorial state rather than an animation. The physical page turn remains the site’s sole non-essential movement, and reduced-motion still suppresses it.
  • Fixed the deep-link opening state: a link to a continuation now paints its destination sheet from the first styled frame, then focuses the exact jump target. This avoids showing the front page before the selected article arrives.
  • Finished the edition: optional font loading preserves the paper’s fallback hierarchy without a late unstyled flash, and social metadata now describes the first issue and its river story.