measuring-stick: per-funnel gauge - #8
Merged
Merged
Conversation
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.
✅ Deploy Preview for liquid-qv ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_OFFSEThorizontally andapexY − hvertically. 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 (instantUpdatepath), 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:
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. Themotion.gfor 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
LiquidQVcomputesisAnyPouring = Boolean(activePour)and passes it to eachFunnel. Funnel uses that flag plus its ownvotesto drive the indicator/ticks cross-fade. No new state at any level.What's preserved
costForVotes,maxVotes,clampVotesAgainstBudget, conservation).4.3 votes 18.2 credits).Verified
tickStyleOpacity: "1",indicatorStyleOpacity: "0"tickStyleOpacity: "0",indicatorStyleOpacity: "1"29 tests passing, ESLint clean,
tsc -b --noEmitclean, 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.mdon this branch documents each state with the verified DOM snapshots from the live preview. Reviewers can also reproduce all three states withnpm run dev.Test plan
npm install && npm run devnpm test && npm run lint && npm run typecheck— green.Don't merge
Per the brief: hold for review.
🤖 Generated with Claude Code