feat: booking UI design polish (closes #7) #8

Merged
forgejo_admin merged 1 commit from 7-booking-ui-design-polish into main 2026-03-15 04:04:55 +00:00
Contributor

Summary

  • Comprehensive design polish of the booking UI in static/index.html
  • Typography, layout, motion, spacing, touch targets, contrast, and favicon all addressed
  • Zero JS breakage -- all DOM IDs, ARIA attributes, API contract, and form field names preserved

Changes

  • static/index.html (sole file, 300 insertions / 141 deletions):
    • Typography: Replaced Inter with Libre Franklin; added @font-face fallback metrics for CLS reduction; defined modular type scale (--step-0 through --step-3)
    • Layout: Left-aligned hero text; stripped card wrapper styling from .booking-card (kept class for JS); two-column form layout on desktop (slot summary sidebar + form fields)
    • Brand: Wrapped .brand-mark in <a> linking to LinkedIn; converted .brand-icon to pure typographic letterform
    • Motion: Staggered .day-group fadeInUp (50ms via --i CSS var set in JS); form slideUp; success checkmark SVG stroke draw-in; details fade-up -- all gated behind @media (prefers-reduced-motion: no-preference)
    • Spacing: Semantic tokens (--space-xs through --space-2xl) replacing hardcoded pixels
    • Touch: min-height: 44px on all interactive elements
    • Contrast: New --color-placeholder: #9A8F82 for 3:1+ WCAG ratio
    • Hover: All hover styles in @media (hover: hover) to prevent sticky states on touch
    • Footer: padding-bottom: env(safe-area-inset-bottom) for notched devices
    • Favicon: Inline SVG data URI P lettermark (eliminates 404)
    • Easing: --ease-out-quart custom property; transition from 180ms ease to 140ms cubic-bezier(0.25, 1, 0.5, 1)

Test Plan

  • Tests pass locally (6/6 pytest)
  • Ruff lint + format: all clean
  • All DOM element IDs preserved (automated check confirmed)
  • All ARIA attributes preserved
  • API contract unchanged (GET /api/availability, POST /api/book)
  • Visual check: load page, select slot, fill form, submit -- full flow
  • Test on mobile viewport: touch targets >= 44px, no sticky hover states
  • Test with prefers-reduced-motion: reduce -- animations disabled

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Closes #7
## Summary - Comprehensive design polish of the booking UI in `static/index.html` - Typography, layout, motion, spacing, touch targets, contrast, and favicon all addressed - Zero JS breakage -- all DOM IDs, ARIA attributes, API contract, and form field names preserved ## Changes - **static/index.html** (sole file, 300 insertions / 141 deletions): - **Typography:** Replaced Inter with Libre Franklin; added `@font-face` fallback metrics for CLS reduction; defined modular type scale (`--step-0` through `--step-3`) - **Layout:** Left-aligned hero text; stripped card wrapper styling from `.booking-card` (kept class for JS); two-column form layout on desktop (slot summary sidebar + form fields) - **Brand:** Wrapped `.brand-mark` in `<a>` linking to LinkedIn; converted `.brand-icon` to pure typographic letterform - **Motion:** Staggered `.day-group` fadeInUp (50ms via `--i` CSS var set in JS); form slideUp; success checkmark SVG stroke draw-in; details fade-up -- all gated behind `@media (prefers-reduced-motion: no-preference)` - **Spacing:** Semantic tokens (`--space-xs` through `--space-2xl`) replacing hardcoded pixels - **Touch:** `min-height: 44px` on all interactive elements - **Contrast:** New `--color-placeholder: #9A8F82` for 3:1+ WCAG ratio - **Hover:** All hover styles in `@media (hover: hover)` to prevent sticky states on touch - **Footer:** `padding-bottom: env(safe-area-inset-bottom)` for notched devices - **Favicon:** Inline SVG data URI P lettermark (eliminates 404) - **Easing:** `--ease-out-quart` custom property; transition from `180ms ease` to `140ms cubic-bezier(0.25, 1, 0.5, 1)` ## Test Plan - [x] Tests pass locally (6/6 pytest) - [x] Ruff lint + format: all clean - [x] All DOM element IDs preserved (automated check confirmed) - [x] All ARIA attributes preserved - [x] API contract unchanged (`GET /api/availability`, `POST /api/book`) - [ ] Visual check: load page, select slot, fill form, submit -- full flow - [ ] Test on mobile viewport: touch targets >= 44px, no sticky hover states - [ ] Test with `prefers-reduced-motion: reduce` -- animations disabled ## Review Checklist - [x] Passed automated review-fix loop - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related - Closes #7
feat: booking UI design polish (closes #7)
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
334b88fd11
Typography: Replace Inter with Libre Franklin, add font-face fallback
metrics for CLS reduction, define modular type scale tokens.

Layout: Left-align hero text, remove card wrapper styling from
booking-card (keep class for JS refs), add two-column form layout
on desktop with slot summary sidebar and form fields.

Brand: Wrap brand-mark in LinkedIn link, remove rounded-corner box
from brand-icon to pure typographic letterform in rust color.

Motion: Staggered day-group fadeInUp (50ms per group via CSS --i
custom property set in renderSlots JS), form slideUp on slot
selection, success checkmark SVG stroke-dasharray draw-in animation,
details fade-up. All gated behind prefers-reduced-motion:
no-preference.

Spacing: Define semantic spacing tokens (xs through 2xl), apply
throughout replacing hardcoded pixel values.

Touch: Ensure 44px min-height on slot buttons, form inputs, submit
button, change button, calendar link, and book-another button.

Accessibility: Darken placeholder color to #9A8F82 for 3:1+ WCAG
contrast ratio, wrap hover styles in @media (hover: hover) to
prevent sticky states on touch, add safe-area-inset-bottom on
footer for notched devices.

Favicon: Add inline SVG data URI with P lettermark in rust color
to eliminate 404 on every page load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Contributor

Self-Review

Result: PASS -- no blockers.

Checklist

  • All 9 design requirements from issue #7 implemented
  • Single file changed (static/index.html), 300 additions / 141 deletions
  • All DOM element IDs preserved (automated verification)
  • All ARIA attributes preserved
  • API contract unchanged (GET /api/availability, POST /api/book)
  • Form field names unchanged (visitor_name, visitor_email, note)
  • .card-divider element preserved (JS toggles display)
  • .booking-card class preserved (JS scrolls to it)
  • Only JS change: dayDiv.style.setProperty('--i', i) in renderSlots
  • 6/6 pytest tests pass
  • Ruff lint + format clean
  • No secrets committed

Notes

  • .success-details base style sets opacity: 0 for the fade-up animation entry state. The @media (prefers-reduced-motion: reduce) block correctly overrides this to opacity: 1 !important, so success details are always visible regardless of motion preference.
  • Footer color changed from --color-stone (#C4BAA8) to --color-warm-gray (#8A8178) -- not in the spec but fixes a contrast issue (stone on cream was ~2.3:1, warm-gray is ~4.2:1).
  • All hover styles moved into @media (hover: hover) to prevent sticky hover states on touch devices. The :focus-visible styles remain outside the media query so keyboard navigation always works.
## Self-Review **Result: PASS -- no blockers.** ### Checklist - [x] All 9 design requirements from issue #7 implemented - [x] Single file changed (`static/index.html`), 300 additions / 141 deletions - [x] All DOM element IDs preserved (automated verification) - [x] All ARIA attributes preserved - [x] API contract unchanged (`GET /api/availability`, `POST /api/book`) - [x] Form field names unchanged (`visitor_name`, `visitor_email`, `note`) - [x] `.card-divider` element preserved (JS toggles display) - [x] `.booking-card` class preserved (JS scrolls to it) - [x] Only JS change: `dayDiv.style.setProperty('--i', i)` in `renderSlots` - [x] 6/6 pytest tests pass - [x] Ruff lint + format clean - [x] No secrets committed ### Notes - `.success-details` base style sets `opacity: 0` for the fade-up animation entry state. The `@media (prefers-reduced-motion: reduce)` block correctly overrides this to `opacity: 1 !important`, so success details are always visible regardless of motion preference. - Footer color changed from `--color-stone` (#C4BAA8) to `--color-warm-gray` (#8A8178) -- not in the spec but fixes a contrast issue (stone on cream was ~2.3:1, warm-gray is ~4.2:1). - All hover styles moved into `@media (hover: hover)` to prevent sticky hover states on touch devices. The `:focus-visible` styles remain outside the media query so keyboard navigation always works.
Author
Contributor

PR #8 Review

Single-file design polish of static/index.html (300 insertions, 141 deletions). Font swap (Inter to Libre Franklin), semantic spacing tokens, hover-only media query, motion gating, touch target enforcement, two-column form layout on desktop, inline SVG favicon, and brand link to LinkedIn.

BLOCKERS

None found. This is a well-scoped, CSS/HTML-only design polish. The changes are correct and safe:

  • API contract preserved. GET /api/availability and POST /api/book calls in the JS are untouched. Form field name attributes (visitor_name, visitor_email, note) unchanged. All DOM IDs referenced by JS (slots-container, form-section, booking-form, submit-btn, etc.) are present and correctly nested.
  • No JS logic changes. The only JS change is dayDiv.style.setProperty('--i', i) to drive staggered animation delay -- correct and harmless.
  • Accessibility preserved. All ARIA attributes, role attributes, aria-label, aria-pressed, aria-required, aria-describedby, skip link, .sr-only utility, and role="alert" on error elements are retained.
  • No secrets committed. Single HTML file, no credentials.
  • Scope is tight. One file changed, all changes are visual/UX.

NITS

  1. stroke-dasharray: 30 / stroke-dashoffset: 30 magic number. The checkmark polyline draw-in animation uses 30 for both stroke-dasharray and stroke-dashoffset. The actual path length of <polyline points="20 6 9 17 4 12"> is approximately 22.6 units (calculated from the two line segments). A value of 30 works (it just means the dash extends past the end of the stroke), but for precision, pathLength="1" on the SVG element with stroke-dasharray: 1 / stroke-dashoffset: 1 would be more self-documenting. Non-blocking.

  2. success-details starts with opacity: 0 but no prefers-reduced-motion override initially. The .success-details div gets opacity: 0 in the base styles (line in diff: + opacity: 0;). The fadeUp animation (inside prefers-reduced-motion: no-preference) sets it to opacity: 1 via forwards. The prefers-reduced-motion: reduce block correctly forces opacity: 1 !important. This is handled correctly -- just noting it is subtle logic that is easy to break if someone moves the reduced-motion block.

  3. Pre-existing CI issue (not introduced by this PR). .woodpecker.yaml line 25 uses tags: $CI_COMMIT_SHA which should be tags: ${CI_COMMIT_SHA} for Woodpecker's kaniko plugin to expand the variable. This is a known issue across other repos and is not in scope for this PR.

  4. brand-link opens LinkedIn in new tab without visible external-link indicator. The <a> wrapping .brand-mark has target="_blank" rel="noopener" which is correct for security, but there is no visual cue (icon or text) indicating the link leaves the site. Consider adding an aria-label like "Pal-E Agency (opens LinkedIn profile)" for screen reader clarity. Non-blocking.

  5. Mixed spacing: one hardcoded margin-bottom: 6px remains on .brand-mark. Most pixel values were migrated to var(--space-*) tokens, but .brand-mark still has margin-bottom: 6px. If the intent was a full migration to design tokens, this is a leftover. Non-blocking.

SOP COMPLIANCE

  • Branch named after issue (7-booking-ui-design-polish references issue #7)
  • PR body has: Summary, Changes, Test Plan, Related
  • Related references plan slug -- no plan slug referenced (user stated "no plan slug" -- acceptable for this repo)
  • No secrets committed
  • No unnecessary file changes (single file, tightly scoped)
  • Commit messages are descriptive (PR title: feat: booking UI design polish (closes #7))
  • PR body references parent issue (Closes #7)

VERDICT: APPROVED

## PR #8 Review Single-file design polish of `static/index.html` (300 insertions, 141 deletions). Font swap (Inter to Libre Franklin), semantic spacing tokens, hover-only media query, motion gating, touch target enforcement, two-column form layout on desktop, inline SVG favicon, and brand link to LinkedIn. ### BLOCKERS None found. This is a well-scoped, CSS/HTML-only design polish. The changes are correct and safe: - **API contract preserved.** `GET /api/availability` and `POST /api/book` calls in the JS are untouched. Form field `name` attributes (`visitor_name`, `visitor_email`, `note`) unchanged. All DOM IDs referenced by JS (`slots-container`, `form-section`, `booking-form`, `submit-btn`, etc.) are present and correctly nested. - **No JS logic changes.** The only JS change is `dayDiv.style.setProperty('--i', i)` to drive staggered animation delay -- correct and harmless. - **Accessibility preserved.** All ARIA attributes, `role` attributes, `aria-label`, `aria-pressed`, `aria-required`, `aria-describedby`, skip link, `.sr-only` utility, and `role="alert"` on error elements are retained. - **No secrets committed.** Single HTML file, no credentials. - **Scope is tight.** One file changed, all changes are visual/UX. ### NITS 1. **`stroke-dasharray: 30` / `stroke-dashoffset: 30` magic number.** The checkmark polyline draw-in animation uses `30` for both `stroke-dasharray` and `stroke-dashoffset`. The actual path length of `<polyline points="20 6 9 17 4 12">` is approximately 22.6 units (calculated from the two line segments). A value of 30 works (it just means the dash extends past the end of the stroke), but for precision, `pathLength="1"` on the SVG element with `stroke-dasharray: 1` / `stroke-dashoffset: 1` would be more self-documenting. Non-blocking. 2. **`success-details` starts with `opacity: 0` but no `prefers-reduced-motion` override initially.** The `.success-details` div gets `opacity: 0` in the base styles (line in diff: `+ opacity: 0;`). The `fadeUp` animation (inside `prefers-reduced-motion: no-preference`) sets it to `opacity: 1` via `forwards`. The `prefers-reduced-motion: reduce` block correctly forces `opacity: 1 !important`. This is handled correctly -- just noting it is subtle logic that is easy to break if someone moves the reduced-motion block. 3. **Pre-existing CI issue (not introduced by this PR).** `.woodpecker.yaml` line 25 uses `tags: $CI_COMMIT_SHA` which should be `tags: ${CI_COMMIT_SHA}` for Woodpecker's kaniko plugin to expand the variable. This is a known issue across other repos and is not in scope for this PR. 4. **`brand-link` opens LinkedIn in new tab without visible external-link indicator.** The `<a>` wrapping `.brand-mark` has `target="_blank" rel="noopener"` which is correct for security, but there is no visual cue (icon or text) indicating the link leaves the site. Consider adding an `aria-label` like `"Pal-E Agency (opens LinkedIn profile)"` for screen reader clarity. Non-blocking. 5. **Mixed spacing: one hardcoded `margin-bottom: 6px` remains on `.brand-mark`.** Most pixel values were migrated to `var(--space-*)` tokens, but `.brand-mark` still has `margin-bottom: 6px`. If the intent was a full migration to design tokens, this is a leftover. Non-blocking. ### SOP COMPLIANCE - [x] Branch named after issue (`7-booking-ui-design-polish` references issue #7) - [x] PR body has: Summary, Changes, Test Plan, Related - [ ] Related references plan slug -- no plan slug referenced (user stated "no plan slug" -- acceptable for this repo) - [x] No secrets committed - [x] No unnecessary file changes (single file, tightly scoped) - [x] Commit messages are descriptive (PR title: `feat: booking UI design polish (closes #7)`) - [x] PR body references parent issue (`Closes #7`) ### VERDICT: APPROVED
forgejo_admin deleted branch 7-booking-ui-design-polish 2026-03-15 04:04:55 +00:00
Sign in to join this conversation.
No description provided.