Skip to content

measuring-stick: per-funnel gauge - #8

Merged
jackhenderson12 merged 1 commit into
mainfrom
measuring-stick
May 7, 2026
Merged

measuring-stick: per-funnel gauge#8
jackhenderson12 merged 1 commit into
mainfrom
measuring-stick

Conversation

@jackhenderson12

Copy link
Copy Markdown
Contributor

Summary

The 2D triangle funnel is mathematically right but reads as abstract. This adds a calm gauge layer on the outer right edge — a live indicator that tracks the water surface and shows the current vote count, plus two faint reference ticks framing the range (half-cap and cap). The funnel becomes a measuring instrument without losing its form.

Layered on top of the existing Funnel.tsx, not a rewrite.

Behaviour

Live indicator — left-pointing arrow + numeric label (1 decimal), anchored at cx + usableHeight + ARROW_OFFSET horizontally and apexY − h vertically. That puts it exactly at the water-surface y, so the indicator and the surface highlight stay co-located at every level. Position updates frame-for-frame during a hold (instantUpdate path), Framer-Motion-smoothed otherwise.

Reference ticks at votes = 5 and votes = 10 (half-cap and rim). No labels, no integer marks at every level — the two anchor ticks are the whole scale.

Cross-fade rule:

showIndicator = round1(votes) > 0
showTicks     = round1(votes) <= 0 && !isAnyPouring

At rest with votes = 0 ⇒ ticks; with votes > 0 ⇒ indicator; during any pour anywhere in the grid ⇒ ticks hide globally, indicators show on funnels with votes > 0. Mutually exclusive per funnel — never both.

Cross-fade timing: 250 ms ease-out via plain CSS opacity transitions. Framer Motion's animate={{ opacity }} on <g aria-hidden> didn't reliably re-render after prop changes here (the attribute path stuck at the initial-mount value). CSS opacity handles it cleanly. The motion.g for indicator position (instant during hold, animated otherwise) stays as before.

Reduced motion: opacity transitions disabled (transition: 'none'); state changes snap. Indicator's vertical position still updates in real time during a hold (input feedback, not decoration).

Layout

36 px reserved past the V's right edge (GAUGE_W = 36) for the gauge anchor. The funnel cavity is correspondingly narrower; everything else (rim line, water polygon, surface highlight, ARIA, keyboard handling, prop interface) is unchanged.

Wiring

LiquidQV computes isAnyPouring = Boolean(activePour) and passes it to each Funnel. Funnel uses that flag plus its own votes to drive the indicator/ticks cross-fade. No new state at any level.

What's preserved

  • All math (costForVotes, maxVotes, clampVotesAgainstBudget, conservation).
  • All interaction (hold-to-pour, continuous values, no integer snap, no tap shortcut).
  • The pool, the pour stream, the intro copy, the "How it works" explainer, the framing prompt, the footer disclaimer, the default ballot, the under-funnel readout (4.3 votes 18.2 credits).

Verified

Scenario What renders DOM check
Empty funnel, no pour Faint reference ticks at half-cap and rim tickStyleOpacity: "1", indicatorStyleOpacity: "0"
Mid-hold (e.g. Harris ≈ 5.9) Indicator on Harris (label "5.9"), ticks hidden across the entire grid tickStyleOpacity: "0", indicatorStyleOpacity: "1"
Post-release, mixed (e.g. Harris = 3.9, others = 0) Indicator on Harris only, ticks return on the empty funnels Harris: indicator 1 / ticks 0; others: indicator 0 / ticks 1

29 tests passing, ESLint clean, tsc -b --noEmit clean, all three build targets succeed.

Screenshots

GitHub doesn't accept binary uploads via gh pr create, and the preview environment couldn't auto-render clean image captures for this round. docs/round-10/README.md on this branch documents each state with the verified DOM snapshots from the live preview. Reviewers can also reproduce all three states with npm run dev.

Test plan

  • npm install && npm run dev
  • Page loads with all funnels at 0 → faint reference ticks visible on each funnel's right edge at half-cap and rim levels. No indicator anywhere.
  • Hold + on Kamala Harris from empty → indicator appears on Harris's right edge, slides up, label updates ("0.5", "1.4", "2.6", ...). Reference ticks fade out across all funnels.
  • Continue holding past 5 → indicator is now above the half-cap reference position; the underlying tick position is hidden but its location is communicated by the funnel rim line.
  • Release → indicator settles at the released level showing the fractional vote count. Reference ticks fade back in on the empty funnels.
  • Tap reset on Harris → indicator fades out as votes return to 0; reference ticks fade in on Harris.
  • Verify indicator's vertical position is exactly at the water surface (no daylight between them at any level).
  • OS reduced-motion → opacity changes snap (no fade); position updates during hold are still real-time.
  • npm test && npm run lint && npm run typecheck — green.

Don't merge

Per the brief: hold for review.

🤖 Generated with Claude Code

The 2D triangle funnel is mathematically right but reads as abstract.
This makes the function more legible without redesigning the form: a
small live indicator on the outer right edge tracks the water surface
and shows the current vote count, plus two faint reference ticks frame
the range (half-cap and cap). The funnel becomes a measuring instrument
— calm, honest, no extra chrome.

Layered on top of the existing Funnel.tsx, not a rewrite.

Behaviour
---------
- Live indicator (left-pointing arrow + numeric label, 1 decimal)
  visible whenever votes > 0. Position updates frame-for-frame during
  a hold (`instantUpdate` path), Framer-Motion-smoothed otherwise.
  Anchored at `cx + usableHeight + ARROW_OFFSET` horizontally and
  `apexY − h` vertically — exactly the water-surface y, so the
  indicator and the surface highlight stay co-located at every level.
- Two faint reference ticks at votes 5 and 10 (half-cap and rim).
  No labels, no integer marks at every level — the two anchor ticks
  are the whole scale.
- Cross-fade rule:
    showIndicator = round1(votes) > 0
    showTicks     = round1(votes) <= 0 && !isAnyPouring
  Result: at rest with votes=0 ⇒ ticks; with votes>0 ⇒ indicator;
  during *any* pour anywhere in the grid ⇒ ticks hide globally,
  indicators show on the funnels with votes>0.
- Cross-fade timing: 250 ms ease-out via CSS opacity. (Framer Motion's
  `animate={{ opacity }}` on `<g aria-hidden>` didn't reliably
  re-render after prop changes here — the attribute path stuck at the
  initial-mount value. Plain CSS opacity transitions handle the
  cross-fade cleanly with no extra cost.)
- Reduced motion: opacity transitions disabled; state changes snap.
  Indicator's vertical position still updates in real time during a
  hold (input feedback, not decoration).

Layout
------
36 px reserved past the V's right edge (`GAUGE_W = 36`) for the gauge
anchor. The funnel cavity is correspondingly narrower; everything else
(rim line, water polygon, surface highlight, ARIA, keyboard handling,
prop interface) is unchanged.

Wiring
------
LiquidQV computes `isAnyPouring = Boolean(activePour)` and passes it
to each Funnel. Funnel uses that flag plus its own `votes` to drive
the indicator/ticks cross-fade. No new state at any level.

What's preserved
----------------
- All math (costForVotes, maxVotes, clampVotesAgainstBudget,
  conservation), all interaction (hold-to-pour, continuous values),
  the pool, the pour stream, intro copy, "How it works" explainer,
  framing prompt, footer disclaimer, default ballot, and the
  under-funnel readout ("4.3 votes  18.2 credits").

Verified
--------
- Empty funnel at rest, no pour: reference ticks visible at half-cap
  and rim. Indicator opacity 0.
- Mid-hold: ticks fade to 0 across all funnels; indicator on the held
  funnel slides up tracking the water surface, label updates frame-
  for-frame.
- Post-release with mixed states (e.g., Harris=3.9, Newsom=0): Harris
  shows indicator only; Newsom shows ticks only.
- 29 tests passing, ESLint clean, typecheck clean, all three build
  targets succeed.

Docs
----
docs/round-10/README.md describes each state with the verified DOM
snapshots from the live preview.
@netlify

netlify Bot commented May 7, 2026

Copy link
Copy Markdown

Deploy Preview for liquid-qv ready!

Name Link
🔨 Latest commit 3906948
🔍 Latest deploy log https://app.netlify.com/projects/liquid-qv/deploys/69fca0a0ce368c00081cf1ae
😎 Deploy Preview https://deploy-preview-8--liquid-qv.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@jackhenderson12
jackhenderson12 merged commit 5957d1e into main May 7, 2026
5 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.

1 participant