Files
intotheeast-com/docs/reference/design-system.md
m038andClaude Opus 5 8202d2a257 docs: reconcile documentation against the code; add a supersession ledger
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>
2026-07-25 00:13:37 +02:00

17 KiB

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 → 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 → 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)