Five weeks of undocumented evolution left the docs describing a site that
partly no longer exists, with nothing marking which documents were historical.
The code is treated as the source of truth throughout; every claim below was
verified against code, config or the Makefile rather than inferred.
Two mechanisms, following patterns the repo already used:
- docs/reference/superseded-decisions.md (new) — one authoritative table of all
14 reversals: what was planned, where, what is true now, when, and why. Plus
a short list of decisions that were NOT reversed, since their planning docs
are old enough to look suspect.
- Inline "> **Superseded ...**" notes at each stale claim, so a claim can never
be read un-corrected. This mirrors the existing "> History:" notes in
architecture.md and "> **Changed 2026-07:**" in trip-switching.md.
Scope split by tense: present-tense docs (CLAUDE.md, reference/, guides/,
README.md, CONCEPTS.md) are corrected; past-tense records (plans/, specs/,
milestones/, summary.md, pm-analysis.md) are annotated only, never rewritten —
their staleness is what makes them records.
Present-tense corrections:
- CLAUDE.md asserted css-compiled/ is generated from css/style.css and
css/tokens.css. That source relationship does not exist: css/ is hand-authored
and served directly via assets.addCss in partials/base.html.twig, while
css-compiled/ is esbuild output from the CSS imports inside js/src/*.js.
Highest-severity finding — an always-loaded file inviting a hand-edit of a
generated bundle.
- README.md documented every remote-* target without the -test/-prod suffix
guard-env requires, so its entire server runbook was unrunnable, and listed
7 of ~20 targets while CLAUDE.md designates it authoritative for the full
list. Rewritten with all targets, grouped, and the suffix rule stated.
- README.md told readers to "git clone" into user/, which is a submodule.
- architecture.md: nav is Home + Trips + (authenticated) New Post, not
"Home + Past Trips only"; template tree omitted trips.html.twig,
post-form.html.twig and forms/, and placed base.html.twig at templates/ root
rather than in partials/; entry-actions has three API routes, not just delete;
added the undocumented css-compiled/maplibre-gl.css output and a section on
the /post pin editor as the one sanctioned non-entry-map map.
- design-system.md: documented 13 colour tokens against 19 in tokens.css
(missing --color-error, --color-draft-accent and four glass overlays); claimed
"all 3 map templates"; and described --color-canvas as "white".
- design-system-light.md documented a light palette in present tense. No light
mode exists — tokens.css has a single :root block, no prefers-color-scheme or
data-theme switch, and no light hex appears in css/. Banner added.
- posting.md said photos were optional; they are required, 1-6. It documented
hero_image, which was removed for entries (stories keep it). It had no mention
of the frontend edit flow or photo editor, both shipped 2026-07-08. Photo
files are photo-01..NN, zero-padded.
- working/README.md advertised summary.md as the project's "current state" while
summary.md describes Leaflet, /tracker, /map and /stats. Most misleading line
in the tree.
- CLAUDE.md: recorded js/src/location-map.js as the one sanctioned exception to
the single-map-path rule, and documented that make start/setup fail on a clean
checkout because docker-compose.yml still builds a travel-memories service
whose source moved out in a80b0a9.
Also: 2026-07-23-post-form-location-override.md read "Not started" while merged
in user/ as dd19995; status corrected.
Findings that are not documentation problems — the compose breakage, a
repeatable drift check, the unused shortcode-gallery-plusplus, and demo fixtures
for retired views — are collected in
docs/working/2026-07-25-doc-drift-recommendations.md and deliberately not acted
on. Design and verification method: docs/working/specs/2026-07-25-docs-reconciliation-design.md
The submodule gitlink is deliberately not bumped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
387 lines
17 KiB
Markdown
387 lines
17 KiB
Markdown
# Into the East — Design Spec
|
|
|
|
**Date:** 2026-06-18
|
|
**Status:** Implemented — dark theme (see `design-system-light.md` for the original light-mode palette)
|
|
|
|
---
|
|
|
|
## 1. Direction
|
|
|
|
**The brief:** A personal travel journal, sole author, trip to East Asia. Three weeks to implement before departure. Audience is both friends/family and the occasional curious stranger.
|
|
|
|
**The position:** Neither Polarsteps nor FindPenguins. Both optimize for social sharing of travel data. This site optimizes for **the story** — and should feel like reading a well-edited travel journal, not using an app.
|
|
|
|
**What we steal from each:**
|
|
- Polarsteps: photography-first hierarchy, airy whitespace, map as the emotional spine of the trip
|
|
- FindPenguins: typography as brand identity, stats as trophy case, hierarchical trip → entry structure
|
|
|
|
**What we do better than both:**
|
|
- Web-native: fast, linkable, no install, works on any browser
|
|
- Single author = pure editorial voice, no social noise
|
|
- Full CSS control = real typographic identity, not generic app chrome
|
|
- Editorial feel: more travel magazine, less productivity dashboard
|
|
|
|
**Aesthetic direction:** Field notes. The kind of journal a thoughtful traveler would carry — clean, direct, lets the photography speak. Sophisticated without effort.
|
|
|
|
**The one aesthetic risk:** Full-bleed hero photography with a translucent date+location overlay at the bottom of each card. The photo IS the entry card — not a thumbnail beside text. This is the single element that distinguishes this design from both reference apps and from typical blog layouts.
|
|
|
|
---
|
|
|
|
## 2. Color System
|
|
|
|
### Palette (dark theme — as implemented)
|
|
|
|
**Dark is the only theme.** `css/tokens.css` has a single `:root` block; there is no
|
|
`prefers-color-scheme` query and no `data-theme` switch. `design-system-light.md` records the
|
|
pre-dark-mode palette, which was never implemented as a switchable theme — see
|
|
[`superseded-decisions.md`](superseded-decisions.md) → R9.
|
|
|
|
The authoritative list is `user/themes/intotheeast/css/tokens.css`.
|
|
|
|
| Token | Hex | Usage |
|
|
|---|---|---|
|
|
| `--color-paper` | `#1A1814` | Page background — warm near-black |
|
|
| `--color-canvas` | `#22201B` | Card surfaces, form backgrounds |
|
|
| `--color-ink` | `#EDE8DF` | Primary text — warm cream |
|
|
| `--color-ink-2` | `#B8B0A4` | Body text — muted warm |
|
|
| `--color-ink-muted` | `#90887E` | Labels, timestamps, captions |
|
|
| `--color-border` | `#2E2B25` | Standard dividers |
|
|
| `--color-border-soft` | `#252219` | Subtle dividers |
|
|
| `--color-accent` | `#2E9880` | Teal — lightened for dark-background contrast |
|
|
| `--color-accent-hover` | `#287A68` | Hover/pressed teal |
|
|
| `--color-accent-light` | `#1A2E29` | Pale teal tint backgrounds |
|
|
| `--color-accent-on` | `#FFFFFF` | Text on accent surfaces |
|
|
| `--color-surface-raised` | `#2A2720` | Elevated surfaces: tooltips, hover |
|
|
| `--color-ink-inverse` | `#17171A` | Text on accent-coloured buttons |
|
|
| `--color-error` | `#c0392b` | Validation errors, form error status |
|
|
| `--color-draft-accent` | `#E0A458` | Warm amber — draft/unpublished badges |
|
|
|
|
#### Glass overlays
|
|
|
|
Paper colour at opacity, used by the story components. Computed with `color-mix()` rather than fixed
|
|
hex, so they track `--color-paper` automatically.
|
|
|
|
| Token | Value | Usage |
|
|
|---|---|---|
|
|
| `--color-paper-glass-low` | `color-mix(in srgb, var(--color-paper) 8%, transparent)` | Faintest scrim |
|
|
| `--color-paper-glass-mid` | `color-mix(in srgb, var(--color-paper) 25%, transparent)` | Standard overlay |
|
|
| `--color-paper-glass-high` | `color-mix(in srgb, var(--color-paper) 55%, transparent)` | Heavy scrim over imagery |
|
|
| `--color-paper-glass-hover` | `color-mix(in srgb, var(--color-paper) 80%, transparent)` | Hover state on a glass surface |
|
|
|
|
### Rationale for accent color
|
|
|
|
Teal was chosen for its associations with bamboo, celadon porcelain, ancient jade, and temple gardens — without being literal or kitsch. On the dark palette, the original `#1F6B5A` was too low-contrast; it was lightened to `#2E9880` to maintain readable contrast against the warm near-black backgrounds. See `design-system-light.md` for the original light-palette values.
|
|
|
|
---
|
|
|
|
## 3. Typography
|
|
|
|
### Fonts
|
|
|
|
| Role | Family | Fallback | Source |
|
|
|---|---|---|---|
|
|
| Display / Headings | DM Serif Display | Georgia, serif | Google Fonts |
|
|
| UI / Body / Labels | DM Sans | -apple-system, BlinkMacSystemFont, sans-serif | Google Fonts |
|
|
|
|
**Google Fonts URL:**
|
|
```
|
|
https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&family=DM+Serif+Display:ital@0;1&display=swap
|
|
```
|
|
|
|
**Why this pairing:**
|
|
DM Serif Display has a calligraphic quality — slightly editorial, authoritative but not stiff. Paired with DM Sans (its designed companion) the system is cohesive. DM Sans is neutral and highly legible at all sizes. Both are under-used relative to Inter/Lato/Playfair, so the combination has a distinctive voice without being trendy.
|
|
|
|
### Type Scale
|
|
|
|
| Token | Size | Line Height | Usage |
|
|
|---|---|---|---|
|
|
| `--text-xs` | 0.75rem (12px) | 1.5 | Badges, captions |
|
|
| `--text-sm` | 0.875rem (14px) | 1.5 | Meta, timestamps, labels |
|
|
| `--text-base` | 1rem (16px) | 1.65 | Body paragraphs |
|
|
| `--text-md` | 1.125rem (18px) | 1.55 | Lead text, intro paragraphs |
|
|
| `--text-lg` | 1.375rem (22px) | 1.35 | Subheadings, card titles (mobile) |
|
|
| `--text-xl` | 1.75rem (28px) | 1.25 | Entry card titles |
|
|
| `--text-2xl` | 2.25rem (36px) | 1.2 | Page headings, entry titles (desktop) |
|
|
| `--text-3xl` | 3rem (48px) | 1.1 | Hero entry title |
|
|
|
|
### Usage rules
|
|
|
|
- Entry titles: `--font-display`, `--text-xl` (mobile) / `--text-2xl` (desktop)
|
|
- Site title in header: `--font-display`, `--text-lg`
|
|
- All other UI text: `--font-ui`
|
|
- Body paragraphs: `--font-ui`, `--text-base`, `--leading-normal`
|
|
- Timestamps/badges: `--font-ui`, `--text-xs`, uppercase, `letter-spacing: 0.07em`
|
|
|
|
---
|
|
|
|
## 4. Spacing & Layout
|
|
|
|
### Spacing scale (4px base unit)
|
|
|
|
| Token | Value |
|
|
|---|---|
|
|
| `--space-1` | 0.25rem (4px) |
|
|
| `--space-2` | 0.5rem (8px) |
|
|
| `--space-3` | 0.75rem (12px) |
|
|
| `--space-4` | 1rem (16px) |
|
|
| `--space-5` | 1.25rem (20px) |
|
|
| `--space-6` | 1.5rem (24px) |
|
|
| `--space-8` | 2rem (32px) |
|
|
| `--space-10` | 2.5rem (40px) |
|
|
| `--space-12` | 3rem (48px) |
|
|
| `--space-16` | 4rem (64px) |
|
|
|
|
### Layout
|
|
|
|
- Content max-width: `720px` (comfortable reading at any font size)
|
|
- Page horizontal padding: `1.25rem` (mobile), `1.5rem` (desktop ≥520px)
|
|
- Header height: `60px` (fixed, for JS offset calculations)
|
|
- Map page: full viewport, no content max-width constraint
|
|
|
|
### Border radius
|
|
|
|
| Token | Value | Usage |
|
|
|---|---|---|
|
|
| `--radius-sm` | 4px | Photo corners, small chips |
|
|
| `--radius-md` | 8px | Cards, buttons, inputs |
|
|
| `--radius-lg` | 12px | Large cards, modals |
|
|
| `--radius-full` | 9999px | Pills, badges |
|
|
|
|
### Shadows
|
|
|
|
| Token | Value | Usage |
|
|
|---|---|---|
|
|
| `--shadow-sm` | `0 1px 3px rgba(0,0,0,0.08)` | Stat blocks, subtle elevation |
|
|
| `--shadow-md` | `0 4px 12px rgba(0,0,0,0.10)` | Cards on hover, dropdowns |
|
|
| `--shadow-lg` | `0 8px 24px rgba(0,0,0,0.14)` | Lightbox, modals |
|
|
|
|
---
|
|
|
|
## 5. Component Inventory
|
|
|
|
### 5.1 Site Header
|
|
|
|
```
|
|
[ into the east ] [ Journal Map Stats ]
|
|
← accent bar across top (3px) ───────────────────────────────
|
|
```
|
|
|
|
- Top border: `3px solid var(--color-accent)` — thin accent bar signals the brand color without decorating
|
|
- Site title: DM Serif Display, `--text-lg`, no decoration
|
|
- Nav links: DM Sans, `--text-sm`, weight 500, `--color-ink-2`
|
|
- Active nav link: `--color-accent`, weight 600
|
|
- Mobile: same layout, title slightly smaller, nav links compact
|
|
- Background: `--color-canvas` (`#22201B` in the dark theme), bottom border `1px solid var(--color-border)`
|
|
|
|
### 5.2 Entry Feed Card — With Photo
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ │
|
|
│ [photo] │ ← full-width, 16:9, rounded corners
|
|
│ │
|
|
│ 18 JUN · 📍 Kyoto, Japan │ ← overlaid at bottom, gradient mask
|
|
└─────────────────────────────────────┘
|
|
Arrived in Tokyo ← DM Serif Display, --text-xl
|
|
After 14 hours of flying I finally ← body excerpt, --color-ink-2
|
|
set foot on Japanese soil...
|
|
Read entry → ← --color-accent, --text-sm
|
|
```
|
|
|
|
- Photo: `aspect-ratio: 16/9`, `object-fit: cover`, `border-radius: var(--radius-md)`
|
|
- Photo has a `linear-gradient(to top, rgba(0,0,0,0.55), transparent)` overlay at the bottom 40%
|
|
- Date + location sit on top of gradient in white text (`rgba(255,255,255,0.92)`)
|
|
- On hover: photo scales to 1.03 (subtle zoom, 0.4s ease)
|
|
- Title below photo: DM Serif Display, hover turns `--color-accent`
|
|
- Card separation: `padding-bottom: var(--space-12)` + `border-bottom: 1px solid var(--color-border)`
|
|
|
|
### 5.3 Entry Feed Card — No Photo
|
|
|
|
When no photo is available, fall back to a text-only layout:
|
|
|
|
```
|
|
18 JUN 2026 · 📍 Kyoto, Japan ← meta row, --text-sm, --color-ink-muted
|
|
|
|
Arrived in Tokyo ← DM Serif Display, --text-xl
|
|
After 14 hours of flying...
|
|
Read entry →
|
|
```
|
|
|
|
- No photo container
|
|
- Meta (date + location) on one line above title, small + muted
|
|
|
|
### 5.4 Single Entry Page
|
|
|
|
```
|
|
Wednesday, 18 June 2026 ← --text-sm, --color-ink-muted, uppercase
|
|
📍 Kyoto, Japan · ⛅ Partly cloudy · 22°C
|
|
|
|
Arrived in Tokyo ← DM Serif Display, --text-2xl / --text-3xl
|
|
─────────────────────────────────────
|
|
Body text content... ← --font-ui, --text-base/md
|
|
|
|
[Photo gallery — 2 or 3 col grid]
|
|
|
|
← Back to journal
|
|
```
|
|
|
|
- The entry title uses `--font-display` at largest scale
|
|
- A thin `--color-border` rule separates the header from the body
|
|
- Body text is `--text-md` (18px) for comfortable long-form reading
|
|
- Full-bleed hero option: if a `hero_image` is set, it spans the full content width with a bottom margin
|
|
|
|
### 5.5 Post Form (Author View)
|
|
|
|
```
|
|
New Entry
|
|
|
|
Title * [________________________]
|
|
Date & Time [2026-06-18 14:30 ]
|
|
What happened [ ]
|
|
today? [ ]
|
|
[ ]
|
|
|
|
Photos [ + Add photos (max 4) ]
|
|
|
|
City [________________________]
|
|
Country [________________________]
|
|
|
|
[ 📍 Get Location ] [ 🌤 Get Weather ]
|
|
✓ Location captured: Kyoto, Japan ← status line
|
|
|
|
[ Post Entry ]
|
|
```
|
|
|
|
UX changes from current:
|
|
- Lat/lng inputs **hidden from the UI** (remain in the form as `display:none` for data capture, filled by JS)
|
|
- Location status shows captured city/country + coordinates in a single line (not separate status paragraphs)
|
|
- Photo upload area: larger touch target, visual indication of count
|
|
- "Post Entry" button: `--color-accent` background, full-width on mobile, `min-height: 52px`
|
|
- Form fields: `--radius-md` corners, `--color-border` border, focus ring in `--color-accent`
|
|
- Section spacing: generous vertical rhythm on mobile
|
|
|
|
### 5.6 Stats Page
|
|
|
|
```
|
|
┌────────────┐ ┌────────────┐
|
|
│ 42 │ │ 18 │
|
|
│ days on │ │ entries │
|
|
│ the road │ │ posted │
|
|
└────────────┘ └────────────┘
|
|
┌────────────┐ ┌────────────┐
|
|
│ 6 │ │ ~14,200 │
|
|
│ countries │ │ km │
|
|
│ visited │ │ traveled │
|
|
└────────────┘ └────────────┘
|
|
|
|
Countries visited
|
|
Japan · South Korea · Mongolia · Russia · Finland · Estonia
|
|
```
|
|
|
|
- Numbers: `--font-display`, `--text-3xl`, `--color-accent`
|
|
- Labels: `--font-ui`, `--text-xs`, uppercase, `--color-ink-muted`
|
|
- Cards: white, `--shadow-sm`, `--radius-md`, centered
|
|
|
|
### 5.7 Map Page
|
|
|
|
Minimal changes — the map itself is good. Style improvements:
|
|
- MapLibre popups: match the new design (DM Sans, `--radius-md`, `--shadow-md`)
|
|
- Markers: keep current circle style, update color to `--color-accent`
|
|
- Feed mini-map wrapper: match `--radius-md`, `--border`
|
|
|
|
---
|
|
|
|
## 6. UX Flows
|
|
|
|
### 6.1 Reader — First Visit
|
|
|
|
1. Land on `/dailies` (journal feed)
|
|
2. See mini-map above fold (if entries exist) — route tells the geographic story at a glance
|
|
3. First entry card: full-bleed hero photo with date/location overlay — immediate emotional pull
|
|
4. Scroll through chronological entries
|
|
5. Tap/click entry → entry detail page
|
|
6. Navigate back via "← Back to journal"
|
|
|
|
**Key principle:** The reader should understand the journey spatially (mini-map) and emotionally (hero photo) before reading a single word.
|
|
|
|
### 6.2 Reader — Navigation
|
|
|
|
- Journal: primary destination, the feed
|
|
- Map: geographic exploration mode
|
|
- Stats: quick numbers, satisfying progress indicator
|
|
- No account required, no social friction, no login prompt for readers
|
|
|
|
### 6.3 Author — Posting from Mobile
|
|
|
|
1. Navigate to `/post` (bookmark on home screen)
|
|
2. Already logged in (Grav session persists) — form loads directly
|
|
3. **Title**: tap → type (autofocused)
|
|
4. **Date & Time**: auto-filled to now, adjust if needed
|
|
5. **Content**: write what happened
|
|
6. **Photos**: tap "Add photos" → camera or gallery → select up to 4
|
|
7. **Location**: tap "📍 Get Location" → GPS fires → status shows "Kyoto, Japan · 34.985, 135.758" in one line
|
|
8. **Weather**: tap "🌤 Get Weather" (works only if location was captured) → status shows "Partly cloudy · 22°C"
|
|
9. **City/Country**: auto-populated from GPS is a nice-to-have for v2; in v1 type manually if needed
|
|
10. Tap "Post Entry" → success message → 2-second pause → redirect to /dailies (new entry visible at top)
|
|
|
|
**Key principles:**
|
|
- One-thumb operation for all critical actions on mobile
|
|
- Location/weather are conveniences, not blockers — can skip both
|
|
- Visual feedback is immediate (status line updates on GPS response)
|
|
- After submit: don't leave author on a success message page; redirect to see their new post
|
|
|
|
---
|
|
|
|
## 7. Mobile Specifics
|
|
|
|
### Touch targets
|
|
- All interactive elements: `min-height: 44px`, `min-width: 44px` (Apple HIG standard)
|
|
- Form buttons: `min-height: 52px` on the post form (primary CTA)
|
|
- Nav links: `padding: 0.5rem 0.75rem`
|
|
|
|
### Viewport concerns
|
|
- Map page: `height: calc(100vh - 60px)`, `touch-action: none` on map container — prevents scroll trap
|
|
- Photo lightbox: full viewport overlay, swipe-friendly (keyboard + click already implemented)
|
|
- Form on mobile: single-column, generous input padding `0.875rem 1rem`, `font-size: 1rem` (prevents iOS zoom on focus)
|
|
|
|
### Performance
|
|
- Google Fonts: loaded with `preconnect` hints
|
|
- Images: `loading="lazy"` on all non-above-fold images (already in place)
|
|
- MapLibre: loaded from CDN, only on pages that need it
|
|
- No new JS frameworks — vanilla JS throughout
|
|
|
|
---
|
|
|
|
## 8. Tech Stack Decision
|
|
|
|
**Keep Grav CMS.** With a 3-week timeline, replacing it would consume all available time on migration rather than design improvements.
|
|
|
|
| Layer | Decision | Rationale |
|
|
|---|---|---|
|
|
| Backend | Grav CMS (PHP, Twig) — unchanged | Works, flat-file, no DB |
|
|
| CSS | Vanilla CSS + custom properties (design tokens) | No build step, full control, ships as one file |
|
|
| JS | Vanilla JS — unchanged | Current JS is well-structured, scope doesn't justify a framework |
|
|
| Icons | Unicode + emoji (current) | No dependency, works everywhere |
|
|
| Fonts | Google Fonts via CDN | Two fonts, display-swap, negligible impact |
|
|
| Maps | MapLibre GL JS | Replaced Leaflet. One shared display-map partial (`partials/entry-map.html.twig`), not three templates — see [`superseded-decisions.md`](superseded-decisions.md) → R12 |
|
|
| Build | None — no build pipeline | Grav's asset pipeline handles minification if needed |
|
|
|
|
**No Alpine.js, no TypeScript, no Tailwind.** The site has clean vanilla JS and CSS today; a redesign is about visual quality, not framework migration. Introducing a build pipeline on a 3-week timeline is a distraction.
|
|
|
|
---
|
|
|
|
## 9. What Changes From Current Design
|
|
|
|
| Area | Current | New |
|
|
|---|---|---|
|
|
| Typography | System sans-serif only | DM Serif Display for headings + DM Sans for UI |
|
|
| Accent color | `#0066cc` (generic blue) | `#1F6B5A` (deep teal) |
|
|
| Background | `#ffffff` (pure white) | `#F7F5F2` (warm paper) |
|
|
| Entry cards | Thumbnail + text below | Full-bleed 16:9 photo with overlay |
|
|
| Header | No visual identity | Accent top-border, typographic title |
|
|
| Design tokens | Hardcoded values throughout | CSS custom properties throughout |
|
|
| Post form | Lat/lng visible inputs | Lat/lng hidden, single status line |
|
|
| Font loading | None | Google Fonts DM pairing |
|
|
| Hover states | Minimal | Photo zoom, title color change |
|
|
| Stat numbers | `#0066cc` | `--color-accent` (#1F6B5A) |
|