# 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 ``` --- ## 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 %}