docs: clean up research notes, add production backlog and working docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
This commit is contained in:
@@ -1,141 +0,0 @@
|
||||
# FindPenguins — Feature Research
|
||||
|
||||
*Researched June 2026. Source: findpenguins.com, App Store, support docs, reviews.*
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
FindPenguins is a German travel tracking and community app. Core features are free; premium subscription ($4.99/month or $32.99/year) unlocks more photos per post and ebook exports. Revenue comes from subscriptions and printed photo books ($40–240). It leans more social than Polarsteps — discovery, community, and inspiring other travelers are central to its identity.
|
||||
|
||||
---
|
||||
|
||||
## Core User Flow
|
||||
|
||||
1. User creates a **Trip** (title, dates, cover)
|
||||
2. App runs in background with **automatic GPS + flight detection tracking**
|
||||
3. User creates **Footprints** — individual journal entries tied to a location and time
|
||||
4. Each Footprint can contain: location, title, date, text story, photos, video, weather
|
||||
5. Footprints appear in a **chronological timeline** per trip
|
||||
6. Trip is shareable; social followers can view, comment, react
|
||||
7. At the end, optionally order a printed **photo book**
|
||||
|
||||
---
|
||||
|
||||
## Map Features
|
||||
|
||||
- **Automatic route tracking**: GPS + flight detection, works offline
|
||||
- **Interactive world map**: route lines drawn between footprints
|
||||
- **3D flyover video**: auto-generated cinematic route visualization, free
|
||||
- **Countries/continents highlighted**: on personal map
|
||||
- **Visited places completion**: stats on what % of a country/region visited
|
||||
- Battery usage: ~4% per day (comparable to Polarsteps)
|
||||
- Route visualized as path on map, not just pins
|
||||
|
||||
---
|
||||
|
||||
## Footprints (Journal Entries)
|
||||
|
||||
Each "Footprint" is the core content unit:
|
||||
|
||||
- **Location**: GPS-detected, shown as city/country; uses reverse geocoding (LocationIQ)
|
||||
- **Title**: required, user-set
|
||||
- **Date**: required, defaults to current time
|
||||
- **Text story**: freeform journal text
|
||||
- **Photos**: 6 (free) / 10 (premium) per footprint
|
||||
- **Videos**: 1 (free) / 2 (premium) per footprint
|
||||
- **Weather**: auto-populated at location + time; manually editable
|
||||
- **Place name**: auto-detected city/neighborhood/country, editable
|
||||
- **Selective sharing**: each footprint can be public, friends-only, or private
|
||||
- **Delayed posting**: option to share location with a time delay (privacy feature)
|
||||
|
||||
---
|
||||
|
||||
## Photo Handling
|
||||
|
||||
- Up to 6 photos per footprint (free), 10 (premium)
|
||||
- 1 video per footprint (free), 2 (premium)
|
||||
- Photos displayed in carousel/grid within footprint
|
||||
- High-res stored for photobook printing
|
||||
- Cover photo selectable per trip
|
||||
|
||||
---
|
||||
|
||||
## Statistics
|
||||
|
||||
- Countries visited (count + list + % world)
|
||||
- Continents visited
|
||||
- Total distance traveled
|
||||
- Number of footprints / trips
|
||||
- Days on the road
|
||||
- World coverage percentage
|
||||
- Shown on profile and within photo books
|
||||
|
||||
---
|
||||
|
||||
## Social & Discovery Features
|
||||
|
||||
- **Follower system**: follow other travelers, see their public footprints
|
||||
- **Comments**: friends/followers can comment on individual footprints
|
||||
- **Reactions**: like/react to footprints
|
||||
- **Discovery**: browse 10M+ travel experiences from other users by destination
|
||||
- **Group trips**: invite co-travelers to add footprints to a shared trip (with known bug: co-travelers can delete each other's content)
|
||||
- **Travel inspiration**: browse community trips to plan your own
|
||||
- **Explore by destination**: search real traveler experiences for any city/country
|
||||
|
||||
---
|
||||
|
||||
## Privacy Controls
|
||||
|
||||
- Per-footprint visibility: public / friends / private
|
||||
- **Delayed sharing**: share location with a configurable time delay (safety feature for solo travelers)
|
||||
- Trip-level privacy: whole trip can be private or public
|
||||
- Can hide real-time location from followers
|
||||
|
||||
---
|
||||
|
||||
## Photo Book (Premium)
|
||||
|
||||
- Printed book with maps, photos, text, statistics, and friend comments
|
||||
- €40–€240 depending on size/format (hardcover or layflat)
|
||||
- Free ebook version for premium subscribers
|
||||
- 5% discount on books with premium
|
||||
|
||||
---
|
||||
|
||||
## 3D Flyover Video
|
||||
|
||||
- Free feature: auto-generates a cinematic 3D video of your route
|
||||
- Shareable directly from the app
|
||||
- No native app required for viewing (shareable link)
|
||||
|
||||
---
|
||||
|
||||
## Offline Capability
|
||||
|
||||
- Tracker works fully offline (GPS, flight detection)
|
||||
- Footprints can be created and edited offline
|
||||
- Syncs when connected
|
||||
|
||||
---
|
||||
|
||||
## What Makes FindPenguins Distinctive
|
||||
|
||||
1. **Flight detection**: auto-detects flights and logs them on the route
|
||||
2. **3D flyover video**: compelling visual output, free
|
||||
3. **Delayed sharing**: useful for solo travelers worried about broadcasting real-time location
|
||||
4. **Richer social layer**: comments on individual footprints, community discovery
|
||||
5. **Destination exploration**: browse real traveler posts for any place (like a user-generated travel guide)
|
||||
6. **Premium photo books**: more polished physical product with friend comments included
|
||||
|
||||
---
|
||||
|
||||
## Limitations (relevant to our context)
|
||||
|
||||
- Requires native app for GPS/flight tracking — not reproducible in a web CMS
|
||||
- Social discovery features irrelevant for a solo personal blog
|
||||
- Group trip feature has a bug (co-travelers can delete your content)
|
||||
- Premium paywall for basic things like more than 6 photos per post
|
||||
- Community/social focus means the UX is designed around a social graph we don't have
|
||||
- 3D flyover video requires proprietary rendering pipeline
|
||||
- Real-time delayed sharing is a privacy feature for apps broadcasting live location — moot for a blog that posts after the fact
|
||||
@@ -1,137 +0,0 @@
|
||||
# Polarsteps — Feature Research
|
||||
|
||||
*Researched June 2026. Source: polarsteps.com, App Store, support docs, reviews.*
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Polarsteps is a travel tracking and journaling app used by 20M+ travelers. It is ad-free, primarily free to use, with paid travel books as the main revenue stream. It positions itself as "by travelers, for travelers" — clean, minimal, focused on personal memory-keeping and sharing with close friends/family rather than a social discovery platform.
|
||||
|
||||
---
|
||||
|
||||
## Core User Flow
|
||||
|
||||
1. User creates a **Trip** (name, start/end dates, cover photo)
|
||||
2. App runs in background and **auto-tracks GPS route** continuously (dots on map)
|
||||
3. App auto-generates **Step Suggestions** when you stay somewhere — a notification asks "Are you in [City]? Add a step?"
|
||||
4. User accepts or manually creates a **Step**: a journal entry tied to a location
|
||||
5. Each Step gets: title, text, photos/videos, date, and auto-populated metadata
|
||||
6. Steps appear in a **timeline feed** ordered chronologically
|
||||
7. Trip is shareable via link; friends/family can follow in real time
|
||||
|
||||
---
|
||||
|
||||
## Map Features
|
||||
|
||||
- **Route tracking**: GPS + WiFi + cell towers → white dots plotted on world map as you move
|
||||
- **Offline tracking**: stores locally, syncs when connected
|
||||
- **Travel Tracker steps**: actual route taken (not straight lines), with transport mode tagging (car, bus, train, taxi, walk, fly)
|
||||
- **Route visualization**: colored line on map connecting all steps
|
||||
- **Countries/continents visited**: highlighted on world map
|
||||
- **Battery usage**: ~4% per day (very efficient)
|
||||
- **World completion %**: gamified stat showing % of the globe visited
|
||||
- Tracks distance, speed, and estimated travel time between steps
|
||||
|
||||
---
|
||||
|
||||
## Steps (Journal Entries)
|
||||
|
||||
Each "Step" is the core content unit:
|
||||
|
||||
- **Location**: auto-detected city/country, adjustable
|
||||
- **Title**: auto-suggested from location, editable
|
||||
- **Date/time**: auto from GPS
|
||||
- **Text**: rich freeform journal text
|
||||
- **Photos**: unlimited (mobile app), displayed in a grid/carousel
|
||||
- **Videos**: supported on mobile only, excluded from printed books
|
||||
- **Weather**: auto-populated (temperature, conditions) at time of step
|
||||
- **Altitude**: recorded from GPS
|
||||
- **GPS coordinates**: stored and displayed
|
||||
- **Transport**: mode of travel to reach this step (car/train/fly/etc.)
|
||||
|
||||
---
|
||||
|
||||
## Photo Handling
|
||||
|
||||
- Add photos directly from camera roll per step
|
||||
- Choose cover photo for the trip
|
||||
- Photos displayed in gallery within each step
|
||||
- High-resolution stored for travel book printing
|
||||
- No hard per-step photo limit mentioned (effectively unlimited)
|
||||
- Videos supported on mobile, excluded from print
|
||||
|
||||
---
|
||||
|
||||
## Statistics
|
||||
|
||||
Displayed on trip and profile level:
|
||||
- Total km/miles traveled
|
||||
- Countries visited (count + list)
|
||||
- Continents visited
|
||||
- Number of steps/entries
|
||||
- Days on the road
|
||||
- World completion percentage
|
||||
- Furthest point from home
|
||||
- Number of followers / following
|
||||
|
||||
---
|
||||
|
||||
## Sharing & Social Features
|
||||
|
||||
- **Privacy**: "Only me", "Followers only", or "Public"
|
||||
- **Shareable link**: send a URL to anyone to follow the trip live
|
||||
- **Followers**: people can follow your profile and see all public trips
|
||||
- **Reactions/comments**: followers can react and comment on steps
|
||||
- **Social media sharing**: export to Facebook, Instagram, etc.
|
||||
- **Travel Buddy**: invite friends to join and co-document a trip together
|
||||
- **Editors' Choice**: curated featured trips for discovery (like a magazine)
|
||||
- **Trip Reels**: auto-generated short video from photos/videos + visited places, shareable
|
||||
|
||||
---
|
||||
|
||||
## Planning Features (2025 addition)
|
||||
|
||||
- **AI Itinerary Builder**: generates multi-stop travel plan on the map, with transport modes
|
||||
- **Accommodation import**: forward booking confirmation emails to plan@polarsteps.app → appears on map
|
||||
- **Activity planning**: add stays, restaurants, activities to itinerary
|
||||
- **Travel DNA**: personality-based personalization for AI suggestions
|
||||
|
||||
---
|
||||
|
||||
## Travel Book
|
||||
|
||||
- Print a hardback book of your trip (€30–80, 24–300 pages)
|
||||
- Each step on its own page: photo, text, map thumbnail, metadata
|
||||
- Statistics page at the end
|
||||
- Designed, high-quality output — main revenue for Polarsteps
|
||||
|
||||
---
|
||||
|
||||
## Offline Capability
|
||||
|
||||
- Full offline posting (text, photos)
|
||||
- GPS route tracking continues offline
|
||||
- All data syncs when back online
|
||||
|
||||
---
|
||||
|
||||
## What Makes Polarsteps Distinctive
|
||||
|
||||
1. **Simplicity** — minimal UI, auto-everything, almost no friction to log a day
|
||||
2. **Route tracking** — actually shows where you walked/drove, not just pins
|
||||
3. **"Step suggestions"** — proactive nudges to journal without opening the app
|
||||
4. **Printed book** — the premium product, excellent quality
|
||||
5. **Ad-free** — rare among free travel apps
|
||||
6. **Battery efficiency** — 4% per day, usable on long trips
|
||||
|
||||
---
|
||||
|
||||
## Limitations (relevant to our context)
|
||||
|
||||
- Requires native mobile app for GPS tracking (cannot do in browser)
|
||||
- Videos excluded from print
|
||||
- Social/discovery features add little value for a solo personal blog
|
||||
- AI itinerary builder overkill for one-person blog
|
||||
- Travel Buddy / follower system assumes a social graph we don't have
|
||||
- Reels require the native app video processing pipeline
|
||||
@@ -4,6 +4,16 @@ Ideas and improvements not yet planned or scheduled.
|
||||
|
||||
---
|
||||
|
||||
## Production — remaining items
|
||||
|
||||
- [ ] Set `twig.cache: true` in `user/config/system.yaml` on the server (do not commit — breaks local dev)
|
||||
- [ ] Smoke test: submit one post via `/post`, confirm entry appears in dailies immediately (verifies cache-on-save with twig cache on)
|
||||
- [ ] Confirm `/post` requires login — unauthenticated visitors must not be able to post
|
||||
- [ ] Register at carto.com and review terms for production traffic
|
||||
- [ ] Japan & Korea 2026 trip page: set `date_start`, add `cover_image`, upload GPX route file(s)
|
||||
|
||||
---
|
||||
|
||||
## GPX Manager (`/gpx-manager`)
|
||||
|
||||
- [ ] **Polish the UI** — the current design is functional but bare; align with the Field Notes aesthetic, add better empty states, drag-and-drop upload area
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Git Sync Plugin — Setup Notes
|
||||
|
||||
## Folders YAML bug
|
||||
|
||||
The plugin UI always saves the folders field as a single comma-string:
|
||||
|
||||
```yaml
|
||||
folders:
|
||||
- 'pages,config,themes'
|
||||
```
|
||||
|
||||
But the plugin code iterates the array expecting separate items. This causes `git status pages,config,themes` to be passed as a single path, so git sees nothing to commit and sync silently does nothing.
|
||||
|
||||
**Fix:** Edit `user/config/plugins/git-sync.yaml` directly:
|
||||
|
||||
```yaml
|
||||
folders:
|
||||
- pages
|
||||
- config
|
||||
- themes
|
||||
```
|
||||
|
||||
Never use the Admin UI to change folders — it will rewrite the broken format.
|
||||
|
||||
## Files to gitignore
|
||||
|
||||
`user/config/plugins/git-sync.yaml` contains an encrypted token and is server-specific. `user/config/security.yaml` contains Grav nonces/salts, also server-specific. Both are in `.gitignore` and must never be committed.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Milestone 2: Template Refactor — Session Brief
|
||||
|
||||
Use this as the starting point for the brainstorm in a new session.
|
||||
Invoke the brainstorming skill (`/brainstorm`) and hand it this file as context.
|
||||
|
||||
---
|
||||
|
||||
## What this milestone is about
|
||||
|
||||
The asset pipeline (Milestone 1) is done — CDN dependencies eliminated, JS deduplicated into shared bundles. The templates themselves still have structural problems that make them hard to maintain and extend.
|
||||
|
||||
## Problems to solve
|
||||
|
||||
### 1. `trip.html.twig` mixes three concerns
|
||||
|
||||
Currently ~384 lines after Milestone 1 cleanup. Still mixes:
|
||||
- Twig data-building loops (collecting `map_entries`, building entry lists, GPX URL arrays)
|
||||
- HTML structure (cards, panels, filter bar)
|
||||
- Inline JS (map init, GPX stats block)
|
||||
|
||||
Goal: split into focused, readable sections or partials.
|
||||
|
||||
### 2. `map_entries` loop is duplicated across 4 templates
|
||||
|
||||
Near-identical Twig loop that builds `[{lat, lng, title, slug, url, type, ...}]` appears in:
|
||||
- `trip.html.twig`
|
||||
- `dailies.html.twig`
|
||||
- `stories.html.twig`
|
||||
- `map.html.twig`
|
||||
|
||||
Candidate for a Twig macro so a change only needs to happen once.
|
||||
|
||||
### 3. Stats computation is slow Twig loops
|
||||
|
||||
Country counting, temperature range, days on road — currently computed in Twig on every uncached page load. At 60–80 entries this is noticeable.
|
||||
|
||||
**Stronger option:** Move to a small PHP Grav plugin that exposes a single `{{ trip_stats }}` Twig variable. PHP loops are significantly faster than Twig loops. This is also the prerequisite for showing stats on other pages (homepage, story pages) in future.
|
||||
|
||||
### 4. Date range formatting duplicated
|
||||
|
||||
Same date formatting logic in both `story.html.twig` and `stories.html.twig`.
|
||||
|
||||
### 5. Latent bugs on inactive pages (fix while touching templates)
|
||||
|
||||
While refactoring, fix these two issues on pages not yet in active use:
|
||||
- `map.html.twig`: inline map init needs `DOMContentLoaded` wrapper; `{% block map_assets %}` nested inside `{% block content %}` (double-registers assets)
|
||||
- `feed-map.html.twig` (partial): `{% do assets.addCss %}` registers after `{{ assets.css()|raw }}` has rendered; inline map init also needs `DOMContentLoaded`
|
||||
|
||||
---
|
||||
|
||||
## Key constraint
|
||||
|
||||
Mischa wants stats and cycling data (distance, elevation gain/loss, moving time) visible on other pages in future (homepage, story pages). Centralising the computation — whether as Twig macros or a PHP plugin — is the prerequisite for that.
|
||||
|
||||
## What NOT to do in this milestone
|
||||
|
||||
- Don't touch JS or asset pipeline (that's Milestone 1, done)
|
||||
- Don't redesign the visual layout
|
||||
- Don't activate `dailies.html.twig`, `stories.html.twig`, or `map.html.twig` as new features — just fix their structural bugs while you're in the templates
|
||||
|
||||
## Relevant files
|
||||
|
||||
- `user/themes/intotheeast/templates/trip.html.twig` — main template (~384 lines)
|
||||
- `user/themes/intotheeast/templates/partials/base.html.twig` — base layout
|
||||
- `user/themes/intotheeast/templates/partials/feed-map.html.twig` — mini-map partial
|
||||
- `user/themes/intotheeast/templates/map.html.twig` — full-page map (inactive)
|
||||
- `user/themes/intotheeast/templates/dailies.html.twig` — journal feed (inactive)
|
||||
- `user/themes/intotheeast/templates/stories.html.twig` — stories grid (inactive)
|
||||
- `user/themes/intotheeast/templates/story.html.twig` — single story page
|
||||
- `user/plugins/` — where a new stats plugin would live
|
||||
|
||||
## Open question for the brainstorm
|
||||
|
||||
The biggest design decision: **PHP plugin vs Twig macro for stats computation.**
|
||||
|
||||
- Twig macro: simpler, no new plugin, but still slow Twig loops
|
||||
- PHP plugin: faster, reusable across pages, but adds a plugin to maintain
|
||||
|
||||
Mischa's stated preference leans toward the PHP plugin given the future-reuse goal, but hasn't committed yet.
|
||||
@@ -1,74 +0,0 @@
|
||||
# Production Todo
|
||||
|
||||
Work through Phase 1 first (local fixes and config), then Phase 2 (server deployment and go-live).
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Local fixes before deploy
|
||||
|
||||
These are changes made in the local dev environment and committed before anything touches the server.
|
||||
|
||||
### 1.1 Fix server-install.sh for Grav 2.0
|
||||
|
||||
`server-install.sh` had a gap: it copied the `grav-admin` bundle (which includes `user/plugins/admin2/`) but then immediately did `rm -rf user && git clone ...`, wiping admin2. It never got reinstalled because GPM doesn't carry Admin2.
|
||||
|
||||
- [x] Updated `server-install.sh` to stash admin2 before wiping user/, then restore it after
|
||||
- [x] Removed `admin` from `plugins.txt` — Admin2 replaces it and both conflict on `/admin`
|
||||
|
||||
### 1.2 Update config for production
|
||||
|
||||
- [x] Cleared `custom_base_url` in `user/config/system.yaml` (was pointing to local dev IP; empty means Grav auto-detects from the request, which works both locally and in production)
|
||||
|
||||
### 1.3 Content and metadata
|
||||
|
||||
- [ ] Set `date_start` on the Japan & Korea 2026 trip page (`user/pages/01.trips/japan-korea-2026/trip.md`)
|
||||
- [ ] Add `cover_image` to the trip page (used on the trips listing)
|
||||
- [ ] Upload actual GPX route file(s) to `/gpx-manager` or drop directly into `user/pages/01.trips/japan-korea-2026/`
|
||||
- [ ] Run `make content-push` to push all local changes to Gitea
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Server deployment and go-live
|
||||
|
||||
### 2.1 Configure .env
|
||||
|
||||
- [x] Set `GRAV_VERSION=2.0.0-rc.10` in `.env` (GitHub releases URL, no channel suffix needed)
|
||||
- [x] Set `REMOTE_HOST`, `REMOTE_USER`, `REMOTE_PORT`, `REMOTE_HOME` for the production server
|
||||
- [ ] Set `USER_REPO` and `MAIN_REPO` (Gitea URLs)
|
||||
- [ ] Set `GITEA_HOST`, `GITEA_USER`, `GITEA_TOKEN` for the install-time clone
|
||||
|
||||
### 2.2 Run the install
|
||||
|
||||
```bash
|
||||
make remote-env-setup # writes Gitea token to server temporarily
|
||||
make remote-install # downloads Grav, clones repos, installs plugins
|
||||
make remote-env-remove # removes token from server
|
||||
```
|
||||
|
||||
After install, the script prints the server's SSH public key. Add it as a deploy key to both Gitea repos so `make remote-fetch` works going forward.
|
||||
|
||||
### 2.3 Verify post-install config
|
||||
|
||||
These are committed to the `user/` repo and should be present after the clone — just confirm:
|
||||
|
||||
- [ ] `user/config/system.yaml` has `accounts.type: flex` and `pages.type: flex`
|
||||
- [ ] `user/accounts/mischa.yaml` has `api.super: true` and `api.access: true`
|
||||
- [ ] Old admin plugin is absent from `plugins.txt` (not installed)
|
||||
|
||||
### 2.4 Switch to production mode
|
||||
|
||||
- [ ] Set `twig.cache: true` in `user/config/system.yaml` on the server (do not commit this to the repo — it would break local dev)
|
||||
- [ ] If Grav can't auto-detect the base URL (e.g. behind a reverse proxy), set `custom_base_url` in `user/config/system.yaml` on the server
|
||||
|
||||
### 2.5 Smoke test
|
||||
|
||||
- [ ] Submit one post via `/post`, confirm entry appears in `/trips/japan-korea-2026/dailies` immediately (verifies cache-on-save plugin works with `twig.cache: true`)
|
||||
|
||||
### 2.6 Security
|
||||
|
||||
- [ ] Change admin password to a strong production password
|
||||
- [ ] Confirm `/post` requires login — unauthenticated visitors must not be able to post
|
||||
|
||||
### 2.7 Map tiles
|
||||
|
||||
- [ ] Register at [carto.com](https://carto.com) and review terms for production traffic (CartoDB dark tiles are free but registration is expected for production use)
|
||||
@@ -0,0 +1,313 @@
|
||||
# Frontend Polish Design Spec
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Elevate the visual identity and consistency of the five primary page templates — home, trip, trips listing, individual entry, and story — without touching the map page or dailies index. Improvements fall into three categories: visual identity (header, stats, pills), typographic consistency (emoji removal), and content pages (trip cards, story transitions).
|
||||
|
||||
**Architecture:** Mostly CSS changes in `style.css`. Two Twig templates need small additions (`trips.html.twig`, `story.html.twig`). One blueprint gets a new field (`blueprints/trip.yaml`). One partial gets emoji removed (`partials/entry-journal.html.twig`). No new JS libraries.
|
||||
|
||||
**Already completed as part of this session:**
|
||||
- `entry.html.twig` unified with `partials/entry-journal.html.twig` — hero image removed, custom lightbox replaced with PhotoSwipe, dead CSS stripped
|
||||
- See git log for the entry template rewrite commit
|
||||
|
||||
---
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- All changes in `user/` — commit with `git -C user`
|
||||
- All new CSS must use token variables — never hardcode hex values
|
||||
- No new JS libraries or CDN dependencies
|
||||
- Changes must degrade gracefully if optional data (cover image, location) is absent
|
||||
- `prefers-reduced-motion` must be respected for any new animations
|
||||
|
||||
---
|
||||
|
||||
## A — Trip Cards: Cover Image
|
||||
|
||||
### Problem
|
||||
The trips listing (`/trips`) renders a vertical stack of text-only cards: title, date range, entry count. For a travel blog, the archive is the viewer's first encounter with trips they haven't visited — showing no visual context is a significant missed opportunity.
|
||||
|
||||
### Design decision
|
||||
Each `.trip-card` gets a full-width banner image above the existing text. Aspect ratio 3:1 — wide enough to suggest landscape/geography without dominating a card in a list.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ [cover image — 3:1 aspect ratio] │
|
||||
├─────────────────────────────────────────┤
|
||||
│ Japan & South Korea │
|
||||
│ Apr 2026 — Jun 2026 · 24 entries │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Image resolution
|
||||
The card is constrained to `--content-width` (720px). Use `cropResize(720, 240)` for the 3:1 crop.
|
||||
|
||||
### Image source priority
|
||||
1. `trip.header.cover_image` — a filename from the trip page's own media (explicit, curated)
|
||||
2. First image from the first published journal entry in the trip (automatic fallback)
|
||||
3. No image — card degrades to text-only (existing layout, unchanged)
|
||||
|
||||
### Blueprint change
|
||||
Add a `cover_image` field to `user/themes/intotheeast/blueprints/trip.yaml`:
|
||||
|
||||
```yaml
|
||||
cover_image:
|
||||
type: filepicker
|
||||
label: Cover Image
|
||||
preview_images: true
|
||||
folder: '@self'
|
||||
accept:
|
||||
- image/*
|
||||
```
|
||||
|
||||
### New CSS
|
||||
|
||||
```css
|
||||
.trip-card-cover {
|
||||
aspect-ratio: 3 / 1;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md) var(--radius-md) 0 0;
|
||||
background: var(--color-border);
|
||||
margin: calc(-1 * var(--space-6)) calc(-1 * var(--space-6)) var(--space-5);
|
||||
}
|
||||
|
||||
.trip-card-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
transition: transform 0.45s ease;
|
||||
}
|
||||
|
||||
.trip-card:hover .trip-card-cover img { transform: scale(1.04); }
|
||||
```
|
||||
|
||||
The negative margin pulls the image flush to the card edges while the card keeps its existing padding for the text below.
|
||||
|
||||
---
|
||||
|
||||
## B — Replace Emoji Icons
|
||||
|
||||
### Problem
|
||||
`partials/entry-journal.html.twig` (which now also powers `entry.html.twig`) uses `📍` for location and emoji for weather conditions (☀️, 🌧️, etc.). These are OS-rendered, variable in size, and break the typographic consistency of the warm-dark palette.
|
||||
|
||||
### Design decision
|
||||
- **Location:** Replace `📍` with a minimal inline SVG mappin. 16×16, `currentColor`, single path.
|
||||
- **Weather:** Drop the emoji prefix entirely. The text description ("Sunny", "Rain", "Partly cloudy") is the information — the emoji is decoration. Text-only is cleaner and the muted color already signals it as secondary metadata.
|
||||
|
||||
### SVG mappin (inline, replaces `📍`)
|
||||
|
||||
```html
|
||||
<svg width="12" height="14" viewBox="0 0 12 14" fill="currentColor" aria-hidden="true" style="flex-shrink:0;margin-top:1px">
|
||||
<path d="M6 0C3.24 0 1 2.24 1 5c0 3.75 5 9 5 9s5-5.25 5-9c0-2.76-2.24-5-5-5zm0 6.75A1.75 1.75 0 1 1 6 3.25a1.75 1.75 0 0 1 0 3.5z"/>
|
||||
</svg>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## C — Header Identity
|
||||
|
||||
### Problem
|
||||
The site header reads like a product app: text logo left, two nav links right, 60px tall, 3px teal stripe on top. The brand "into the east" at `--text-lg` with `-0.01em` tracking is timid. The content pages are atmospheric and cinematic; the header is functional and forgettable.
|
||||
|
||||
### Design decision
|
||||
Two targeted CSS-only changes:
|
||||
|
||||
1. **Site title tracking:** Increase from `--text-lg` to `--text-xl`, set `letter-spacing: 0.06em`. Wider tracking on a dark background is a deliberate typographic mark — it reads as a designed wordmark rather than placeholder text.
|
||||
|
||||
2. **Accent stripe:** Increase from `3px` to `4px`. Apply a two-stop gradient along the 90deg axis: `linear-gradient(90deg, var(--color-accent), var(--color-accent-hover))`. This gives the stripe direction (reads left-to-right like a journey) and signals it was chosen, not defaulted.
|
||||
|
||||
No layout change, no template change, no height change.
|
||||
|
||||
---
|
||||
|
||||
## D — Story Opening Transition
|
||||
|
||||
### Problem
|
||||
After the Ken Burns hero and the 40vh spacer, `story.html.twig` begins the body content immediately with prose. There is no visual breath between the cinematic full-screen image and the reading experience. The reader has no bearing — no confirmation of where they are or when.
|
||||
|
||||
### Design decision
|
||||
Add a `.story-opener` block at the top of `.story-body`, before `{{ page.content|raw }}`. It displays the location and formatted date string centered, separated from the prose below by a thin ruled line.
|
||||
|
||||
```
|
||||
Sorano, Italy · 14–16 June 2026
|
||||
────────────────────────────────
|
||||
[prose begins here]
|
||||
```
|
||||
|
||||
Data comes from `location` and `date_str`, already computed at the top of `story.html.twig`. If both are empty the opener renders nothing (zero markup visible).
|
||||
|
||||
The opener fades in using the existing `storyReveal` keyframe (`filter: blur → 0`, `opacity: 0 → 1`, `translateY(22px → 0)`) with a 0.8s delay so it appears after the hero title animation completes.
|
||||
|
||||
### New CSS
|
||||
|
||||
```css
|
||||
.story-opener {
|
||||
text-align: center;
|
||||
padding-bottom: var(--space-12);
|
||||
margin-bottom: var(--space-12);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
opacity: 0;
|
||||
animation: storyReveal 0.9s cubic-bezier(.16,1,.3,1) 0.8s both;
|
||||
}
|
||||
|
||||
.story-opener__text {
|
||||
font-family: var(--font-ui);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-ink-muted);
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.story-opener { opacity: 1; animation: none; }
|
||||
}
|
||||
```
|
||||
|
||||
### Template addition (in `story.html.twig`, inside `.story-body`, before `page.content`)
|
||||
|
||||
```twig
|
||||
{% if location or date_str %}
|
||||
<div class="story-opener">
|
||||
<span class="story-opener__text">
|
||||
{{- date_str -}}
|
||||
{%- if location and date_str %} · {% endif -%}
|
||||
{{- location -}}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## E — Reading Progress Bar
|
||||
|
||||
### Problem
|
||||
Story pages are long-form — the longest may scroll for several minutes of reading. There is no visual feedback about progress through the piece. This is a small but meaningful quality signal on an immersive reading experience.
|
||||
|
||||
### Design decision
|
||||
A 2px teal bar fixed to the bottom edge of the site header (`top: var(--site-header-height)`), filling left-to-right as the reader scrolls through `.story-body`. Progress is calculated relative to the story body element (not the full page including the hero), so the bar reads 0% when the hero exits and 100% when the last line of content reaches the viewport bottom.
|
||||
|
||||
The bar is invisible before the story body enters view. It does not render at all if `prefers-reduced-motion` is set — there should be no static `width: 0` bar for reduced-motion users.
|
||||
|
||||
### New CSS
|
||||
|
||||
```css
|
||||
.story-progress {
|
||||
position: fixed;
|
||||
top: var(--site-header-height);
|
||||
left: 0;
|
||||
height: 2px;
|
||||
width: 0%;
|
||||
background: var(--color-accent);
|
||||
z-index: 200;
|
||||
pointer-events: none;
|
||||
will-change: width;
|
||||
}
|
||||
```
|
||||
|
||||
### JS logic (no transition — rAF-driven for smoothness)
|
||||
|
||||
```javascript
|
||||
(function () {
|
||||
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||
var bar = document.getElementById('story-progress');
|
||||
var body = document.querySelector('.story-body');
|
||||
if (!bar || !body) return;
|
||||
|
||||
function update() {
|
||||
var rect = body.getBoundingClientRect();
|
||||
var total = body.offsetHeight - window.innerHeight;
|
||||
var scrolled = -rect.top;
|
||||
var pct = total > 0 ? Math.min(100, Math.max(0, (scrolled / total) * 100)) : 0;
|
||||
bar.style.width = pct.toFixed(1) + '%';
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', update, { passive: true });
|
||||
update();
|
||||
})();
|
||||
```
|
||||
|
||||
The element `<div class="story-progress" id="story-progress"></div>` is added to `story.html.twig` immediately after the opening `{% block content %}`.
|
||||
|
||||
---
|
||||
|
||||
## F — Pill Shape Differentiation
|
||||
|
||||
### Problem
|
||||
All interactive pills use `border-radius: 9999px` regardless of their role. Back-navigation pills, filter buttons, panel toggles, sort toggles — they all look identical, which collapses the visual grammar. A reader cannot tell at a glance whether tapping a pill will navigate them away or toggle a filter.
|
||||
|
||||
### Design decision
|
||||
Establish a two-shape grammar:
|
||||
|
||||
| Role | Shape | Classes |
|
||||
|---|---|---|
|
||||
| Navigation (go somewhere, leave the page) | Full pill `9999px` | `.back-pill`, `.story-escape`, `.story-totop` |
|
||||
| Controls (toggle, filter, sort in place) | Rounded rect `var(--radius-sm)` = 4px | `.trip-filter-btn`, `.trip-stats-btn` |
|
||||
| Panel toggles (secondary, in-place) | Full pill (keep — less prominent than controls) | `.trip-panel-toggle` |
|
||||
|
||||
CSS-only change. `.back-pill`, `.story-escape`, `.story-totop` are unchanged. Only `.trip-filter-btn` and `.trip-stats-btn` change from `border-radius: var(--radius-full)` to `border-radius: var(--radius-sm)`.
|
||||
|
||||
---
|
||||
|
||||
## G — Stats: Field Notes Treatment
|
||||
|
||||
### Problem
|
||||
`.stat-block` renders as a bordered card with a `background: var(--color-canvas)` surface, box shadow, and teal accent numbers. This reads as a metrics dashboard — every SaaS product uses this pattern. For a travel journal, numbers like "1,847 km" and "3 countries" should feel earned and written, not computed and charted.
|
||||
|
||||
### Design decision
|
||||
Two changes:
|
||||
|
||||
1. **Remove the box.** Drop `background`, `border`, and `box-shadow` from `.stat-block`. Replace with a `border-left: 2px solid var(--color-accent)` and `padding-left: var(--space-4)`. Text left-aligns. Numbers feel like notes in a margin, not cells in a table.
|
||||
|
||||
2. **Change number color.** `stat-value` moves from `var(--color-accent)` to `var(--color-ink)`. Teal numbers on dark are a SaaS color decision. Cream numbers on dark with a teal accent stripe are a traveler's notation.
|
||||
|
||||
The teal accent is now only the left rule — restrained, singular.
|
||||
|
||||
### CSS change
|
||||
|
||||
```css
|
||||
/* Before */
|
||||
.stat-block {
|
||||
background: var(--color-canvas);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-6) var(--space-5);
|
||||
text-align: center;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
color: var(--color-accent);
|
||||
...
|
||||
}
|
||||
|
||||
/* After */
|
||||
.stat-block {
|
||||
border-left: 2px solid var(--color-accent);
|
||||
padding: var(--space-2) 0 var(--space-2) var(--space-4);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
color: var(--color-ink);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The `.trip-stats-grid` and `.stats-grid` gap/column settings are unchanged.
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
After full implementation, check each page:
|
||||
|
||||
| Page | Check |
|
||||
|---|---|
|
||||
| `/trips` | Trip cards show cover image (or degrade to text-only gracefully); hover scales image |
|
||||
| `/trips/<any-trip>` | Stats panel shows left-rule style, cream numbers; filter buttons are rounded-rect |
|
||||
| `/trips/<any-trip>/dailies/<any-entry>` (standalone) | Photo strip renders via PhotoSwipe, no broken lightbox; no hero image at top |
|
||||
| `/trips/<any-trip>/<any-story>` | Opener block shows location + date; progress bar fills while scrolling; no bar if reduced-motion |
|
||||
| Any page | Header title has wider tracking; accent stripe is slightly thicker with gradient |
|
||||
| Any page with journal entries | Location shows SVG pin; weather shows text only, no emoji |
|
||||
Reference in New Issue
Block a user