docs: add UI/UX alignment implementation plan
This commit is contained in:
@@ -0,0 +1,624 @@
|
|||||||
|
# UI/UX Alignment Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Unify three micro-interaction patterns across the site: back navigation pills, card hover lift, and a map-to-card flash highlight.
|
||||||
|
|
||||||
|
**Architecture:** CSS-first — shared `.back-pill` class drives visual consistency; entry card markup is collapsed from a two-level `article > a` to a flat `<a>` to align hover targets across all three card types; map flash is a short CSS keyframe triggered by a JS-added class.
|
||||||
|
|
||||||
|
**Tech Stack:** Twig templates, vanilla CSS custom properties, vanilla JS, Playwright for tests.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Dev server: `http://localhost:8081` — must be running (`make start`) before any Playwright run
|
||||||
|
- Playwright: `npx playwright test --project=chromium tests/ui/<file>.spec.js` — always run the affected spec after changes
|
||||||
|
- Demo data required for story/map tests: `make demo-load`
|
||||||
|
- All CSS uses design tokens from `user/themes/intotheeast/css/tokens.css` — never hard-code colours
|
||||||
|
- `--color-paper: #1A1814`, `--color-canvas: #22201B`, `--color-ink: #EDE8DF` — the site is dark-themed
|
||||||
|
- `--site-header-height: 60px` — fixed pills must clear the site nav
|
||||||
|
- Never read `.env` directly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Map
|
||||||
|
|
||||||
|
| File | What changes |
|
||||||
|
|---|---|
|
||||||
|
| `user/themes/intotheeast/css/style.css` | Add `.back-pill` class; remove duplicate `.story-escape` block; migrate `.entry-card-inner` hover rules to `.entry-card`; add uniform card hover lift; add `@keyframes card-highlight` |
|
||||||
|
| `user/themes/intotheeast/templates/story.html.twig` | Add `class="back-pill"` to story-footer back link (line 61) |
|
||||||
|
| `user/themes/intotheeast/templates/entry.html.twig` | Add fixed top back pill before `<article class="entry">`; replace footer teal link with `.back-pill`; add `.entry-back-fixed` CSS |
|
||||||
|
| `user/themes/intotheeast/templates/trip.html.twig` | Collapse `<article class="entry-card"><a class="entry-card-inner">` to `<a class="entry-card">` for both card variants; update marker click handler with flash delay |
|
||||||
|
| `tests/ui/dailies.spec.js` | Update T2 selectors from `.entry-card a[href*="..."]` to `.entry-card[href*="..."]`; add T6 (back pills on entry page) |
|
||||||
|
| `tests/ui/maps.spec.js` | Add M7 (marker click adds `is-highlighted` class) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: CSS foundation — `.back-pill`, card hover lift, flash keyframe, story-escape cleanup
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `user/themes/intotheeast/css/style.css`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `.back-pill` class (surface pill), `.entry-card.is-highlighted` animation, uniform hover lift on `.trip-card:hover`, `.entry-card:hover`, `.story-card:hover`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add `.back-pill` surface pill class**
|
||||||
|
|
||||||
|
Find the `/* ── Back to top pill ──` section (around line 1217). Insert the following block immediately **before** it:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* ── Back pill (shared navigation pill component) ───────────────────── */
|
||||||
|
.back-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-ink);
|
||||||
|
text-decoration: none;
|
||||||
|
background: var(--color-canvas);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
padding: 0.4rem 0.9rem;
|
||||||
|
transition: border-color 0.15s, color 0.15s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.back-pill:hover { border-color: var(--color-accent); color: var(--color-accent); }
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Remove the duplicate `.story-escape` block**
|
||||||
|
|
||||||
|
Around line 958 there is a `/* ── Story page escape link ──` section with a `.story-escape` rule that is overridden later by the story-section block. Remove this entire section:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* ── Story page escape link ──────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.story-escape {
|
||||||
|
position: fixed;
|
||||||
|
top: var(--space-5);
|
||||||
|
left: var(--space-5);
|
||||||
|
z-index: 200;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-ink);
|
||||||
|
text-decoration: none;
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
padding: var(--space-2) var(--space-4);
|
||||||
|
border-radius: var(--radius-full);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.story-escape:hover { color: var(--color-accent); }
|
||||||
|
```
|
||||||
|
|
||||||
|
The authoritative `.story-escape` definition remains in the `/* ── Story pages ──` section (~line 1056).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add uniform card hover lift + fix story-card transition**
|
||||||
|
|
||||||
|
Find the `.trip-card:hover` rule (in `/* ── Past trips archive ──`). After the existing `.trip-card:hover` block, add:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.trip-card:hover,
|
||||||
|
.entry-card:hover,
|
||||||
|
.story-card:hover {
|
||||||
|
background: var(--color-surface-raised);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then find `.story-card` in the `/* ── Stories listing ──` section and add `background 0.15s` to its existing transition so the lift animates:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Before: */
|
||||||
|
.story-card {
|
||||||
|
...
|
||||||
|
transition: box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* After: */
|
||||||
|
.story-card {
|
||||||
|
...
|
||||||
|
transition: box-shadow 0.2s, background 0.15s;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Add map flash keyframe**
|
||||||
|
|
||||||
|
At the end of the `/* ── Feed ──` section (after `.entry-card` and related rules, around line 210), add:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@keyframes card-highlight {
|
||||||
|
0% { background-color: color-mix(in srgb, var(--color-accent) 12%, transparent); }
|
||||||
|
100% { background-color: transparent; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-card.is-highlighted {
|
||||||
|
animation: card-highlight 0.7s ease-out forwards;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify no JS errors on the site**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/maps.spec.js -g "M1:"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS (map page loads without errors — confirms CSS is valid).
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add user/themes/intotheeast/css/style.css
|
||||||
|
git commit -m "feat: add back-pill class, card hover lift, flash keyframe; remove duplicate story-escape"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: Story template — apply `.back-pill` to body back link
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `user/themes/intotheeast/templates/story.html.twig`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `.back-pill` class from Task 1
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing test**
|
||||||
|
|
||||||
|
Add to `tests/ui/stories.spec.js`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ── S7: Story body back link is styled as a back-pill ────────────────────────
|
||||||
|
test('S7: story body back link has back-pill class', async ({ page }) => {
|
||||||
|
await page.goto('/trips/italy-2025/stories/val-dorcia-dawn');
|
||||||
|
await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 });
|
||||||
|
// Scroll past the hero to reveal the story body
|
||||||
|
await page.evaluate(() => window.scrollBy(0, window.innerHeight * 1.5));
|
||||||
|
await page.waitForTimeout(300);
|
||||||
|
const bodyBack = page.locator('.story-footer .back-pill');
|
||||||
|
await expect(bodyBack).toBeAttached();
|
||||||
|
await expect(bodyBack).toHaveText(/← Back/);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/stories.spec.js -g "S7:"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL — "locator('.story-footer .back-pill')" found 0 elements.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Apply `.back-pill` to the story footer back link + fix `.story-footer a` conflict**
|
||||||
|
|
||||||
|
In `story.html.twig`, the story footer currently reads:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
<footer class="story-footer">
|
||||||
|
<a href="{{ page.parent().url }}" onclick="if(history.length > 1){ history.back(); return false; }">← Back</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
Change the `<a>` to:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
<footer class="story-footer">
|
||||||
|
<a class="back-pill" href="{{ page.parent().url }}" onclick="if(history.length > 1){ history.back(); return false; }">← Back</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in `style.css`, find `.story-footer a` and add `:not(.back-pill)` so it no longer overrides the pill colour:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Before: */
|
||||||
|
.story-footer a {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* After: */
|
||||||
|
.story-footer a:not(.back-pill) {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run test to verify it passes**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/stories.spec.js -g "S7:"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run full stories suite to check no regressions**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/stories.spec.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: All S1–S7 pass.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add user/themes/intotheeast/templates/story.html.twig tests/ui/stories.spec.js
|
||||||
|
git commit -m "feat: apply back-pill class to story footer back link"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: Entry page — fixed top back pill + footer back pill
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `user/themes/intotheeast/templates/entry.html.twig`
|
||||||
|
- Modify: `user/themes/intotheeast/css/style.css`
|
||||||
|
- Modify: `tests/ui/dailies.spec.js`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `.back-pill` class from Task 1
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing test**
|
||||||
|
|
||||||
|
Add to `tests/ui/dailies.spec.js`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const KNOWN_ENTRY = '/trips/japan-korea-2026/dailies/2026-03-25-1540-wheels-down-narita.entry';
|
||||||
|
|
||||||
|
// ── T6: Entry page has a fixed top back pill and a footer back pill ───────────
|
||||||
|
test('T6: entry page has fixed back pill at top and back pill in footer', async ({ page }) => {
|
||||||
|
await page.goto(KNOWN_ENTRY);
|
||||||
|
await expect(page.locator('article.entry')).toBeVisible();
|
||||||
|
// Fixed top pill (outside the article, before it)
|
||||||
|
const topPill = page.locator('.entry-back-fixed');
|
||||||
|
await expect(topPill).toBeVisible();
|
||||||
|
await expect(topPill).toHaveText(/← Back/);
|
||||||
|
// Footer pill
|
||||||
|
const footerPill = page.locator('.entry-footer .back-pill');
|
||||||
|
await expect(footerPill).toBeVisible();
|
||||||
|
await expect(footerPill).toHaveText(/← Back/);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/dailies.spec.js -g "T6:"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL — `.entry-back-fixed` not found.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add fixed top back pill to entry template**
|
||||||
|
|
||||||
|
In `entry.html.twig`, the content block currently starts with `<article class="entry">`. Add the fixed pill immediately before it:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
<a class="back-pill entry-back-fixed" href="{{ page.parent().url }}" onclick="if(history.length > 1){ history.back(); return false; }">← Back</a>
|
||||||
|
|
||||||
|
<article class="entry">
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Replace footer teal text link with `.back-pill`**
|
||||||
|
|
||||||
|
The current entry footer (around line 124 of entry.html.twig):
|
||||||
|
|
||||||
|
```twig
|
||||||
|
<footer class="entry-footer">
|
||||||
|
<a href="{{ page.parent().url }}" onclick="if(history.length>1){event.preventDefault();history.back()}">← Back</a>
|
||||||
|
</footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
<footer class="entry-footer">
|
||||||
|
<a class="back-pill" href="{{ page.parent().url }}" onclick="if(history.length > 1){ history.back(); return false; }">← Back</a>
|
||||||
|
</footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Add `.entry-back-fixed` positioning to CSS**
|
||||||
|
|
||||||
|
In `style.css`, in the `/* ── Single entry ──` section, add after the existing `.entry-hero` rules:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.entry-back-fixed {
|
||||||
|
position: fixed;
|
||||||
|
top: calc(var(--site-header-height) + var(--space-3));
|
||||||
|
left: var(--space-4);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run test to verify it passes**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/dailies.spec.js -g "T6:"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Run full dailies suite to check no regressions**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/dailies.spec.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: T1–T6 all pass.
|
||||||
|
|
||||||
|
- [ ] **Step 8: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add user/themes/intotheeast/templates/entry.html.twig user/themes/intotheeast/css/style.css tests/ui/dailies.spec.js
|
||||||
|
git commit -m "feat: add fixed top and footer back pills to entry page"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: Entry card structural refactor + CSS migration
|
||||||
|
|
||||||
|
Collapse the two-level `<article class="entry-card"><a class="entry-card-inner">` to a flat `<a class="entry-card">`, matching the structure of trip and story cards.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `user/themes/intotheeast/templates/trip.html.twig`
|
||||||
|
- Modify: `user/themes/intotheeast/css/style.css`
|
||||||
|
- Modify: `tests/ui/dailies.spec.js`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `.entry-card` is now an `<a>` element; `id`, `data-type`, `data-lat`, `data-lng` attributes remain on the card root; `.entry-card-inner` class is eliminated
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update T2 test selectors before touching the templates**
|
||||||
|
|
||||||
|
In `tests/ui/dailies.spec.js`, find the T2 test and replace:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// OLD — inner <a> is nested inside .entry-card
|
||||||
|
const newerCard = page.locator(`.entry-card a[href*="${NEWER_SLUG}"]`);
|
||||||
|
const olderCard = page.locator(`.entry-card a[href*="${OLDER_SLUG}"]`);
|
||||||
|
|
||||||
|
await expect(newerCard).toBeVisible();
|
||||||
|
await expect(olderCard).toBeVisible();
|
||||||
|
|
||||||
|
const newerIdx = await newerCard.evaluate(el => {
|
||||||
|
return [...document.querySelectorAll('.entry-card')].findIndex(c => c.contains(el));
|
||||||
|
});
|
||||||
|
const olderIdx = await olderCard.evaluate(el => {
|
||||||
|
return [...document.querySelectorAll('.entry-card')].findIndex(c => c.contains(el));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
With:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// NEW — .entry-card is itself the <a>
|
||||||
|
const newerCard = page.locator(`.entry-card[href*="${NEWER_SLUG}"]`);
|
||||||
|
const olderCard = page.locator(`.entry-card[href*="${OLDER_SLUG}"]`);
|
||||||
|
|
||||||
|
await expect(newerCard).toBeVisible();
|
||||||
|
await expect(olderCard).toBeVisible();
|
||||||
|
|
||||||
|
const newerIdx = await newerCard.evaluate(el => {
|
||||||
|
return [...document.querySelectorAll('.entry-card')].findIndex(c => c === el);
|
||||||
|
});
|
||||||
|
const olderIdx = await olderCard.evaluate(el => {
|
||||||
|
return [...document.querySelectorAll('.entry-card')].findIndex(c => c === el);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run T2 to verify it fails (not yet refactored)**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/dailies.spec.js -g "T2:"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL — `.entry-card[href*="..."]` finds 0 elements (the `href` is on the inner `<a>`, not the article).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Refactor journal entry card markup in `trip.html.twig`**
|
||||||
|
|
||||||
|
Find the journal card block:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
{% if item.type == 'journal' %}
|
||||||
|
<article class="entry-card" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}">
|
||||||
|
<a class="entry-card-inner" href="{{ entry.url }}">
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
{% if item.type == 'journal' %}
|
||||||
|
<a class="entry-card" id="entry-{{ entry.slug }}" data-type="journal" data-lat="{{ entry.header.lat }}" data-lng="{{ entry.header.lng }}" href="{{ entry.url }}">
|
||||||
|
```
|
||||||
|
|
||||||
|
And close the card with `</a>` instead of `</a></article>`. The closing tags currently are:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Refactor story-in-feed card markup in `trip.html.twig`**
|
||||||
|
|
||||||
|
Find the story-in-feed card block:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
<article class="entry-card entry-card--story" id="entry-{{ entry.slug }}" data-type="story">
|
||||||
|
<a class="entry-card-inner" href="{{ entry.url }}">
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
<a class="entry-card entry-card--story" id="entry-{{ entry.slug }}" data-type="story" href="{{ entry.url }}">
|
||||||
|
```
|
||||||
|
|
||||||
|
And its closing tags (currently `</a></article>`) become:
|
||||||
|
|
||||||
|
```twig
|
||||||
|
</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Migrate `.entry-card-inner` CSS rules to `.entry-card`**
|
||||||
|
|
||||||
|
In `style.css`, find the `/* ── Feed ──` section. Currently:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.entry-card { border-bottom: 1px solid var(--color-border); padding-bottom: var(--space-12); }
|
||||||
|
|
||||||
|
.entry-card-inner {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with (merge inner styles onto card, `.entry-card-inner` is eliminated):
|
||||||
|
|
||||||
|
```css
|
||||||
|
.entry-card {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
padding-bottom: var(--space-12);
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then find the two `.entry-card-inner:hover` rules and rename them to `.entry-card:hover`:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Before: */
|
||||||
|
.entry-card-inner:hover .entry-card-photo img { transform: scale(1.04); }
|
||||||
|
/* After: */
|
||||||
|
.entry-card:hover .entry-card-photo img { transform: scale(1.04); }
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Before: */
|
||||||
|
.entry-card-inner:hover .entry-title { color: var(--color-accent); }
|
||||||
|
/* After: */
|
||||||
|
.entry-card:hover .entry-title { color: var(--color-accent); }
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run T2 to verify it passes**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/dailies.spec.js -g "T2:"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Run full test suites to check no regressions**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/dailies.spec.js tests/ui/trip-filter.spec.js tests/ui/maps.spec.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: T1–T6, F1–F7, M1–M6 all pass.
|
||||||
|
|
||||||
|
- [ ] **Step 8: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add user/themes/intotheeast/templates/trip.html.twig user/themes/intotheeast/css/style.css tests/ui/dailies.spec.js
|
||||||
|
git commit -m "refactor: collapse entry card article+a to flat <a>, unify hover targets across card types"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: Map flash — JS update + test
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `user/themes/intotheeast/templates/trip.html.twig`
|
||||||
|
- Modify: `tests/ui/maps.spec.js`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `.entry-card.is-highlighted` CSS animation from Task 1; `id="entry-{{ slug }}"` on `<a class="entry-card">` from Task 4
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing test**
|
||||||
|
|
||||||
|
Add to `tests/ui/maps.spec.js`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// ── M7: Clicking a trip-page map marker adds is-highlighted to the entry card ──
|
||||||
|
test('M7: clicking map marker briefly highlights the corresponding entry card', async ({ page }) => {
|
||||||
|
await page.goto('/trips/japan-korea-2026');
|
||||||
|
// Wait for map canvas and at least one marker
|
||||||
|
await expect(page.locator('#trip-map canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 });
|
||||||
|
await expect(page.locator('.maplibregl-marker').first()).toBeVisible({ timeout: 15000 });
|
||||||
|
|
||||||
|
// Click the first marker
|
||||||
|
await page.locator('.maplibregl-marker').first().click();
|
||||||
|
|
||||||
|
// Within 500ms of click + delay, one entry-card should have is-highlighted
|
||||||
|
await expect(page.locator('.entry-card.is-highlighted')).toBeVisible({ timeout: 1500 });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/maps.spec.js -g "M7:"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: FAIL — `.entry-card.is-highlighted` not found.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update the marker click handler in `trip.html.twig`**
|
||||||
|
|
||||||
|
Find the existing marker click handler in `trip.html.twig`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
el.addEventListener('click', function () {
|
||||||
|
var card = document.getElementById('entry-' + entry.slug);
|
||||||
|
if (card) card.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
|
||||||
|
```js
|
||||||
|
el.addEventListener('click', function () {
|
||||||
|
var card = document.getElementById('entry-' + entry.slug);
|
||||||
|
if (!card) return;
|
||||||
|
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
setTimeout(function () {
|
||||||
|
card.classList.add('is-highlighted');
|
||||||
|
setTimeout(function () { card.classList.remove('is-highlighted'); }, 700);
|
||||||
|
}, 350);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run test to verify it passes**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/maps.spec.js -g "M7:"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run full maps suite**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/maps.spec.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: M1–M7 all pass.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run all affected suites for final check**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test --project=chromium tests/ui/dailies.spec.js tests/ui/trip-filter.spec.js tests/ui/maps.spec.js tests/ui/stories.spec.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: All pass.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add user/themes/intotheeast/templates/trip.html.twig tests/ui/maps.spec.js
|
||||||
|
git commit -m "feat: add map-to-card flash highlight on marker click"
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user