├── .npmrc ├── src ├── canvas-dither.d.ts ├── lib │ └── index.ts ├── utils │ ├── number.ts │ ├── vibration.ts │ ├── webcam.ts │ ├── frames.ts │ ├── canvas.ts │ ├── cursor.ts │ └── filmstrip.ts ├── routes │ ├── (app) │ │ ├── rooms │ │ │ └── [roomId] │ │ │ │ ├── +page.ts │ │ │ │ └── +page.svelte │ │ ├── +layout.svelte │ │ ├── +page.svelte │ │ └── camera │ │ │ └── +page.svelte │ ├── (social) │ │ └── og │ │ │ ├── +layout.svelte │ │ │ └── +page.svelte │ └── +error.svelte ├── icons │ ├── arrow-right.svelte │ ├── select-arrows.svelte │ ├── caret-left.svelte │ ├── x.svelte │ ├── send-arrow.svelte │ ├── aperture.svelte │ └── loading.svelte ├── app.d.ts ├── firebase.ts ├── components │ ├── webcam-permission-button.svelte │ ├── render-if-visible.svelte │ ├── room-list │ │ ├── room-list.svelte │ │ └── room-list-item.svelte │ ├── nav.svelte │ ├── resolution-selector.svelte │ ├── progress.svelte │ ├── color-palette-selector.svelte │ ├── message.svelte │ ├── select.svelte │ ├── button.svelte │ ├── filmstrip.svelte │ ├── dithered-bg.svelte │ ├── new-message.svelte │ └── stylized-webcam-feed.svelte ├── constants.ts ├── app.html └── store.svelte.ts ├── cypress.json ├── firestore.indexes.json ├── static ├── arrow.png ├── text.png ├── favicon.png ├── og-image.jpg ├── pointer.png ├── home-promo-filmstrip.png ├── crt.css └── global.css ├── .prettierignore ├── .firebaserc ├── design_assets └── cursors.afdesign ├── cors_config.json ├── vite.config.ts ├── cypress ├── fixtures │ └── example.json ├── integration │ └── spec.js ├── plugins │ └── index.js └── support │ ├── index.js │ └── commands.js ├── tests └── test.ts ├── .prettierrc.json ├── playwright.config.ts ├── .gitignore ├── storage.rules ├── README.md ├── firestore.rules ├── tsconfig.json ├── firebase.json ├── eslint.config.js ├── svelte.config.js └── package.json /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /src/canvas-dither.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'canvas-dither'; 2 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "video": false 4 | } -------------------------------------------------------------------------------- /firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [], 3 | "fieldOverrides": [] 4 | } 5 | -------------------------------------------------------------------------------- /static/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takempf/dither/HEAD/static/arrow.png -------------------------------------------------------------------------------- /static/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takempf/dither/HEAD/static/text.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takempf/dither/HEAD/static/favicon.png -------------------------------------------------------------------------------- /static/og-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takempf/dither/HEAD/static/og-image.jpg -------------------------------------------------------------------------------- /static/pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takempf/dither/HEAD/static/pointer.png -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "not-firebase-58b83" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | // place files you want to import through the `$lib` alias in this folder. 2 | -------------------------------------------------------------------------------- /design_assets/cursors.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takempf/dither/HEAD/design_assets/cursors.afdesign -------------------------------------------------------------------------------- /static/home-promo-filmstrip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/takempf/dither/HEAD/static/home-promo-filmstrip.png -------------------------------------------------------------------------------- /src/utils/number.ts: -------------------------------------------------------------------------------- 1 | export function clamp(min: number, input: number, max: number): number { 2 | return Math.max(min, Math.min(max, input)); 3 | } 4 | -------------------------------------------------------------------------------- /cors_config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "origin": ["https://dither.kempf.dev"], 4 | "method": ["GET"], 5 | "responseHeader": ["image/png"], 6 | "maxAgeSeconds": 3600 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /tests/test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test('home page has expected h1', async ({ page }) => { 4 | await page.goto('/'); 5 | await expect(page.locator('h1')).toBeVisible(); 6 | }); 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/vibration.ts: -------------------------------------------------------------------------------- 1 | export function hapticBuzz() { 2 | if ('vibrate' in navigator) { 3 | try { 4 | // A short, sharp vibration pattern, 50ms 5 | navigator.vibrate([50]); 6 | } catch (error) { 7 | console.error('Error triggering vibration:', error); 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/routes/(app)/rooms/[roomId]/+page.ts: -------------------------------------------------------------------------------- 1 | import type { LoadEvent } from '@sveltejs/kit'; 2 | 3 | import { error } from '@sveltejs/kit'; 4 | 5 | export function load({ params }: LoadEvent) { 6 | if (!params.roomId) { 7 | throw error(404, 'Room not found'); 8 | } 9 | 10 | return { roomId: params.roomId }; 11 | } 12 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from '@playwright/test'; 2 | 3 | const config: PlaywrightTestConfig = { 4 | webServer: { 5 | command: 'npm run build && npm run preview', 6 | port: 4173 7 | }, 8 | testDir: 'tests', 9 | testMatch: /(.+\.)?(test|spec)\.[jt]s/ 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /src/icons/arrow-right.svelte: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /src/routes/(social)/og/+layout.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |