e1b7fd4b0d
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>
1332 lines
38 KiB
HTML
1332 lines
38 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>Flight Row — Six Variants</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=Anton&family=Allerta+Stencil&family=Caveat:wght@500;700&family=DM+Mono:wght@400;500&family=Fraunces:opsz,wght@9..144,400;9..144,700;9..144,900&family=JetBrains+Mono:wght@400;500;700;800&family=VT323&display=swap" rel="stylesheet">
|
||
<style>
|
||
/* =========================================================
|
||
Variables — palette comes straight from the SwiftUI design
|
||
system the engineer has already committed to.
|
||
========================================================= */
|
||
:root {
|
||
--orange: #FF5722;
|
||
--orange-deep: #D9461A;
|
||
--orange-soft: #FF8C59;
|
||
--amber: #FFB938;
|
||
|
||
--navy: #0A1424;
|
||
--navy-mid: #142440;
|
||
--navy-soft: #0F1E38;
|
||
--navy-deep: #050A14;
|
||
|
||
--cream: #F4ECD8;
|
||
--cream-soft: #FAF5E8;
|
||
--cream-deep: #E8DDC4;
|
||
--kraft: #C9B68A;
|
||
|
||
--stamp-green: #2D5A3D;
|
||
--postal-red: #B53026;
|
||
--gold: #C8A951;
|
||
}
|
||
|
||
[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 280ms ease;
|
||
}
|
||
|
||
/* =========================================================
|
||
Page header
|
||
========================================================= */
|
||
.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 of variants
|
||
========================================================= */
|
||
.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;
|
||
}
|
||
|
||
/* iPhone-ish frame so the rows feel placed */
|
||
.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;
|
||
}
|
||
|
||
/* =========================================================
|
||
VARIANT 1 — BOARDING PASS STUB
|
||
A perforated row with a left "stub" and right "body".
|
||
Mono numerics. Subtle barcode strip. Felt-tip date stamp.
|
||
========================================================= */
|
||
.v1-row {
|
||
display: grid;
|
||
grid-template-columns: 88px 1fr;
|
||
background: var(--surface);
|
||
border-radius: 14px;
|
||
overflow: hidden;
|
||
position: relative;
|
||
min-height: 102px;
|
||
cursor: pointer;
|
||
transition: transform 180ms ease;
|
||
}
|
||
|
||
.v1-row:hover { transform: translateY(-1px); }
|
||
|
||
.v1-row::before {
|
||
/* Perforation between stub and body */
|
||
content: "";
|
||
position: absolute;
|
||
left: 88px;
|
||
top: 8px; bottom: 8px;
|
||
width: 1px;
|
||
background: repeating-linear-gradient(
|
||
to bottom,
|
||
var(--ink-3) 0 4px,
|
||
transparent 4px 8px
|
||
);
|
||
}
|
||
|
||
.v1-row::after {
|
||
/* Two punched holes top-bottom on the perforation */
|
||
content: "";
|
||
position: absolute;
|
||
left: 82px; top: -8px;
|
||
width: 14px; height: 14px;
|
||
background: var(--bg);
|
||
border-radius: 50%;
|
||
box-shadow: 0 110px 0 var(--bg);
|
||
}
|
||
|
||
.v1-stub {
|
||
background: linear-gradient(135deg, var(--orange) 0%, var(--orange-deep) 100%);
|
||
padding: 14px 12px 10px;
|
||
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.2em;
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.v1-stub-flight {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-weight: 800;
|
||
font-size: 28px;
|
||
line-height: 1;
|
||
letter-spacing: -0.02em;
|
||
}
|
||
|
||
.v1-stub-bar {
|
||
/* Faux barcode */
|
||
height: 12px;
|
||
background-image: repeating-linear-gradient(
|
||
to right,
|
||
white 0 1px,
|
||
transparent 1px 3px,
|
||
white 3px 4px,
|
||
transparent 4px 7px,
|
||
white 7px 9px,
|
||
transparent 9px 11px
|
||
);
|
||
border-radius: 1px;
|
||
opacity: 0.95;
|
||
}
|
||
|
||
.v1-body {
|
||
padding: 14px 16px;
|
||
display: grid;
|
||
grid-template-columns: 1fr auto;
|
||
gap: 8px;
|
||
align-items: start;
|
||
}
|
||
|
||
.v1-route {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 8px;
|
||
}
|
||
|
||
.v1-iata {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-weight: 800;
|
||
font-size: 22px;
|
||
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;
|
||
text-align: right;
|
||
}
|
||
|
||
.v1-meta {
|
||
grid-column: 1 / -1;
|
||
display: flex;
|
||
gap: 18px;
|
||
margin-top: 4px;
|
||
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 14px 0 0;
|
||
font-weight: 600;
|
||
color: var(--ink);
|
||
}
|
||
|
||
/* =========================================================
|
||
VARIANT 2 — HERO PHOTO DOMINANT
|
||
Wide airframe photo. Bottom gradient. Type overlaid.
|
||
Strongest visual identity per row.
|
||
========================================================= */
|
||
.v2-row {
|
||
position: relative;
|
||
border-radius: 14px;
|
||
overflow: hidden;
|
||
aspect-ratio: 16/9;
|
||
background: #1c2a40;
|
||
box-shadow: 0 1px 0 var(--hairline) inset;
|
||
cursor: pointer;
|
||
transition: transform 180ms ease;
|
||
}
|
||
.v2-row:hover { transform: translateY(-1px); }
|
||
|
||
.v2-photo {
|
||
position: absolute; inset: 0;
|
||
background-size: cover;
|
||
background-position: center;
|
||
filter: saturate(1.05) contrast(1.05);
|
||
}
|
||
|
||
.v2-scrim {
|
||
position: absolute; inset: 0;
|
||
background:
|
||
linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.0) 55%),
|
||
linear-gradient(to bottom, rgba(0,0,0,0.3) 0%, rgba(0,0,0,0) 30%);
|
||
}
|
||
|
||
.v2-top {
|
||
position: absolute; top: 12px; left: 14px; right: 14px;
|
||
display: flex; justify-content: space-between; align-items: flex-start;
|
||
}
|
||
|
||
.v2-flight {
|
||
font-family: "Anton", sans-serif;
|
||
color: white;
|
||
font-size: 22px;
|
||
letter-spacing: 0.08em;
|
||
line-height: 1;
|
||
text-shadow: 0 2px 12px rgba(0,0,0,0.5);
|
||
}
|
||
|
||
.v2-date {
|
||
font-family: "JetBrains Mono", monospace;
|
||
color: white;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.2em;
|
||
text-transform: uppercase;
|
||
opacity: 0.95;
|
||
text-shadow: 0 2px 12px rgba(0,0,0,0.5);
|
||
text-align: right;
|
||
}
|
||
|
||
.v2-bottom {
|
||
position: absolute; bottom: 12px; left: 14px; right: 14px;
|
||
display: flex; align-items: flex-end; justify-content: space-between;
|
||
color: white;
|
||
}
|
||
|
||
.v2-route {
|
||
font-family: "Anton", sans-serif;
|
||
font-size: 38px;
|
||
letter-spacing: 0.04em;
|
||
line-height: 1;
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10px;
|
||
}
|
||
.v2-route .arrow {
|
||
color: var(--orange);
|
||
font-size: 22px;
|
||
transform: translateY(-4px);
|
||
}
|
||
|
||
.v2-pill {
|
||
background: rgba(255,255,255,0.18);
|
||
backdrop-filter: blur(8px);
|
||
padding: 5px 10px;
|
||
border-radius: 999px;
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
/* =========================================================
|
||
VARIANT 3 — LUGGAGE TAG
|
||
Off-cream tag shape. Eyelet. Italic serif "destination" line.
|
||
Punched-card aesthetic. Slight rotation for charm.
|
||
========================================================= */
|
||
.v3-row {
|
||
background:
|
||
radial-gradient(circle at 28px 24px, transparent 9px, var(--kraft) 9.5px),
|
||
var(--kraft);
|
||
color: #1a1408;
|
||
border-radius: 14px 14px 14px 22px;
|
||
padding: 18px 18px 16px 50px;
|
||
position: relative;
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255,255,255,0.25),
|
||
0 1px 0 rgba(0,0,0,0.05);
|
||
min-height: 102px;
|
||
cursor: pointer;
|
||
transition: transform 180ms ease;
|
||
}
|
||
.v3-row:hover { transform: translateY(-1px); }
|
||
|
||
.v3-row::before {
|
||
/* Eyelet ring */
|
||
content: "";
|
||
position: absolute;
|
||
left: 18px; top: 14px;
|
||
width: 20px; height: 20px;
|
||
border-radius: 50%;
|
||
box-shadow:
|
||
inset 0 0 0 1.5px rgba(0,0,0,0.25),
|
||
inset 0 0 0 4px var(--kraft);
|
||
background: var(--bg);
|
||
}
|
||
|
||
.v3-row::after {
|
||
/* Subtle noise / paper texture */
|
||
content: "";
|
||
position: absolute; inset: 0;
|
||
background-image: radial-gradient(rgba(0,0,0,0.05) 1px, transparent 1.5px);
|
||
background-size: 4px 4px;
|
||
border-radius: inherit;
|
||
pointer-events: none;
|
||
mix-blend-mode: multiply;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.v3-eyebrow {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: rgba(26,20,8,0.55);
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.v3-route {
|
||
font-family: "Fraunces", serif;
|
||
font-variation-settings: "opsz" 144, "wght" 900;
|
||
font-size: 26px;
|
||
line-height: 1;
|
||
letter-spacing: -0.02em;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.v3-route em {
|
||
color: var(--orange-deep);
|
||
font-style: italic;
|
||
font-variation-settings: "opsz" 144, "wght" 700;
|
||
}
|
||
|
||
.v3-info {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-end;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.v3-flight {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-weight: 700;
|
||
font-size: 12px;
|
||
letter-spacing: 0.1em;
|
||
color: rgba(26,20,8,0.8);
|
||
}
|
||
|
||
.v3-date {
|
||
font-family: "Fraunces", serif;
|
||
font-style: italic;
|
||
font-size: 13px;
|
||
color: rgba(26,20,8,0.6);
|
||
}
|
||
|
||
/* =========================================================
|
||
VARIANT 4 — SPLIT-FLAP BOARD
|
||
Solari mechanical timetable. Pure black. Amber pixel font.
|
||
Each "letter" in a tiny flap-style chiclet.
|
||
========================================================= */
|
||
.v4-row {
|
||
background: #0a0a0a;
|
||
border-radius: 14px;
|
||
padding: 14px 16px 16px;
|
||
cursor: pointer;
|
||
border: 1px solid #1f1f1f;
|
||
transition: transform 180ms ease;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.v4-row:hover { transform: translateY(-1px); }
|
||
|
||
/* Scanline effect for CRT/board vibe */
|
||
.v4-row::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background-image: repeating-linear-gradient(
|
||
to bottom,
|
||
rgba(255, 255, 255, 0.025) 0 1px,
|
||
transparent 1px 3px
|
||
);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.v4-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px dashed rgba(255, 185, 56, 0.2);
|
||
}
|
||
|
||
.v4-tag {
|
||
font-family: "VT323", monospace;
|
||
font-size: 14px;
|
||
letter-spacing: 0.16em;
|
||
color: var(--amber);
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.v4-date {
|
||
font-family: "VT323", monospace;
|
||
color: rgba(255, 185, 56, 0.6);
|
||
font-size: 14px;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.v4-route {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
}
|
||
|
||
.v4-iata {
|
||
display: flex; gap: 2px;
|
||
}
|
||
|
||
.v4-flap {
|
||
background: #1c1c1c;
|
||
color: var(--amber);
|
||
width: 26px;
|
||
height: 38px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-family: "VT323", monospace;
|
||
font-size: 30px;
|
||
line-height: 1;
|
||
border-radius: 2px;
|
||
box-shadow:
|
||
inset 0 -1px 0 rgba(0,0,0,0.4),
|
||
inset 0 1px 0 rgba(255,185,56,0.05);
|
||
position: relative;
|
||
}
|
||
|
||
.v4-flap::after {
|
||
/* mid-line gap to simulate split flap */
|
||
content: "";
|
||
position: absolute;
|
||
left: 0; right: 0; top: 50%;
|
||
height: 1px;
|
||
background: rgba(0,0,0,0.6);
|
||
transform: translateY(-0.5px);
|
||
}
|
||
|
||
.v4-arrow {
|
||
color: var(--amber);
|
||
font-family: "VT323", monospace;
|
||
font-size: 26px;
|
||
}
|
||
|
||
.v4-meta {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-top: 14px;
|
||
color: rgba(255, 185, 56, 0.85);
|
||
font-family: "VT323", monospace;
|
||
font-size: 16px;
|
||
letter-spacing: 0.06em;
|
||
}
|
||
|
||
/* =========================================================
|
||
VARIANT 5 — POSTCARD STAMP
|
||
Square photo as the "postcard front" on the left.
|
||
Postage stamp with airline. Cancellation circle stamp.
|
||
Date in postal typography.
|
||
========================================================= */
|
||
.v5-row {
|
||
background: var(--surface);
|
||
border-radius: 14px;
|
||
overflow: hidden;
|
||
display: grid;
|
||
grid-template-columns: 116px 1fr;
|
||
position: relative;
|
||
cursor: pointer;
|
||
transition: transform 180ms ease;
|
||
min-height: 116px;
|
||
}
|
||
.v5-row:hover { transform: translateY(-1px); }
|
||
|
||
.v5-photo {
|
||
background-size: cover;
|
||
background-position: center;
|
||
background-color: #1c2a40;
|
||
position: relative;
|
||
}
|
||
|
||
.v5-stamp {
|
||
/* Postage stamp w/ serrated edge using radial-gradient mask */
|
||
position: absolute;
|
||
top: 8px; left: 8px;
|
||
width: 38px; height: 46px;
|
||
background:
|
||
linear-gradient(135deg, var(--orange), var(--orange-deep));
|
||
color: white;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-family: "Allerta Stencil", sans-serif;
|
||
font-size: 8px;
|
||
letter-spacing: 0.15em;
|
||
line-height: 1;
|
||
gap: 3px;
|
||
/* faux serrated edge via box-shadow steps */
|
||
box-shadow:
|
||
0 0 0 2px var(--surface),
|
||
0 1px 6px rgba(0,0,0,0.3);
|
||
}
|
||
|
||
.v5-stamp-num {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-weight: 800;
|
||
font-size: 14px;
|
||
letter-spacing: 0;
|
||
}
|
||
|
||
.v5-stamp-wn {
|
||
font-size: 7px;
|
||
}
|
||
|
||
.v5-body {
|
||
padding: 14px 16px;
|
||
position: relative;
|
||
}
|
||
|
||
.v5-cancel {
|
||
/* Cancellation stamp circle */
|
||
position: absolute;
|
||
top: 10px; right: 10px;
|
||
width: 64px; height: 64px;
|
||
border-radius: 50%;
|
||
border: 1.5px solid var(--postal-red);
|
||
color: var(--postal-red);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-family: "Allerta Stencil", sans-serif;
|
||
font-size: 9px;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
text-align: center;
|
||
line-height: 1.1;
|
||
transform: rotate(-12deg);
|
||
opacity: 0.85;
|
||
background:
|
||
repeating-linear-gradient(
|
||
45deg,
|
||
transparent 0 6px,
|
||
rgba(181, 48, 38, 0.05) 6px 8px
|
||
);
|
||
}
|
||
|
||
[data-theme="dark"] .v5-cancel {
|
||
border-color: #FF8567;
|
||
color: #FF8567;
|
||
}
|
||
|
||
.v5-route {
|
||
font-family: "Fraunces", serif;
|
||
font-variation-settings: "opsz" 144, "wght" 900;
|
||
font-size: 22px;
|
||
letter-spacing: -0.02em;
|
||
margin: 0 80px 6px 0;
|
||
line-height: 1.05;
|
||
}
|
||
|
||
.v5-meta {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 11px;
|
||
color: var(--ink-2);
|
||
letter-spacing: 0.05em;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.v5-stack {
|
||
display: flex;
|
||
gap: 10px;
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 10px;
|
||
color: var(--ink);
|
||
font-weight: 700;
|
||
}
|
||
|
||
.v5-stack span {
|
||
background: var(--bg);
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
/* =========================================================
|
||
VARIANT 6 — BRUTALIST MONO
|
||
No photo. Pure typography. Aggressive hierarchy. Reads
|
||
like a price-list or a Jasper Morrison product card.
|
||
========================================================= */
|
||
.v6-row {
|
||
background: var(--surface);
|
||
border-radius: 14px;
|
||
padding: 16px 16px 16px 18px;
|
||
cursor: pointer;
|
||
transition: transform 180ms ease;
|
||
border-left: 4px solid var(--orange);
|
||
display: grid;
|
||
grid-template-columns: 1fr auto;
|
||
grid-template-rows: auto auto auto;
|
||
column-gap: 16px;
|
||
row-gap: 4px;
|
||
}
|
||
.v6-row:hover { transform: translateY(-1px); }
|
||
|
||
.v6-route {
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-weight: 800;
|
||
font-size: 32px;
|
||
letter-spacing: -0.04em;
|
||
line-height: 1;
|
||
grid-column: 1;
|
||
grid-row: 1 / span 2;
|
||
color: var(--ink);
|
||
}
|
||
|
||
.v6-route .sep {
|
||
color: var(--orange);
|
||
margin: 0 4px;
|
||
font-weight: 400;
|
||
}
|
||
|
||
.v6-flight {
|
||
grid-column: 2;
|
||
grid-row: 1;
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-weight: 800;
|
||
font-size: 14px;
|
||
letter-spacing: 0.08em;
|
||
color: var(--ink);
|
||
text-align: right;
|
||
}
|
||
|
||
.v6-date {
|
||
grid-column: 2;
|
||
grid-row: 2;
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 11px;
|
||
color: var(--ink-3);
|
||
letter-spacing: 0.1em;
|
||
text-align: right;
|
||
}
|
||
|
||
.v6-tags {
|
||
grid-column: 1 / -1;
|
||
grid-row: 3;
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 6px;
|
||
padding-top: 10px;
|
||
border-top: 1px solid var(--hairline);
|
||
font-family: "JetBrains Mono", monospace;
|
||
font-size: 10px;
|
||
color: var(--ink-2);
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
.v6-tags em {
|
||
color: var(--orange);
|
||
font-style: normal;
|
||
font-weight: 800;
|
||
}
|
||
|
||
/* =========================================================
|
||
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);
|
||
}
|
||
|
||
/* =========================================================
|
||
Tiny utilities
|
||
========================================================= */
|
||
.skel {
|
||
background: linear-gradient(90deg,
|
||
var(--surface-2) 25%,
|
||
var(--surface) 50%,
|
||
var(--surface-2) 75%);
|
||
background-size: 200% 100%;
|
||
animation: skel 1.4s ease-in-out infinite;
|
||
}
|
||
@keyframes skel { from { background-position: 200% 0 } to { background-position: -200% 0 } }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="page">
|
||
|
||
<header class="page-head">
|
||
<div>
|
||
<div class="page-eyebrow">FLIGHT ROW · SIX DIRECTIONS</div>
|
||
<h1 class="page-title">Make the row earn its rent</h1>
|
||
<p class="page-sub">Six aesthetic directions for the History tab's flight row, each executed as 3 stacked rows so you can see how they sit in a feed. Pick one — or mix elements across — and we'll port it back to SwiftUI.</p>
|
||
</div>
|
||
<button class="theme-toggle" id="themeToggle" type="button">
|
||
<span id="themeLabel">DARK</span>
|
||
</button>
|
||
</header>
|
||
|
||
<div class="grid">
|
||
|
||
<!-- ============================================
|
||
V1 — BOARDING PASS STUB
|
||
============================================ -->
|
||
<section class="variant">
|
||
<div class="variant-head">
|
||
<span class="variant-no">01</span>
|
||
<h2 class="variant-name">Boarding Pass Stub</h2>
|
||
</div>
|
||
<p class="variant-note">Perforated tear-line, mono numerics, faux barcode strip. Reads like the stub you'd tear off at the gate. Most "I flew this" of the bunch.</p>
|
||
<div class="phone">
|
||
<div class="phone-screen">
|
||
<div class="island"></div>
|
||
<div class="rows">
|
||
|
||
<!-- Row 1 -->
|
||
<div class="v1-row">
|
||
<div class="v1-stub">
|
||
<div class="v1-stub-iata">WN</div>
|
||
<div class="v1-stub-flight">0007</div>
|
||
<div class="v1-stub-bar"></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>
|
||
|
||
<!-- Row 2 -->
|
||
<div class="v1-row">
|
||
<div class="v1-stub">
|
||
<div class="v1-stub-iata">WN</div>
|
||
<div class="v1-stub-flight">1942</div>
|
||
<div class="v1-stub-bar"></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>
|
||
|
||
<!-- Row 3 -->
|
||
<div class="v1-row">
|
||
<div class="v1-stub">
|
||
<div class="v1-stub-iata">WN</div>
|
||
<div class="v1-stub-flight">5476</div>
|
||
<div class="v1-stub-bar"></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 — HERO PHOTO DOMINANT
|
||
============================================ -->
|
||
<section class="variant">
|
||
<div class="variant-head">
|
||
<span class="variant-no">02</span>
|
||
<h2 class="variant-name">Hero Photo Dominant</h2>
|
||
</div>
|
||
<p class="variant-note">Full-width airframe photo. Bottom gradient. Anton display sets the route in capital letters. The most magazine-like.</p>
|
||
<div class="phone">
|
||
<div class="phone-screen">
|
||
<div class="island"></div>
|
||
<div class="rows">
|
||
|
||
<div class="v2-row">
|
||
<div class="v2-photo" style="background-image:url('https://images.unsplash.com/photo-1583416750470-965b2707b355?auto=format&fit=crop&w=900&q=70')"></div>
|
||
<div class="v2-scrim"></div>
|
||
<div class="v2-top">
|
||
<div class="v2-flight">WN 0007</div>
|
||
<div class="v2-date">MAY 27 · 2026</div>
|
||
</div>
|
||
<div class="v2-bottom">
|
||
<div class="v2-route">DAL <span class="arrow">▶</span> HOU</div>
|
||
<div class="v2-pill">239 NM · B737</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v2-row">
|
||
<div class="v2-photo" style="background-image:url('https://images.unsplash.com/photo-1542296332-2e4473faf563?auto=format&fit=crop&w=900&q=70')"></div>
|
||
<div class="v2-scrim"></div>
|
||
<div class="v2-top">
|
||
<div class="v2-flight">WN 1942</div>
|
||
<div class="v2-date">JAN 27 · 2024</div>
|
||
</div>
|
||
<div class="v2-bottom">
|
||
<div class="v2-route">LAS <span class="arrow">▶</span> DAL</div>
|
||
<div class="v2-pill">1056 NM · B738</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v2-row">
|
||
<!-- Skeleton fallback row -->
|
||
<div class="v2-photo skel"></div>
|
||
<div class="v2-scrim" style="background:linear-gradient(to top,rgba(0,0,0,0.85),rgba(0,0,0,0) 55%)"></div>
|
||
<div class="v2-top">
|
||
<div class="v2-flight">WN 5476</div>
|
||
<div class="v2-date">MAR 19 · 2024</div>
|
||
</div>
|
||
<div class="v2-bottom">
|
||
<div class="v2-route">BNA <span class="arrow">▶</span> BOS</div>
|
||
<div class="v2-pill">943 NM · B38M</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================
|
||
V3 — LUGGAGE TAG
|
||
============================================ -->
|
||
<section class="variant">
|
||
<div class="variant-head">
|
||
<span class="variant-no">03</span>
|
||
<h2 class="variant-name">Luggage Tag</h2>
|
||
</div>
|
||
<p class="variant-note">Kraft-paper colorway, eyelet rivet, Fraunces italic for the destination. Reads like a souvenir glued in a scrapbook. Cleanly distinct from anything else in the app.</p>
|
||
<div class="phone">
|
||
<div class="phone-screen">
|
||
<div class="island"></div>
|
||
<div class="rows">
|
||
|
||
<div class="v3-row">
|
||
<div class="v3-eyebrow">SOUTHWEST · WN 7</div>
|
||
<div class="v3-route">DAL <em>to</em> HOU</div>
|
||
<div class="v3-info">
|
||
<span class="v3-flight">B737 · N7747C · 239 mi</span>
|
||
<span class="v3-date">May 27, 2026</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v3-row">
|
||
<div class="v3-eyebrow">SOUTHWEST · WN 1942</div>
|
||
<div class="v3-route">LAS <em>to</em> DAL</div>
|
||
<div class="v3-info">
|
||
<span class="v3-flight">B738 · N281WN · 1056 mi</span>
|
||
<span class="v3-date">Jan 27, 2024</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v3-row">
|
||
<div class="v3-eyebrow">SOUTHWEST · WN 1136</div>
|
||
<div class="v3-route">SJU <em>to</em> MCO</div>
|
||
<div class="v3-info">
|
||
<span class="v3-flight">B738 · N280WN · 1187 mi</span>
|
||
<span class="v3-date">Apr 11, 2022</span>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================
|
||
V4 — SPLIT-FLAP BOARD
|
||
============================================ -->
|
||
<section class="variant">
|
||
<div class="variant-head">
|
||
<span class="variant-no">04</span>
|
||
<h2 class="variant-name">Split-Flap Departures</h2>
|
||
</div>
|
||
<p class="variant-note">Solari mechanical aesthetic. Pure black, amber pixel-font, faux scanlines, individual character chiclets with a split line. Lives outside light/dark mode by design.</p>
|
||
<div class="phone">
|
||
<div class="phone-screen">
|
||
<div class="island"></div>
|
||
<div class="rows">
|
||
|
||
<div class="v4-row">
|
||
<div class="v4-head">
|
||
<span class="v4-tag">WN 0007</span>
|
||
<span class="v4-date">27 MAY 26</span>
|
||
</div>
|
||
<div class="v4-route">
|
||
<div class="v4-iata">
|
||
<div class="v4-flap">D</div><div class="v4-flap">A</div><div class="v4-flap">L</div>
|
||
</div>
|
||
<span class="v4-arrow">▶▶▶</span>
|
||
<div class="v4-iata">
|
||
<div class="v4-flap">H</div><div class="v4-flap">O</div><div class="v4-flap">U</div>
|
||
</div>
|
||
</div>
|
||
<div class="v4-meta">
|
||
<span>B737</span>
|
||
<span>N7747C</span>
|
||
<span>239 NM</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v4-row">
|
||
<div class="v4-head">
|
||
<span class="v4-tag">WN 1942</span>
|
||
<span class="v4-date">27 JAN 24</span>
|
||
</div>
|
||
<div class="v4-route">
|
||
<div class="v4-iata">
|
||
<div class="v4-flap">L</div><div class="v4-flap">A</div><div class="v4-flap">S</div>
|
||
</div>
|
||
<span class="v4-arrow">▶▶▶</span>
|
||
<div class="v4-iata">
|
||
<div class="v4-flap">D</div><div class="v4-flap">A</div><div class="v4-flap">L</div>
|
||
</div>
|
||
</div>
|
||
<div class="v4-meta">
|
||
<span>B738</span>
|
||
<span>N281WN</span>
|
||
<span>1056 NM</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v4-row">
|
||
<div class="v4-head">
|
||
<span class="v4-tag">WN 5476</span>
|
||
<span class="v4-date">19 MAR 24</span>
|
||
</div>
|
||
<div class="v4-route">
|
||
<div class="v4-iata">
|
||
<div class="v4-flap">B</div><div class="v4-flap">N</div><div class="v4-flap">A</div>
|
||
</div>
|
||
<span class="v4-arrow">▶▶▶</span>
|
||
<div class="v4-iata">
|
||
<div class="v4-flap">B</div><div class="v4-flap">O</div><div class="v4-flap">S</div>
|
||
</div>
|
||
</div>
|
||
<div class="v4-meta">
|
||
<span>B38M</span>
|
||
<span>N8642E</span>
|
||
<span>943 NM</span>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================
|
||
V5 — POSTCARD STAMP
|
||
============================================ -->
|
||
<section class="variant">
|
||
<div class="variant-head">
|
||
<span class="variant-no">05</span>
|
||
<h2 class="variant-name">Postcard + Cancellation</h2>
|
||
</div>
|
||
<p class="variant-note">Photo as postcard front. Postage stamp overlays it. Red cancellation circle on the body shouts the route. Most "collected stamp in a passport" of all six.</p>
|
||
<div class="phone">
|
||
<div class="phone-screen">
|
||
<div class="island"></div>
|
||
<div class="rows">
|
||
|
||
<div class="v5-row">
|
||
<div class="v5-photo" style="background-image:url('https://images.unsplash.com/photo-1583416750470-965b2707b355?auto=format&fit=crop&w=600&q=70')">
|
||
<div class="v5-stamp">
|
||
<span class="v5-stamp-num">WN7</span>
|
||
<span class="v5-stamp-wn">★ ★ ★</span>
|
||
</div>
|
||
</div>
|
||
<div class="v5-body">
|
||
<div class="v5-cancel">DAL · HOU<br>MAY 27 26</div>
|
||
<h3 class="v5-route">Dallas to Houston</h3>
|
||
<p class="v5-meta">239 mi · 50 m airborne</p>
|
||
<div class="v5-stack">
|
||
<span>B737</span><span>N7747C</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v5-row">
|
||
<div class="v5-photo" style="background-image:url('https://images.unsplash.com/photo-1436491865332-7a61a109cc05?auto=format&fit=crop&w=600&q=70')">
|
||
<div class="v5-stamp">
|
||
<span class="v5-stamp-num">WN1942</span>
|
||
<span class="v5-stamp-wn">★ ★ ★</span>
|
||
</div>
|
||
</div>
|
||
<div class="v5-body">
|
||
<div class="v5-cancel">LAS · DAL<br>JAN 27 24</div>
|
||
<h3 class="v5-route">Las Vegas to Dallas</h3>
|
||
<p class="v5-meta">1,056 mi · 2h 14m airborne</p>
|
||
<div class="v5-stack">
|
||
<span>B738</span><span>N281WN</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v5-row">
|
||
<div class="v5-photo skel"></div>
|
||
<div class="v5-body">
|
||
<div class="v5-cancel">BNA · BOS<br>MAR 19 24</div>
|
||
<h3 class="v5-route">Nashville to Boston</h3>
|
||
<p class="v5-meta">943 mi · 2h 08m airborne</p>
|
||
<div class="v5-stack">
|
||
<span>B38M</span><span>N8642E</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============================================
|
||
V6 — BRUTALIST MONO
|
||
============================================ -->
|
||
<section class="variant">
|
||
<div class="variant-head">
|
||
<span class="variant-no">06</span>
|
||
<h2 class="variant-name">Brutalist Mono</h2>
|
||
</div>
|
||
<p class="variant-note">No photo. No chrome. Type does all the work. Reads like a Swiss timetable or a Jasper Morrison product card. Pure information density.</p>
|
||
<div class="phone">
|
||
<div class="phone-screen">
|
||
<div class="island"></div>
|
||
<div class="rows">
|
||
|
||
<div class="v6-row">
|
||
<div class="v6-route">DAL<span class="sep">/</span>HOU</div>
|
||
<div class="v6-flight">WN0007</div>
|
||
<div class="v6-date">27.05.26</div>
|
||
<div class="v6-tags">
|
||
<span>B737</span>·
|
||
<span>N7747C</span>·
|
||
<span><em>239</em> nm</span>·
|
||
<span>2nd time on this airframe</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v6-row">
|
||
<div class="v6-route">LAS<span class="sep">/</span>DAL</div>
|
||
<div class="v6-flight">WN1942</div>
|
||
<div class="v6-date">27.01.24</div>
|
||
<div class="v6-tags">
|
||
<span>B738</span>·
|
||
<span>N281WN</span>·
|
||
<span><em>1056</em> nm</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="v6-row">
|
||
<div class="v6-route">BNA<span class="sep">/</span>BOS</div>
|
||
<div class="v6-flight">WN5476</div>
|
||
<div class="v6-date">19.03.24</div>
|
||
<div class="v6-tags">
|
||
<span>B38M</span>·
|
||
<span>N8642E</span>·
|
||
<span><em>943</em> nm</span>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<footer class="footer">
|
||
Tap the toggle to switch theme. All six are buildable in SwiftUI — variant 4 (split-flap) needs the most custom views, variant 6 (brutalist mono) needs the least. Photo-dominant variants assume planespotters photos load (~50–150KB ea.). Skeleton states are shown for variants 2 + 5. Edit <code>flight-row-variants.html</code> to tweak.
|
||
</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>
|