diff --git a/web/src/assets/main.css b/web/src/assets/main.css index ca27bfc..5b86b90 100644 --- a/web/src/assets/main.css +++ b/web/src/assets/main.css @@ -72,6 +72,19 @@ button { font-family: inherit; } +/* The native number-input spinners render as light boxes that clash with + the dark theme and crowd the compact fields; arrow keys and typing keep + working without them. */ +input[type='number'] { + appearance: textfield; +} + +input[type='number']::-webkit-outer-spin-button, +input[type='number']::-webkit-inner-spin-button { + appearance: none; + margin: 0; +} + .visually-hidden { position: absolute; width: 1px; diff --git a/web/src/components/NetworkView.vue b/web/src/components/NetworkView.vue index a7d48c8..5b6f853 100644 --- a/web/src/components/NetworkView.vue +++ b/web/src/components/NetworkView.vue @@ -75,7 +75,7 @@ function onNodeClick(nodeIndex: number, event: MouseEvent) { // the panel stats), then fade in over the rest of the interval; playback // never shows a finished still frame between rounds. const roundIntervalMs = computed(() => ROUND_MS / store.state.speed) -const popDurationMs = computed(() => roundIntervalMs.value * (1 - APPEAR_WINDOW) + 120) +const popDurationMs = computed(() => roundIntervalMs.value * 0.3) function popDelay(nodeIndex: number): string { return `${appearanceJitter(nodeIndex) * APPEAR_WINDOW * roundIntervalMs.value}ms` diff --git a/web/src/composables/usePlayback.ts b/web/src/composables/usePlayback.ts index cfe584b..83d53ab 100644 --- a/web/src/composables/usePlayback.ts +++ b/web/src/composables/usePlayback.ts @@ -6,8 +6,8 @@ import { useSimStore, type SimStore } from './useSimStore' // one-time autoplay). Playback never loops; at the final round the play // button becomes a replay affordance. -export const ROUND_MS = 700 -export const REDUCED_MOTION_ROUND_MS = 1000 +export const ROUND_MS = 1000 +export const REDUCED_MOTION_ROUND_MS = 1200 const SPEED_CYCLE: Record = { 0.5: 1, 1: 2, 2: 0.5 } diff --git a/web/src/lib/reach.ts b/web/src/lib/reach.ts index fadb9cb..e979910 100644 --- a/web/src/lib/reach.ts +++ b/web/src/lib/reach.ts @@ -1,5 +1,3 @@ -import { mulberry32 } from './mulberry32' - // Cumulative reach at a playback round, shared by the panel stats and the // reach chart so both always agree with the network picture. export function reachedCountAtRound(reachedAtRound: number[], round: number): number { @@ -12,10 +10,19 @@ export function reachedCountAtRound(reachedAtRound: number[], round: number): nu // jitter drives the dot's animation-delay and the live counter, so the // percentage ticks up exactly as dots start appearing. -export const APPEAR_WINDOW = 0.7 +export const APPEAR_WINDOW = 0.95 +// A full-avalanche integer hash (murmur3 finalizer): consecutive node +// indices land anywhere in [0, 1), so the appearance moments of a round's +// nodes are spread evenly instead of clustering into one flash. export function appearanceJitter(nodeIndex: number): number { - return mulberry32(nodeIndex * 7919 + 17)() + let mixed = Math.imul(nodeIndex + 1, 0x9e3779b1) + mixed ^= mixed >>> 16 + mixed = Math.imul(mixed, 0x85ebca6b) + mixed ^= mixed >>> 13 + mixed = Math.imul(mixed, 0xc2b2ae35) + mixed ^= mixed >>> 16 + return (mixed >>> 0) / 4294967296 } // Reach as displayed mid-transition: every earlier round fully, plus the