Files
Flights/design/boarding-pass-variants.html
Trey T e1b7fd4b0d History row: boarding-pass classic design
Replaces PassportFlightRow with the "Classic" boarding-pass design
selected from design/boarding-pass-variants.html.

Anatomy:
- BoardingPassShape: custom SwiftUI Shape — rounded rect with two
  semicircular cutouts at the perforation column (top + bottom).
  Hand-drawn clockwise from top-left so the path closes cleanly.
- Stub (88pt wide): orange linear-gradient background. "WN"
  monospaced eyebrow at 9pt/tracking 2.2 at top, padded flight
  number ("0007") at 28pt monospaced heavy in the middle,
  BarcodeStripe at the bottom.
- BarcodeStripe: Canvas-drawn faux barcode — 16-element width
  pattern cycles across the width, even indices fill, odd are gaps.
- Body (flex): card background. Route IATA pair at 24pt mono heavy
  with an orange ▶ between, date in 10pt tracked mono uppercase,
  meta row of EQP / TAIL / MI metadata with mono labels in tertiary
  ink and values in primary.
- Perforation: GeometryReader-driven dashed line drawn between stub
  and body, inset top/bottom to stop short of the cutouts.

Distance is recomputed inline via haversine from the AirportDatabase
since the row doesn't get the FlightHistoryStore passed in. Mile
display only — clean integer rounded value.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 18:08:55 -05:00

1146 lines
36 KiB
HTML

<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Boarding Pass — Six Variations</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,700;9..144,900&family=JetBrains+Mono:wght@400;500;700;800&family=Major+Mono+Display&family=Playfair+Display+SC:wght@700;900&family=Cormorant+Garamond:ital,wght@0,400;0,700;1,700&family=Allerta+Stencil&display=swap" rel="stylesheet">
<style>
:root {
--orange: #FF5722;
--orange-deep: #D9461A;
--orange-soft: #FF8C59;
--amber: #FFB938;
--navy: #0A1424;
--navy-mid: #142440;
--navy-deep: #050A14;
--navy-soft: #0F1E38;
--cream: #F4ECD8;
--cream-soft: #FAF5E8;
--cream-deep: #E8DDC4;
--kraft: #C9B68A;
--gold: #C8A951;
--gold-deep: #8A6F25;
--gold-light: #E7CC7F;
--postal-red: #B53026;
}
[data-theme="dark"] {
--bg: var(--navy);
--bg-2: var(--navy-deep);
--surface: var(--navy-mid);
--surface-2: var(--navy-soft);
--ink: #F4ECD8;
--ink-2: rgba(244, 236, 216, 0.65);
--ink-3: rgba(244, 236, 216, 0.4);
--hairline: rgba(255, 255, 255, 0.08);
--shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
--frame-bezel: #1a1a1a;
}
[data-theme="light"] {
--bg: var(--cream);
--bg-2: var(--cream-deep);
--surface: var(--cream-soft);
--surface-2: #FFFFFF;
--ink: #0A1424;
--ink-2: rgba(10, 20, 36, 0.65);
--ink-3: rgba(10, 20, 36, 0.45);
--hairline: rgba(0, 0, 0, 0.08);
--shadow: 0 8px 22px rgba(60, 30, 0, 0.12);
--frame-bezel: #d9d3c0;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--ink);
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
transition: background 240ms ease;
}
.page { max-width: 1240px; margin: 0 auto; padding: 32px 24px 96px; }
.page-head {
display: flex;
align-items: baseline;
gap: 24px;
flex-wrap: wrap;
padding-bottom: 16px;
border-bottom: 1px solid var(--hairline);
margin-bottom: 40px;
}
.page-eyebrow {
font-family: "JetBrains Mono", monospace;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--orange);
}
.page-title {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 144, "wght" 900;
font-size: 56px;
line-height: 1;
letter-spacing: -0.02em;
margin: 4px 0 0;
}
.page-sub {
font-size: 14px;
color: var(--ink-2);
max-width: 540px;
margin: 8px 0 0;
line-height: 1.5;
}
.theme-toggle {
margin-left: auto;
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
border: 1px solid var(--hairline);
border-radius: 999px;
cursor: pointer;
background: var(--surface);
color: var(--ink);
font-family: "JetBrains Mono", monospace;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.15em;
text-transform: uppercase;
user-select: none;
}
.theme-toggle:hover { border-color: var(--orange); color: var(--orange); }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
gap: 48px;
}
.variant {
background: var(--bg-2);
border-radius: 28px;
padding: 28px 24px 36px;
border: 1px solid var(--hairline);
}
.variant-head {
display: flex;
align-items: baseline;
gap: 16px;
margin-bottom: 4px;
flex-wrap: wrap;
}
.variant-no {
font-family: "JetBrains Mono", monospace;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.2em;
color: var(--orange);
}
.variant-name {
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 96, "wght" 800;
font-size: 26px;
line-height: 1.1;
margin: 0;
}
.variant-note {
font-size: 13px;
color: var(--ink-2);
line-height: 1.5;
margin: 8px 0 24px;
max-width: 38ch;
}
.phone {
width: 100%;
max-width: 394px;
margin: 0 auto;
background: var(--frame-bezel);
padding: 10px;
border-radius: 44px;
box-shadow: var(--shadow);
}
.phone-screen {
background: var(--bg);
border-radius: 36px;
padding: 18px 14px 22px;
position: relative;
overflow: hidden;
}
.island {
width: 92px; height: 28px;
background: #000;
border-radius: 16px;
margin: 0 auto 14px;
}
.rows { display: flex; flex-direction: column; gap: 12px; }
/* =========================================================
SHARED — boarding pass anatomy primitives
========================================================= */
.bp-row {
display: grid;
border-radius: 14px;
overflow: hidden;
position: relative;
min-height: 108px;
cursor: pointer;
transition: transform 180ms ease;
}
.bp-row:hover { transform: translateY(-1px); }
/* Two-circle "punch" effect on perforation column */
.punch::after {
content: "";
position: absolute;
width: 14px; height: 14px;
background: var(--bg);
border-radius: 50%;
top: -8px;
box-shadow: 0 116px 0 var(--bg);
}
/* Perforation dashed line */
.perf {
position: absolute;
top: 8px; bottom: 8px;
width: 1px;
background: repeating-linear-gradient(
to bottom,
currentColor 0 4px,
transparent 4px 8px
);
opacity: 0.32;
}
/* Barcode strip — repeating vertical lines */
.barcode {
height: 12px;
background-image: repeating-linear-gradient(
to right,
currentColor 0 1px,
transparent 1px 3px,
currentColor 3px 4px,
transparent 4px 7px,
currentColor 7px 9px,
transparent 9px 11px
);
border-radius: 1px;
opacity: 0.95;
}
/* =========================================================
V1 — THE CLASSIC (orange stub, left)
========================================================= */
.v1 { grid-template-columns: 88px 1fr; background: var(--surface); }
.v1 .perf { left: 88px; color: var(--ink); }
.v1 .punch::after { left: 82px; }
.v1 .punch:nth-child(2)::after { top: auto; bottom: -8px; box-shadow: 0 -116px 0 var(--bg); }
.v1-stub {
background: linear-gradient(135deg, var(--orange) 0%, var(--orange-deep) 100%);
padding: 14px 12px 12px;
color: white;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.v1-stub-iata {
font-family: "JetBrains Mono", monospace;
font-size: 9px;
font-weight: 700;
letter-spacing: 0.22em;
opacity: 0.85;
}
.v1-stub-num {
font-family: "JetBrains Mono", monospace;
font-weight: 800;
font-size: 28px;
line-height: 1;
letter-spacing: -0.02em;
}
.v1-body { padding: 14px 16px; }
.v1-route {
display: flex; align-items: baseline; gap: 8px;
margin-bottom: 4px;
}
.v1-iata {
font-family: "JetBrains Mono", monospace;
font-weight: 800;
font-size: 24px;
letter-spacing: -0.02em;
color: var(--ink);
}
.v1-arrow {
color: var(--orange);
font-weight: 900;
font-size: 13px;
}
.v1-date {
font-family: "JetBrains Mono", monospace;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.18em;
color: var(--ink-3);
text-transform: uppercase;
margin-bottom: 8px;
}
.v1-meta {
display: flex; gap: 16px;
font-family: "JetBrains Mono", monospace;
font-size: 10px;
color: var(--ink-2);
}
.v1-meta dt {
display: inline; color: var(--ink-3);
margin-right: 4px; letter-spacing: 0.1em; text-transform: uppercase;
}
.v1-meta dd { display: inline; margin: 0 12px 0 0; font-weight: 600; color: var(--ink); }
/* =========================================================
V2 — REVERSE STUB (stub on right, photo-thumbnail left)
The stub is the "tear here" piece on the right.
Photo of airframe sits on the body's left edge.
========================================================= */
.v2 { grid-template-columns: 1fr 86px; background: var(--surface); }
.v2 .perf { right: 86px; color: var(--ink); }
.v2 .punch::after { right: 80px; }
.v2 .punch:nth-child(2)::after { top: auto; bottom: -8px; box-shadow: 0 -116px 0 var(--bg); }
.v2-body {
display: grid;
grid-template-columns: 70px 1fr;
gap: 12px;
padding: 12px 12px 12px 12px;
}
.v2-photo {
background-size: cover;
background-position: center;
background-color: #1c2a40;
border-radius: 8px;
}
.v2-info {
display: flex; flex-direction: column; justify-content: space-between;
padding: 2px 0;
}
.v2-route {
display: flex; align-items: baseline; gap: 6px;
}
.v2-iata {
font-family: "JetBrains Mono", monospace;
font-weight: 800;
font-size: 22px;
letter-spacing: -0.02em;
color: var(--ink);
line-height: 1;
}
.v2-arrow {
color: var(--orange);
font-weight: 900;
font-size: 12px;
}
.v2-tag {
font-family: "JetBrains Mono", monospace;
font-size: 10px;
font-weight: 700;
color: var(--ink-2);
letter-spacing: 0.06em;
margin-top: 6px;
}
.v2-meta {
font-family: "JetBrains Mono", monospace;
font-size: 9px;
color: var(--ink-3);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.v2-stub {
background: linear-gradient(225deg, var(--orange) 0%, var(--orange-deep) 100%);
padding: 14px 10px;
color: white;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
text-align: center;
}
.v2-stub-num {
writing-mode: vertical-rl;
transform: rotate(180deg);
font-family: "JetBrains Mono", monospace;
font-weight: 800;
font-size: 22px;
letter-spacing: 0.06em;
line-height: 1;
}
.v2-stub-iata {
font-family: "JetBrains Mono", monospace;
font-size: 9px;
font-weight: 700;
letter-spacing: 0.22em;
opacity: 0.9;
}
/* =========================================================
V3 — HERITAGE NAVY (mid-century cream pass + navy stub)
1960s Pan Am / Braniff style. Serif logotype. Cream paper.
Stub is small dignified navy block.
========================================================= */
.v3 { grid-template-columns: 100px 1fr; background: var(--cream); color: var(--navy); }
.v3 .perf {
left: 100px;
color: var(--navy);
background: repeating-linear-gradient(to bottom, var(--navy) 0 2px, transparent 2px 6px);
opacity: 0.3;
}
.v3 .punch::after { left: 94px; background: var(--bg-2); }
.v3 .punch:nth-child(2)::after { top: auto; bottom: -8px; box-shadow: 0 -116px 0 var(--bg-2); }
.v3-stub {
background: var(--navy);
color: var(--cream);
padding: 12px 12px;
display: flex; flex-direction: column; justify-content: space-between;
}
.v3-airline {
font-family: "Playfair Display SC", serif;
font-weight: 900;
font-size: 14px;
letter-spacing: 0.05em;
line-height: 1.05;
color: var(--cream);
}
.v3-stub-num {
font-family: "Cormorant Garamond", serif;
font-weight: 700;
font-size: 26px;
line-height: 1;
color: var(--gold-light);
font-style: italic;
}
.v3-stub-num small {
font-family: "JetBrains Mono", monospace;
font-style: normal;
font-size: 9px;
display: block;
letter-spacing: 0.2em;
color: var(--cream);
opacity: 0.7;
margin-bottom: 2px;
}
.v3-body {
padding: 12px 16px;
color: var(--navy);
}
.v3-route {
font-family: "Cormorant Garamond", serif;
font-weight: 700;
font-size: 22px;
line-height: 1.1;
margin-bottom: 2px;
}
.v3-route i {
font-style: italic;
color: var(--gold-deep);
font-weight: 400;
margin: 0 4px;
font-size: 17px;
}
.v3-meta {
font-family: "JetBrains Mono", monospace;
font-size: 10px;
color: rgba(10, 20, 36, 0.6);
letter-spacing: 0.08em;
margin-bottom: 8px;
text-transform: uppercase;
}
.v3-date {
font-family: "Cormorant Garamond", serif;
font-style: italic;
font-size: 13px;
color: var(--navy);
border-top: 1px dashed rgba(10, 20, 36, 0.25);
padding-top: 6px;
margin-top: 4px;
}
/* =========================================================
V4 — FOIL PREMIUM (cream + gold)
First-class energy. Embossed feel. Gold gradient on the
stub mimics foil block printing.
========================================================= */
.v4 { grid-template-columns: 96px 1fr; background: var(--cream-soft); color: var(--navy); }
.v4 .perf { left: 96px; color: var(--gold-deep); }
.v4 .punch::after { left: 90px; background: var(--bg-2); }
.v4 .punch:nth-child(2)::after { top: auto; bottom: -8px; box-shadow: 0 -116px 0 var(--bg-2); }
.v4-stub {
background:
linear-gradient(135deg,
var(--gold) 0%,
var(--gold-light) 40%,
var(--gold) 65%,
var(--gold-deep) 100%);
padding: 12px 12px;
color: #2A1E08;
display: flex; flex-direction: column; justify-content: space-between;
position: relative;
overflow: hidden;
}
.v4-stub::after {
/* Foil shimmer streak */
content: "";
position: absolute;
top: 0; bottom: 0;
left: 30%; right: 30%;
background: linear-gradient(to right, transparent, rgba(255,255,255,0.4), transparent);
transform: skewX(-20deg);
pointer-events: none;
}
.v4-eyebrow {
font-family: "Allerta Stencil", sans-serif;
font-size: 10px;
letter-spacing: 0.2em;
text-transform: uppercase;
line-height: 1;
color: #2A1E08;
position: relative;
}
.v4-stub-num {
font-family: "Cormorant Garamond", serif;
font-weight: 700;
font-size: 30px;
font-style: italic;
line-height: 1;
position: relative;
}
.v4-stub-num small {
font-family: "JetBrains Mono", monospace;
font-weight: 700;
font-style: normal;
font-size: 9px;
letter-spacing: 0.18em;
display: block;
margin-bottom: 4px;
}
.v4-body { padding: 14px 16px; }
.v4-route {
font-family: "Cormorant Garamond", serif;
font-weight: 700;
font-size: 26px;
line-height: 1;
color: var(--navy);
margin-bottom: 4px;
}
.v4-route i { font-style: italic; color: var(--gold-deep); margin: 0 4px; font-weight: 400; }
.v4-meta {
font-family: "Allerta Stencil", sans-serif;
font-size: 10px;
letter-spacing: 0.15em;
color: rgba(10, 20, 36, 0.7);
margin-bottom: 6px;
}
.v4-foot {
display: flex; justify-content: space-between;
border-top: 0.5px solid rgba(138, 111, 37, 0.4);
padding-top: 6px;
font-family: "JetBrains Mono", monospace;
font-size: 10px;
color: var(--navy);
}
.v4-foot strong { color: var(--gold-deep); }
/* =========================================================
V5 — BRUTALIST MONO (black stub, terminal aesthetic)
Hi-contrast. Scanlines. Reads like a UNIX boarding pass.
========================================================= */
.v5 { grid-template-columns: 76px 1fr; background: #0c0c0c; color: var(--cream); }
.v5 .perf { left: 76px; color: var(--cream); }
.v5 .punch::after { left: 70px; }
.v5 .punch:nth-child(2)::after { top: auto; bottom: -8px; box-shadow: 0 -116px 0 var(--bg); }
.v5::before {
content: "";
position: absolute; inset: 0;
background-image: repeating-linear-gradient(
to bottom,
rgba(255,255,255,0.02) 0 1px,
transparent 1px 3px
);
pointer-events: none;
}
.v5-stub {
background: #000;
color: var(--orange);
padding: 12px 10px;
display: flex; flex-direction: column; justify-content: space-between;
align-items: flex-start;
border-right: 2px solid var(--orange);
}
.v5-stub-iata {
font-family: "Major Mono Display", monospace;
font-size: 11px;
letter-spacing: 0.15em;
color: var(--cream);
opacity: 0.6;
}
.v5-stub-num {
font-family: "Major Mono Display", monospace;
font-size: 22px;
line-height: 1;
color: var(--orange);
}
.v5-stub-tag {
font-family: "JetBrains Mono", monospace;
font-size: 8px;
color: var(--orange);
letter-spacing: 0.2em;
opacity: 0.7;
}
.v5-body {
padding: 12px 14px;
color: var(--cream);
}
.v5-tag {
font-family: "JetBrains Mono", monospace;
font-size: 9px;
color: var(--orange);
letter-spacing: 0.22em;
margin-bottom: 6px;
text-transform: uppercase;
}
.v5-route {
font-family: "Major Mono Display", monospace;
font-size: 30px;
letter-spacing: -0.02em;
line-height: 1;
color: var(--cream);
}
.v5-route .arrow {
color: var(--orange);
font-size: 16px;
margin: 0 6px;
}
.v5-meta {
font-family: "JetBrains Mono", monospace;
font-size: 10px;
color: rgba(244, 236, 216, 0.55);
letter-spacing: 0.1em;
margin-top: 8px;
text-transform: uppercase;
display: flex; gap: 14px;
}
.v5-meta strong { color: var(--cream); font-weight: 700; }
/* =========================================================
V6 — PHOTO STUB HYBRID
The "stub" is the airframe photo itself, with text overlay.
Body holds the data. Most photogenic option.
========================================================= */
.v6 { grid-template-columns: 112px 1fr; background: var(--surface); }
.v6 .perf { left: 112px; color: var(--ink); }
.v6 .punch::after { left: 106px; }
.v6 .punch:nth-child(2)::after { top: auto; bottom: -8px; box-shadow: 0 -116px 0 var(--bg); }
.v6-photo {
position: relative;
background-size: cover;
background-position: center;
background-color: #1c2a40;
overflow: hidden;
}
.v6-photo::before {
/* darkening scrim */
content: "";
position: absolute; inset: 0;
background: linear-gradient(135deg, rgba(0,0,0,0.6), rgba(0,0,0,0.25) 60%, rgba(0,0,0,0.6));
}
.v6-overlay {
position: relative; z-index: 1;
padding: 12px 10px;
color: white;
height: 100%;
display: flex; flex-direction: column; justify-content: space-between;
}
.v6-overlay .iata {
font-family: "JetBrains Mono", monospace;
font-size: 9px;
letter-spacing: 0.22em;
opacity: 0.85;
}
.v6-overlay .num {
font-family: "JetBrains Mono", monospace;
font-weight: 800;
font-size: 26px;
letter-spacing: -0.02em;
line-height: 1;
text-shadow: 0 1px 8px rgba(0,0,0,0.5);
}
.v6-overlay .tail {
font-family: "JetBrains Mono", monospace;
font-size: 9px;
letter-spacing: 0.15em;
opacity: 0.85;
}
.v6-body { padding: 14px 16px; display: flex; flex-direction: column; justify-content: space-between; }
.v6-route {
display: flex; align-items: baseline; gap: 8px;
font-family: "Fraunces", serif;
font-variation-settings: "opsz" 144, "wght" 900;
font-size: 24px;
color: var(--ink);
line-height: 1;
letter-spacing: -0.02em;
}
.v6-route span { color: var(--orange); font-family: "JetBrains Mono", monospace; font-size: 14px; font-weight: 800; }
.v6-date {
font-family: "JetBrains Mono", monospace;
font-size: 10px;
letter-spacing: 0.18em;
color: var(--ink-3);
text-transform: uppercase;
margin: 4px 0 8px;
}
.v6-meta {
display: flex; gap: 6px;
font-family: "JetBrains Mono", monospace;
font-size: 10px;
}
.v6-meta span {
background: var(--bg);
padding: 3px 7px;
border-radius: 4px;
color: var(--ink-2);
letter-spacing: 0.06em;
}
/* ============== Footer note ============== */
.footer {
margin-top: 64px;
padding-top: 24px;
border-top: 1px solid var(--hairline);
font-size: 12px;
color: var(--ink-3);
line-height: 1.6;
}
.footer code {
font-family: "JetBrains Mono", monospace;
background: var(--surface);
padding: 2px 6px;
border-radius: 4px;
color: var(--orange);
}
</style>
</head>
<body>
<div class="page">
<header class="page-head">
<div>
<div class="page-eyebrow">BOARDING PASS · 6 DIRECTIONS</div>
<h1 class="page-title">Same anatomy, six personalities</h1>
<p class="page-sub">All six are boarding-pass-shaped (stub + perforation + body), but each commits to a distinct visual personality. Pick one and we port it to SwiftUI.</p>
</div>
<button class="theme-toggle" id="themeToggle" type="button">
<span id="themeLabel">DARK</span>
</button>
</header>
<div class="grid">
<!-- V1 — THE CLASSIC -->
<section class="variant">
<div class="variant-head"><span class="variant-no">01</span><h2 class="variant-name">The Classic</h2></div>
<p class="variant-note">Refined version of the original. Orange stub on the left, vertical perforation, faux barcode footer, condensed mono numerics.</p>
<div class="phone"><div class="phone-screen"><div class="island"></div><div class="rows">
<div class="bp-row v1 punch">
<div class="perf"></div>
<div class="v1-stub">
<div class="v1-stub-iata">WN</div>
<div class="v1-stub-num">0007</div>
<div class="barcode"></div>
</div>
<div class="v1-body">
<div class="v1-route">
<span class="v1-iata">DAL</span>
<span class="v1-arrow"></span>
<span class="v1-iata">HOU</span>
</div>
<div class="v1-date">27 MAY 26</div>
<dl class="v1-meta">
<dt>EQP</dt><dd>B737</dd>
<dt>TAIL</dt><dd>N7747C</dd>
<dt>NM</dt><dd>239</dd>
</dl>
</div>
</div>
<div class="bp-row v1 punch">
<div class="perf"></div>
<div class="v1-stub">
<div class="v1-stub-iata">WN</div>
<div class="v1-stub-num">1942</div>
<div class="barcode"></div>
</div>
<div class="v1-body">
<div class="v1-route">
<span class="v1-iata">LAS</span>
<span class="v1-arrow"></span>
<span class="v1-iata">DAL</span>
</div>
<div class="v1-date">27 JAN 24</div>
<dl class="v1-meta">
<dt>EQP</dt><dd>B738</dd>
<dt>TAIL</dt><dd>N281WN</dd>
<dt>NM</dt><dd>1056</dd>
</dl>
</div>
</div>
<div class="bp-row v1 punch">
<div class="perf"></div>
<div class="v1-stub">
<div class="v1-stub-iata">WN</div>
<div class="v1-stub-num">5476</div>
<div class="barcode"></div>
</div>
<div class="v1-body">
<div class="v1-route">
<span class="v1-iata">BNA</span>
<span class="v1-arrow"></span>
<span class="v1-iata">BOS</span>
</div>
<div class="v1-date">19 MAR 24</div>
<dl class="v1-meta">
<dt>EQP</dt><dd>B38M</dd>
<dt>TAIL</dt><dd>N8642E</dd>
<dt>NM</dt><dd>943</dd>
</dl>
</div>
</div>
</div></div></div>
</section>
<!-- V2 — REVERSE STUB -->
<section class="variant">
<div class="variant-head"><span class="variant-no">02</span><h2 class="variant-name">Reverse Stub</h2></div>
<p class="variant-note">Stub flipped to the right (mirrors what you keep after the gate agent tears your pass). Photo thumb sits on the body's left. Flight number rotated vertical in the stub.</p>
<div class="phone"><div class="phone-screen"><div class="island"></div><div class="rows">
<div class="bp-row v2 punch">
<div class="perf"></div>
<div class="v2-body">
<div class="v2-photo" style="background-image:url('https://images.unsplash.com/photo-1583416750470-965b2707b355?auto=format&fit=crop&w=400&q=70')"></div>
<div class="v2-info">
<div>
<div class="v2-route"><span class="v2-iata">DAL</span><span class="v2-arrow"></span><span class="v2-iata">HOU</span></div>
<div class="v2-tag">B737 · N7747C</div>
</div>
<div class="v2-meta">27 May 26 · 239 nm</div>
</div>
</div>
<div class="v2-stub">
<div class="v2-stub-iata">WN</div>
<div class="v2-stub-num">0007</div>
<div class="v2-stub-iata"></div>
</div>
</div>
<div class="bp-row v2 punch">
<div class="perf"></div>
<div class="v2-body">
<div class="v2-photo" style="background-image:url('https://images.unsplash.com/photo-1542296332-2e4473faf563?auto=format&fit=crop&w=400&q=70')"></div>
<div class="v2-info">
<div>
<div class="v2-route"><span class="v2-iata">LAS</span><span class="v2-arrow"></span><span class="v2-iata">DAL</span></div>
<div class="v2-tag">B738 · N281WN</div>
</div>
<div class="v2-meta">27 Jan 24 · 1056 nm</div>
</div>
</div>
<div class="v2-stub">
<div class="v2-stub-iata">WN</div>
<div class="v2-stub-num">1942</div>
<div class="v2-stub-iata"></div>
</div>
</div>
<div class="bp-row v2 punch">
<div class="perf"></div>
<div class="v2-body">
<div class="v2-photo" style="background-image:url('https://images.unsplash.com/photo-1436491865332-7a61a109cc05?auto=format&fit=crop&w=400&q=70')"></div>
<div class="v2-info">
<div>
<div class="v2-route"><span class="v2-iata">BNA</span><span class="v2-arrow"></span><span class="v2-iata">BOS</span></div>
<div class="v2-tag">B38M · N8642E</div>
</div>
<div class="v2-meta">19 Mar 24 · 943 nm</div>
</div>
</div>
<div class="v2-stub">
<div class="v2-stub-iata">WN</div>
<div class="v2-stub-num">5476</div>
<div class="v2-stub-iata"></div>
</div>
</div>
</div></div></div>
</section>
<!-- V3 — HERITAGE NAVY -->
<section class="variant">
<div class="variant-head"><span class="variant-no">03</span><h2 class="variant-name">Heritage Navy</h2></div>
<p class="variant-note">Mid-century airline aesthetic. Cream paper with a small navy stub. Cormorant italic for cities, Playfair small caps for the airline mark. Quietly dignified — feels like a 1968 TWA pass.</p>
<div class="phone"><div class="phone-screen"><div class="island"></div><div class="rows">
<div class="bp-row v3 punch">
<div class="perf"></div>
<div class="v3-stub">
<div class="v3-airline">South<br>west</div>
<div class="v3-stub-num"><small>FLT</small>0007</div>
</div>
<div class="v3-body">
<h3 class="v3-route">Dallas<i>to</i>Houston</h3>
<div class="v3-meta">DAL → HOU · B737 · N7747C</div>
<div class="v3-date">Wednesday, May 27 · 239 mi</div>
</div>
</div>
<div class="bp-row v3 punch">
<div class="perf"></div>
<div class="v3-stub">
<div class="v3-airline">South<br>west</div>
<div class="v3-stub-num"><small>FLT</small>1942</div>
</div>
<div class="v3-body">
<h3 class="v3-route">Las Vegas<i>to</i>Dallas</h3>
<div class="v3-meta">LAS → DAL · B738 · N281WN</div>
<div class="v3-date">Saturday, January 27 · 1,056 mi</div>
</div>
</div>
<div class="bp-row v3 punch">
<div class="perf"></div>
<div class="v3-stub">
<div class="v3-airline">South<br>west</div>
<div class="v3-stub-num"><small>FLT</small>5476</div>
</div>
<div class="v3-body">
<h3 class="v3-route">Nashville<i>to</i>Boston</h3>
<div class="v3-meta">BNA → BOS · B38M · N8642E</div>
<div class="v3-date">Tuesday, March 19 · 943 mi</div>
</div>
</div>
</div></div></div>
</section>
<!-- V4 — FOIL PREMIUM -->
<section class="variant">
<div class="variant-head"><span class="variant-no">04</span><h2 class="variant-name">Foil Premium</h2></div>
<p class="variant-note">Cream pass + a gold-foil stub with a shimmer streak. Allerta Stencil + Cormorant italic + JetBrains Mono. Reads like a Concorde-era first-class pass.</p>
<div class="phone"><div class="phone-screen"><div class="island"></div><div class="rows">
<div class="bp-row v4 punch">
<div class="perf"></div>
<div class="v4-stub">
<div class="v4-eyebrow">SOUTHWEST</div>
<div class="v4-stub-num"><small>WN</small>0007</div>
<div class="v4-eyebrow">★ ★ ★</div>
</div>
<div class="v4-body">
<div class="v4-route">Dallas<i>to</i>Houston</div>
<div class="v4-meta">B737 · TAIL N7747C</div>
<div class="v4-foot">
<span>27 MAY 2026</span>
<span><strong>239</strong> NM</span>
</div>
</div>
</div>
<div class="bp-row v4 punch">
<div class="perf"></div>
<div class="v4-stub">
<div class="v4-eyebrow">SOUTHWEST</div>
<div class="v4-stub-num"><small>WN</small>1942</div>
<div class="v4-eyebrow">★ ★ ★</div>
</div>
<div class="v4-body">
<div class="v4-route">Las Vegas<i>to</i>Dallas</div>
<div class="v4-meta">B738 · TAIL N281WN</div>
<div class="v4-foot">
<span>27 JAN 2024</span>
<span><strong>1056</strong> NM</span>
</div>
</div>
</div>
<div class="bp-row v4 punch">
<div class="perf"></div>
<div class="v4-stub">
<div class="v4-eyebrow">SOUTHWEST</div>
<div class="v4-stub-num"><small>WN</small>5476</div>
<div class="v4-eyebrow">★ ★ ★</div>
</div>
<div class="v4-body">
<div class="v4-route">Nashville<i>to</i>Boston</div>
<div class="v4-meta">B38M · TAIL N8642E</div>
<div class="v4-foot">
<span>19 MAR 2024</span>
<span><strong>943</strong> NM</span>
</div>
</div>
</div>
</div></div></div>
</section>
<!-- V5 — BRUTALIST MONO -->
<section class="variant">
<div class="variant-head"><span class="variant-no">05</span><h2 class="variant-name">Brutalist Terminal</h2></div>
<p class="variant-note">Pure black canvas. Major Mono Display for the route. Faux scanlines. Orange spine on the stub. Reads like a UNIX boarding pass printed at 4am.</p>
<div class="phone"><div class="phone-screen"><div class="island"></div><div class="rows">
<div class="bp-row v5 punch">
<div class="perf"></div>
<div class="v5-stub">
<div class="v5-stub-iata">WN</div>
<div class="v5-stub-num">0007</div>
<div class="v5-stub-tag">SWA</div>
</div>
<div class="v5-body">
<div class="v5-tag">FLT WN0007 · ETD 0830 CDT</div>
<div class="v5-route">DAL<span class="arrow"></span>HOU</div>
<div class="v5-meta">
<span>EQP <strong>B737</strong></span>
<span>TAIL <strong>N7747C</strong></span>
<span>NM <strong>239</strong></span>
</div>
</div>
</div>
<div class="bp-row v5 punch">
<div class="perf"></div>
<div class="v5-stub">
<div class="v5-stub-iata">WN</div>
<div class="v5-stub-num">1942</div>
<div class="v5-stub-tag">SWA</div>
</div>
<div class="v5-body">
<div class="v5-tag">FLT WN1942 · ETD 0900 PST</div>
<div class="v5-route">LAS<span class="arrow"></span>DAL</div>
<div class="v5-meta">
<span>EQP <strong>B738</strong></span>
<span>TAIL <strong>N281WN</strong></span>
<span>NM <strong>1056</strong></span>
</div>
</div>
</div>
<div class="bp-row v5 punch">
<div class="perf"></div>
<div class="v5-stub">
<div class="v5-stub-iata">WN</div>
<div class="v5-stub-num">5476</div>
<div class="v5-stub-tag">SWA</div>
</div>
<div class="v5-body">
<div class="v5-tag">FLT WN5476 · ETD 2010 CDT</div>
<div class="v5-route">BNA<span class="arrow"></span>BOS</div>
<div class="v5-meta">
<span>EQP <strong>B38M</strong></span>
<span>TAIL <strong>N8642E</strong></span>
<span>NM <strong>943</strong></span>
</div>
</div>
</div>
</div></div></div>
</section>
<!-- V6 — PHOTO STUB HYBRID -->
<section class="variant">
<div class="variant-head"><span class="variant-no">06</span><h2 class="variant-name">Photo Stub Hybrid</h2></div>
<p class="variant-note">The stub IS the airframe photo. Flight number and tail overlay on top. Body holds the route in Fraunces serif. Most photogenic of the six.</p>
<div class="phone"><div class="phone-screen"><div class="island"></div><div class="rows">
<div class="bp-row v6 punch">
<div class="perf"></div>
<div class="v6-photo" style="background-image:url('https://images.unsplash.com/photo-1583416750470-965b2707b355?auto=format&fit=crop&w=500&q=70')">
<div class="v6-overlay">
<div class="iata">WN</div>
<div class="num">0007</div>
<div class="tail">N7747C</div>
</div>
</div>
<div class="v6-body">
<div class="v6-route">DAL <span></span> HOU</div>
<div class="v6-date">27 MAY 2026 · 239 NM</div>
<div class="v6-meta"><span>B737</span><span>SWA</span></div>
</div>
</div>
<div class="bp-row v6 punch">
<div class="perf"></div>
<div class="v6-photo" style="background-image:url('https://images.unsplash.com/photo-1542296332-2e4473faf563?auto=format&fit=crop&w=500&q=70')">
<div class="v6-overlay">
<div class="iata">WN</div>
<div class="num">1942</div>
<div class="tail">N281WN</div>
</div>
</div>
<div class="v6-body">
<div class="v6-route">LAS <span></span> DAL</div>
<div class="v6-date">27 JAN 2024 · 1056 NM</div>
<div class="v6-meta"><span>B738</span><span>SWA</span></div>
</div>
</div>
<div class="bp-row v6 punch">
<div class="perf"></div>
<div class="v6-photo" style="background-image:url('https://images.unsplash.com/photo-1436491865332-7a61a109cc05?auto=format&fit=crop&w=500&q=70')">
<div class="v6-overlay">
<div class="iata">WN</div>
<div class="num">5476</div>
<div class="tail">N8642E</div>
</div>
</div>
<div class="v6-body">
<div class="v6-route">BNA <span></span> BOS</div>
<div class="v6-date">19 MAR 2024 · 943 NM</div>
<div class="v6-meta"><span>B38M</span><span>SWA</span></div>
</div>
</div>
</div></div></div>
</section>
</div>
<footer class="footer">
All six share the same DOM anatomy (<code>.bp-row</code> + <code>.perf</code> + <code>.punch</code>) so porting back to SwiftUI is straightforward — the differences live almost entirely in the stub side and typography choices.
</footer>
</div>
<script>
const root = document.documentElement;
const btn = document.getElementById('themeToggle');
const label = document.getElementById('themeLabel');
btn.addEventListener('click', () => {
const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', next);
label.textContent = next.toUpperCase();
});
</script>
</body>
</html>