MouvementInk · 835 N Lombard, Portland OR · Launching Fall 2026

Eagle
Portland

The brief

Most bar websites are a poster with a phone number. This one is a system — built for what actually happens running a venue, not what a template assumes happens.

Eagle Portland needed to survive the real mess of a working bar: recurring nights that repeat forever until they don't, a guest list that has to remember one person across a dozen different forms, and a search footprint that can't depend on someone remembering to update a meta tag by hand. Built through MouvementInk on Next.js, Sanity and Vercel — not proposed, shipped.

Eagle Portland, the live production build
The live production build — pre-launch, not yet public.

Rolling event expansion window

0

Recurring nights expand into real calendar dates, live, on a rolling basis.

01 / 04

Special-event page templates

0

Default, Poster, Timeline, Custom — one CMS field switches the whole layout.

02 / 04

Custom content types in the CMS

0

Structured editorial control, not a generic page builder.

03 / 04

Deterministic ID, per real customer

0

One person, one record — never three rows that happen to share an email.

04 / 04


The event
engine

System 01 — Recurring nights, done right

Recurrence · Timezone · Truth

Staff pick a night and a cadence in plain language — "every other Tuesday," "last Sunday of the month" — and the site turns that into real calendar dates, correctly, for as long as the night keeps running.

Core logic

Recurrence,
done right

src/lib/recurrence.ts

A recurring night is stored once — an anchor date plus a rule — and expanded server-side into a rolling 90-day window of real occurrences. Biweekly nights re-align to the anchor event's own week, so a night created on a Tuesday stays locked to that Tuesday's cadence instead of drifting. Every date resolves against the venue's own timezone, not whatever server happens to render the page.

// Sanity's date field is timezone-less — this pins every
// occurrence to 835 N Lombard, not the server's clock.
const VENUE_TIMEZONE = 'America/Los_Angeles'
export function dateKey(d: Date): string {
  return new Intl.DateTimeFormat('en-CA', {
    timeZone: VENUE_TIMEZONE,
  }).format(d)
}

Ships correctly across DST, timezones, and legacy recurrence rules.

Editorial flexibility

Four ways to
tell a story

specialEvent.ts

A single template field switches the entire page layout: Default for a standard night, Poster for a full-bleed hero with a configurable overlay, Timeline for a multi-round tournament, Custom for a one-off page that needs its own shape entirely. One CMS, four genuinely different page shapes, zero custom code per event.

Guest flow

RSVP,
per event

RsvpCta.tsx

One field decides the entire call to action per event: informational only, a free in-house guest list, an external ticket link, or both at once.

Capacity is staff-editable, but the submit path doesn't yet stop RSVPs once a night is full — a wired field, not yet an enforced cutoff.

The client
directory

System 02 — One guest, one record

Every guest who RSVPs, joins the list, or fills out the contact form should be one person in the system — not three unrelated rows that happen to share an email address.

Identity

One person,
one record

clientDirectory.ts

Every client's ID is a deterministic hash of their email — not an auto-incrementing number. An RSVP, a contact form and a bulk spreadsheet import from the same person all resolve to the same record automatically. For the harder case — same person, different email — the system doesn't guess: a name-and-phone match only flags a possible duplicate for a human to confirm.

Zero-duplicate identity for the common case, human judgment kept for the ambiguous one.

Trust & safety

Signed
unsubscribe

unsubscribeToken.ts

Every unsubscribe link is signed with HMAC-SHA256 and verified with a timing-safe comparison, not a plain equality check that can leak the correct signature to an attacker one character at a time. No token table to maintain — the link verifies itself against a server secret.

Door ops

The 86 list,
with a paper trail

clientDirectory.ts · deskStructure.ts

Any staff member can flag a guest as barred; only an admin can lift it. Every action is designed to create its own incident record — who did it, when — not a boolean that silently flips.

The underlying client dataset is being rebuilt following a security-driven reset, and the door check-in flow is mid-migration to the new data layer — this system is designed and shipped, not yet back in daily use.

Migration

5,000 contacts,
zero duplicates

AdminDirectoryImportTool.tsx

A custom, admin-gated tool lets staff drag in a legacy spreadsheet and import it as real client records — reusing the same identity logic as the live site, so a bulk import can't fragment the directory the way a naive CSV import would.

The SEO
layer

System 03 — Show up, and look right

A bar's website should show up when someone searches for it, and look right the moment someone shares it — without a developer touching a meta tag by hand for every new event.

Launch control

One switch,
whole site

seoSettings.ts

A single boolean governs the sitemap's disallow rules and every page's indexing behavior at once. Launch readiness is a content decision, not a code deploy — the exact mechanism behind the site's current pre-launch state, ahead of the Fall 2026 public launch.

Structured data

What Google
actually reads

seo.ts

Every page ships real schema.org JSON-LD — venue details, individual events with parsed pricing and cancellation status, breadcrumbs — generated server-side from the same content editors already fill in. Everything is escaped before injection so a pasted character can't break out of its own script tag.

Social

No blank
share cards

api/og/route.tsx

Social preview images generate at request time — page-specific image first, then a site default, then a branded fallback rendered live at the edge. Nothing on the site can be shared with an empty gray box where the image should be.


The brand
system

Identity — archetypes, color, poster language

Americana · Heritage · Underground

Eagle carries fifty-plus years of leather-bar and queer-nightlife heritage, and no Eagle bar anywhere in the world had ever turned that into a real visual system — every one of them runs on community loyalty and a rotating flyer, nothing more. The identity built for Portland runs four layers deep: retro athletic Americana, leather heritage, gay ephemera and underground print culture, and a holographic/cyber-underground finish over all of it. Different events draw from different layers, on purpose.

Six color systems anchor it — Holographic/UV, Oil Slick, Liquid Chrome, Acid Green, Kodachrome Amber, Blue Steel — each one keyed to a recurring archetype (the Pool Shark, the Leatherman, the Bear, and so on), so "run it in Oil Slick" is a complete creative brief in three words. Typography locks across every event — four fonts, fixed roles, never swapped — while color and imagery are what actually change week to week. Special occasions get their own one-off take instead of borrowing from the six — Pride, Halloween, NYE and the like each earn a custom palette (Liquid Gold, Obsidian, and others), used once and retired rather than folded into the regular rotation.

Foundation — six near-blacks, one base

Void
#0D0D0D · pure black
Black Plum
#2A0836 · Holographic shadow
Tar
#0D0800 · Oil Slick shadow
Charcoal Navy
#1C2230 · Chrome shadow
Black Green
#0A0A05 · Acid shadow
Deep Navy
#000510 · Blue Steel shadow

Not one black — a family of near-blacks, each pulled as the deepest shadow stop of a color system above it. Which one (or two) becomes the fixed institutional base is still an open call, not yet locked.

Six systems, six archetypes — a persona, not a lock

01 House palette

The Jock

Holographic / UV

Mesh shorts, tube socks, no shirt — the 70s gym-rat figure who wears the house colors because he shows up for everything.

02

The
Leatherman

Oil Slick

The founding archetype — leather heritage made human, petroleum-dark iridescence.

03

The Pool
Shark

Liquid Chrome

70s athletic build, tube socks, at the billiards table — print-clean and institutional, the Naked Pool Championship's figure.

04

The Rubberman

Acid Green

Techno-era, industrial, the darkest archetype — less anatomy, more silhouette and surface.

05

The Bear

Kodachrome Amber

Broader, bearded, relaxed authority — the only fully analog system, no VHS treatment.

06

The Alley
Figure

Blue Steel

Biker jacket, streetlight, always backlit — he arrives, he doesn't pose.

Naked Pool poster, Thursdays 6pm, text-wallpaper of pool-hall slang behind a cropped figure
Naked Pool Championship — the text-wallpaper template, pure typography
L.U.R.E. poster, full-bleed template
L.U.R.E. — the full-bleed template, figures integrated with type
Classic Cock poster, disco balls and crowned figure, music by Bio Hue
Classic Cock — the same system pushed into disco and after-hours

Three structural templates cover the entire real weekly line-up — S.I.N., Growth, Classic Cock w/ Bro Hoe, The Locker Room, AWOO!, BRIEFD — so a new night gets a poster that belongs to the system on day one, not a one-off. The goal, stated plainly in the brand doc: Eagle's flyers should become collectible over time, the way the bar itself already is.

The mark system — the actual logo — is still in development; two identity marks are built (a circle badge, a hand-drawn Eagle icon) but the full ranked system isn't locked yet. Posters and color system are live; the logo is the one piece still catching up.


Role

Built through MouvementInk. Brand identity — archetypes, color systems, typography, poster language — plus systems design, CMS architecture and the SEO layer. Next.js, Sanity, Vercel.

Status

Built and running, not proposed — in a deliberate pre-launch state ahead of a Fall 2026 public launch. This is a preliminary write-up; case study will expand once the site is public.

Preview the build →

Next Back to the chapters All work