Skip to content

feat(ui): calmer motion + first-paint primitives across paramant frontend#26

Merged
Apolloccrypt merged 3 commits into
mainfrom
claude/improve-windstil-ux-dpwTF
Apr 25, 2026
Merged

feat(ui): calmer motion + first-paint primitives across paramant frontend#26
Apolloccrypt merged 3 commits into
mainfrom
claude/improve-windstil-ux-dpwTF

Conversation

@Apolloccrypt
Copy link
Copy Markdown
Owner

Summary

Three commits that tighten the "windstil" feel of the paramant frontend without breaking its brutalist tone. The brief was: improve usability via animations, visuals, and how navigation behaves — and address pages that open "too zoomed-out" (no orientation, sparse forms, frozen 0% bars, silent failures).

1. Calmer motion across nav, status & cards (f49c85d)

  • New tokens: --duration-slow, --ease-soft, --ease-out.
  • Status dot: harsh blink-step swapped for an eased opacity+scale status-pulse.
  • Buttons: subtle translateY(1px) press feedback.
  • :focus-visible gets a small offset transition.
  • Help-cards lift on hover with the arrow nudging right.
  • Page entrance utilities: .page-enter + .rise-in (with delay-1..5 stagger).
  • scroll-padding-top: 72px so anchor links clear the sticky 56px navbar (auto under reduced-motion).
  • Nav: dropdowns fade+rise+scale instead of display:block flits; mobile drawer slide-in; mobile accordion uses max-height transition; hamburger cross is now CSS-driven via [aria-expanded="true"] (inline JS span transforms removed); active link gets an animated underline via ::after.

2. First-paint primitives in the design system (d76f3b9)

Opt-in components for surfaces that currently feel empty or stuck:

  • Dropzone breathe — 3.6s lime ring on idle that pauses on hover/focus/drag, distinct cobalt 4px ring on .drag-over.
  • .empty-state (+ .is-compact) — glyph + title + hint + actions slot for zero-data surfaces.
  • .skeleton + .skeleton-line (is-sm/is-md/is-lg) + .skeleton-block — calm shimmer for loading placeholders.
  • .progress-indeterminate — 3px bar with sliding cobalt segment for unknown-ETA states.
  • All four respect prefers-reduced-motion with sensible static fallbacks.

3. Wired into the worst offenders (d36e8b0)

  • dashboard.html: stat-cards use skeleton lines instead of flickering opacity:.3 divs; delivery table shows indeterminate bar + skeleton rows on load and an .empty-state with a "Run E2E test" CTA when zero deliveries; audit log gets the same load + empty treatment.
  • get.html: "Preparing…" no longer shows a frozen 0% bar — setStatus() swaps an indeterminate sliding bar in until a real percentage arrives.
  • drop.html: camera failure (NotAllowedError / NotFoundError / non-secure context) replaces the silent display:none with an .empty-state explaining the cause and pointing at the manual-link fallback.

Files touched

  • frontend/design-system.css — tokens, motion utilities, dropzone, empty-state, skeleton, indeterminate, motion-safety updates.
  • frontend/nav.css — animated dropdown/drawer/accordion/hamburger/active-link.
  • frontend/nav.js — drop the inline span style writes (CSS owns hamburger state now).
  • frontend/dashboard.html — wire skeleton + empty-state into stat-cards / delivery / audit.
  • frontend/get.html — wire indeterminate bar into Preparing state.
  • frontend/drop.html — wire empty-state into camera failure path.

Not in this PR (intentional)

  • Page-level rollout of .empty-state / .skeleton to account.html, auth/*, admin.html — those need copywriting decisions first.
  • The .page-enter / .rise-in utilities are defined but not yet applied to <main> blocks across the 48 marketing pages.
  • Dashboard-login onboarding copy expansion.

Test plan

  • Visit /dashboard without an API key — login form renders unchanged.
  • Sign in to /dashboard with a valid key — stat-cards show shimmering skeletons until the first poll, then real numbers.
  • On /dashboard with no transfers yet — delivery table shows the empty-state with the "Run E2E test" CTA; clicking it triggers the existing E2E flow.
  • On /dashboard with no audit events — audit log section shows the empty-state hint.
  • Open a /get link on a slow connection — confirm the indeterminate bar slides during "Preparing…" and is replaced by the determinate bar once decryption progress is reported.
  • On /drop in receive mode, deny camera permission — confirm the black box is replaced by an explanatory empty-state.
  • On /send — confirm dropzone shows the subtle lime breathe ring on idle, switches to a cobalt ring during a drag-over.
  • Open any page with prefers-reduced-motion: reduce — skeletons stay grey (not transparent), indeterminate bar pins to a visible position, dropzone ring stops, dropdown/drawer/page transitions instant.
  • Open the desktop nav dropdowns — they fade+rise in instead of snapping; mobile drawer slides in; mobile accordion expands smoothly; hamburger animates to a clean cross via CSS only.
  • Tab through nav — focus rings show, active link has the animated underline, anchor jumps land below the sticky navbar.

Generated by Claude Code

claude added 3 commits April 25, 2026 16:57
Improves the calm "windstil" feel of the design system without breaking
its brutalist tone:

- design-system: add --duration-slow, --ease-soft, --ease-out tokens;
  swap harsh blink-step status pulse for an eased opacity+scale pulse;
  add page-enter / rise-in entrance utilities with stagger delays;
  add subtle press feedback (translateY 1px) on .btn:active;
  give :focus-visible a small offset transition; lift help-card on
  hover with arrow nudge; add scroll-padding-top so anchor targets
  clear the sticky 56px navbar (auto under prefers-reduced-motion).
- nav: animate desktop dropdown reveal (fade+rise+scale); slide-in
  mobile drawer; max-height transition on mobile accordion groups;
  CSS-driven hamburger cross via [aria-expanded="true"] (replaces
  inline JS transforms); animated underline on .nav-link via ::after.
- nav.js: drop inline span style writes now that CSS owns the
  hamburger state.
… + indeterminate

Audit found several "map too far zoomed out at start of nav" UX
moments: the dropzone reads as a cold rectangle on first visit,
dashboard tables show only "—" for new users, and "Preparing…"
states leave users staring at frozen 0% bars. These additions are
opt-in primitives that surfaces can adopt page by page.

- Dropzone: subtle 3.6s lime breathe ring on idle (pauses on
  hover/focus/drag), distinct cobalt 4px ring on .drag-over so the
  active drop target is unmissable.
- .empty-state component (with .is-compact variant) — glyph + title
  + hint + actions slot; replaces walls of em-dashes in zero-data
  tables with an inviting "what to do next" surface.
- .skeleton + .skeleton-line (sm/md/lg) + .skeleton-block — calm
  shimmer for loading placeholders; the dashboard's flickering
  height:28px placeholder divs can adopt this directly.
- .progress-indeterminate — slim 3px bar with a sliding cobalt
  segment for "Preparing…" / "Connecting…" states with unknown ETA.
- Reduced-motion: skeleton stays grey (not transparent), indeterminate
  bar pins to a visible position, dropzone breathe stops.
…ss key pages

Wires the new design-system primitives into the three pages flagged
as worst offenders for "starts too zoomed-out" UX.

dashboard.html:
- stat-card placeholders now use .skeleton lines instead of
  flickering opacity:.3 height:28px divs (reads as loading, not as
  broken)
- delivery table loading state shows progress-indeterminate + skeleton
  rows in place of "Loading..."
- delivery table zero-data shows .empty-state with title, hint and
  a "Run E2E test" CTA so new users have a clear first move
- audit log gets the same treatment (skeleton on load, .empty-state
  with explanatory hint when data has loaded but is empty)

get.html:
- "Preparing..." no longer leaves the user staring at a 0% bar — an
  indeterminate sliding bar shows up first; once a real percentage
  arrives, setStatus swaps in the determinate progress-bar

drop.html:
- camera failure (NotAllowedError / NotFoundError / non-secure
  context) no longer silently hides the scanner box; it shows an
  .empty-state explaining the cause and pointing at the manual-link
  fallback below
@Apolloccrypt Apolloccrypt merged commit 900eaba into main Apr 25, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants