1387 lines
46 KiB
Markdown
1387 lines
46 KiB
Markdown
# UI Redesign 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:** Redesign the Into the East travel blog UI using the design spec in `user/docs/design/design-spec.md` — introducing DM Serif Display + DM Sans fonts, a deep teal design system, full-bleed entry card photos, and polished mobile UX across all pages.
|
||
|
||
**Architecture:** Pure CSS + HTML/Twig changes on top of existing Grav CMS stack. New `tokens.css` file holds all design tokens as CSS custom properties. Existing `style.css` is rewritten to use those tokens. No new JS frameworks, no build pipeline.
|
||
|
||
**Tech Stack:** Grav CMS (PHP/Twig), Vanilla CSS (custom properties), Vanilla JS, Google Fonts CDN (DM Serif Display + DM Sans), Leaflet.js (unchanged)
|
||
|
||
## Global Constraints
|
||
|
||
- **No JS framework** — all interactivity stays vanilla JS
|
||
- **No build pipeline** — CSS ships as plain files loaded by Grav
|
||
- **No new Grav plugins** — only modify existing theme + content files
|
||
- **Grav file paths:** theme is at `user/themes/intotheeast/`, pages at `user/pages/`
|
||
- **Design tokens source of truth:** `user/themes/intotheeast/css/tokens.css`
|
||
- **Accent color:** `#1F6B5A` (deep teal) — used everywhere `#0066cc` is today
|
||
- **Font stack display:** `'DM Serif Display', Georgia, serif`
|
||
- **Font stack UI:** `'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif`
|
||
- **All interactive elements:** `min-height: 44px`
|
||
- **Content max-width:** `720px`
|
||
- **Only edit files inside `user/`** — never touch the Grav core
|
||
- **Verify in browser at each task:** `http://100.96.115.96:8081`
|
||
|
||
---
|
||
|
||
### Task 1: Design tokens file + font loading
|
||
|
||
**Files:**
|
||
- Create: `user/themes/intotheeast/css/tokens.css`
|
||
- Modify: `user/themes/intotheeast/templates/partials/base.html.twig` (add font preconnect + tokens import)
|
||
|
||
**Interfaces:**
|
||
- Produces: all CSS custom properties consumed by Tasks 2–8
|
||
|
||
- [ ] **Step 1: Create tokens.css**
|
||
|
||
Create `user/themes/intotheeast/css/tokens.css` with this exact content:
|
||
|
||
```css
|
||
:root {
|
||
/* ── Colors ─────────────────────────────────────────────── */
|
||
--color-ink: #17171A;
|
||
--color-ink-2: #4A4850;
|
||
--color-ink-muted: #9896A0;
|
||
--color-paper: #F7F5F2;
|
||
--color-canvas: #FFFFFF;
|
||
--color-border: #E8E6E3;
|
||
--color-border-soft: #F0EDEA;
|
||
--color-accent: #1F6B5A;
|
||
--color-accent-hover: #185647;
|
||
--color-accent-light: #EBF5F2;
|
||
--color-accent-on: #FFFFFF;
|
||
|
||
/* ── Fonts ───────────────────────────────────────────────── */
|
||
--font-display: 'DM Serif Display', Georgia, serif;
|
||
--font-ui: 'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif;
|
||
|
||
/* ── Type scale ──────────────────────────────────────────── */
|
||
--text-xs: 0.75rem;
|
||
--text-sm: 0.875rem;
|
||
--text-base: 1rem;
|
||
--text-md: 1.125rem;
|
||
--text-lg: 1.375rem;
|
||
--text-xl: 1.75rem;
|
||
--text-2xl: 2.25rem;
|
||
--text-3xl: 3rem;
|
||
|
||
/* ── Leading ─────────────────────────────────────────────── */
|
||
--leading-tight: 1.2;
|
||
--leading-snug: 1.35;
|
||
--leading-normal: 1.65;
|
||
|
||
/* ── Spacing (4px grid) ──────────────────────────────────── */
|
||
--space-1: 0.25rem;
|
||
--space-2: 0.5rem;
|
||
--space-3: 0.75rem;
|
||
--space-4: 1rem;
|
||
--space-5: 1.25rem;
|
||
--space-6: 1.5rem;
|
||
--space-8: 2rem;
|
||
--space-10: 2.5rem;
|
||
--space-12: 3rem;
|
||
--space-16: 4rem;
|
||
|
||
/* ── Radius ──────────────────────────────────────────────── */
|
||
--radius-sm: 4px;
|
||
--radius-md: 8px;
|
||
--radius-lg: 12px;
|
||
--radius-full: 9999px;
|
||
|
||
/* ── Shadows ─────────────────────────────────────────────── */
|
||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
|
||
--shadow-md: 0 4px 12px rgba(0,0,0,0.10);
|
||
--shadow-lg: 0 8px 24px rgba(0,0,0,0.14);
|
||
|
||
/* ── Layout ──────────────────────────────────────────────── */
|
||
--content-width: 720px;
|
||
--site-header-height: 60px;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Add font loading + tokens import to base.html.twig**
|
||
|
||
In `user/themes/intotheeast/templates/partials/base.html.twig`, replace the `<head>` block with:
|
||
|
||
```twig
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>{% if page.title %}{{ page.title }} | {% endif %}{{ site.title }}</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="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" rel="stylesheet">
|
||
<link rel="stylesheet" href="{{ url('theme://css/tokens.css') }}">
|
||
<link rel="stylesheet" href="{{ url('theme://css/style.css') }}">
|
||
</head>
|
||
```
|
||
|
||
- [ ] **Step 3: Verify fonts load**
|
||
|
||
Open `http://100.96.115.96:8081/tracker` in the browser. Open DevTools → Network → filter "fonts.gstatic.com". Both `DM_Sans` and `DM_Serif_Display` should appear in the network log. If not, check the `<link>` href in page source.
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast/user
|
||
git add themes/intotheeast/css/tokens.css themes/intotheeast/templates/partials/base.html.twig
|
||
git commit -m "feat: add design tokens and DM font loading"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2: Rewrite global styles with design tokens
|
||
|
||
**Files:**
|
||
- Modify: `user/themes/intotheeast/css/style.css` — global reset and base styles only (header, nav, body, site-main). Per-component CSS is updated in Tasks 3–8.
|
||
|
||
**Interfaces:**
|
||
- Consumes: all tokens from `tokens.css`
|
||
- Produces: base body/typography/layout styles consumed by all templates
|
||
|
||
- [ ] **Step 1: Replace the top section of style.css**
|
||
|
||
Open `user/themes/intotheeast/css/style.css`. Replace from line 1 through the end of the `.site-main` block (approximately lines 1–41) with:
|
||
|
||
```css
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
body {
|
||
font-family: var(--font-ui);
|
||
font-size: var(--text-base);
|
||
line-height: var(--leading-normal);
|
||
color: var(--color-ink);
|
||
background: var(--color-paper);
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
.site-main {
|
||
max-width: var(--content-width);
|
||
margin: 0 auto;
|
||
padding: var(--space-8) var(--space-5);
|
||
}
|
||
|
||
@media (min-width: 520px) {
|
||
.site-main { padding: var(--space-10) var(--space-6); }
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Update the login form section to use tokens**
|
||
|
||
Find the `/* ── Login form ──` section and update input/button colors from hardcoded to tokens. Replace the `.login-form` block with:
|
||
|
||
```css
|
||
.login-form { max-width: 400px; margin: var(--space-8) auto; padding: 0 var(--space-4); }
|
||
.login-form .form-field { margin-bottom: var(--space-5); }
|
||
.login-form .form-label label { display: block; font-size: var(--text-sm); font-weight: 600; margin-bottom: var(--space-2); }
|
||
.login-form input[type="text"],
|
||
.login-form input[type="password"],
|
||
.login-form input[type="email"] {
|
||
width: 100%;
|
||
font-family: var(--font-ui);
|
||
font-size: var(--text-base);
|
||
padding: 0.75rem 1rem;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-md);
|
||
min-height: 44px;
|
||
background: var(--color-canvas);
|
||
color: var(--color-ink);
|
||
}
|
||
.login-form input:focus {
|
||
outline: 2px solid var(--color-accent);
|
||
outline-offset: 1px;
|
||
border-color: var(--color-accent);
|
||
}
|
||
.login-form .form-actions { margin-top: var(--space-6); display: flex; flex-direction: column; gap: var(--space-3); }
|
||
.login-form .button {
|
||
display: block; width: 100%; text-align: center;
|
||
padding: 0.85rem 1rem; min-height: 44px;
|
||
border-radius: var(--radius-md); font-size: var(--text-base);
|
||
font-family: var(--font-ui); font-weight: 600;
|
||
cursor: pointer; border: none;
|
||
}
|
||
.login-form .button.primary { background: var(--color-accent); color: var(--color-accent-on); }
|
||
.login-form .button.primary:hover { background: var(--color-accent-hover); }
|
||
.login-form .button.secondary { background: #f0f0f0; color: #333; text-decoration: none; line-height: 44px; padding: 0 1rem; }
|
||
.login-form .rememberme { display: flex; align-items: center; gap: var(--space-2); font-size: var(--text-sm); }
|
||
```
|
||
|
||
- [ ] **Step 3: Verify base styles apply**
|
||
|
||
Open `http://100.96.115.96:8081`. Confirm:
|
||
- Page background is warm paper white (not pure white)
|
||
- Body text uses DM Sans
|
||
- No visual regressions on login page
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast/user
|
||
git add themes/intotheeast/css/style.css
|
||
git commit -m "feat: update global styles to use design tokens"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3: Site header redesign
|
||
|
||
**Files:**
|
||
- Modify: `user/themes/intotheeast/templates/partials/base.html.twig` (header HTML)
|
||
- Modify: `user/themes/intotheeast/css/style.css` (header CSS section)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `--font-display`, `--color-accent`, `--color-border`, `--color-ink-2`
|
||
- Produces: site header used by all pages
|
||
|
||
- [ ] **Step 1: Update header HTML in base.html.twig**
|
||
|
||
Replace the existing `<header>` element (the `<header class="site-header">` block) with:
|
||
|
||
```twig
|
||
<header class="site-header">
|
||
<a class="site-title" href="{{ base_url_absolute }}">into the east</a>
|
||
<nav class="site-nav" aria-label="Main navigation">
|
||
<a href="{{ base_url_absolute }}/tracker"{% if page.url starts with '/tracker' or page.template == 'entry' %} aria-current="page"{% endif %}>Journal</a>
|
||
<a href="{{ base_url_absolute }}/map"{% if page.url starts with '/map' %} aria-current="page"{% endif %}>Map</a>
|
||
<a href="{{ base_url_absolute }}/stats"{% if page.url starts with '/stats' %} aria-current="page"{% endif %}>Stats</a>
|
||
</nav>
|
||
</header>
|
||
```
|
||
|
||
- [ ] **Step 2: Replace the header CSS section**
|
||
|
||
Find `/* ── Feed ──` comment in style.css. Replace everything from the start of the file up to (but not including) the Feed comment with the new header CSS:
|
||
|
||
```css
|
||
/* ── Header ─────────────────────────────────────────────────────────────────── */
|
||
|
||
.site-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 var(--space-5);
|
||
height: var(--site-header-height);
|
||
background: var(--color-canvas);
|
||
border-top: 3px solid var(--color-accent);
|
||
border-bottom: 1px solid var(--color-border);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
}
|
||
|
||
.site-title {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-lg);
|
||
font-weight: 400;
|
||
letter-spacing: -0.01em;
|
||
text-decoration: none;
|
||
color: var(--color-ink);
|
||
line-height: 1;
|
||
}
|
||
|
||
.site-nav {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-1);
|
||
}
|
||
|
||
.site-nav a {
|
||
font-family: var(--font-ui);
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
color: var(--color-ink-2);
|
||
text-decoration: none;
|
||
padding: var(--space-2) var(--space-3);
|
||
border-radius: var(--radius-sm);
|
||
min-height: 44px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
transition: color 0.15s, background 0.15s;
|
||
}
|
||
|
||
.site-nav a:hover { color: var(--color-ink); background: var(--color-paper); }
|
||
.site-nav a[aria-current="page"] { color: var(--color-accent); font-weight: 600; }
|
||
```
|
||
|
||
- [ ] **Step 3: Verify header**
|
||
|
||
Open `http://100.96.115.96:8081/tracker`. Verify:
|
||
- Thin teal bar at the very top of the header
|
||
- "into the east" title in DM Serif Display, slightly italic quality
|
||
- Nav links in small DM Sans
|
||
- Active page link is teal
|
||
- Header sticks on scroll (sticky position)
|
||
|
||
Check mobile at 375px viewport: title and nav should both fit in one row. If they don't, reduce `--text-lg` to `--text-md` for the title on mobile via media query.
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast/user
|
||
git add themes/intotheeast/templates/partials/base.html.twig themes/intotheeast/css/style.css
|
||
git commit -m "feat: redesign site header with accent bar and DM Serif title"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4: Entry feed card redesign
|
||
|
||
**Files:**
|
||
- Modify: `user/themes/intotheeast/templates/tracker.html.twig`
|
||
- Modify: `user/themes/intotheeast/css/style.css` (Feed section)
|
||
|
||
**Interfaces:**
|
||
- Consumes: entry frontmatter fields: `header.lat`, `header.lng`, `header.location_city`, `header.location_country`, `header.hero_image`, `media.images`
|
||
- Produces: entry cards used in the tracker feed
|
||
|
||
- [ ] **Step 1: Rewrite the entry card HTML in tracker.html.twig**
|
||
|
||
Find the `{% for entry in entries %}` loop in `tracker.html.twig`. Replace the `<article class="entry-card">` block (everything from `<article` through `</article>`) with:
|
||
|
||
```twig
|
||
<article class="entry-card">
|
||
{% set hero = null %}
|
||
{% if entry.header.hero_image and entry.media[entry.header.hero_image] is defined %}
|
||
{% set hero = entry.media[entry.header.hero_image] %}
|
||
{% elseif entry.media.images|length > 0 %}
|
||
{% set hero = entry.media.images|first %}
|
||
{% endif %}
|
||
|
||
<a class="entry-card-inner" href="{{ entry.url }}">
|
||
{% if hero %}
|
||
<div class="entry-card-photo">
|
||
<img src="{{ hero.cropResize(720, 405).url }}" alt="{{ entry.title }}" loading="lazy">
|
||
<div class="entry-card-photo-overlay">
|
||
<time class="entry-date-overlay" datetime="{{ entry.date|date('Y-m-d') }}">
|
||
{{ entry.date|date('d M Y')|upper }}
|
||
</time>
|
||
{% if entry.header.location_city or entry.header.location_country %}
|
||
<span class="entry-location-overlay">
|
||
📍
|
||
{% if entry.header.location_city %}{{ entry.header.location_city|slice(0,20) }}{% endif %}
|
||
{% if entry.header.location_city and entry.header.location_country %}, {% endif %}
|
||
{% if entry.header.location_country %}{{ entry.header.location_country }}{% endif %}
|
||
</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<div class="entry-card-textmeta">
|
||
<time class="entry-date-plain" datetime="{{ entry.date|date('Y-m-d') }}">
|
||
{{ entry.date|date('d M Y')|upper }}
|
||
</time>
|
||
{% if entry.header.location_city or entry.header.location_country %}
|
||
<span class="entry-location-plain">
|
||
📍
|
||
{% if entry.header.location_city %}{{ entry.header.location_city }}{% endif %}
|
||
{% if entry.header.location_city and entry.header.location_country %}, {% endif %}
|
||
{% if entry.header.location_country %}{{ entry.header.location_country }}{% endif %}
|
||
</span>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<div class="entry-card-body">
|
||
<h2 class="entry-title">{{ entry.title }}</h2>
|
||
<p class="entry-excerpt">{{ entry.summary }}</p>
|
||
<span class="entry-read-more">Read entry →</span>
|
||
</div>
|
||
</a>
|
||
</article>
|
||
```
|
||
|
||
- [ ] **Step 2: Replace the Feed CSS section in style.css**
|
||
|
||
Find `/* ── Feed ──` through to the end of the `/* ── Location & Weather badges ──` section. Replace it entirely with:
|
||
|
||
```css
|
||
/* ── Feed ───────────────────────────────────────────────────────────────────── */
|
||
|
||
.feed { display: flex; flex-direction: column; gap: var(--space-12); }
|
||
.feed-empty { color: var(--color-ink-muted); font-style: italic; }
|
||
|
||
.entry-card { border-bottom: 1px solid var(--color-border); padding-bottom: var(--space-12); }
|
||
|
||
.entry-card-inner {
|
||
display: block;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
}
|
||
|
||
/* ── Card: photo variant ──────────────────────────────────────────────────── */
|
||
|
||
.entry-card-photo {
|
||
position: relative;
|
||
aspect-ratio: 16 / 9;
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
background: var(--color-border);
|
||
margin-bottom: var(--space-5);
|
||
}
|
||
|
||
.entry-card-photo img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
transition: transform 0.45s ease;
|
||
}
|
||
|
||
.entry-card-inner:hover .entry-card-photo img { transform: scale(1.04); }
|
||
|
||
.entry-card-photo-overlay {
|
||
position: absolute;
|
||
inset: auto 0 0 0;
|
||
padding: var(--space-5) var(--space-4) var(--space-3);
|
||
background: linear-gradient(to top, rgba(0,0,0,0.58) 0%, transparent 100%);
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: var(--space-3);
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.entry-date-overlay {
|
||
font-size: var(--text-xs);
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
color: rgba(255,255,255,0.92);
|
||
}
|
||
|
||
.entry-location-overlay {
|
||
font-size: var(--text-xs);
|
||
color: rgba(255,255,255,0.85);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
max-width: 180px;
|
||
}
|
||
|
||
/* ── Card: text-only variant ──────────────────────────────────────────────── */
|
||
|
||
.entry-card-textmeta {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--space-3);
|
||
margin-bottom: var(--space-3);
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.entry-date-plain {
|
||
font-size: var(--text-xs);
|
||
font-weight: 700;
|
||
letter-spacing: 0.07em;
|
||
color: var(--color-ink-muted);
|
||
}
|
||
|
||
.entry-location-plain {
|
||
font-size: var(--text-xs);
|
||
color: var(--color-ink-muted);
|
||
}
|
||
|
||
/* ── Card body ────────────────────────────────────────────────────────────── */
|
||
|
||
.entry-card-body {}
|
||
|
||
.entry-card .entry-title {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-xl);
|
||
font-weight: 400;
|
||
line-height: var(--leading-snug);
|
||
color: var(--color-ink);
|
||
margin-bottom: var(--space-3);
|
||
transition: color 0.15s;
|
||
}
|
||
|
||
.entry-card-inner:hover .entry-title { color: var(--color-accent); }
|
||
|
||
.entry-excerpt {
|
||
font-size: var(--text-base);
|
||
line-height: var(--leading-normal);
|
||
color: var(--color-ink-2);
|
||
margin-bottom: var(--space-3);
|
||
}
|
||
|
||
.entry-read-more {
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
color: var(--color-accent);
|
||
}
|
||
|
||
/* ── Location & weather badges (single entry page) ───────────────────────── */
|
||
|
||
.entry-location {
|
||
font-size: var(--text-sm);
|
||
color: var(--color-ink-2);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: var(--space-1);
|
||
}
|
||
|
||
.entry-weather {
|
||
font-size: var(--text-sm);
|
||
color: var(--color-ink-2);
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Verify feed cards**
|
||
|
||
Open `http://100.96.115.96:8081/tracker`. Verify:
|
||
- Entry cards with photos show full-bleed 16:9 images
|
||
- Date and location text overlay visible on the photo (white on gradient)
|
||
- Photo zooms subtly on hover (desktop)
|
||
- Entry title in DM Serif Display below the photo
|
||
- Text-only cards show date+location meta row above the title
|
||
- Mobile at 375px: images fill full width, overlay is legible
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast/user
|
||
git add themes/intotheeast/templates/tracker.html.twig themes/intotheeast/css/style.css
|
||
git commit -m "feat: redesign entry feed cards with full-bleed photo + overlay"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5: Single entry page redesign
|
||
|
||
**Files:**
|
||
- Modify: `user/themes/intotheeast/templates/entry.html.twig`
|
||
- Modify: `user/themes/intotheeast/css/style.css` (Single entry + Gallery sections)
|
||
|
||
**Interfaces:**
|
||
- Consumes: `page.title`, `page.date`, `page.content`, `page.header.*`, `page.media.images`
|
||
- Produces: the full entry detail page
|
||
|
||
- [ ] **Step 1: Update entry.html.twig header section**
|
||
|
||
Replace the `<article class="entry">` through `<h1 class="entry-title">` block with:
|
||
|
||
```twig
|
||
<article class="entry">
|
||
{% set hero = null %}
|
||
{% if page.header.hero_image and page.media[page.header.hero_image] is defined %}
|
||
{% set hero = page.media[page.header.hero_image] %}
|
||
{% elseif page.media.images|length > 0 %}
|
||
{% set hero = page.media.images|first %}
|
||
{% endif %}
|
||
|
||
{% if hero %}
|
||
<div class="entry-hero">
|
||
<img src="{{ hero.cropResize(1440, 720).url }}" alt="{{ page.title }}" loading="eager">
|
||
</div>
|
||
{% endif %}
|
||
|
||
<header class="entry-header">
|
||
<div class="entry-header-meta">
|
||
<time class="entry-date" datetime="{{ page.date|date('Y-m-d') }}">
|
||
{{ page.date|date('l, d F Y') }}
|
||
</time>
|
||
{% if page.header.location_city or page.header.location_country %}
|
||
<p class="entry-location">
|
||
📍
|
||
{% if page.header.location_city %}{{ page.header.location_city }}{% endif %}
|
||
{% if page.header.location_city and page.header.location_country %}, {% endif %}
|
||
{% if page.header.location_country %}{{ page.header.location_country }}{% endif %}
|
||
</p>
|
||
{% endif %}
|
||
{% if page.header.weather_desc or page.header.weather_temp_c %}
|
||
<p class="entry-weather">
|
||
{% if page.header.weather_desc %}
|
||
{{ weather_icons[page.header.weather_desc] ?? '🌡️' }} {{ page.header.weather_desc }}
|
||
{% endif %}
|
||
{% if page.header.weather_temp_c %}
|
||
· {{ page.header.weather_temp_c|round }}°C
|
||
{% endif %}
|
||
</p>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<h1 class="entry-title">{{ page.title }}</h1>
|
||
<div class="entry-title-rule"></div>
|
||
</header>
|
||
```
|
||
|
||
(Keep the `{% set weather_icons = ... %}` block at the top before `<article>` — move it above the article element, not inside.)
|
||
|
||
- [ ] **Step 2: Replace the Single entry + Gallery CSS sections**
|
||
|
||
Find `/* ── Single entry ──` through the end of `/* ── Lightbox ──` section. Replace entirely with:
|
||
|
||
```css
|
||
/* ── Single entry ───────────────────────────────────────────────────────────── */
|
||
|
||
.entry-hero {
|
||
width: 100%;
|
||
max-height: 480px;
|
||
overflow: hidden;
|
||
margin-bottom: var(--space-8);
|
||
}
|
||
|
||
.entry-hero img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
|
||
.entry-header { margin-bottom: var(--space-8); }
|
||
|
||
.entry-header-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: var(--space-3);
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
|
||
.entry-header .entry-date {
|
||
font-size: var(--text-sm);
|
||
font-weight: 600;
|
||
letter-spacing: 0.06em;
|
||
text-transform: uppercase;
|
||
color: var(--color-ink-muted);
|
||
}
|
||
|
||
.entry .entry-title {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-2xl);
|
||
font-weight: 400;
|
||
line-height: var(--leading-snug);
|
||
color: var(--color-ink);
|
||
margin-bottom: var(--space-4);
|
||
}
|
||
|
||
@media (min-width: 520px) {
|
||
.entry .entry-title { font-size: var(--text-3xl); }
|
||
}
|
||
|
||
.entry-title-rule {
|
||
height: 1px;
|
||
background: var(--color-border);
|
||
margin-bottom: var(--space-8);
|
||
}
|
||
|
||
.entry-body { margin-bottom: var(--space-10); }
|
||
.entry-body p { margin-bottom: 1.1em; font-size: var(--text-md); line-height: var(--leading-normal); color: var(--color-ink-2); }
|
||
.entry-body img { max-width: 100%; height: auto; border-radius: var(--radius-sm); }
|
||
|
||
.entry-footer { border-top: 1px solid var(--color-border); padding-top: var(--space-5); }
|
||
.entry-footer a { color: var(--color-accent); text-decoration: none; font-size: var(--text-sm); font-weight: 500; }
|
||
.entry-footer a:hover { color: var(--color-accent-hover); }
|
||
|
||
/* ── Photo gallery ───────────────────────────────────────────────────────────── */
|
||
|
||
.entry-gallery {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 3px;
|
||
margin-bottom: var(--space-10);
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
}
|
||
|
||
@media (min-width: 520px) {
|
||
.entry-gallery { grid-template-columns: repeat(3, 1fr); }
|
||
}
|
||
|
||
.gallery-thumb {
|
||
background: none;
|
||
border: none;
|
||
padding: 0;
|
||
cursor: pointer;
|
||
display: block;
|
||
aspect-ratio: 1;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.gallery-thumb img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
transition: opacity 0.15s;
|
||
}
|
||
|
||
.gallery-thumb:hover img,
|
||
.gallery-thumb:focus img { opacity: 0.82; }
|
||
|
||
.gallery-thumb:focus { outline: 2px solid var(--color-accent); outline-offset: 2px; }
|
||
|
||
/* ── Lightbox ────────────────────────────────────────────────────────────────── */
|
||
|
||
.lightbox {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0,0,0,0.94);
|
||
z-index: 1000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.lightbox[hidden] { display: none; }
|
||
|
||
.lightbox-img {
|
||
max-width: 92vw;
|
||
max-height: 90vh;
|
||
object-fit: contain;
|
||
border-radius: var(--radius-sm);
|
||
display: block;
|
||
}
|
||
|
||
.lightbox-close,
|
||
.lightbox-prev,
|
||
.lightbox-next {
|
||
position: absolute;
|
||
background: rgba(255,255,255,0.12);
|
||
border: none;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
border-radius: 50%;
|
||
width: 44px;
|
||
height: 44px;
|
||
font-size: 1.4rem;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: background 0.15s;
|
||
}
|
||
|
||
.lightbox-close { top: 1rem; right: 1rem; }
|
||
.lightbox-prev { left: 0.75rem; top: 50%; transform: translateY(-50%); }
|
||
.lightbox-next { right: 0.75rem; top: 50%; transform: translateY(-50%); }
|
||
.lightbox-close:hover,
|
||
.lightbox-prev:hover,
|
||
.lightbox-next:hover { background: rgba(255,255,255,0.26); }
|
||
```
|
||
|
||
- [ ] **Step 3: Move weather_icons to top of entry.html.twig**
|
||
|
||
Ensure the `{% set weather_icons = {...} %}` block appears before `<article class="entry">`, not inside the header block.
|
||
|
||
- [ ] **Step 4: Verify entry page**
|
||
|
||
Open any entry at `http://100.96.115.96:8081/tracker/<slug>`. Verify:
|
||
- If entry has photos: full-width hero image at top, max-height 480px
|
||
- Title in DM Serif Display, large, below the header meta
|
||
- Thin rule under the title before body text
|
||
- Body text at 18px, comfortable line height
|
||
- Gallery grid below body (2-col / 3-col)
|
||
- "← Back to journal" footer link in teal
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast/user
|
||
git add themes/intotheeast/templates/entry.html.twig themes/intotheeast/css/style.css
|
||
git commit -m "feat: redesign single entry page with hero image and display typography"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6: Post form UX redesign
|
||
|
||
**Files:**
|
||
- Modify: `user/pages/02.post/post-form.md` (hide lat/lng from UI)
|
||
- Modify: `user/themes/intotheeast/templates/post-form.html.twig`
|
||
- Modify: `user/themes/intotheeast/css/style.css` (Post form section)
|
||
|
||
**Interfaces:**
|
||
- Consumes: Grav form plugin field rendering (inputs named `data[fieldname]`)
|
||
- Produces: improved mobile posting form
|
||
|
||
- [ ] **Step 1: Hide lat/lng inputs in post-form.md**
|
||
|
||
In `user/pages/02.post/post-form.md`, add a `classes` key to the lat and lng field definitions so they can be hidden via CSS:
|
||
|
||
Change the lat field from:
|
||
```yaml
|
||
-
|
||
name: lat
|
||
label: Latitude
|
||
type: text
|
||
placeholder: 'tap "Get Location" below'
|
||
```
|
||
|
||
To:
|
||
```yaml
|
||
-
|
||
name: lat
|
||
label: Latitude
|
||
type: text
|
||
placeholder: ''
|
||
classes: gps-hidden-field
|
||
```
|
||
|
||
Change the lng field from:
|
||
```yaml
|
||
-
|
||
name: lng
|
||
label: Longitude
|
||
type: text
|
||
placeholder: ''
|
||
```
|
||
|
||
To:
|
||
```yaml
|
||
-
|
||
name: lng
|
||
label: Longitude
|
||
type: text
|
||
placeholder: ''
|
||
classes: gps-hidden-field
|
||
```
|
||
|
||
Note: Grav form plugin applies the `classes` value to the field wrapper div. Verify this by inspecting the rendered form HTML after the change.
|
||
|
||
- [ ] **Step 2: Update post-form.html.twig**
|
||
|
||
Replace the entire content of `user/themes/intotheeast/templates/post-form.html.twig` with:
|
||
|
||
```twig
|
||
{% extends 'default.html.twig' %}
|
||
|
||
{% block content %}
|
||
<div class="post-form-wrap">
|
||
<h1>New Entry</h1>
|
||
{% include 'forms/form.html.twig' ignore missing %}
|
||
|
||
<div class="form-action-row">
|
||
<button type="button" id="get-location" class="btn-action">📍 Get Location</button>
|
||
<button type="button" id="get-weather" class="btn-action">🌤 Get Weather</button>
|
||
</div>
|
||
<p id="location-status" class="form-status"></p>
|
||
<p id="weather-status" class="form-status"></p>
|
||
</div>
|
||
|
||
<script>
|
||
var WMO_MAP = {
|
||
0:'Sunny',1:'Partly cloudy',2:'Partly cloudy',3:'Cloudy',
|
||
45:'Foggy',48:'Foggy',
|
||
51:'Drizzle',53:'Drizzle',55:'Drizzle',56:'Drizzle',57:'Drizzle',
|
||
61:'Rain',63:'Rain',65:'Rain',66:'Rain',67:'Rain',80:'Rain',81:'Rain',82:'Rain',
|
||
71:'Snow',73:'Snow',75:'Snow',77:'Snow',85:'Snow',86:'Snow',
|
||
95:'Thunderstorm',96:'Thunderstorm',99:'Thunderstorm'
|
||
};
|
||
|
||
function getField(name) {
|
||
return document.querySelector('input[name="data[' + name + ']"]');
|
||
}
|
||
|
||
document.getElementById('get-location').addEventListener('click', function() {
|
||
var status = document.getElementById('location-status');
|
||
status.textContent = 'Getting location…';
|
||
if (!navigator.geolocation) {
|
||
status.textContent = 'Geolocation not supported.';
|
||
return;
|
||
}
|
||
navigator.geolocation.getCurrentPosition(function(pos) {
|
||
var lat = pos.coords.latitude.toFixed(6);
|
||
var lng = pos.coords.longitude.toFixed(6);
|
||
var latField = getField('lat');
|
||
var lngField = getField('lng');
|
||
if (latField) latField.value = lat;
|
||
if (lngField) lngField.value = lng;
|
||
status.textContent = '✓ Location captured · ' + lat + ', ' + lng;
|
||
status.classList.add('form-status--ok');
|
||
}, function(err) {
|
||
status.textContent = '✗ Could not get location: ' + err.message;
|
||
status.classList.add('form-status--err');
|
||
});
|
||
});
|
||
|
||
document.getElementById('get-weather').addEventListener('click', function() {
|
||
var status = document.getElementById('weather-status');
|
||
var latField = getField('lat');
|
||
var lngField = getField('lng');
|
||
var lat = latField ? latField.value.trim() : '';
|
||
var lng = lngField ? lngField.value.trim() : '';
|
||
if (!lat || !lng) {
|
||
status.textContent = 'Get location first, then fetch weather.';
|
||
return;
|
||
}
|
||
status.textContent = 'Fetching weather…';
|
||
var url = 'https://api.open-meteo.com/v1/forecast?latitude=' + lat +
|
||
'&longitude=' + lng +
|
||
'¤t=temperature_2m,weather_code&temperature_unit=celsius';
|
||
fetch(url)
|
||
.then(function(r) { return r.json(); })
|
||
.then(function(data) {
|
||
var temp = Math.round(data.current.temperature_2m);
|
||
var code = data.current.weather_code;
|
||
var desc = WMO_MAP[code] || 'Cloudy';
|
||
var tempField = getField('weather_temp_c');
|
||
var descField = getField('weather_desc');
|
||
if (tempField) tempField.value = temp;
|
||
if (descField) descField.value = desc;
|
||
status.textContent = '✓ Weather set · ' + desc + ' · ' + temp + '°C';
|
||
status.classList.add('form-status--ok');
|
||
})
|
||
.catch(function() {
|
||
status.textContent = '✗ Could not fetch weather — enter manually if needed.';
|
||
status.classList.add('form-status--err');
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|
||
```
|
||
|
||
- [ ] **Step 3: Replace the Post form CSS section**
|
||
|
||
Find `/* ── Post form ──` in style.css. Replace it entirely with:
|
||
|
||
```css
|
||
/* ── Post form ──────────────────────────────────────────────────────────────── */
|
||
|
||
.post-form-wrap h1 {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-xl);
|
||
font-weight: 400;
|
||
margin-bottom: var(--space-6);
|
||
color: var(--color-ink);
|
||
}
|
||
|
||
/* Hide GPS coordinate fields — filled by JS, not user-facing */
|
||
.gps-hidden-field { display: none !important; }
|
||
|
||
/* Grav form field wrappers */
|
||
.form-field { margin-bottom: var(--space-5); }
|
||
.form-label label {
|
||
display: block;
|
||
font-size: var(--text-sm);
|
||
font-weight: 600;
|
||
color: var(--color-ink);
|
||
margin-bottom: var(--space-2);
|
||
}
|
||
|
||
.form-field input[type="text"],
|
||
.form-field input[type="email"],
|
||
.form-field input[type="datetime-local"],
|
||
.form-field textarea {
|
||
width: 100%;
|
||
font-family: var(--font-ui);
|
||
font-size: var(--text-base);
|
||
padding: 0.875rem 1rem;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-md);
|
||
background: var(--color-canvas);
|
||
color: var(--color-ink);
|
||
min-height: 44px;
|
||
transition: border-color 0.15s;
|
||
-webkit-appearance: none;
|
||
}
|
||
|
||
.form-field input:focus,
|
||
.form-field textarea:focus {
|
||
outline: 2px solid var(--color-accent);
|
||
outline-offset: 1px;
|
||
border-color: var(--color-accent);
|
||
}
|
||
|
||
.form-field textarea { resize: vertical; min-height: 160px; line-height: var(--leading-normal); }
|
||
|
||
/* Submit button — Grav renders it as .btn or input[type=submit] */
|
||
.form-actions input[type="submit"],
|
||
.form-actions .btn,
|
||
.form-actions button[type="submit"] {
|
||
display: block;
|
||
width: 100%;
|
||
padding: 1rem;
|
||
min-height: 52px;
|
||
background: var(--color-accent);
|
||
color: var(--color-accent-on);
|
||
border: none;
|
||
border-radius: var(--radius-md);
|
||
font-family: var(--font-ui);
|
||
font-size: var(--text-base);
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: background 0.15s;
|
||
margin-top: var(--space-6);
|
||
}
|
||
|
||
.form-actions input[type="submit"]:hover,
|
||
.form-actions button[type="submit"]:hover { background: var(--color-accent-hover); }
|
||
|
||
/* Action buttons row (Get Location, Get Weather) */
|
||
.form-action-row {
|
||
display: flex;
|
||
gap: var(--space-3);
|
||
margin-top: var(--space-5);
|
||
}
|
||
|
||
.btn-action {
|
||
flex: 1;
|
||
padding: 0.75rem var(--space-3);
|
||
min-height: 44px;
|
||
background: var(--color-canvas);
|
||
border: 1px solid var(--color-border);
|
||
border-radius: var(--radius-md);
|
||
font-family: var(--font-ui);
|
||
font-size: var(--text-sm);
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
color: var(--color-ink);
|
||
transition: background 0.15s, border-color 0.15s;
|
||
}
|
||
|
||
.btn-action:hover { background: var(--color-paper); border-color: var(--color-accent); }
|
||
|
||
/* Status feedback lines */
|
||
.form-status {
|
||
font-size: var(--text-sm);
|
||
color: var(--color-ink-muted);
|
||
margin-top: var(--space-2);
|
||
min-height: 1.4em;
|
||
}
|
||
|
||
.form-status--ok { color: var(--color-accent); }
|
||
.form-status--err { color: #B44A2A; }
|
||
```
|
||
|
||
- [ ] **Step 4: Verify post form**
|
||
|
||
Open `http://100.96.115.96:8081/post` (logged in). Verify:
|
||
- Lat/lng inputs not visible (`.gps-hidden-field` hidden via CSS)
|
||
- Inputs have rounded corners, proper padding, focus ring in teal
|
||
- "Get Location" and "Get Weather" buttons side by side, same width
|
||
- "Post Entry" (or whatever the submit label is) in teal, full-width
|
||
- Tap "Get Location" → status line shows "✓ Location captured · lat, lng" in teal
|
||
- Mobile at 375px: all inputs and buttons are thumb-friendly
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast/user
|
||
git add pages/02.post/post-form.md themes/intotheeast/templates/post-form.html.twig themes/intotheeast/css/style.css
|
||
git commit -m "feat: redesign post form — hide GPS fields, teal CTA, better mobile UX"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 7: Stats + map + mini-map styling
|
||
|
||
**Files:**
|
||
- Modify: `user/themes/intotheeast/templates/stats.html.twig`
|
||
- Modify: `user/themes/intotheeast/css/style.css` (Map, Stats, Mini-map sections)
|
||
|
||
**Interfaces:**
|
||
- Produces: styled stats page and map page using design tokens
|
||
|
||
- [ ] **Step 1: Update stats page heading**
|
||
|
||
In `user/themes/intotheeast/templates/stats.html.twig`, replace the `<h1 style="...">` tag with:
|
||
|
||
```twig
|
||
<div class="stats-page">
|
||
<h1 class="stats-heading">Trip Statistics</h1>
|
||
```
|
||
|
||
(Remove the inline `style` attribute from the existing h1.)
|
||
|
||
- [ ] **Step 2: Replace Stats + Map + Mini-map CSS sections**
|
||
|
||
Find `/* ── Map page ──` through end of `/* ── Mini-map on tracker feed ──`. Replace all three sections with:
|
||
|
||
```css
|
||
/* ── Map page ───────────────────────────────────────────────────────────────── */
|
||
|
||
.map-page .site-main { max-width: none; padding: 0; }
|
||
|
||
.map-container {
|
||
height: calc(100vh - var(--site-header-height));
|
||
width: 100%;
|
||
}
|
||
|
||
.map-empty {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100%;
|
||
color: var(--color-ink-muted);
|
||
font-style: italic;
|
||
}
|
||
|
||
/* ── Stats page ─────────────────────────────────────────────────────────────── */
|
||
|
||
.stats-heading {
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-2xl);
|
||
font-weight: 400;
|
||
margin-bottom: var(--space-8);
|
||
color: var(--color-ink);
|
||
}
|
||
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: var(--space-4);
|
||
margin-bottom: var(--space-8);
|
||
}
|
||
|
||
.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 {
|
||
display: block;
|
||
font-family: var(--font-display);
|
||
font-size: var(--text-3xl);
|
||
font-weight: 400;
|
||
color: var(--color-accent);
|
||
line-height: 1.1;
|
||
margin-bottom: var(--space-2);
|
||
}
|
||
|
||
.stat-label {
|
||
display: block;
|
||
font-size: var(--text-xs);
|
||
font-weight: 600;
|
||
color: var(--color-ink-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.07em;
|
||
}
|
||
|
||
.stats-countries {
|
||
font-size: var(--text-sm);
|
||
color: var(--color-ink-2);
|
||
text-align: center;
|
||
line-height: 1.9;
|
||
}
|
||
|
||
.stats-countries-label {
|
||
font-weight: 600;
|
||
display: block;
|
||
margin-bottom: var(--space-2);
|
||
color: var(--color-ink);
|
||
text-transform: uppercase;
|
||
font-size: var(--text-xs);
|
||
letter-spacing: 0.07em;
|
||
}
|
||
|
||
.stats-note {
|
||
font-size: var(--text-xs);
|
||
color: var(--color-ink-muted);
|
||
text-align: center;
|
||
margin-top: var(--space-6);
|
||
}
|
||
|
||
/* ── Mini-map on tracker feed ────────────────────────────────────────────────── */
|
||
|
||
.feed-map-wrap {
|
||
margin-bottom: var(--space-10);
|
||
border-radius: var(--radius-md);
|
||
overflow: hidden;
|
||
border: 1px solid var(--color-border);
|
||
box-shadow: var(--shadow-sm);
|
||
}
|
||
|
||
.feed-map {
|
||
height: 240px;
|
||
width: 100%;
|
||
}
|
||
|
||
@media (min-width: 520px) {
|
||
.feed-map { height: 300px; }
|
||
}
|
||
|
||
.feed-map-link {
|
||
display: block;
|
||
text-align: right;
|
||
font-size: var(--text-xs);
|
||
font-weight: 500;
|
||
color: var(--color-accent);
|
||
text-decoration: none;
|
||
padding: var(--space-2) var(--space-4);
|
||
background: var(--color-paper);
|
||
border-top: 1px solid var(--color-border);
|
||
}
|
||
|
||
.feed-map-link:hover { color: var(--color-accent-hover); }
|
||
```
|
||
|
||
- [ ] **Step 3: Update Leaflet marker colors**
|
||
|
||
In `tracker.html.twig`, find the JS that sets marker colors. Update from `#0066cc` to `#1F6B5A` and from `#0044aa` to `#155244`:
|
||
|
||
```js
|
||
var color = isLatest ? '#155244' : '#1F6B5A';
|
||
```
|
||
|
||
Also update the polyline color:
|
||
```js
|
||
L.polyline(latLngs, { color: '#1F6B5A', weight: 3, opacity: 0.7 }).addTo(map);
|
||
```
|
||
|
||
Do the same in `map.html.twig` — update any hardcoded `#0066cc` colors to `#1F6B5A`.
|
||
|
||
- [ ] **Step 4: Verify stats and map pages**
|
||
|
||
Open `http://100.96.115.96:8081/stats`. Verify:
|
||
- Heading in DM Serif Display
|
||
- Numbers in DM Serif Display, teal color
|
||
- Stat cards on warm white background with subtle shadow
|
||
- Labels uppercase, muted gray
|
||
|
||
Open `http://100.96.115.96:8081/map`. Verify:
|
||
- Map fills viewport below header
|
||
- Markers are teal circles
|
||
- Route line is teal
|
||
- No horizontal scroll or layout issues
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast/user
|
||
git add themes/intotheeast/templates/stats.html.twig themes/intotheeast/templates/tracker.html.twig themes/intotheeast/templates/map.html.twig themes/intotheeast/css/style.css
|
||
git commit -m "feat: apply design tokens to stats, map, and mini-map"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 8: Mobile polish + reduced motion + final QA
|
||
|
||
**Files:**
|
||
- Modify: `user/themes/intotheeast/css/style.css` (add responsive + motion CSS)
|
||
|
||
**Interfaces:**
|
||
- Produces: fully responsive, accessible design at 320px–1440px viewport widths
|
||
|
||
- [ ] **Step 1: Add reduced-motion support**
|
||
|
||
At the bottom of `style.css`, append:
|
||
|
||
```css
|
||
/* ── Accessibility ───────────────────────────────────────────────────────────── */
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
*,
|
||
*::before,
|
||
*::after {
|
||
animation-duration: 0.01ms !important;
|
||
animation-iteration-count: 1 !important;
|
||
transition-duration: 0.01ms !important;
|
||
}
|
||
}
|
||
|
||
/* Keyboard focus ring: visible on all interactive elements */
|
||
:focus-visible {
|
||
outline: 2px solid var(--color-accent);
|
||
outline-offset: 2px;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Verify header on 320px (smallest phone)**
|
||
|
||
Set browser devtools to 320px viewport. Verify:
|
||
- Site title and nav links both visible and not overlapping
|
||
- If title overflows, add this to style.css:
|
||
```css
|
||
@media (max-width: 380px) {
|
||
.site-title { font-size: var(--text-md); }
|
||
.site-nav a { padding: var(--space-2); font-size: 0.8rem; }
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Verify post form on mobile**
|
||
|
||
On a real phone (or devtools 375px), open `/post` and verify:
|
||
- Inputs do not trigger zoom on focus (font-size is 1rem ≥ 16px — already set)
|
||
- "Post Entry" button is thumb-reachable (full-width, 52px min height)
|
||
- Get Location / Get Weather buttons are side-by-side, each at least 44px tall
|
||
- Status feedback visible after tapping location/weather
|
||
|
||
- [ ] **Step 4: Cross-page smoke test checklist**
|
||
|
||
Check each of these manually in the browser:
|
||
|
||
| Page | Check |
|
||
|---|---|
|
||
| `/tracker` | Feed loads, entry cards show hero photos with overlays |
|
||
| `/tracker` | Text-only cards (no photo) show date+location meta above title |
|
||
| `/tracker` | Mini-map renders, teal markers and route |
|
||
| `/map` | Full-height map, teal markers, route polyline |
|
||
| `/map` | Tap marker → popup with date, title, "Read entry →" link |
|
||
| `/stats` | 2×2 grid of stat blocks, teal numbers, correct counts |
|
||
| `/tracker/<slug>` | Hero image full-width at top (if photos exist) |
|
||
| `/tracker/<slug>` | Title in DM Serif Display, large |
|
||
| `/tracker/<slug>` | Photo gallery (2/3-col grid), lightbox opens on tap |
|
||
| `/post` | Lat/lng fields NOT visible |
|
||
| `/post` | Tap Post Entry → success message → redirects to /tracker |
|
||
| Mobile 375px | All pages usable without horizontal scroll |
|
||
| Mobile 375px | No font size < 14px for readable text |
|
||
|
||
- [ ] **Step 5: Final commit**
|
||
|
||
```bash
|
||
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast/user
|
||
git add themes/intotheeast/css/style.css
|
||
git commit -m "feat: add reduced-motion support, keyboard focus, mobile polish"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 9: Update documentation
|
||
|
||
**Files:**
|
||
- Modify: `user/docs/qa-test-plan.md` (add visual QA section)
|
||
|
||
- [ ] **Step 1: Add visual design QA section to qa-test-plan.md**
|
||
|
||
Append a new section to `user/docs/qa-test-plan.md`:
|
||
|
||
```markdown
|
||
## Visual Design QA — Redesign Checklist
|
||
|
||
**Design spec:** `user/docs/design/design-spec.md`
|
||
|
||
### Typography
|
||
- [ ] DM Serif Display loads on entry titles, page headings, stats numbers, site title
|
||
- [ ] DM Sans loads on all body text, nav, labels, form fields
|
||
- [ ] No fallback font (Georgia/system-sans) visible in place of custom fonts
|
||
|
||
### Colors
|
||
- [ ] Page background is warm paper (#F7F5F2), not pure white
|
||
- [ ] All links and CTAs use teal (#1F6B5A), not blue (#0066cc)
|
||
- [ ] Active nav link is teal
|
||
- [ ] Map markers and route polyline are teal
|
||
|
||
### Entry cards
|
||
- [ ] Cards with photos show full-bleed 16:9 image
|
||
- [ ] Date + location overlay visible on photo gradient
|
||
- [ ] Entry title below photo in DM Serif Display
|
||
- [ ] Cards without photos show date/location meta row above title
|
||
- [ ] Photo zoom on hover (desktop only)
|
||
|
||
### Header
|
||
- [ ] 3px teal bar at top of header
|
||
- [ ] "into the east" title in DM Serif Display
|
||
- [ ] Sticky on scroll
|
||
|
||
### Post form
|
||
- [ ] Lat/lng inputs not visible
|
||
- [ ] "✓ Location captured" feedback in teal on success
|
||
- [ ] Submit button full-width, teal, 52px+ height
|
||
|
||
### Mobile
|
||
- [ ] All interactive elements ≥ 44px touch target
|
||
- [ ] No horizontal scroll at 375px
|
||
- [ ] iOS: no font-size zoom on input focus
|
||
```
|
||
|
||
- [ ] **Step 2: Commit documentation**
|
||
|
||
```bash
|
||
cd /home/mischa/Nextcloud/Projects/travel-blog-intotheeast/user
|
||
git add docs/qa-test-plan.md docs/design/design-spec.md docs/working/plans/2026-06-18-ui-redesign.md
|
||
git commit -m "docs: add UI redesign spec, plan, and visual QA checklist"
|
||
```
|