Built with Fresh 2, Preact Signals, and Tailwind CSS v4, runnable on the Web and as a Desktop app via Deno.
-
Live Sorting: Add participants and update their scores. Cards automatically re-order by score (descending) with stable tie-breaking.
-
Animated Re-ordering: Uses the FLIP (First, Last, Invert, Play) technique to smoothly slide cards into their new positions when scores change.
-
Hold to Delete: Long-press the delete button (~850ms) to remove a participant. Displays a rising fill and shake animation, and cancels safely if released early.
-
Canvas Background: Animated HTML5 canvas background with falling glyphs, spotlight effects, and a blueprint grid pattern.
-
Web & Desktop Support: Run in the browser during web development or build as a desktop application using Deno Desktop (CEF backend).
-
Desktop Window Controls: Provides a window close button and Esc key shortcut wired to Deno Desktop bindings.
-
Tailwind CSS & Custom Theme: Styled using Tailwind CSS v4, custom OKLCH color palettes, and fonts (Righteous and JetBrains Mono).
All animations across the project are implemented with native Web APIs and CSS, without external animation dependencies:
- Vanilla FLIP Layouts
(
ParticipantList.tsx): Card re-ordering is animated using the FLIP (First, Last, Invert, Play) technique insideuseLayoutEffect. Previous and next card bounding boxes are measured viagetBoundingClientRect(), inverted using CSS transforms (translate(deltaX, deltaY)), and transitioned to their target positions on the next animation frame. - Topology-Matched Path Morphing
(
ParticipantIcon.css): The+add icon morphs into a user avatar on hover using CSSd: path(...)transitions. Both shapes share identical cubic Bézier segment topologies, enabling the browser to interpolate the path geometry without JavaScript. - Stateful Hold-to-Delete (
Participant.tsx): The 850ms hold interaction runs on arequestAnimationFrameloop driven byperformance.now(). Releasing early triggers a dynamic drain loop that rewinds progress back to 0 proportionally instead of resetting instantly. - Dual-Layer Canvas Background
(
GlyphsBackground.tsx): To avoid redundant per-frame redraws of static blueprint geometry, the background dot grid and frame guides are drawn once to an offscreen canvas. The active canvas renders falling glyphs and usesdestination-outcomposite operations for radial spotlight masking.
| Layer | Technology |
|---|---|
| Runtime & Toolchain | Deno 2 |
| Web Framework | Fresh 2 (@fresh/core, @fresh/plugin-vite) |
| UI Library | Preact + @preact/signals |
| Desktop Shell | Deno Desktop (Chromium Embedded Framework backend) |
| Dev Server & Bundler | Vite 7 |
| Styling | Tailwind CSS v4 + @tailwindcss/vite |
CounterFresh/
├── assets/ # Stylesheets, theme configuration & showcase media
│ ├── demo/ # Documentation animation assets & previews
│ │ └── participant-morph.svg # Animated SVG demo preview
│ └── styles.css # Tailwind CSS v4 setup, OKLCH tokens & keyframe animations
├── components/ # Reusable UI components & SVG icons
│ ├── CounterIcons/ # Plus and Minus SVG icons
│ ├── DeleteIcon/ # Animated delete icon
│ ├── ParticipantIcon/ # Add participant icon
│ ├── Button.tsx # Base styled button
│ ├── HoldDeleteButton.tsx# Reusable hold-to-delete trigger
│ └── Surface.tsx # Translucent card container
├── islands/ # Interactive Preact islands (client-hydrated)
│ ├── CloseButton.tsx # Desktop window close trigger & keybinds
│ ├── Countdown.tsx # Animated countdown island
│ ├── Counter.tsx # Score increment/decrement island
│ ├── GlyphsBackground.tsx# Animated canvas background
│ ├── Participant.tsx # Participant card with hold-to-delete logic
│ ├── Participant_test.ts # Colocated unit tests for hold-to-delete
│ ├── ParticipantList.tsx # FLIP-animated leaderboard list
│ └── ParticipantList_test.ts # Colocated unit tests for leaderboard sorting
├── routes/ # File-system routing
│ ├── api/ # API route handlers
│ │ └── [name].tsx # Sample API handler
│ ├── _app.tsx # Global HTML document shell & background mounting
│ ├── about.tsx # About page
│ └── index.tsx # Home page
├── tests/ # Integration & server endpoint test suites
│ └── api_test.ts # API route & shutdown handler integration tests
├── static/ # Static public assets (favicons, logos)
├── icons/ # Application icons for desktop builds
├── bindings.d.ts # Desktop native bindings TypeScript definitions
├── client.ts # Client entry point for Vite HMR & assets
├── deno.json # Deno configuration, tasks, desktop settings & imports
├── main.ts # Fresh 2 server entry point & desktop window lifecycle
└── vite.config.ts # Vite plugin configuration
Make sure you have Deno 2.x installed:
# macOS / Linux
curl -fsSL https://deno.land/install.sh | sh
# Windows (PowerShell)
irm https://deno.land/install.ps1 | iexClone the repository and enter the directory:
git clone https://github.com/Tablerase/CounterFresh.git
cd CounterFresh# Install dependencies (Deno will cache them automatically)
deno installscreenrecording-2026-08-25_16-12-31.mp4
Starts the Vite development server with HMR:
deno task devOpen http://localhost:5173 in your browser.
Launches the application in a desktop window with HMR:
deno task d-devRun the test suite across unit calculations, sorting logic, and API endpoints:
deno task testRun format check, linter, and typechecker:
deno task checkBuild client assets and start the production server:
# Build client assets
deno task build
# Start server
deno task startPackage the application as a standalone desktop binary:
deno task d-buildOutput directory (dist/):
- macOS:
dist/CounterFresh.app - Windows:
dist/CounterFresh.exe - Linux:
dist/counter-fresh
Desktop packaging options are configured under the "desktop" key in
deno.json:
{
"desktop": {
"app": {
"name": "Counter Fresh",
"identifier": "com.example.myapp",
"icons": {
"macos": "./icons/app.png",
"windows": "./icons/app.ico",
"linux": "./icons/app.png"
},
"deepLinks": ["counter-fresh"]
},
"backend": "cef",
"output": {
"macos": "./dist/CounterFresh.app",
"windows": "./dist/CounterFresh",
"linux": "./dist/counter-fresh"
}
}
}