🚧 Work in Progress — Active development, things are moving fast.
Tecta is not another Planning Poker tool. It's a video game.
Turn story point estimation into a real-time, arcade-style multiplayer card game with CRT scanlines, card flips, screen shake, confetti, and synthwave sound effects. No login, no accounts, no backend — just share a link and play.
Not your average Planning Poker. Here's what sets it apart:
- It's a video game, not a spreadsheet — CRT scanline overlay, 3D card flip animations, screen shake on reveal, confetti explosions on consensus, synthwave SFX, ambient music. Every interaction feels like something.
- Zero infrastructure, peer-to-peer — No servers, no databases, no accounts. Uses WebRTC mesh networking via Yjs. Share a link, that's it. $0 to run.
- Cryptographically secret voting — SHA-256 commit-reveal scheme prevents the last voter from anchoring to the group. No one sees your vote until everyone has locked in.
- Bring your own soundtrack — Drop an
.mp3/.ogg/.wavintopublic/music/and it loops during gameplay. Falls back to a generative synth drone if no file is found.
- Host opens the app and shares the room URL
- Team opens the link — no login, no accounts, no install
- Start estimating — import tickets, vote, reveal, discuss
bash setup.sh
# or manually:
npm install
npm run devOpen the URL printed in your terminal and share your room link. Works the same way — just on your machine.
You and your team enter a room. One ticket at a time, you estimate story points — but it feels like a game show.
- Lobby — Join a room via link. See who's here. Ready up when your team's complete. The room code is in the URL — bookmark it, share it, done.
- Vote — The current ticket appears. Everyone secretly picks an estimate (0, ½, 1, 2, 3, 5, 8, 13, 21, ?, ☕). Your vote is SHA-256 hashed before it leaves your browser — nobody sees it until the reveal.
- Reveal — Everyone has voted. Cards flip simultaneously with a 3D animation. The table sees all estimates at once. No anchoring, no bias.
- Discuss — Votes diverged? Outliers explain their reasoning. The screen shakes on big swings. Synth effects punctuate the moment.
- Results — Consensus reached? Confetti fires. Move to the next ticket or export the session.
Every vote is commit-reveal: submit a hash first, reveal the plaintext after everyone commits. Cryptographically impossible to cheat. Browser-native SubtleCrypto — no external dependencies.
- Share a link, that's it — Zero-friction join. No login, no accounts, no signup flow.
- Real-time P2P — WebRTC mesh via Yjs. Every player connected directly. No backend, no server costs, no data leaves your mesh.
- Room-based sessions — Unique room ID in the URL. Share it, bookmark it, done.
- CRT scanline overlay — That retro monitor feel, baked into the UI
- 3D card flips — Framer-motion animated reveal with perspective transforms
- Screen shake — CSS animation on vote reveal and high-impact moments
- Confetti explosions — Canvas-confetti fires on consensus
- Synthwave SFX — Web Audio API generated sound effects for every action
- Ambient music — Generative synth drone or custom file-based soundtrack
- CSV/JSON import — Drag-and-drop ticket loading via PapaParse
- Jira/Linear export — Download formatted CSV of final estimates
- Secret voting — SHA-256 commit-reveal prevents anchoring bias
- Custom deck presets — Choose your estimation scale
- Voting timer — Set a countdown for each round
- Custom music — Drop
theme.mp3/.ogg/.wavinpublic/music/to set your own soundtrack
| Layer | Technology |
|---|---|
| Framework | React 18 + TypeScript |
| Build | Vite 5 |
| Styling | Tailwind CSS 3 |
| Real-time sync | Yjs + y-webrtc |
| Animations | framer-motion 11 |
| Drag & drop | @dnd-kit |
| Audio | Web Audio API (synth SFX + ambient music) |
| Voting | SHA-256 commit-reveal (SubtleCrypto) |
| CSV parsing | PapaParse |
| File export | file-saver |
| Confetti | canvas-confetti |
tecta/
├── public/
│ ├── favicon.svg # App icon (spade in gold)
│ └── music/ # Drop custom theme audio here
├── src/
│ ├── App.tsx # Main app — orchestration, state, routing
│ ├── main.tsx # React entry point
│ ├── index.css # Tailwind + custom animations
│ ├── lib/
│ │ ├── cards.ts # Card values, colors, glow styles
│ │ ├── crdt-schema.ts # Yjs shared types (Phase, Player, Ticket, VoteEntry)
│ │ └── room.ts # Room ID generation & URL helpers
│ ├── hooks/
│ │ ├── useYjsDoc.ts # Yjs + WebRTC connection
│ │ ├── useGameState.ts # Game state from Yjs doc
│ │ ├── useCurrentUser.ts# Persistent player ID (localStorage)
│ │ ├── useCommitReveal.ts # SHA-256 voting scheme
│ │ ├── useAmbientMusic.ts # Background music (file or synth)
│ │ ├── useSynthAudio.ts # Sound effects
│ │ ├── useScreenShake.ts # Screen shake animation
│ │ └── useTimer.ts # Countdown timer
│ └── components/
│ ├── AnimatedBackground.tsx # Canvas radial gradients
│ ├── CRTEffect.tsx # Scanline overlay
│ ├── ErrorBoundary.tsx # Error catch + reload
│ ├── ExportView.tsx # Jira/Linear CSV export
│ ├── GameView.tsx # Vote → Reveal → Discuss → Results
│ ├── ImportView.tsx # CSV/JSON drag-and-drop import
│ ├── Lobby.tsx # Pre-game lobby
│ ├── PlayingCard.tsx # 3D flip card (framer-motion)
│ ├── SettingsPanel.tsx # Timer, deck, sound controls
│ ├── TicketList.tsx # Sortable ticket list (dnd-kit)
│ ├── TicketView.tsx # Single ticket display
│ └── VoteDistribution.tsx # Vote bar chart
├── index.html # HTML shell
├── package.json
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.js
└── postcss.config.js
| Command | Description |
|---|---|
npm run dev |
Start dev server (hot reload) |
npm run build |
Typecheck + production build |
npm run preview |
Preview production build locally |
npm run lint |
ESLint check (ts, tsx) |
npm run typecheck |
TypeScript type check only |
Place a file in public/music/ named theme.mp3, theme.ogg, or theme.wav. The app loads it via Web Audio API and loops it during voting/reveal/discussion phases. Falls back to a synth drone if no file is found. Respects the volume slider and mute toggle in Settings.
See public/music/README.md for details.