13 KiB
Frontend Polish Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use
superpowers:subagent-driven-development(recommended) orsuperpowers:executing-plansto implement this plan task-by-task. Steps use checkbox (- [x]) syntax for tracking.
Status: ✅ Complete — implemented 2026-06-24
Spec: docs/working/specs/2026-06-24-frontend-polish-design.md
Goal: Visual polish across the five primary page templates: pill grammar, stats field-notes style, header identity, emoji replacement, trip card cover images, story progress bar, and story opening transition.
Architecture: Tasks 1–2 are pure CSS (style.css only). Task 3 touches one partial (emoji). Task 4 adds a blueprint field and updates one template. Tasks 5–6 each add CSS + a small Twig block to story.html.twig.
Already done (this session):
entry.html.twigunified with feed partial — hero removed, PhotoSwipe replaces broken lightbox- Dead CSS from old entry layout stripped from
style.css
Global Constraints
- All changes in
user/— commit withgit -C user, not main-repo git - All new CSS uses token variables only — no hardcoded hex values
- Changes must degrade gracefully when optional data (cover image, location) is absent
prefers-reduced-motionmust be respected for any animations in Tasks 5–6- Clear Grav cache after each template change:
make remote-cache-clearor via Admin
Task 1: Pure CSS — Pill grammar + Stats style + Header identity
Files:
- Modify:
user/themes/intotheeast/css/style.css
Interfaces:
-
Produces: visual changes to trip filter buttons, stats blocks, and site header across all pages
-
Step 1: Pill grammar — change filter/sort buttons to rounded-rect
Find .trip-filter-btn, selector block:
.trip-filter-btn,
.trip-stats-btn {
...
border-radius: var(--radius-full);
...
}
Change only border-radius to var(--radius-sm). Leave all other properties unchanged.
- Step 2: Stats — remove box, add left rule
Find .stat-block rule:
.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);
}
Replace with:
.stat-block {
border-left: 2px solid var(--color-accent);
padding: var(--space-2) 0 var(--space-2) var(--space-4);
text-align: left;
}
- Step 3: Stats — change number color from accent to ink
Find .stat-value rule. It contains color: var(--color-accent). Change to color: var(--color-ink). Leave all other properties unchanged.
- Step 4: Header — widen site title tracking and size
Find .site-title rule:
.site-title {
font-family: var(--font-display);
font-size: var(--text-lg);
font-weight: 400;
letter-spacing: -0.01em;
...
}
Change:
-
font-size: var(--text-lg)→font-size: var(--text-xl) -
letter-spacing: -0.01em→letter-spacing: 0.06em -
Step 5: Header — thicken and gradient the accent stripe
Find .site-header rule. It contains border-top: 3px solid var(--color-accent).
Change to:
border-top: 4px solid transparent;
border-image: linear-gradient(90deg, var(--color-accent), var(--color-accent-hover)) 1;
- Step 6: Visual smoke check
Open browser and verify:
-
/trips— trip filter buttons are square-cornered (not pill-shaped) -
/trips/<any-trip>— stats panel shows left accent stripe, cream numbers, no box border -
Header — "into the east" is slightly larger with wider tracking; accent stripe has gradient
-
Step 7: Commit
git -C user add themes/intotheeast/css/style.css
git -C user commit -m "style: pill grammar, stats field-notes, header identity"
Task 2: Replace emoji icons in journal entry partial
Files:
- Modify:
user/themes/intotheeast/templates/partials/entry-journal.html.twig
Interfaces:
-
Affects: every journal entry in the home feed, trip feed, and standalone entry page
-
Step 1: Replace location emoji with SVG pin
Find in the partial:
· 📍
Replace with:
· <svg width="11" height="13" viewBox="0 0 12 14" fill="currentColor" aria-hidden="true" style="flex-shrink:0;vertical-align:-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>
- Step 2: Strip weather emoji prefix
Find in the partial:
<span class="journal-post-weather">· {{ weather_icons[entry.header.weather_desc] ?? '' }} {{ entry.header.weather_desc }}</span>
Replace with:
<span class="journal-post-weather">· {{ entry.header.weather_desc }}</span>
The weather_icons map at the top of the partial can stay (removing it is optional cleanup); it will simply go unused.
- Step 3: Smoke check
Open any trip page in browser. Confirm:
-
Location shows small SVG pin instead of 📍
-
Weather shows plain text (e.g. "· Sunny") with no emoji
-
Step 4: Commit
git -C user add themes/intotheeast/templates/partials/entry-journal.html.twig
git -C user commit -m "style: replace emoji icons with SVG pin and plain weather text"
Task 3: Trip cards — cover image
Files:
- Modify:
user/themes/intotheeast/blueprints/trip.yaml - Modify:
user/themes/intotheeast/templates/trips.html.twig - Modify:
user/themes/intotheeast/css/style.css
Interfaces:
-
Produces: optional cover image banner on each trip card
-
Consumes:
trip.header.cover_image(new field) or first image from first published entry -
Step 1: Read the current trip blueprint
cat user/themes/intotheeast/blueprints/trip.yaml
Locate the correct position to insert the new field (after tagline or near other media fields).
- Step 2: Add cover_image field to blueprint
Insert in trip.yaml at an appropriate location:
cover_image:
type: filepicker
label: Cover Image
preview_images: true
folder: '@self'
accept:
- image/*
- Step 3: Add cover image CSS to style.css
In the /* ── Past trips archive */ section, add after .trip-card-counts:
.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); }
- Step 4: Update trips.html.twig to render cover image
Inside the {% for trip in trips %} loop, before the .trip-card-title div, add:
{# Cover image: explicit field first, then first entry's first image #}
{% set cover = null %}
{% if trip.header.cover_image and trip.media[trip.header.cover_image] is defined %}
{% set cover = trip.media[trip.header.cover_image] %}
{% elseif dailies_page %}
{% set first_entry = dailies_page.children.published()|first %}
{% if first_entry and first_entry.media.images|length > 0 %}
{% set cover = first_entry.media.images|first %}
{% endif %}
{% endif %}
{% if cover %}
<div class="trip-card-cover">
<img src="{{ cover.cropResize(720, 240).url }}" alt="{{ trip.title }}" loading="lazy">
</div>
{% endif %}
- Step 5: Smoke check
Open /trips in browser. Confirm:
-
Trips with media show a 3:1 cover photo banner
-
Trips without media show text-only card (no broken image element)
-
Hover scales the image slightly
-
Step 6: Commit
git -C user add themes/intotheeast/blueprints/trip.yaml themes/intotheeast/templates/trips.html.twig themes/intotheeast/css/style.css
git -C user commit -m "feat: trip cards show cover image with 3:1 crop and hover zoom"
Task 4: Story opening transition
Files:
- Modify:
user/themes/intotheeast/templates/story.html.twig - Modify:
user/themes/intotheeast/css/style.css
Interfaces:
-
Consumes:
date_strandlocationalready computed at the top ofstory.html.twig -
Produces: a centered location/date block at the top of
.story-bodywith fade-in animation -
Step 1: Add story-opener CSS to style.css
In the /* ── Story pages */ section, after .story-body p rules, add:
.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; }
}
- Step 2: Add opener block to story.html.twig
Inside .story-body, immediately before {{ page.content|raw }}, add:
{% 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 %}
- Step 3: Smoke check
Open any published story in browser. Confirm:
-
A small uppercase line showing date and location appears below the hero spacer
-
It is separated from the prose by a thin horizontal rule
-
It fades in after the hero title animation completes
-
On a story with no location set: only date appears (or nothing if both are absent)
-
Step 4: Commit
git -C user add themes/intotheeast/templates/story.html.twig themes/intotheeast/css/style.css
git -C user commit -m "feat: story opening transition with location and date eyebrow"
Task 5: Reading progress bar on story pages
Files:
- Modify:
user/themes/intotheeast/templates/story.html.twig - Modify:
user/themes/intotheeast/css/style.css
Interfaces:
-
Produces: 2px teal bar fixed at bottom of site header, progress tied to
.story-bodyscroll position -
No bar rendered at all if
prefers-reduced-motionis set (JS skips creating it) -
Step 1: Add progress bar CSS to style.css
In the /* ── Story pages */ section, add:
.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;
}
- Step 2: Add progress bar element and JS to story.html.twig
Immediately after {% block content %} (before the hero markup), add:
<div class="story-progress" id="story-progress"></div>
In the <script> block at the bottom (after the existing scroll/reveal scripts), add:
/* ── Reading progress bar ────────────────────────────────── */
(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();
})();
- Step 3: Smoke check
Open any published story in browser. Confirm:
-
A thin teal line appears at the top of the content area (below the sticky header) as you scroll into the story body
-
Bar is at 0% when the hero is visible, fills to 100% as you reach the story footer
-
Bar is invisible (absent) on a device with
prefers-reduced-motion -
Step 4: Commit
git -C user add themes/intotheeast/templates/story.html.twig themes/intotheeast/css/style.css
git -C user commit -m "feat: reading progress bar on story pages"
Final Verification
After all tasks complete:
-
Visual check list (browser):
/trips— cover images on trip cards, hover scales; text-only fallback if no media/trips/<any-trip>— filter buttons square-cornered; stats left-rule style with cream numbers/trips/<any-trip>/dailies/<any-entry>(standalone URL) — SVG location pin, plain weather text, photo strip + PhotoSwipe, no hero image/trips/<any-trip>/<any-story>— opener block visible below hero; progress bar fills on scroll; no emoji anywhere- Any page header — "into the east" wider-tracked; accent stripe slightly thicker with gradient
-
Reduced-motion check: simulate
prefers-reduced-motion: reducein browser devtools and confirm no animations fire on story pages (opener snaps visible immediately, progress bar absent). -
Empty-data check: visit a trip with no media attached and confirm
/tripsdegrades to text-only card without errors.