├── .gitignore ├── LICENSE ├── README.md ├── data ├── analyze.ts ├── deno.json ├── leagues.json ├── make-leagues.ts ├── race.ts ├── sip-data │ ├── compiled-edited.json │ ├── compiled.json │ ├── exemplar.json │ └── other │ │ ├── 1733044381741.json │ │ ├── 1733062381491.json │ │ ├── 1733080381252.json │ │ ├── 1733098381427.json │ │ ├── 1733116381694.json │ │ ├── 1733135641759.json │ │ ├── 1733153641438.json │ │ ├── 1733171641952.json │ │ ├── 1733189641496.json │ │ ├── 1733207641508.json │ │ ├── 1733226361490.json │ │ ├── 1733244361357.json │ │ ├── 1733262361873.json │ │ └── 1733280361510.json └── sip.ts ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── audio │ ├── chime-better.mp3 │ ├── click-short.mp3 │ └── decadent-depraved-mix-20250314.mp3 ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── hose-race-gameplay.png ├── leagues.json ├── site.webmanifest └── vite.svg ├── src ├── App.css ├── App.tsx ├── assets │ ├── astro-turf-feature-small.jpg │ ├── astro-turf-feature.jpg │ ├── hose.png │ ├── nozzle-for-favicon.png │ ├── nozzle1-transp.png │ ├── nozzle1.png │ ├── nozzle2-transp.png │ ├── nozzle2.png │ ├── nozzle3-transp.png │ ├── nozzle3.png │ ├── nozzle4-transp.png │ └── react.svg ├── audio │ ├── AudioApiContext.tsx │ └── AudioProvider.tsx ├── components │ ├── AudioTesting.tsx │ ├── Button │ │ ├── Button.css │ │ └── Button.tsx │ ├── Game │ │ ├── Game.css │ │ └── Game.tsx │ ├── GithubLogo.tsx │ ├── Header │ │ ├── Header.css │ │ └── Header.tsx │ ├── Hose │ │ ├── Hose.css │ │ └── Hose.tsx │ ├── Lines │ │ ├── Lines.css │ │ └── Lines.tsx │ ├── Onboard │ │ └── Onboard.tsx │ ├── PostCard │ │ ├── PostCard.css │ │ └── PostCard.tsx │ ├── Racetrack │ │ ├── Racetrack.css │ │ └── Racetrack.tsx │ ├── Racetracks │ │ ├── Racetracks.css │ │ └── Racetracks.tsx │ ├── Result │ │ ├── Result.css │ │ └── Result.tsx │ ├── SelectWord │ │ └── SelectWord.tsx │ └── Trophy │ │ └── Trophy.tsx ├── hooks │ ├── useAudio.tsx │ ├── useAudioTracks.tsx │ ├── useFirehose.tsx │ ├── useLoadLeague.tsx │ └── usePostLightStates.tsx ├── index.css ├── main.tsx ├── types │ └── types.ts ├── utils │ └── getResultsData.ts └── vite-env.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .vercel 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Nate May 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hose Race 2 | 3 | ![Hose Race gameplay](/public/hose-race-gameplay.png) 4 | 5 | Guess which words will appear the most on English-language Bluesky in realtime. 6 | 7 | This was a learning project for me to get comfortable with React and play with the Bluesky firehose. 8 | 9 | ## Stack 10 | 11 | - React (only client-side) 12 | - Typescript 13 | - Vite 14 | - Deno (for `data` folder only) 15 | 16 | ## Status 17 | 18 | This was a learning project for me and I'm not actively maintaining it. 19 | 20 | ## Data 21 | 22 | In order to make the game fun I needed to match up sets of words ("leagues") that appear at roughly the same rate of occurence on Bluesky. For this I periodically took "sips" from the firehose and analyzed the top words used. I then aggregated this data, manually eliminated some words, and grouped words by their median occurrences across all of the sips. The result is in `data/leagues.json`. I've also copied this file into the `public` folder so the app can access it in the frontend. 23 | 24 | ## Getting Started 25 | 26 | To run the web app locally: 27 | 28 | First clone this repo, then: 29 | 30 | ```bash 31 | cd hose-race 32 | npm install 33 | npm run dev 34 | ``` 35 | 36 | To explore the `data` folder, you'll need to [install the Deno runtime](https://docs.deno.com/runtime/). 37 | 38 | Then: 39 | 40 | ```bash 41 | cd hose-race/data 42 | deno run --allow-read --allow-write --allow-net name_of_file.ts 43 | ``` 44 | -------------------------------------------------------------------------------- /data/analyze.ts: -------------------------------------------------------------------------------- 1 | const exemplarText = await Deno.readTextFile("./sip-data/exemplar.json"); 2 | const exemplarData = JSON.parse(exemplarText); 3 | 4 | export function median(arrIn: number[]): number | null { 5 | if (arrIn.length === 0) return null; 6 | 7 | const arr = arrIn.toSorted((a, b) => a - b); 8 | 9 | const middleRaw = arr.length / 2; 10 | const floor = Math.floor(middleRaw); 11 | const ceil = Math.ceil(middleRaw); 12 | const hasMiddle = floor !== ceil; 13 | 14 | return hasMiddle ? arr[floor] : (arr[floor - 1] + arr[floor]) / 2; 15 | } 16 | 17 | interface CompiledData { 18 | word: string; 19 | occurrences?: number[]; 20 | median: number | undefined; 21 | } 22 | 23 | const compiledData = [] as CompiledData[]; 24 | 25 | // compile the data from the exemplar and add the first occurences value 26 | for (const word in exemplarData.values) { 27 | compiledData.push({ 28 | word: word, 29 | occurrences: [exemplarData.values[word]], 30 | median: undefined, 31 | }); 32 | } 33 | 34 | const sipDataDir = "./sip-data/other/"; 35 | 36 | // add the other occurrence values, or delete the entry if it's missing from any 37 | for await (const dirEntry of Deno.readDir(sipDataDir)) { 38 | const decoder = new TextDecoder(); 39 | const dataRaw = await Deno.readFile(sipDataDir + dirEntry.name); 40 | const data = JSON.parse(decoder.decode(dataRaw)); 41 | 42 | for (let i = compiledData.length - 1; i >= 0; i--) { 43 | const wordEntry = compiledData[i]; 44 | if (!(wordEntry.word in data.values)) { 45 | compiledData.splice(i, 1); 46 | } else { 47 | wordEntry.occurrences!.push(data.values[wordEntry.word]); 48 | } 49 | } 50 | } 51 | 52 | // add the median value for each entry & delete occurrences 53 | for (const wordEntry of compiledData) { 54 | wordEntry.median = median(wordEntry.occurrences!)!; 55 | delete wordEntry.occurrences; 56 | } 57 | 58 | // sort by median 59 | compiledData.sort((a, b) => a.median! - b.median!); 60 | 61 | // write to file 62 | const compiledJson = JSON.stringify(compiledData, null, " "); 63 | await Deno.writeTextFile("./sip-data/compiled.json", compiledJson); 64 | 65 | /* 66 | Okay this needs to 67 | x. Load in the first file (with 1000 entries), and copy everything into an object that's like 68 | 69 | [ 70 | { 71 | word: string, 72 | occurrences: number[], 73 | median: number | undefined, 74 | }, 75 | ... 76 | ] 77 | x. Look at each of the rest of the files (using https://docs.deno.com/api/deno/~/Deno.readDir). If the word appears, add the number of occurrences to the occurrences array. If it doesn't appear, delete the whole entry, since we only want words that are in all of them. 78 | x. Write a function that calculates the median from an array. 79 | x. Run each word entry through that function and add the median value. 80 | x. Order the entries by median. 81 | x. Make a JSON so I can manually go through and remove the ones I don't want. 82 | 7. Create an array of arrays that groups them by fours. Randomize the order within each group. 83 | 84 | */ 85 | -------------------------------------------------------------------------------- /data/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "dev": "deno run --watch main.ts" 4 | }, 5 | "imports": { 6 | "@std/assert": "jsr:@std/assert@1" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /data/leagues.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "words": [ 4 | "space", 5 | "piece", 6 | "gave", 7 | "ones" 8 | ], 9 | "medianPerSecond": 0.13333333333333333, 10 | "rangePerSecond": 0.03333333333333333 11 | }, 12 | { 13 | "words": [ 14 | "isn’t", 15 | "seems", 16 | "question", 17 | "choose" 18 | ], 19 | "medianPerSecond": 0.13333333333333333, 20 | "rangePerSecond": 0 21 | }, 22 | { 23 | "words": [ 24 | "working", 25 | "movie", 26 | "season", 27 | "during" 28 | ], 29 | "medianPerSecond": 0.16666666666666666, 30 | "rangePerSecond": 0 31 | }, 32 | { 33 | "words": [ 34 | "sounds", 35 | "able", 36 | "taking", 37 | "welcome" 38 | ], 39 | "medianPerSecond": 0.16666666666666666, 40 | "rangePerSecond": 0 41 | }, 42 | { 43 | "words": [ 44 | "finally", 45 | "honestly", 46 | "weird", 47 | "story" 48 | ], 49 | "medianPerSecond": 0.16666666666666666, 50 | "rangePerSecond": 0 51 | }, 52 | { 53 | "words": [ 54 | "exactly", 55 | "link", 56 | "job", 57 | "under" 58 | ], 59 | "medianPerSecond": 0.16666666666666666, 60 | "rangePerSecond": 0 61 | }, 62 | { 63 | "words": [ 64 | "content", 65 | "came", 66 | "behind", 67 | "especially" 68 | ], 69 | "medianPerSecond": 0.16666666666666666, 70 | "rangePerSecond": 0 71 | }, 72 | { 73 | "words": [ 74 | "video", 75 | "he’s", 76 | "talking", 77 | "used" 78 | ], 79 | "medianPerSecond": 0.2, 80 | "rangePerSecond": 0.03333333333333333 81 | }, 82 | { 83 | "words": [ 84 | "literally", 85 | "open", 86 | "must", 87 | "almost" 88 | ], 89 | "medianPerSecond": 0.2, 90 | "rangePerSecond": 0 91 | }, 92 | { 93 | "words": [ 94 | "feeling", 95 | "hate", 96 | "okay", 97 | "lost" 98 | ], 99 | "medianPerSecond": 0.2, 100 | "rangePerSecond": 0 101 | }, 102 | { 103 | "words": [ 104 | "less", 105 | "social", 106 | "person", 107 | "past" 108 | ], 109 | "medianPerSecond": 0.2, 110 | "rangePerSecond": 0 111 | }, 112 | { 113 | "words": [ 114 | "instead", 115 | "gets", 116 | "called", 117 | "ok" 118 | ], 119 | "medianPerSecond": 0.2, 120 | "rangePerSecond": 0 121 | }, 122 | { 123 | "words": [ 124 | "call", 125 | "else", 126 | "house", 127 | "may" 128 | ], 129 | "medianPerSecond": 0.23333333333333334, 130 | "rangePerSecond": 0.03333333333333333 131 | }, 132 | { 133 | "words": [ 134 | "friend", 135 | "music", 136 | "god", 137 | "you’re" 138 | ], 139 | "medianPerSecond": 0.23333333333333334, 140 | "rangePerSecond": 0 141 | }, 142 | { 143 | "words": [ 144 | "free", 145 | "times", 146 | "definitely", 147 | "change" 148 | ], 149 | "medianPerSecond": 0.23333333333333334, 150 | "rangePerSecond": 0 151 | }, 152 | { 153 | "words": [ 154 | "seen", 155 | "check", 156 | "cool", 157 | "care" 158 | ], 159 | "medianPerSecond": 0.23333333333333334, 160 | "rangePerSecond": 0 161 | }, 162 | { 163 | "words": [ 164 | "didn’t", 165 | "went", 166 | "looking", 167 | "twitter" 168 | ], 169 | "medianPerSecond": 0.23333333333333334, 170 | "rangePerSecond": 0 171 | }, 172 | { 173 | "words": [ 174 | "away", 175 | "myself", 176 | "absolutely", 177 | "different" 178 | ], 179 | "medianPerSecond": 0.23333333333333334, 180 | "rangePerSecond": 0.03333333333333333 181 | }, 182 | { 183 | "words": [ 184 | "wait", 185 | "playing", 186 | "true", 187 | "started" 188 | ], 189 | "medianPerSecond": 0.26666666666666666, 190 | "rangePerSecond": 0 191 | }, 192 | { 193 | "words": [ 194 | "ago", 195 | "family", 196 | "games", 197 | "saw" 198 | ], 199 | "medianPerSecond": 0.26666666666666666, 200 | "rangePerSecond": 0 201 | }, 202 | { 203 | "words": [ 204 | "believe", 205 | "support", 206 | "black", 207 | "list" 208 | ], 209 | "medianPerSecond": 0.26666666666666666, 210 | "rangePerSecond": 0 211 | }, 212 | { 213 | "words": [ 214 | "try", 215 | "tell", 216 | "against", 217 | "everything" 218 | ], 219 | "medianPerSecond": 0.26666666666666666, 220 | "rangePerSecond": 0 221 | }, 222 | { 223 | "words": [ 224 | "having", 225 | "week", 226 | "watch", 227 | "mean" 228 | ], 229 | "medianPerSecond": 0.3, 230 | "rangePerSecond": 0 231 | }, 232 | { 233 | "words": [ 234 | "bit", 235 | "already", 236 | "least", 237 | "start" 238 | ], 239 | "medianPerSecond": 0.3, 240 | "rangePerSecond": 0 241 | }, 242 | { 243 | "words": [ 244 | "full", 245 | "home", 246 | "sorry", 247 | "i’ve" 248 | ], 249 | "medianPerSecond": 0.31666666666666665, 250 | "rangePerSecond": 0.03333333333333333 251 | }, 252 | { 253 | "words": [ 254 | "can’t", 255 | "stuff", 256 | "live", 257 | "money" 258 | ], 259 | "medianPerSecond": 0.3333333333333333, 260 | "rangePerSecond": 0 261 | }, 262 | { 263 | "words": [ 264 | "said", 265 | "probably", 266 | "place", 267 | "book" 268 | ], 269 | "medianPerSecond": 0.3333333333333333, 270 | "rangePerSecond": 0 271 | }, 272 | { 273 | "words": [ 274 | "give", 275 | "making", 276 | "nothing", 277 | "media" 278 | ], 279 | "medianPerSecond": 0.3333333333333333, 280 | "rangePerSecond": 0.03333333333333333 281 | }, 282 | { 283 | "words": [ 284 | "through", 285 | "play", 286 | "might", 287 | "part" 288 | ], 289 | "medianPerSecond": 0.36666666666666664, 290 | "rangePerSecond": 0 291 | }, 292 | { 293 | "words": [ 294 | "makes", 295 | "getting", 296 | "without", 297 | "gonna" 298 | ], 299 | "medianPerSecond": 0.36666666666666664, 300 | "rangePerSecond": 0 301 | }, 302 | { 303 | "words": [ 304 | "put", 305 | "pretty", 306 | "looks", 307 | "own" 308 | ], 309 | "medianPerSecond": 0.36666666666666664, 310 | "rangePerSecond": 0 311 | }, 312 | { 313 | "words": [ 314 | "follow", 315 | "both", 316 | "books", 317 | "old" 318 | ], 319 | "medianPerSecond": 0.36666666666666664, 320 | "rangePerSecond": 0 321 | }, 322 | { 323 | "words": [ 324 | "thought", 325 | "bluesky", 326 | "until", 327 | "please" 328 | ], 329 | "medianPerSecond": 0.4, 330 | "rangePerSecond": 0 331 | }, 332 | { 333 | "words": [ 334 | "done", 335 | "though", 336 | "everyone", 337 | "world" 338 | ], 339 | "medianPerSecond": 0.4, 340 | "rangePerSecond": 0 341 | }, 342 | { 343 | "words": [ 344 | "nice", 345 | "come", 346 | "next", 347 | "show" 348 | ], 349 | "medianPerSecond": 0.4, 350 | "rangePerSecond": 0 351 | }, 352 | { 353 | "words": [ 354 | "life", 355 | "fun", 356 | "man", 357 | "anything" 358 | ], 359 | "medianPerSecond": 0.43333333333333335, 360 | "rangePerSecond": 0 361 | }, 362 | { 363 | "words": [ 364 | "real", 365 | "hard", 366 | "does", 367 | "maybe" 368 | ], 369 | "medianPerSecond": 0.43333333333333335, 370 | "rangePerSecond": 0 371 | }, 372 | { 373 | "words": [ 374 | "let", 375 | "happy", 376 | "days", 377 | "help" 378 | ], 379 | "medianPerSecond": 0.43333333333333335, 380 | "rangePerSecond": 0 381 | }, 382 | { 383 | "words": [ 384 | "while", 385 | "find", 386 | "down", 387 | "that’s" 388 | ], 389 | "medianPerSecond": 0.4666666666666667, 390 | "rangePerSecond": 0 391 | }, 392 | { 393 | "words": [ 394 | "read", 395 | "someone", 396 | "another", 397 | "again" 398 | ], 399 | "medianPerSecond": 0.4666666666666667, 400 | "rangePerSecond": 0 401 | }, 402 | { 403 | "words": [ 404 | "use", 405 | "big", 406 | "since", 407 | "yeah" 408 | ], 409 | "medianPerSecond": 0.5, 410 | "rangePerSecond": 0 411 | }, 412 | { 413 | "words": [ 414 | "post", 415 | "lot", 416 | "few", 417 | "best" 418 | ], 419 | "medianPerSecond": 0.5, 420 | "rangePerSecond": 0 421 | }, 422 | { 423 | "words": [ 424 | "doing", 425 | "little", 426 | "bad", 427 | "two" 428 | ], 429 | "medianPerSecond": 0.5166666666666667, 430 | "rangePerSecond": 0.03333333333333333 431 | }, 432 | { 433 | "words": [ 434 | "actually", 435 | "made", 436 | "today", 437 | "hope" 438 | ], 439 | "medianPerSecond": 0.5666666666666667, 440 | "rangePerSecond": 0.03333333333333333 441 | }, 442 | { 443 | "words": [ 444 | "art", 445 | "keep", 446 | "before", 447 | "always" 448 | ], 449 | "medianPerSecond": 0.5666666666666667, 450 | "rangePerSecond": 0 451 | }, 452 | { 453 | "words": [ 454 | "take", 455 | "every", 456 | "those", 457 | "look" 458 | ], 459 | "medianPerSecond": 0.6, 460 | "rangePerSecond": 0.03333333333333333 461 | }, 462 | { 463 | "words": [ 464 | "lol", 465 | "feel", 466 | "thanks", 467 | "same" 468 | ], 469 | "medianPerSecond": 0.6, 470 | "rangePerSecond": 0 471 | }, 472 | { 473 | "words": [ 474 | "yes", 475 | "sure", 476 | "say", 477 | "most" 478 | ], 479 | "medianPerSecond": 0.6333333333333333, 480 | "rangePerSecond": 0.06666666666666667 481 | }, 482 | { 483 | "words": [ 484 | "many", 485 | "year", 486 | "better", 487 | "game" 488 | ], 489 | "medianPerSecond": 0.6666666666666666, 490 | "rangePerSecond": 0 491 | }, 492 | { 493 | "words": [ 494 | "which", 495 | "great", 496 | "something", 497 | "last" 498 | ], 499 | "medianPerSecond": 0.6833333333333333, 500 | "rangePerSecond": 0.03333333333333333 501 | }, 502 | { 503 | "words": [ 504 | "thing", 505 | "never", 506 | "where", 507 | "after" 508 | ], 509 | "medianPerSecond": 0.7, 510 | "rangePerSecond": 0 511 | }, 512 | { 513 | "words": [ 514 | "off", 515 | "work", 516 | "should", 517 | "any" 518 | ], 519 | "medianPerSecond": 0.7333333333333333, 520 | "rangePerSecond": 0 521 | }, 522 | { 523 | "words": [ 524 | "very", 525 | "well", 526 | "she", 527 | "first" 528 | ], 529 | "medianPerSecond": 0.7666666666666667, 530 | "rangePerSecond": 0 531 | }, 532 | { 533 | "words": [ 534 | "than", 535 | "am", 536 | "way", 537 | "thank" 538 | ], 539 | "medianPerSecond": 0.8, 540 | "rangePerSecond": 0.03333333333333333 541 | }, 542 | { 543 | "words": [ 544 | "oh", 545 | "why", 546 | "right", 547 | "these" 548 | ], 549 | "medianPerSecond": 0.8333333333333334, 550 | "rangePerSecond": 0.03333333333333333 551 | }, 552 | { 553 | "words": [ 554 | "other", 555 | "her", 556 | "could", 557 | "into" 558 | ], 559 | "medianPerSecond": 0.8666666666666667, 560 | "rangePerSecond": 0.03333333333333333 561 | }, 562 | { 563 | "words": [ 564 | "don’t", 565 | "even", 566 | "our", 567 | "then" 568 | ], 569 | "medianPerSecond": 0.8833333333333333, 570 | "rangePerSecond": 0.03333333333333333 571 | }, 572 | { 573 | "words": [ 574 | "did", 575 | "us", 576 | "over", 577 | "go" 578 | ], 579 | "medianPerSecond": 0.9333333333333333, 580 | "rangePerSecond": 0.03333333333333333 581 | }, 582 | { 583 | "words": [ 584 | "only", 585 | "back", 586 | "make", 587 | "him" 588 | ], 589 | "medianPerSecond": 1.0166666666666666, 590 | "rangePerSecond": 0.03333333333333333 591 | }, 592 | { 593 | "words": [ 594 | "because", 595 | "new", 596 | "much", 597 | "being" 598 | ], 599 | "medianPerSecond": 1.0333333333333334, 600 | "rangePerSecond": 0 601 | }, 602 | { 603 | "words": [ 604 | "still", 605 | "also", 606 | "want", 607 | "going" 608 | ], 609 | "medianPerSecond": 1.1333333333333333, 610 | "rangePerSecond": 0.1 611 | }, 612 | { 613 | "words": [ 614 | "got", 615 | "i’m", 616 | "need", 617 | "day" 618 | ], 619 | "medianPerSecond": 1.2333333333333334, 620 | "rangePerSecond": 0.06666666666666667 621 | }, 622 | { 623 | "words": [ 624 | "too", 625 | "really", 626 | "see", 627 | "had" 628 | ], 629 | "medianPerSecond": 1.3, 630 | "rangePerSecond": 0.06666666666666667 631 | }, 632 | { 633 | "words": [ 634 | "who", 635 | "it’s", 636 | "been", 637 | "know" 638 | ], 639 | "medianPerSecond": 1.3333333333333333, 640 | "rangePerSecond": 0.03333333333333333 641 | }, 642 | { 643 | "words": [ 644 | "would", 645 | "here", 646 | "think", 647 | "their" 648 | ], 649 | "medianPerSecond": 1.45, 650 | "rangePerSecond": 0.06666666666666667 651 | }, 652 | { 653 | "words": [ 654 | "them", 655 | "love", 656 | "has", 657 | "there" 658 | ], 659 | "medianPerSecond": 1.5333333333333334, 660 | "rangePerSecond": 0.06666666666666667 661 | }, 662 | { 663 | "words": [ 664 | "an", 665 | "his", 666 | "how", 667 | "some" 668 | ], 669 | "medianPerSecond": 1.65, 670 | "rangePerSecond": 0.06666666666666667 671 | }, 672 | { 673 | "words": [ 674 | "now", 675 | "when", 676 | "time", 677 | "by" 678 | ], 679 | "medianPerSecond": 1.8, 680 | "rangePerSecond": 0.1 681 | }, 682 | { 683 | "words": [ 684 | "he", 685 | "more", 686 | "no", 687 | "people" 688 | ], 689 | "medianPerSecond": 1.9333333333333333, 690 | "rangePerSecond": 0.16666666666666666 691 | }, 692 | { 693 | "words": [ 694 | "get", 695 | "from", 696 | "up", 697 | "good" 698 | ], 699 | "medianPerSecond": 2.033333333333333, 700 | "rangePerSecond": 0.06666666666666667 701 | }, 702 | { 703 | "words": [ 704 | "will", 705 | "or", 706 | "can", 707 | "out" 708 | ], 709 | "medianPerSecond": 2.1166666666666667, 710 | "rangePerSecond": 0.1 711 | }, 712 | { 713 | "words": [ 714 | "do", 715 | "your", 716 | "about", 717 | "at" 718 | ], 719 | "medianPerSecond": 2.433333333333333, 720 | "rangePerSecond": 0.3 721 | }, 722 | { 723 | "words": [ 724 | "one", 725 | "we", 726 | "what", 727 | "if" 728 | ], 729 | "medianPerSecond": 2.716666666666667, 730 | "rangePerSecond": 0.3333333333333333 731 | }, 732 | { 733 | "words": [ 734 | "as", 735 | "just", 736 | "all", 737 | "they" 738 | ], 739 | "medianPerSecond": 3.1333333333333333, 740 | "rangePerSecond": 0.5 741 | }, 742 | { 743 | "words": [ 744 | "me", 745 | "was", 746 | "are", 747 | "not" 748 | ], 749 | "medianPerSecond": 3.8666666666666667, 750 | "rangePerSecond": 0.06666666666666667 751 | }, 752 | { 753 | "words": [ 754 | "like", 755 | "so", 756 | "be", 757 | "have" 758 | ], 759 | "medianPerSecond": 4.466666666666667, 760 | "rangePerSecond": 0.6333333333333333 761 | }, 762 | { 763 | "words": [ 764 | "with", 765 | "but", 766 | "my", 767 | "on" 768 | ], 769 | "medianPerSecond": 5.3, 770 | "rangePerSecond": 1.3666666666666667 771 | }, 772 | { 773 | "words": [ 774 | "it", 775 | "that", 776 | "for", 777 | "this" 778 | ], 779 | "medianPerSecond": 8.483333333333333, 780 | "rangePerSecond": 2.066666666666667 781 | }, 782 | { 783 | "words": [ 784 | "of", 785 | "you", 786 | "in", 787 | "is" 788 | ], 789 | "medianPerSecond": 9.6, 790 | "rangePerSecond": 3.066666666666667 791 | }, 792 | { 793 | "words": [ 794 | "i", 795 | "to", 796 | "and", 797 | "a" 798 | ], 799 | "medianPerSecond": 18.45, 800 | "rangePerSecond": 5.233333333333333 801 | } 802 | ] -------------------------------------------------------------------------------- /data/make-leagues.ts: -------------------------------------------------------------------------------- 1 | import { median } from "./analyze.ts"; 2 | 3 | function randomizeOrder(arr: T[]): T[] { 4 | const array = [...arr]; 5 | const out = []; 6 | while (array.length > 0) { 7 | const randIndex = Math.floor(Math.random() * array.length); 8 | out.push(array[randIndex]); 9 | array.splice(randIndex, 1); 10 | } 11 | return out; 12 | } 13 | 14 | const compiledText = await Deno.readTextFile("./sip-data/compiled-edited.json"); 15 | const compiledData = JSON.parse(compiledText) as { 16 | word: string; 17 | median: number; 18 | }[]; 19 | 20 | while (compiledData.length % 4 !== 0) { 21 | compiledData.pop(); 22 | } 23 | 24 | type FourWords = [string, string, string, string]; 25 | 26 | interface League { 27 | words: FourWords; 28 | medianPerSecond: number; 29 | rangePerSecond: number; 30 | } 31 | 32 | const leagues = [] as League[]; 33 | 34 | for (let i = 0; i < compiledData.length; i += 4) { 35 | const slice = compiledData.slice(i, i + 4); 36 | const words = randomizeOrder(slice.map((entry) => entry.word)) as FourWords; 37 | const theMedian = median(slice.map((entry) => entry.median))! / 30; 38 | slice.sort((a, b) => a.median - b.median); 39 | const range = (slice[slice.length - 1].median - slice[0].median) / 30; 40 | 41 | leagues.push({ 42 | words: words, 43 | medianPerSecond: theMedian, 44 | rangePerSecond: range, 45 | }); 46 | } 47 | 48 | console.log("Number of leagues created: ", leagues.length); 49 | 50 | const leaguesJson = JSON.stringify(leagues, null, "\t"); 51 | await Deno.writeTextFile("leagues.json", leaguesJson); 52 | -------------------------------------------------------------------------------- /data/race.ts: -------------------------------------------------------------------------------- 1 | const firehoseSocket = new WebSocket( 2 | "wss://jetstream2.us-west.bsky.network/subscribe" 3 | ); 4 | const english = /^en(-[A-Z0-9]{2,3})?$/; 5 | const wordsToRace = ["window", "soul", "flower", "eyes"]; 6 | let wordCount = {} as { [index: string]: number }; 7 | wordsToRace.forEach((word: string) => { 8 | wordCount = { ...wordCount, [word]: 0 }; 9 | }); 10 | 11 | function matchWord(wordToMatch: string, testString: string) { 12 | const regex = new RegExp(`\\b${wordToMatch}\\b`, "g"); 13 | return regex.test(testString.toLowerCase()); 14 | } 15 | 16 | firehoseSocket.onmessage = (event) => { 17 | const data = JSON.parse(event.data); 18 | if ( 19 | data.kind === "commit" && 20 | data.commit?.record?.$type === "app.bsky.feed.post" && 21 | data.commit.record.langs?.some((locale: string) => english.test(locale)) 22 | ) { 23 | const text = data.commit.record.text.toLowerCase() as string; 24 | for (let word of wordsToRace) { 25 | if (matchWord(word, text)) { 26 | console.log(`+++${word.toUpperCase()}\n${text}`); 27 | wordCount[word] += 1; 28 | console.log("----------------"); 29 | } 30 | } 31 | } 32 | }; 33 | setTimeout(() => { 34 | firehoseSocket.close(); 35 | console.log(wordCount); 36 | }, 6000); 37 | -------------------------------------------------------------------------------- /data/sip-data/compiled-edited.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "word": "piece", 4 | "median": 3 5 | }, 6 | { 7 | "word": "ones", 8 | "median": 4 9 | }, 10 | { 11 | "word": "space", 12 | "median": 4 13 | }, 14 | { 15 | "word": "gave", 16 | "median": 4 17 | }, 18 | { 19 | "word": "choose", 20 | "median": 4 21 | }, 22 | { 23 | "word": "isn’t", 24 | "median": 4 25 | }, 26 | { 27 | "word": "question", 28 | "median": 4 29 | }, 30 | { 31 | "word": "seems", 32 | "median": 4 33 | }, 34 | { 35 | "word": "movie", 36 | "median": 5 37 | }, 38 | { 39 | "word": "during", 40 | "median": 5 41 | }, 42 | { 43 | "word": "season", 44 | "median": 5 45 | }, 46 | { 47 | "word": "working", 48 | "median": 5 49 | }, 50 | { 51 | "word": "sounds", 52 | "median": 5 53 | }, 54 | { 55 | "word": "able", 56 | "median": 5 57 | }, 58 | { 59 | "word": "welcome", 60 | "median": 5 61 | }, 62 | { 63 | "word": "taking", 64 | "median": 5 65 | }, 66 | { 67 | "word": "weird", 68 | "median": 5 69 | }, 70 | { 71 | "word": "finally", 72 | "median": 5 73 | }, 74 | { 75 | "word": "story", 76 | "median": 5 77 | }, 78 | { 79 | "word": "honestly", 80 | "median": 5 81 | }, 82 | { 83 | "word": "job", 84 | "median": 5 85 | }, 86 | { 87 | "word": "link", 88 | "median": 5 89 | }, 90 | { 91 | "word": "exactly", 92 | "median": 5 93 | }, 94 | { 95 | "word": "under", 96 | "median": 5 97 | }, 98 | { 99 | "word": "content", 100 | "median": 5 101 | }, 102 | { 103 | "word": "came", 104 | "median": 5 105 | }, 106 | { 107 | "word": "behind", 108 | "median": 5 109 | }, 110 | { 111 | "word": "especially", 112 | "median": 5 113 | }, 114 | { 115 | "word": "used", 116 | "median": 5 117 | }, 118 | { 119 | "word": "video", 120 | "median": 6 121 | }, 122 | { 123 | "word": "talking", 124 | "median": 6 125 | }, 126 | { 127 | "word": "he’s", 128 | "median": 6 129 | }, 130 | { 131 | "word": "literally", 132 | "median": 6 133 | }, 134 | { 135 | "word": "must", 136 | "median": 6 137 | }, 138 | { 139 | "word": "open", 140 | "median": 6 141 | }, 142 | { 143 | "word": "almost", 144 | "median": 6 145 | }, 146 | { 147 | "word": "feeling", 148 | "median": 6 149 | }, 150 | { 151 | "word": "okay", 152 | "median": 6 153 | }, 154 | { 155 | "word": "lost", 156 | "median": 6 157 | }, 158 | { 159 | "word": "hate", 160 | "median": 6 161 | }, 162 | { 163 | "word": "less", 164 | "median": 6 165 | }, 166 | { 167 | "word": "person", 168 | "median": 6 169 | }, 170 | { 171 | "word": "social", 172 | "median": 6 173 | }, 174 | { 175 | "word": "past", 176 | "median": 6 177 | }, 178 | { 179 | "word": "ok", 180 | "median": 6 181 | }, 182 | { 183 | "word": "called", 184 | "median": 6 185 | }, 186 | { 187 | "word": "instead", 188 | "median": 6 189 | }, 190 | { 191 | "word": "gets", 192 | "median": 6 193 | }, 194 | { 195 | "word": "call", 196 | "median": 6 197 | }, 198 | { 199 | "word": "may", 200 | "median": 7 201 | }, 202 | { 203 | "word": "house", 204 | "median": 7 205 | }, 206 | { 207 | "word": "else", 208 | "median": 7 209 | }, 210 | { 211 | "word": "you’re", 212 | "median": 7 213 | }, 214 | { 215 | "word": "god", 216 | "median": 7 217 | }, 218 | { 219 | "word": "music", 220 | "median": 7 221 | }, 222 | { 223 | "word": "friend", 224 | "median": 7 225 | }, 226 | { 227 | "word": "change", 228 | "median": 7 229 | }, 230 | { 231 | "word": "definitely", 232 | "median": 7 233 | }, 234 | { 235 | "word": "times", 236 | "median": 7 237 | }, 238 | { 239 | "word": "free", 240 | "median": 7 241 | }, 242 | { 243 | "word": "cool", 244 | "median": 7 245 | }, 246 | { 247 | "word": "care", 248 | "median": 7 249 | }, 250 | { 251 | "word": "seen", 252 | "median": 7 253 | }, 254 | { 255 | "word": "check", 256 | "median": 7 257 | }, 258 | { 259 | "word": "didn’t", 260 | "median": 7 261 | }, 262 | { 263 | "word": "went", 264 | "median": 7 265 | }, 266 | { 267 | "word": "twitter", 268 | "median": 7 269 | }, 270 | { 271 | "word": "looking", 272 | "median": 7 273 | }, 274 | { 275 | "word": "different", 276 | "median": 7 277 | }, 278 | { 279 | "word": "absolutely", 280 | "median": 7 281 | }, 282 | { 283 | "word": "away", 284 | "median": 7 285 | }, 286 | { 287 | "word": "myself", 288 | "median": 8 289 | }, 290 | { 291 | "word": "started", 292 | "median": 8 293 | }, 294 | { 295 | "word": "playing", 296 | "median": 8 297 | }, 298 | { 299 | "word": "wait", 300 | "median": 8 301 | }, 302 | { 303 | "word": "true", 304 | "median": 8 305 | }, 306 | { 307 | "word": "games", 308 | "median": 8 309 | }, 310 | { 311 | "word": "family", 312 | "median": 8 313 | }, 314 | { 315 | "word": "ago", 316 | "median": 8 317 | }, 318 | { 319 | "word": "saw", 320 | "median": 8 321 | }, 322 | { 323 | "word": "black", 324 | "median": 8 325 | }, 326 | { 327 | "word": "support", 328 | "median": 8 329 | }, 330 | { 331 | "word": "list", 332 | "median": 8 333 | }, 334 | { 335 | "word": "believe", 336 | "median": 8 337 | }, 338 | { 339 | "word": "try", 340 | "median": 8 341 | }, 342 | { 343 | "word": "tell", 344 | "median": 8 345 | }, 346 | { 347 | "word": "everything", 348 | "median": 8 349 | }, 350 | { 351 | "word": "against", 352 | "median": 8 353 | }, 354 | { 355 | "word": "watch", 356 | "median": 9 357 | }, 358 | { 359 | "word": "mean", 360 | "median": 9 361 | }, 362 | { 363 | "word": "having", 364 | "median": 9 365 | }, 366 | { 367 | "word": "week", 368 | "median": 9 369 | }, 370 | { 371 | "word": "bit", 372 | "median": 9 373 | }, 374 | { 375 | "word": "least", 376 | "median": 9 377 | }, 378 | { 379 | "word": "start", 380 | "median": 9 381 | }, 382 | { 383 | "word": "already", 384 | "median": 9 385 | }, 386 | { 387 | "word": "full", 388 | "median": 9 389 | }, 390 | { 391 | "word": "home", 392 | "median": 9 393 | }, 394 | { 395 | "word": "i’ve", 396 | "median": 10 397 | }, 398 | { 399 | "word": "sorry", 400 | "median": 10 401 | }, 402 | { 403 | "word": "can’t", 404 | "median": 10 405 | }, 406 | { 407 | "word": "money", 408 | "median": 10 409 | }, 410 | { 411 | "word": "stuff", 412 | "median": 10 413 | }, 414 | { 415 | "word": "live", 416 | "median": 10 417 | }, 418 | { 419 | "word": "said", 420 | "median": 10 421 | }, 422 | { 423 | "word": "place", 424 | "median": 10 425 | }, 426 | { 427 | "word": "book", 428 | "median": 10 429 | }, 430 | { 431 | "word": "probably", 432 | "median": 10 433 | }, 434 | { 435 | "word": "nothing", 436 | "median": 10 437 | }, 438 | { 439 | "word": "making", 440 | "median": 10 441 | }, 442 | { 443 | "word": "media", 444 | "median": 10 445 | }, 446 | { 447 | "word": "give", 448 | "median": 11 449 | }, 450 | { 451 | "word": "play", 452 | "median": 11 453 | }, 454 | { 455 | "word": "through", 456 | "median": 11 457 | }, 458 | { 459 | "word": "might", 460 | "median": 11 461 | }, 462 | { 463 | "word": "part", 464 | "median": 11 465 | }, 466 | { 467 | "word": "gonna", 468 | "median": 11 469 | }, 470 | { 471 | "word": "makes", 472 | "median": 11 473 | }, 474 | { 475 | "word": "without", 476 | "median": 11 477 | }, 478 | { 479 | "word": "getting", 480 | "median": 11 481 | }, 482 | { 483 | "word": "pretty", 484 | "median": 11 485 | }, 486 | { 487 | "word": "looks", 488 | "median": 11 489 | }, 490 | { 491 | "word": "put", 492 | "median": 11 493 | }, 494 | { 495 | "word": "own", 496 | "median": 11 497 | }, 498 | { 499 | "word": "both", 500 | "median": 11 501 | }, 502 | { 503 | "word": "old", 504 | "median": 11 505 | }, 506 | { 507 | "word": "follow", 508 | "median": 11 509 | }, 510 | { 511 | "word": "books", 512 | "median": 11 513 | }, 514 | { 515 | "word": "thought", 516 | "median": 12 517 | }, 518 | { 519 | "word": "please", 520 | "median": 12 521 | }, 522 | { 523 | "word": "bluesky", 524 | "median": 12 525 | }, 526 | { 527 | "word": "until", 528 | "median": 12 529 | }, 530 | { 531 | "word": "done", 532 | "median": 12 533 | }, 534 | { 535 | "word": "everyone", 536 | "median": 12 537 | }, 538 | { 539 | "word": "world", 540 | "median": 12 541 | }, 542 | { 543 | "word": "though", 544 | "median": 12 545 | }, 546 | { 547 | "word": "come", 548 | "median": 12 549 | }, 550 | { 551 | "word": "next", 552 | "median": 12 553 | }, 554 | { 555 | "word": "nice", 556 | "median": 12 557 | }, 558 | { 559 | "word": "show", 560 | "median": 12 561 | }, 562 | { 563 | "word": "life", 564 | "median": 13 565 | }, 566 | { 567 | "word": "man", 568 | "median": 13 569 | }, 570 | { 571 | "word": "fun", 572 | "median": 13 573 | }, 574 | { 575 | "word": "anything", 576 | "median": 13 577 | }, 578 | { 579 | "word": "maybe", 580 | "median": 13 581 | }, 582 | { 583 | "word": "does", 584 | "median": 13 585 | }, 586 | { 587 | "word": "hard", 588 | "median": 13 589 | }, 590 | { 591 | "word": "real", 592 | "median": 13 593 | }, 594 | { 595 | "word": "days", 596 | "median": 13 597 | }, 598 | { 599 | "word": "happy", 600 | "median": 13 601 | }, 602 | { 603 | "word": "let", 604 | "median": 13 605 | }, 606 | { 607 | "word": "help", 608 | "median": 13 609 | }, 610 | { 611 | "word": "while", 612 | "median": 14 613 | }, 614 | { 615 | "word": "that’s", 616 | "median": 14 617 | }, 618 | { 619 | "word": "find", 620 | "median": 14 621 | }, 622 | { 623 | "word": "down", 624 | "median": 14 625 | }, 626 | { 627 | "word": "again", 628 | "median": 14 629 | }, 630 | { 631 | "word": "read", 632 | "median": 14 633 | }, 634 | { 635 | "word": "another", 636 | "median": 14 637 | }, 638 | { 639 | "word": "someone", 640 | "median": 14 641 | }, 642 | { 643 | "word": "yeah", 644 | "median": 15 645 | }, 646 | { 647 | "word": "since", 648 | "median": 15 649 | }, 650 | { 651 | "word": "big", 652 | "median": 15 653 | }, 654 | { 655 | "word": "use", 656 | "median": 15 657 | }, 658 | { 659 | "word": "post", 660 | "median": 15 661 | }, 662 | { 663 | "word": "few", 664 | "median": 15 665 | }, 666 | { 667 | "word": "best", 668 | "median": 15 669 | }, 670 | { 671 | "word": "lot", 672 | "median": 15 673 | }, 674 | { 675 | "word": "bad", 676 | "median": 15 677 | }, 678 | { 679 | "word": "two", 680 | "median": 15 681 | }, 682 | { 683 | "word": "little", 684 | "median": 16 685 | }, 686 | { 687 | "word": "doing", 688 | "median": 16 689 | }, 690 | { 691 | "word": "hope", 692 | "median": 16 693 | }, 694 | { 695 | "word": "today", 696 | "median": 17 697 | }, 698 | { 699 | "word": "made", 700 | "median": 17 701 | }, 702 | { 703 | "word": "actually", 704 | "median": 17 705 | }, 706 | { 707 | "word": "always", 708 | "median": 17 709 | }, 710 | { 711 | "word": "before", 712 | "median": 17 713 | }, 714 | { 715 | "word": "keep", 716 | "median": 17 717 | }, 718 | { 719 | "word": "art", 720 | "median": 17 721 | }, 722 | { 723 | "word": "those", 724 | "median": 17 725 | }, 726 | { 727 | "word": "every", 728 | "median": 18 729 | }, 730 | { 731 | "word": "look", 732 | "median": 18 733 | }, 734 | { 735 | "word": "take", 736 | "median": 18 737 | }, 738 | { 739 | "word": "lol", 740 | "median": 18 741 | }, 742 | { 743 | "word": "thanks", 744 | "median": 18 745 | }, 746 | { 747 | "word": "same", 748 | "median": 18 749 | }, 750 | { 751 | "word": "feel", 752 | "median": 18 753 | }, 754 | { 755 | "word": "most", 756 | "median": 18 757 | }, 758 | { 759 | "word": "yes", 760 | "median": 19 761 | }, 762 | { 763 | "word": "sure", 764 | "median": 19 765 | }, 766 | { 767 | "word": "say", 768 | "median": 20 769 | }, 770 | { 771 | "word": "many", 772 | "median": 20 773 | }, 774 | { 775 | "word": "better", 776 | "median": 20 777 | }, 778 | { 779 | "word": "game", 780 | "median": 20 781 | }, 782 | { 783 | "word": "year", 784 | "median": 20 785 | }, 786 | { 787 | "word": "last", 788 | "median": 20 789 | }, 790 | { 791 | "word": "which", 792 | "median": 20 793 | }, 794 | { 795 | "word": "great", 796 | "median": 21 797 | }, 798 | { 799 | "word": "something", 800 | "median": 21 801 | }, 802 | { 803 | "word": "thing", 804 | "median": 21 805 | }, 806 | { 807 | "word": "never", 808 | "median": 21 809 | }, 810 | { 811 | "word": "where", 812 | "median": 21 813 | }, 814 | { 815 | "word": "after", 816 | "median": 21 817 | }, 818 | { 819 | "word": "should", 820 | "median": 22 821 | }, 822 | { 823 | "word": "off", 824 | "median": 22 825 | }, 826 | { 827 | "word": "work", 828 | "median": 22 829 | }, 830 | { 831 | "word": "any", 832 | "median": 22 833 | }, 834 | { 835 | "word": "she", 836 | "median": 23 837 | }, 838 | { 839 | "word": "first", 840 | "median": 23 841 | }, 842 | { 843 | "word": "very", 844 | "median": 23 845 | }, 846 | { 847 | "word": "well", 848 | "median": 23 849 | }, 850 | { 851 | "word": "thank", 852 | "median": 23 853 | }, 854 | { 855 | "word": "way", 856 | "median": 24 857 | }, 858 | { 859 | "word": "than", 860 | "median": 24 861 | }, 862 | { 863 | "word": "am", 864 | "median": 24 865 | }, 866 | { 867 | "word": "why", 868 | "median": 24 869 | }, 870 | { 871 | "word": "oh", 872 | "median": 25 873 | }, 874 | { 875 | "word": "right", 876 | "median": 25 877 | }, 878 | { 879 | "word": "these", 880 | "median": 25 881 | }, 882 | { 883 | "word": "into", 884 | "median": 25 885 | }, 886 | { 887 | "word": "her", 888 | "median": 26 889 | }, 890 | { 891 | "word": "other", 892 | "median": 26 893 | }, 894 | { 895 | "word": "could", 896 | "median": 26 897 | }, 898 | { 899 | "word": "then", 900 | "median": 26 901 | }, 902 | { 903 | "word": "don’t", 904 | "median": 26 905 | }, 906 | { 907 | "word": "even", 908 | "median": 27 909 | }, 910 | { 911 | "word": "our", 912 | "median": 27 913 | }, 914 | { 915 | "word": "us", 916 | "median": 28 917 | }, 918 | { 919 | "word": "over", 920 | "median": 28 921 | }, 922 | { 923 | "word": "did", 924 | "median": 28 925 | }, 926 | { 927 | "word": "go", 928 | "median": 29 929 | }, 930 | { 931 | "word": "make", 932 | "median": 30 933 | }, 934 | { 935 | "word": "him", 936 | "median": 30 937 | }, 938 | { 939 | "word": "only", 940 | "median": 31 941 | }, 942 | { 943 | "word": "back", 944 | "median": 31 945 | }, 946 | { 947 | "word": "much", 948 | "median": 31 949 | }, 950 | { 951 | "word": "because", 952 | "median": 31 953 | }, 954 | { 955 | "word": "new", 956 | "median": 31 957 | }, 958 | { 959 | "word": "being", 960 | "median": 31 961 | }, 962 | { 963 | "word": "still", 964 | "median": 32 965 | }, 966 | { 967 | "word": "going", 968 | "median": 33 969 | }, 970 | { 971 | "word": "also", 972 | "median": 35 973 | }, 974 | { 975 | "word": "want", 976 | "median": 35 977 | }, 978 | { 979 | "word": "need", 980 | "median": 36 981 | }, 982 | { 983 | "word": "day", 984 | "median": 36 985 | }, 986 | { 987 | "word": "got", 988 | "median": 38 989 | }, 990 | { 991 | "word": "i’m", 992 | "median": 38 993 | }, 994 | { 995 | "word": "really", 996 | "median": 38 997 | }, 998 | { 999 | "word": "see", 1000 | "median": 39 1001 | }, 1002 | { 1003 | "word": "had", 1004 | "median": 39 1005 | }, 1006 | { 1007 | "word": "too", 1008 | "median": 40 1009 | }, 1010 | { 1011 | "word": "it’s", 1012 | "median": 40 1013 | }, 1014 | { 1015 | "word": "been", 1016 | "median": 40 1017 | }, 1018 | { 1019 | "word": "know", 1020 | "median": 40 1021 | }, 1022 | { 1023 | "word": "who", 1024 | "median": 41 1025 | }, 1026 | { 1027 | "word": "think", 1028 | "median": 42 1029 | }, 1030 | { 1031 | "word": "their", 1032 | "median": 43 1033 | }, 1034 | { 1035 | "word": "here", 1036 | "median": 44 1037 | }, 1038 | { 1039 | "word": "would", 1040 | "median": 44 1041 | }, 1042 | { 1043 | "word": "love", 1044 | "median": 46 1045 | }, 1046 | { 1047 | "word": "has", 1048 | "median": 46 1049 | }, 1050 | { 1051 | "word": "there", 1052 | "median": 46 1053 | }, 1054 | { 1055 | "word": "them", 1056 | "median": 48 1057 | }, 1058 | { 1059 | "word": "some", 1060 | "median": 49 1061 | }, 1062 | { 1063 | "word": "how", 1064 | "median": 49 1065 | }, 1066 | { 1067 | "word": "his", 1068 | "median": 50 1069 | }, 1070 | { 1071 | "word": "an", 1072 | "median": 51 1073 | }, 1074 | { 1075 | "word": "now", 1076 | "median": 52 1077 | }, 1078 | { 1079 | "word": "by", 1080 | "median": 54 1081 | }, 1082 | { 1083 | "word": "when", 1084 | "median": 54 1085 | }, 1086 | { 1087 | "word": "time", 1088 | "median": 55 1089 | }, 1090 | { 1091 | "word": "more", 1092 | "median": 55 1093 | }, 1094 | { 1095 | "word": "people", 1096 | "median": 57 1097 | }, 1098 | { 1099 | "word": "no", 1100 | "median": 59 1101 | }, 1102 | { 1103 | "word": "he", 1104 | "median": 60 1105 | }, 1106 | { 1107 | "word": "up", 1108 | "median": 60 1109 | }, 1110 | { 1111 | "word": "get", 1112 | "median": 60 1113 | }, 1114 | { 1115 | "word": "good", 1116 | "median": 62 1117 | }, 1118 | { 1119 | "word": "from", 1120 | "median": 62 1121 | }, 1122 | { 1123 | "word": "out", 1124 | "median": 62 1125 | }, 1126 | { 1127 | "word": "or", 1128 | "median": 63 1129 | }, 1130 | { 1131 | "word": "can", 1132 | "median": 64 1133 | }, 1134 | { 1135 | "word": "will", 1136 | "median": 65 1137 | }, 1138 | { 1139 | "word": "about", 1140 | "median": 67 1141 | }, 1142 | { 1143 | "word": "your", 1144 | "median": 71 1145 | }, 1146 | { 1147 | "word": "do", 1148 | "median": 75 1149 | }, 1150 | { 1151 | "word": "at", 1152 | "median": 76 1153 | }, 1154 | { 1155 | "word": "one", 1156 | "median": 76 1157 | }, 1158 | { 1159 | "word": "we", 1160 | "median": 79 1161 | }, 1162 | { 1163 | "word": "if", 1164 | "median": 84 1165 | }, 1166 | { 1167 | "word": "what", 1168 | "median": 86 1169 | }, 1170 | { 1171 | "word": "as", 1172 | "median": 89 1173 | }, 1174 | { 1175 | "word": "all", 1176 | "median": 90 1177 | }, 1178 | { 1179 | "word": "they", 1180 | "median": 98 1181 | }, 1182 | { 1183 | "word": "just", 1184 | "median": 104 1185 | }, 1186 | { 1187 | "word": "me", 1188 | "median": 115 1189 | }, 1190 | { 1191 | "word": "are", 1192 | "median": 116 1193 | }, 1194 | { 1195 | "word": "not", 1196 | "median": 116 1197 | }, 1198 | { 1199 | "word": "was", 1200 | "median": 117 1201 | }, 1202 | { 1203 | "word": "like", 1204 | "median": 117 1205 | }, 1206 | { 1207 | "word": "so", 1208 | "median": 133 1209 | }, 1210 | { 1211 | "word": "be", 1212 | "median": 135 1213 | }, 1214 | { 1215 | "word": "have", 1216 | "median": 136 1217 | }, 1218 | { 1219 | "word": "with", 1220 | "median": 141 1221 | }, 1222 | { 1223 | "word": "but", 1224 | "median": 144 1225 | }, 1226 | { 1227 | "word": "on", 1228 | "median": 174 1229 | }, 1230 | { 1231 | "word": "my", 1232 | "median": 182 1233 | }, 1234 | { 1235 | "word": "this", 1236 | "median": 213 1237 | }, 1238 | { 1239 | "word": "that", 1240 | "median": 239 1241 | }, 1242 | { 1243 | "word": "for", 1244 | "median": 270 1245 | }, 1246 | { 1247 | "word": "it", 1248 | "median": 275 1249 | }, 1250 | { 1251 | "word": "in", 1252 | "median": 277 1253 | }, 1254 | { 1255 | "word": "you", 1256 | "median": 282 1257 | }, 1258 | { 1259 | "word": "is", 1260 | "median": 294 1261 | }, 1262 | { 1263 | "word": "of", 1264 | "median": 369 1265 | }, 1266 | { 1267 | "word": "and", 1268 | "median": 447 1269 | }, 1270 | { 1271 | "word": "a", 1272 | "median": 543 1273 | }, 1274 | { 1275 | "word": "i", 1276 | "median": 564 1277 | }, 1278 | { 1279 | "word": "to", 1280 | "median": 604 1281 | }, 1282 | { 1283 | "word": "the", 1284 | "median": 829 1285 | } 1286 | ] 1287 | -------------------------------------------------------------------------------- /data/sip-data/compiled.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "word": "12", 4 | "median": 3 5 | }, 6 | { 7 | "word": "15", 8 | "median": 3 9 | }, 10 | { 11 | "word": "piece", 12 | "median": 3 13 | }, 14 | { 15 | "word": "ones", 16 | "median": 4 17 | }, 18 | { 19 | "word": "space", 20 | "median": 4 21 | }, 22 | { 23 | "word": "gave", 24 | "median": 4 25 | }, 26 | { 27 | "word": "choose", 28 | "median": 4 29 | }, 30 | { 31 | "word": "isn’t", 32 | "median": 4 33 | }, 34 | { 35 | "word": "question", 36 | "median": 4 37 | }, 38 | { 39 | "word": "seems", 40 | "median": 4 41 | }, 42 | { 43 | "word": "10", 44 | "median": 5 45 | }, 46 | { 47 | "word": "movie", 48 | "median": 5 49 | }, 50 | { 51 | "word": "during", 52 | "median": 5 53 | }, 54 | { 55 | "word": "season", 56 | "median": 5 57 | }, 58 | { 59 | "word": "working", 60 | "median": 5 61 | }, 62 | { 63 | "word": "sounds", 64 | "median": 5 65 | }, 66 | { 67 | "word": "able", 68 | "median": 5 69 | }, 70 | { 71 | "word": "welcome", 72 | "median": 5 73 | }, 74 | { 75 | "word": "taking", 76 | "median": 5 77 | }, 78 | { 79 | "word": "weird", 80 | "median": 5 81 | }, 82 | { 83 | "word": "finally", 84 | "median": 5 85 | }, 86 | { 87 | "word": "story", 88 | "median": 5 89 | }, 90 | { 91 | "word": "honestly", 92 | "median": 5 93 | }, 94 | { 95 | "word": "job", 96 | "median": 5 97 | }, 98 | { 99 | "word": "link", 100 | "median": 5 101 | }, 102 | { 103 | "word": "exactly", 104 | "median": 5 105 | }, 106 | { 107 | "word": "under", 108 | "median": 5 109 | }, 110 | { 111 | "word": "content", 112 | "median": 5 113 | }, 114 | { 115 | "word": "came", 116 | "median": 5 117 | }, 118 | { 119 | "word": "behind", 120 | "median": 5 121 | }, 122 | { 123 | "word": "especially", 124 | "median": 5 125 | }, 126 | { 127 | "word": "used", 128 | "median": 5 129 | }, 130 | { 131 | "word": "video", 132 | "median": 6 133 | }, 134 | { 135 | "word": "talking", 136 | "median": 6 137 | }, 138 | { 139 | "word": "he’s", 140 | "median": 6 141 | }, 142 | { 143 | "word": "literally", 144 | "median": 6 145 | }, 146 | { 147 | "word": "must", 148 | "median": 6 149 | }, 150 | { 151 | "word": "open", 152 | "median": 6 153 | }, 154 | { 155 | "word": "almost", 156 | "median": 6 157 | }, 158 | { 159 | "word": "feeling", 160 | "median": 6 161 | }, 162 | { 163 | "word": "okay", 164 | "median": 6 165 | }, 166 | { 167 | "word": "theres", 168 | "median": 6 169 | }, 170 | { 171 | "word": "lost", 172 | "median": 6 173 | }, 174 | { 175 | "word": "hate", 176 | "median": 6 177 | }, 178 | { 179 | "word": "less", 180 | "median": 6 181 | }, 182 | { 183 | "word": "person", 184 | "median": 6 185 | }, 186 | { 187 | "word": "social", 188 | "median": 6 189 | }, 190 | { 191 | "word": "past", 192 | "median": 6 193 | }, 194 | { 195 | "word": "ok", 196 | "median": 6 197 | }, 198 | { 199 | "word": "called", 200 | "median": 6 201 | }, 202 | { 203 | "word": "instead", 204 | "median": 6 205 | }, 206 | { 207 | "word": "gets", 208 | "median": 6 209 | }, 210 | { 211 | "word": "call", 212 | "median": 6 213 | }, 214 | { 215 | "word": "4", 216 | "median": 7 217 | }, 218 | { 219 | "word": "2024", 220 | "median": 7 221 | }, 222 | { 223 | "word": "may", 224 | "median": 7 225 | }, 226 | { 227 | "word": "house", 228 | "median": 7 229 | }, 230 | { 231 | "word": "else", 232 | "median": 7 233 | }, 234 | { 235 | "word": "you’re", 236 | "median": 7 237 | }, 238 | { 239 | "word": "god", 240 | "median": 7 241 | }, 242 | { 243 | "word": "music", 244 | "median": 7 245 | }, 246 | { 247 | "word": "friend", 248 | "median": 7 249 | }, 250 | { 251 | "word": "change", 252 | "median": 7 253 | }, 254 | { 255 | "word": "definitely", 256 | "median": 7 257 | }, 258 | { 259 | "word": "times", 260 | "median": 7 261 | }, 262 | { 263 | "word": "free", 264 | "median": 7 265 | }, 266 | { 267 | "word": "december", 268 | "median": 7 269 | }, 270 | { 271 | "word": "😭", 272 | "median": 7 273 | }, 274 | { 275 | "word": "cool", 276 | "median": 7 277 | }, 278 | { 279 | "word": "care", 280 | "median": 7 281 | }, 282 | { 283 | "word": "seen", 284 | "median": 7 285 | }, 286 | { 287 | "word": "check", 288 | "median": 7 289 | }, 290 | { 291 | "word": "didn’t", 292 | "median": 7 293 | }, 294 | { 295 | "word": "went", 296 | "median": 7 297 | }, 298 | { 299 | "word": "twitter", 300 | "median": 7 301 | }, 302 | { 303 | "word": "looking", 304 | "median": 7 305 | }, 306 | { 307 | "word": "different", 308 | "median": 7 309 | }, 310 | { 311 | "word": "isnt", 312 | "median": 7 313 | }, 314 | { 315 | "word": "absolutely", 316 | "median": 7 317 | }, 318 | { 319 | "word": "away", 320 | "median": 7 321 | }, 322 | { 323 | "word": "myself", 324 | "median": 8 325 | }, 326 | { 327 | "word": "started", 328 | "median": 8 329 | }, 330 | { 331 | "word": "playing", 332 | "median": 8 333 | }, 334 | { 335 | "word": "wait", 336 | "median": 8 337 | }, 338 | { 339 | "word": "ill", 340 | "median": 8 341 | }, 342 | { 343 | "word": "true", 344 | "median": 8 345 | }, 346 | { 347 | "word": "games", 348 | "median": 8 349 | }, 350 | { 351 | "word": "family", 352 | "median": 8 353 | }, 354 | { 355 | "word": "ago", 356 | "median": 8 357 | }, 358 | { 359 | "word": "saw", 360 | "median": 8 361 | }, 362 | { 363 | "word": "black", 364 | "median": 8 365 | }, 366 | { 367 | "word": "support", 368 | "median": 8 369 | }, 370 | { 371 | "word": "list", 372 | "median": 8 373 | }, 374 | { 375 | "word": "believe", 376 | "median": 8 377 | }, 378 | { 379 | "word": "try", 380 | "median": 8 381 | }, 382 | { 383 | "word": "tell", 384 | "median": 8 385 | }, 386 | { 387 | "word": "everything", 388 | "median": 8 389 | }, 390 | { 391 | "word": "against", 392 | "median": 8 393 | }, 394 | { 395 | "word": "watch", 396 | "median": 9 397 | }, 398 | { 399 | "word": "mean", 400 | "median": 9 401 | }, 402 | { 403 | "word": "having", 404 | "median": 9 405 | }, 406 | { 407 | "word": "week", 408 | "median": 9 409 | }, 410 | { 411 | "word": "bit", 412 | "median": 9 413 | }, 414 | { 415 | "word": "least", 416 | "median": 9 417 | }, 418 | { 419 | "word": "start", 420 | "median": 9 421 | }, 422 | { 423 | "word": "already", 424 | "median": 9 425 | }, 426 | { 427 | "word": "full", 428 | "median": 9 429 | }, 430 | { 431 | "word": "home", 432 | "median": 9 433 | }, 434 | { 435 | "word": "doesnt", 436 | "median": 9 437 | }, 438 | { 439 | "word": "5", 440 | "median": 10 441 | }, 442 | { 443 | "word": "fuck", 444 | "median": 10 445 | }, 446 | { 447 | "word": "i’ve", 448 | "median": 10 449 | }, 450 | { 451 | "word": "sorry", 452 | "median": 10 453 | }, 454 | { 455 | "word": "can’t", 456 | "median": 10 457 | }, 458 | { 459 | "word": "money", 460 | "median": 10 461 | }, 462 | { 463 | "word": "stuff", 464 | "median": 10 465 | }, 466 | { 467 | "word": "live", 468 | "median": 10 469 | }, 470 | { 471 | "word": "said", 472 | "median": 10 473 | }, 474 | { 475 | "word": "place", 476 | "median": 10 477 | }, 478 | { 479 | "word": "book", 480 | "median": 10 481 | }, 482 | { 483 | "word": "probably", 484 | "median": 10 485 | }, 486 | { 487 | "word": "nothing", 488 | "median": 10 489 | }, 490 | { 491 | "word": "making", 492 | "median": 10 493 | }, 494 | { 495 | "word": "media", 496 | "median": 10 497 | }, 498 | { 499 | "word": "😂", 500 | "median": 10 501 | }, 502 | { 503 | "word": "20", 504 | "median": 11 505 | }, 506 | { 507 | "word": "give", 508 | "median": 11 509 | }, 510 | { 511 | "word": "play", 512 | "median": 11 513 | }, 514 | { 515 | "word": "through", 516 | "median": 11 517 | }, 518 | { 519 | "word": "might", 520 | "median": 11 521 | }, 522 | { 523 | "word": "part", 524 | "median": 11 525 | }, 526 | { 527 | "word": "gonna", 528 | "median": 11 529 | }, 530 | { 531 | "word": "makes", 532 | "median": 11 533 | }, 534 | { 535 | "word": "without", 536 | "median": 11 537 | }, 538 | { 539 | "word": "getting", 540 | "median": 11 541 | }, 542 | { 543 | "word": "pretty", 544 | "median": 11 545 | }, 546 | { 547 | "word": "looks", 548 | "median": 11 549 | }, 550 | { 551 | "word": "put", 552 | "median": 11 553 | }, 554 | { 555 | "word": "own", 556 | "median": 11 557 | }, 558 | { 559 | "word": "both", 560 | "median": 11 561 | }, 562 | { 563 | "word": "old", 564 | "median": 11 565 | }, 566 | { 567 | "word": "follow", 568 | "median": 11 569 | }, 570 | { 571 | "word": "books", 572 | "median": 11 573 | }, 574 | { 575 | "word": "thought", 576 | "median": 12 577 | }, 578 | { 579 | "word": "please", 580 | "median": 12 581 | }, 582 | { 583 | "word": "hes", 584 | "median": 12 585 | }, 586 | { 587 | "word": "bluesky", 588 | "median": 12 589 | }, 590 | { 591 | "word": "until", 592 | "median": 12 593 | }, 594 | { 595 | "word": "done", 596 | "median": 12 597 | }, 598 | { 599 | "word": "everyone", 600 | "median": 12 601 | }, 602 | { 603 | "word": "world", 604 | "median": 12 605 | }, 606 | { 607 | "word": "though", 608 | "median": 12 609 | }, 610 | { 611 | "word": "come", 612 | "median": 12 613 | }, 614 | { 615 | "word": "next", 616 | "median": 12 617 | }, 618 | { 619 | "word": "didnt", 620 | "median": 12 621 | }, 622 | { 623 | "word": "nice", 624 | "median": 12 625 | }, 626 | { 627 | "word": "show", 628 | "median": 12 629 | }, 630 | { 631 | "word": "1", 632 | "median": 13 633 | }, 634 | { 635 | "word": "life", 636 | "median": 13 637 | }, 638 | { 639 | "word": "shit", 640 | "median": 13 641 | }, 642 | { 643 | "word": "man", 644 | "median": 13 645 | }, 646 | { 647 | "word": "fun", 648 | "median": 13 649 | }, 650 | { 651 | "word": "anything", 652 | "median": 13 653 | }, 654 | { 655 | "word": "maybe", 656 | "median": 13 657 | }, 658 | { 659 | "word": "does", 660 | "median": 13 661 | }, 662 | { 663 | "word": "hard", 664 | "median": 13 665 | }, 666 | { 667 | "word": "real", 668 | "median": 13 669 | }, 670 | { 671 | "word": "days", 672 | "median": 13 673 | }, 674 | { 675 | "word": "happy", 676 | "median": 13 677 | }, 678 | { 679 | "word": "let", 680 | "median": 13 681 | }, 682 | { 683 | "word": "help", 684 | "median": 13 685 | }, 686 | { 687 | "word": "while", 688 | "median": 14 689 | }, 690 | { 691 | "word": "that’s", 692 | "median": 14 693 | }, 694 | { 695 | "word": "find", 696 | "median": 14 697 | }, 698 | { 699 | "word": "youre", 700 | "median": 14 701 | }, 702 | { 703 | "word": "down", 704 | "median": 14 705 | }, 706 | { 707 | "word": "again", 708 | "median": 14 709 | }, 710 | { 711 | "word": "read", 712 | "median": 14 713 | }, 714 | { 715 | "word": "another", 716 | "median": 14 717 | }, 718 | { 719 | "word": "someone", 720 | "median": 14 721 | }, 722 | { 723 | "word": "de", 724 | "median": 14 725 | }, 726 | { 727 | "word": "christmas", 728 | "median": 14 729 | }, 730 | { 731 | "word": "yeah", 732 | "median": 15 733 | }, 734 | { 735 | "word": "since", 736 | "median": 15 737 | }, 738 | { 739 | "word": "big", 740 | "median": 15 741 | }, 742 | { 743 | "word": "use", 744 | "median": 15 745 | }, 746 | { 747 | "word": "post", 748 | "median": 15 749 | }, 750 | { 751 | "word": "few", 752 | "median": 15 753 | }, 754 | { 755 | "word": "best", 756 | "median": 15 757 | }, 758 | { 759 | "word": "lot", 760 | "median": 15 761 | }, 762 | { 763 | "word": "bad", 764 | "median": 15 765 | }, 766 | { 767 | "word": "two", 768 | "median": 15 769 | }, 770 | { 771 | "word": "little", 772 | "median": 16 773 | }, 774 | { 775 | "word": "doing", 776 | "median": 16 777 | }, 778 | { 779 | "word": "hope", 780 | "median": 16 781 | }, 782 | { 783 | "word": "today", 784 | "median": 17 785 | }, 786 | { 787 | "word": "cant", 788 | "median": 17 789 | }, 790 | { 791 | "word": "made", 792 | "median": 17 793 | }, 794 | { 795 | "word": "actually", 796 | "median": 17 797 | }, 798 | { 799 | "word": "always", 800 | "median": 17 801 | }, 802 | { 803 | "word": "before", 804 | "median": 17 805 | }, 806 | { 807 | "word": "ive", 808 | "median": 17 809 | }, 810 | { 811 | "word": "keep", 812 | "median": 17 813 | }, 814 | { 815 | "word": "art", 816 | "median": 17 817 | }, 818 | { 819 | "word": "those", 820 | "median": 17 821 | }, 822 | { 823 | "word": "2", 824 | "median": 18 825 | }, 826 | { 827 | "word": "every", 828 | "median": 18 829 | }, 830 | { 831 | "word": "look", 832 | "median": 18 833 | }, 834 | { 835 | "word": "take", 836 | "median": 18 837 | }, 838 | { 839 | "word": "lol", 840 | "median": 18 841 | }, 842 | { 843 | "word": "thanks", 844 | "median": 18 845 | }, 846 | { 847 | "word": "same", 848 | "median": 18 849 | }, 850 | { 851 | "word": "feel", 852 | "median": 18 853 | }, 854 | { 855 | "word": "most", 856 | "median": 18 857 | }, 858 | { 859 | "word": "yes", 860 | "median": 19 861 | }, 862 | { 863 | "word": "sure", 864 | "median": 19 865 | }, 866 | { 867 | "word": "3", 868 | "median": 20 869 | }, 870 | { 871 | "word": "say", 872 | "median": 20 873 | }, 874 | { 875 | "word": "many", 876 | "median": 20 877 | }, 878 | { 879 | "word": "better", 880 | "median": 20 881 | }, 882 | { 883 | "word": "game", 884 | "median": 20 885 | }, 886 | { 887 | "word": "years", 888 | "median": 20 889 | }, 890 | { 891 | "word": "year", 892 | "median": 20 893 | }, 894 | { 895 | "word": "last", 896 | "median": 20 897 | }, 898 | { 899 | "word": "which", 900 | "median": 20 901 | }, 902 | { 903 | "word": "great", 904 | "median": 21 905 | }, 906 | { 907 | "word": "things", 908 | "median": 21 909 | }, 910 | { 911 | "word": "something", 912 | "median": 21 913 | }, 914 | { 915 | "word": "thats", 916 | "median": 21 917 | }, 918 | { 919 | "word": "thing", 920 | "median": 21 921 | }, 922 | { 923 | "word": "never", 924 | "median": 21 925 | }, 926 | { 927 | "word": "where", 928 | "median": 21 929 | }, 930 | { 931 | "word": "trump", 932 | "median": 21 933 | }, 934 | { 935 | "word": "after", 936 | "median": 21 937 | }, 938 | { 939 | "word": "should", 940 | "median": 22 941 | }, 942 | { 943 | "word": "off", 944 | "median": 22 945 | }, 946 | { 947 | "word": "work", 948 | "median": 22 949 | }, 950 | { 951 | "word": "any", 952 | "median": 22 953 | }, 954 | { 955 | "word": "she", 956 | "median": 23 957 | }, 958 | { 959 | "word": "first", 960 | "median": 23 961 | }, 962 | { 963 | "word": "very", 964 | "median": 23 965 | }, 966 | { 967 | "word": "well", 968 | "median": 23 969 | }, 970 | { 971 | "word": "thank", 972 | "median": 23 973 | }, 974 | { 975 | "word": "way", 976 | "median": 24 977 | }, 978 | { 979 | "word": "than", 980 | "median": 24 981 | }, 982 | { 983 | "word": "am", 984 | "median": 24 985 | }, 986 | { 987 | "word": "why", 988 | "median": 24 989 | }, 990 | { 991 | "word": "oh", 992 | "median": 25 993 | }, 994 | { 995 | "word": "right", 996 | "median": 25 997 | }, 998 | { 999 | "word": "these", 1000 | "median": 25 1001 | }, 1002 | { 1003 | "word": "into", 1004 | "median": 25 1005 | }, 1006 | { 1007 | "word": "her", 1008 | "median": 26 1009 | }, 1010 | { 1011 | "word": "other", 1012 | "median": 26 1013 | }, 1014 | { 1015 | "word": "could", 1016 | "median": 26 1017 | }, 1018 | { 1019 | "word": "then", 1020 | "median": 26 1021 | }, 1022 | { 1023 | "word": "don’t", 1024 | "median": 26 1025 | }, 1026 | { 1027 | "word": "even", 1028 | "median": 27 1029 | }, 1030 | { 1031 | "word": "our", 1032 | "median": 27 1033 | }, 1034 | { 1035 | "word": "us", 1036 | "median": 28 1037 | }, 1038 | { 1039 | "word": "over", 1040 | "median": 28 1041 | }, 1042 | { 1043 | "word": "did", 1044 | "median": 28 1045 | }, 1046 | { 1047 | "word": "go", 1048 | "median": 29 1049 | }, 1050 | { 1051 | "word": "make", 1052 | "median": 30 1053 | }, 1054 | { 1055 | "word": "him", 1056 | "median": 30 1057 | }, 1058 | { 1059 | "word": "only", 1060 | "median": 31 1061 | }, 1062 | { 1063 | "word": "back", 1064 | "median": 31 1065 | }, 1066 | { 1067 | "word": "much", 1068 | "median": 31 1069 | }, 1070 | { 1071 | "word": "because", 1072 | "median": 31 1073 | }, 1074 | { 1075 | "word": "new", 1076 | "median": 31 1077 | }, 1078 | { 1079 | "word": "being", 1080 | "median": 31 1081 | }, 1082 | { 1083 | "word": "still", 1084 | "median": 32 1085 | }, 1086 | { 1087 | "word": "going", 1088 | "median": 33 1089 | }, 1090 | { 1091 | "word": "also", 1092 | "median": 35 1093 | }, 1094 | { 1095 | "word": "want", 1096 | "median": 35 1097 | }, 1098 | { 1099 | "word": "dont", 1100 | "median": 35 1101 | }, 1102 | { 1103 | "word": "were", 1104 | "median": 35 1105 | }, 1106 | { 1107 | "word": "need", 1108 | "median": 36 1109 | }, 1110 | { 1111 | "word": "day", 1112 | "median": 36 1113 | }, 1114 | { 1115 | "word": "got", 1116 | "median": 38 1117 | }, 1118 | { 1119 | "word": "i’m", 1120 | "median": 38 1121 | }, 1122 | { 1123 | "word": "really", 1124 | "median": 38 1125 | }, 1126 | { 1127 | "word": "see", 1128 | "median": 39 1129 | }, 1130 | { 1131 | "word": "had", 1132 | "median": 39 1133 | }, 1134 | { 1135 | "word": "too", 1136 | "median": 40 1137 | }, 1138 | { 1139 | "word": "it’s", 1140 | "median": 40 1141 | }, 1142 | { 1143 | "word": "been", 1144 | "median": 40 1145 | }, 1146 | { 1147 | "word": "know", 1148 | "median": 40 1149 | }, 1150 | { 1151 | "word": "who", 1152 | "median": 41 1153 | }, 1154 | { 1155 | "word": "think", 1156 | "median": 42 1157 | }, 1158 | { 1159 | "word": "their", 1160 | "median": 43 1161 | }, 1162 | { 1163 | "word": "here", 1164 | "median": 44 1165 | }, 1166 | { 1167 | "word": "would", 1168 | "median": 44 1169 | }, 1170 | { 1171 | "word": "love", 1172 | "median": 46 1173 | }, 1174 | { 1175 | "word": "has", 1176 | "median": 46 1177 | }, 1178 | { 1179 | "word": "there", 1180 | "median": 46 1181 | }, 1182 | { 1183 | "word": "them", 1184 | "median": 48 1185 | }, 1186 | { 1187 | "word": "some", 1188 | "median": 49 1189 | }, 1190 | { 1191 | "word": "how", 1192 | "median": 49 1193 | }, 1194 | { 1195 | "word": "his", 1196 | "median": 50 1197 | }, 1198 | { 1199 | "word": "an", 1200 | "median": 51 1201 | }, 1202 | { 1203 | "word": "now", 1204 | "median": 52 1205 | }, 1206 | { 1207 | "word": "by", 1208 | "median": 54 1209 | }, 1210 | { 1211 | "word": "when", 1212 | "median": 54 1213 | }, 1214 | { 1215 | "word": "time", 1216 | "median": 55 1217 | }, 1218 | { 1219 | "word": "more", 1220 | "median": 55 1221 | }, 1222 | { 1223 | "word": "people", 1224 | "median": 57 1225 | }, 1226 | { 1227 | "word": "no", 1228 | "median": 59 1229 | }, 1230 | { 1231 | "word": "he", 1232 | "median": 60 1233 | }, 1234 | { 1235 | "word": "up", 1236 | "median": 60 1237 | }, 1238 | { 1239 | "word": "get", 1240 | "median": 60 1241 | }, 1242 | { 1243 | "word": "good", 1244 | "median": 62 1245 | }, 1246 | { 1247 | "word": "from", 1248 | "median": 62 1249 | }, 1250 | { 1251 | "word": "out", 1252 | "median": 62 1253 | }, 1254 | { 1255 | "word": "or", 1256 | "median": 63 1257 | }, 1258 | { 1259 | "word": "can", 1260 | "median": 64 1261 | }, 1262 | { 1263 | "word": "im", 1264 | "median": 64 1265 | }, 1266 | { 1267 | "word": "will", 1268 | "median": 65 1269 | }, 1270 | { 1271 | "word": "about", 1272 | "median": 67 1273 | }, 1274 | { 1275 | "word": "your", 1276 | "median": 71 1277 | }, 1278 | { 1279 | "word": "its", 1280 | "median": 75 1281 | }, 1282 | { 1283 | "word": "do", 1284 | "median": 75 1285 | }, 1286 | { 1287 | "word": "at", 1288 | "median": 76 1289 | }, 1290 | { 1291 | "word": "one", 1292 | "median": 76 1293 | }, 1294 | { 1295 | "word": "we", 1296 | "median": 79 1297 | }, 1298 | { 1299 | "word": "if", 1300 | "median": 84 1301 | }, 1302 | { 1303 | "word": "what", 1304 | "median": 86 1305 | }, 1306 | { 1307 | "word": "as", 1308 | "median": 89 1309 | }, 1310 | { 1311 | "word": "all", 1312 | "median": 90 1313 | }, 1314 | { 1315 | "word": "they", 1316 | "median": 98 1317 | }, 1318 | { 1319 | "word": "just", 1320 | "median": 104 1321 | }, 1322 | { 1323 | "word": "me", 1324 | "median": 115 1325 | }, 1326 | { 1327 | "word": "are", 1328 | "median": 116 1329 | }, 1330 | { 1331 | "word": "not", 1332 | "median": 116 1333 | }, 1334 | { 1335 | "word": "was", 1336 | "median": 117 1337 | }, 1338 | { 1339 | "word": "like", 1340 | "median": 117 1341 | }, 1342 | { 1343 | "word": "so", 1344 | "median": 133 1345 | }, 1346 | { 1347 | "word": "be", 1348 | "median": 135 1349 | }, 1350 | { 1351 | "word": "have", 1352 | "median": 136 1353 | }, 1354 | { 1355 | "word": "with", 1356 | "median": 141 1357 | }, 1358 | { 1359 | "word": "but", 1360 | "median": 144 1361 | }, 1362 | { 1363 | "word": "on", 1364 | "median": 174 1365 | }, 1366 | { 1367 | "word": "my", 1368 | "median": 182 1369 | }, 1370 | { 1371 | "word": "this", 1372 | "median": 213 1373 | }, 1374 | { 1375 | "word": "that", 1376 | "median": 239 1377 | }, 1378 | { 1379 | "word": "for", 1380 | "median": 270 1381 | }, 1382 | { 1383 | "word": "it", 1384 | "median": 275 1385 | }, 1386 | { 1387 | "word": "in", 1388 | "median": 277 1389 | }, 1390 | { 1391 | "word": "you", 1392 | "median": 282 1393 | }, 1394 | { 1395 | "word": "is", 1396 | "median": 294 1397 | }, 1398 | { 1399 | "word": "of", 1400 | "median": 369 1401 | }, 1402 | { 1403 | "word": "and", 1404 | "median": 447 1405 | }, 1406 | { 1407 | "word": "a", 1408 | "median": 543 1409 | }, 1410 | { 1411 | "word": "i", 1412 | "median": 564 1413 | }, 1414 | { 1415 | "word": "to", 1416 | "median": 604 1417 | }, 1418 | { 1419 | "word": "the", 1420 | "median": 829 1421 | } 1422 | ] -------------------------------------------------------------------------------- /data/sip-data/exemplar.json: -------------------------------------------------------------------------------- 1 | { 2 | "time": "Sat Nov 30 2024 20:00:01 GMT-0800 (Pacific Standard Time)", 3 | "durationSeconds": 30, 4 | "values": { 5 | "0": 3, 6 | "1": 17, 7 | "2": 18, 8 | "3": 15, 9 | "4": 9, 10 | "5": 8, 11 | "6": 7, 12 | "7": 8, 13 | "8": 5, 14 | "10": 5, 15 | "11": 3, 16 | "12": 7, 17 | "13": 3, 18 | "14": 3, 19 | "15": 5, 20 | "16": 3, 21 | "18": 4, 22 | "20": 9, 23 | "27": 3, 24 | "28": 3, 25 | "30": 7, 26 | "2016": 3, 27 | "2018": 3, 28 | "2024": 7, 29 | "2025": 3, 30 | "the": 746, 31 | "i": 533, 32 | "to": 469, 33 | "a": 460, 34 | "and": 407, 35 | "of": 329, 36 | "in": 285, 37 | "is": 275, 38 | "it": 234, 39 | "for": 220, 40 | "this": 205, 41 | "that": 192, 42 | "you": 190, 43 | "my": 168, 44 | "on": 159, 45 | "with": 141, 46 | "so": 133, 47 | "but": 130, 48 | "be": 124, 49 | "was": 117, 50 | "are": 113, 51 | "not": 109, 52 | "have": 105, 53 | "me": 101, 54 | "just": 97, 55 | "like": 88, 56 | "what": 87, 57 | "if": 84, 58 | "we": 83, 59 | "they": 78, 60 | "as": 77, 61 | "all": 76, 62 | "at": 74, 63 | "one": 68, 64 | "its": 67, 65 | "good": 66, 66 | "your": 66, 67 | "or": 65, 68 | "can": 64, 69 | "about": 64, 70 | "im": 59, 71 | "from": 56, 72 | "out": 56, 73 | "now": 56, 74 | "will": 55, 75 | "time": 55, 76 | "more": 55, 77 | "no": 53, 78 | "he": 52, 79 | "up": 52, 80 | "do": 51, 81 | "some": 51, 82 | "get": 50, 83 | "how": 50, 84 | "here": 48, 85 | "love": 46, 86 | "people": 45, 87 | "an": 45, 88 | "by": 44, 89 | "would": 44, 90 | "her": 43, 91 | "too": 43, 92 | "them": 43, 93 | "it’s": 43, 94 | "has": 42, 95 | "got": 42, 96 | "think": 42, 97 | "i’m": 41, 98 | "when": 41, 99 | "see": 41, 100 | "make": 39, 101 | "who": 39, 102 | "been": 39, 103 | "his": 38, 104 | "she": 37, 105 | "still": 35, 106 | "really": 35, 107 | "know": 35, 108 | "also": 35, 109 | "want": 35, 110 | "great": 33, 111 | "there": 33, 112 | "need": 32, 113 | "day": 32, 114 | "only": 32, 115 | "back": 31, 116 | "first": 31, 117 | "him": 30, 118 | "should": 30, 119 | "much": 30, 120 | "because": 29, 121 | "us": 28, 122 | "their": 28, 123 | "yes": 28, 124 | "over": 28, 125 | "say": 28, 126 | "dont": 27, 127 | "little": 27, 128 | "other": 27, 129 | "oh": 26, 130 | "could": 26, 131 | "then": 26, 132 | "had": 26, 133 | "life": 26, 134 | "right": 25, 135 | "even": 25, 136 | "new": 25, 137 | "going": 24, 138 | "go": 24, 139 | "way": 24, 140 | "things": 24, 141 | "our": 24, 142 | "than": 23, 143 | "these": 23, 144 | "today": 23, 145 | "into": 23, 146 | "being": 23, 147 | "very": 23, 148 | "many": 23, 149 | "off": 22, 150 | "work": 22, 151 | "don’t": 22, 152 | "every": 22, 153 | "am": 21, 154 | "something": 21, 155 | "give": 20, 156 | "any": 20, 157 | "look": 20, 158 | "fuck": 20, 159 | "better": 20, 160 | "why": 20, 161 | "game": 20, 162 | "play": 20, 163 | "years": 20, 164 | "yeah": 19, 165 | "sure": 19, 166 | "since": 19, 167 | "shit": 19, 168 | "i’ve": 19, 169 | "thats": 19, 170 | "were": 19, 171 | "cant": 18, 172 | "take": 18, 173 | "while": 18, 174 | "made": 18, 175 | "did": 18, 176 | "lol": 18, 177 | "thought": 17, 178 | "may": 17, 179 | "man": 17, 180 | "actually": 17, 181 | "that’s": 17, 182 | "always": 17, 183 | "thing": 17, 184 | "well": 17, 185 | "big": 17, 186 | "use": 16, 187 | "doing": 16, 188 | "post": 16, 189 | "thanks": 16, 190 | "ever": 16, 191 | "stop": 16, 192 | "before": 16, 193 | "fun": 16, 194 | "o": 16, 195 | "long": 16, 196 | "never": 15, 197 | "é": 15, 198 | "few": 15, 199 | "thank": 15, 200 | "please": 15, 201 | "hes": 15, 202 | "same": 15, 203 | "watch": 15, 204 | "where": 15, 205 | "video": 15, 206 | "hope": 15, 207 | "best": 15, 208 | "through": 15, 209 | "ive": 14, 210 | "end": 14, 211 | "keep": 14, 212 | "watching": 14, 213 | "feel": 14, 214 | "bluesky": 14, 215 | "find": 14, 216 | "trump": 14, 217 | "until": 14, 218 | "art": 14, 219 | "those": 14, 220 | "might": 14, 221 | "after": 14, 222 | "youre": 14, 223 | "done": 14, 224 | "part": 14, 225 | "myself": 13, 226 | "gonna": 13, 227 | "sorry": 13, 228 | "down": 13, 229 | "lot": 13, 230 | "ass": 13, 231 | "everyone": 13, 232 | "makes": 13, 233 | "fucking": 13, 234 | "year": 13, 235 | "kind": 13, 236 | "again": 13, 237 | "world": 13, 238 | "house": 13, 239 | "though": 12, 240 | "come": 12, 241 | "can’t": 12, 242 | "without": 12, 243 | "anything": 12, 244 | "money": 12, 245 | "next": 12, 246 | "didnt": 12, 247 | "stuff": 12, 248 | "anistia": 12, 249 | "caralho": 12, 250 | "started": 12, 251 | "maybe": 12, 252 | "guy": 12, 253 | "nice": 12, 254 | "does": 12, 255 | "mean": 11, 256 | "hard": 11, 257 | "real": 11, 258 | "hell": 11, 259 | "getting": 11, 260 | "bad": 11, 261 | "days": 11, 262 | "around": 11, 263 | "two": 11, 264 | "thanksgiving": 11, 265 | "else": 11, 266 | "playing": 11, 267 | "you’re": 11, 268 | "happy": 11, 269 | "found": 11, 270 | "pretty": 11, 271 | "having": 11, 272 | "talking": 11, 273 | "u": 11, 274 | "most": 11, 275 | "he’s": 11, 276 | "live": 10, 277 | ">": 10, 278 | "god": 10, 279 | "let": 10, 280 | "last": 10, 281 | "single": 10, 282 | "help": 10, 283 | "music": 10, 284 | "amazing": 10, 285 | "friend": 10, 286 | "week": 10, 287 | "bit": 10, 288 | "literally": 10, 289 | "read": 10, 290 | "wait": 10, 291 | "said": 10, 292 | "reason": 10, 293 | "show": 10, 294 | "current": 10, 295 | "change": 10, 296 | "took": 10, 297 | "place": 10, 298 | "song": 10, 299 | "havent": 9, 300 | "ill": 9, 301 | "run": 9, 302 | "shes": 9, 303 | "another": 9, 304 | "looks": 9, 305 | "book": 9, 306 | "must": 9, 307 | "open": 9, 308 | "true": 9, 309 | "wont": 9, 310 | "least": 9, 311 | "account": 9, 312 | "bc": 9, 313 | "almost": 9, 314 | "start": 9, 315 | "games": 9, 316 | "probably": 9, 317 | "put": 9, 318 | "between": 9, 319 | "vs": 9, 320 | "such": 9, 321 | "public": 9, 322 | "there’s": 9, 323 | "definitely": 9, 324 | "movie": 9, 325 | "ai": 9, 326 | "family": 9, 327 | "times": 9, 328 | "wanna": 9, 329 | "own": 9, 330 | "head": 9, 331 | "nothing": 9, 332 | "whole": 9, 333 | "blue": 8, 334 | "free": 8, 335 | "app": 8, 336 | "someone": 8, 337 | "weather": 8, 338 | "december": 8, 339 | "buy": 8, 340 | "wrong": 8, 341 | "😭": 8, 342 | "both": 8, 343 | "during": 8, 344 | "saying": 8, 345 | "reading": 8, 346 | "high": 8, 347 | "de": 8, 348 | "favorite": 8, 349 | "ago": 8, 350 | "longer": 8, 351 | "break": 8, 352 | "enough": 8, 353 | "cute": 8, 354 | "already": 8, 355 | "america": 8, 356 | "saw": 8, 357 | "season": 8, 358 | "biden": 8, 359 | "city": 8, 360 | "sad": 8, 361 | "black": 8, 362 | "understand": 8, 363 | "full": 8, 364 | "point": 8, 365 | "home": 8, 366 | "felt": 8, 367 | "…": 8, 368 | "enjoy": 8, 369 | "feeling": 8, 370 | "aww": 8, 371 | "cool": 7, 372 | "doesnt": 7, 373 | "w": 7, 374 | "christmas": 7, 375 | "+": 7, 376 | "care": 7, 377 | "via": 7, 378 | "haven’t": 7, 379 | "image": 7, 380 | "knew": 7, 381 | "waiting": 7, 382 | "top": 7, 383 | "making": 7, 384 | "working": 7, 385 | "old": 7, 386 | "mom": 7, 387 | "water": 7, 388 | "follow": 7, 389 | "months": 7, 390 | "episode": 7, 391 | "night": 7, 392 | "guys": 7, 393 | "—": 7, 394 | "men": 7, 395 | "thread": 7, 396 | "okay": 7, 397 | "cat": 7, 398 | "ready": 7, 399 | "seen": 7, 400 | "early": 7, 401 | "check": 7, 402 | "which": 7, 403 | "media": 7, 404 | "white": 7, 405 | "lots": 7, 406 | "girl": 7, 407 | "country": 7, 408 | "didn’t": 7, 409 | "close": 7, 410 | "went": 7, 411 | "running": 7, 412 | "left": 7, 413 | "dangit": 7, 414 | "side": 6, 415 | "turn": 6, 416 | "dear": 6, 417 | "block": 6, 418 | "sounds": 6, 419 | "theres": 6, 420 | "half": 6, 421 | "totally": 6, 422 | "trying": 6, 423 | "listen": 6, 424 | "energy": 6, 425 | "usually": 6, 426 | "support": 6, 427 | "doesn’t": 6, 428 | "cause": 6, 429 | "easy": 6, 430 | "series": 6, 431 | "poor": 6, 432 | "list": 6, 433 | "able": 6, 434 | "line": 6, 435 | "available": 6, 436 | "using": 6, 437 | "seriously": 6, 438 | "decided": 6, 439 | "martian": 6, 440 | "tired": 6, 441 | "believe": 6, 442 | "mad": 6, 443 | "general": 6, 444 | "feed": 6, 445 | "guess": 6, 446 | "save": 6, 447 | "giving": 6, 448 | "becoming": 6, 449 | "president": 6, 450 | "lost": 6, 451 | "welcome": 6, 452 | "taking": 6, 453 | "lured": 6, 454 | "hate": 6, 455 | "quite": 6, 456 | "wish": 6, 457 | "happening": 6, 458 | "putting": 6, 459 | "minutes": 6, 460 | "less": 6, 461 | "whatever": 6, 462 | "team": 6, 463 | "local": 6, 464 | "state": 6, 465 | "each": 6, 466 | "person": 6, 467 | "tv": 6, 468 | "remember": 6, 469 | "idk": 6, 470 | "|": 6, 471 | "crypto": 6, 472 | "social": 6, 473 | "maga": 6, 474 | "anya": 6, 475 | "matusevich": 6, 476 | "far": 6, 477 | "anyone": 6, 478 | "fine": 6, 479 | "vote": 6, 480 | "power": 6, 481 | "surprised": 6, 482 | "try": 5, 483 | "history": 5, 484 | "wasn’t": 5, 485 | "tell": 5, 486 | "stupid": 5, 487 | "ones": 5, 488 | "won": 5, 489 | "past": 5, 490 | "sky": 5, 491 | "nearly": 5, 492 | "nobody": 5, 493 | "weird": 5, 494 | "crazy": 5, 495 | "finally": 5, 496 | "amazon": 5, 497 | "star": 5, 498 | "ok": 5, 499 | "let’s": 5, 500 | "soul": 5, 501 | "tonight": 5, 502 | "films": 5, 503 | "hours": 5, 504 | "followers": 5, 505 | "fake": 5, 506 | "posted": 5, 507 | "mine": 5, 508 | "starting": 5, 509 | "holy": 5, 510 | "corrupt": 5, 511 | "story": 5, 512 | "etc": 5, 513 | "particular": 5, 514 | "draw": 5, 515 | "mars": 5, 516 | "hot": 5, 517 | "type": 5, 518 | "create": 5, 519 | "become": 5, 520 | "glad": 5, 521 | "despite": 5, 522 | "paul": 5, 523 | "women": 5, 524 | "children": 5, 525 | "agree": 5, 526 | "honestly": 5, 527 | "american": 5, 528 | "wife": 5, 529 | "pay": 5, 530 | "cuz": 5, 531 | "sometimes": 5, 532 | "straight": 5, 533 | "photo": 5, 534 | "imagine": 5, 535 | "aside": 5, 536 | "worry": 5, 537 | "photos": 5, 538 | "himself": 5, 539 | "gotten": 5, 540 | "hair": 5, 541 | "hi": 5, 542 | "bought": 5, 543 | "job": 5, 544 | "youtube": 5, 545 | "beat": 5, 546 | "funny": 5, 547 | "course": 5, 548 | "fast": 5, 549 | "apparently": 5, 550 | "number": 5, 551 | "food": 5, 552 | "style": 5, 553 | "hey": 5, 554 | "either": 5, 555 | "friends": 5, 556 | "outside": 5, 557 | "count": 5, 558 | "seeing": 5, 559 | "🔥": 5, 560 | "saturday": 5, 561 | "lets": 5, 562 | "whats": 5, 563 | "lmao": 5, 564 | "twitter": 5, 565 | "24h": 5, 566 | "thinking": 5, 567 | "party": 5, 568 | "gay": 5, 569 | "🤣": 5, 570 | "yet": 5, 571 | "random": 5, 572 | "needs": 5, 573 | "blood": 5, 574 | "id": 5, 575 | "site": 5, 576 | "🥰": 5, 577 | "together": 5, 578 | "looking": 5, 579 | "talk": 5, 580 | "name": 5, 581 | "safe": 5, 582 | "link": 5, 583 | "political": 5, 584 | "russia": 5, 585 | "month": 5, 586 | "once": 5, 587 | "base": 5, 588 | "duty": 5, 589 | "books": 5, 590 | "chance": 4, 591 | "space": 4, 592 | "leave": 4, 593 | "different": 4, 594 | "school": 4, 595 | "win": 4, 596 | "duck": 4, 597 | "fans": 4, 598 | "heres": 4, 599 | "sharing": 4, 600 | "kinda": 4, 601 | "exactly": 4, 602 | "usa": 4, 603 | "humidity": 4, 604 | "yay": 4, 605 | "possibly": 4, 606 | "gotta": 4, 607 | "hear": 4, 608 | "ghost": 4, 609 | "hand": 4, 610 | "heart": 4, 611 | "sit": 4, 612 | "under": 4, 613 | "works": 4, 614 | "peak": 4, 615 | "breakfast": 4, 616 | "happen": 4, 617 | "recent": 4, 618 | "john": 4, 619 | "fall": 4, 620 | "step": 4, 621 | "content": 4, 622 | "happens": 4, 623 | "algorithm": 4, 624 | "woman": 4, 625 | "won’t": 4, 626 | "japanese": 4, 627 | "mind": 4, 628 | "seem": 4, 629 | "taken": 4, 630 | "boyfriend": 4, 631 | "husband": 4, 632 | "called": 4, 633 | "sweet": 4, 634 | "finding": 4, 635 | "🕒列車遅延": 4, 636 | "ℹ️運転状況": 4, 637 | "military": 4, 638 | "bring": 4, 639 | "son": 4, 640 | "daughter": 4, 641 | "instead": 4, 642 | "idea": 4, 643 | "potential": 4, 644 | "gets": 4, 645 | "film": 4, 646 | "missing": 4, 647 | "sex": 4, 648 | "jokes": 4, 649 | "data": 4, 650 | "tomorrow": 4, 651 | "i’ll": 4, 652 | "youve": 4, 653 | "interesting": 4, 654 | "below": 4, 655 | "gave": 4, 656 | "serious": 4, 657 | "gorgeous": 4, 658 | "insane": 4, 659 | "words": 4, 660 | "ideas": 4, 661 | "face": 4, 662 | "ur": 4, 663 | "came": 4, 664 | "wednesday": 4, 665 | "park": 4, 666 | "certainly": 4, 667 | "😂": 4, 668 | "plenty": 4, 669 | "friday": 4, 670 | "second": 4, 671 | "following": 4, 672 | "schools": 4, 673 | "becomes": 4, 674 | "stay": 4, 675 | "she’s": 4, 676 | "forget": 4, 677 | "choose": 4, 678 | "voice": 4, 679 | "plot": 4, 680 | "cartoon": 4, 681 | "thing?": 4, 682 | "system": 4, 683 | "call": 4, 684 | "tho": 4, 685 | "comes": 4, 686 | "caught": 4, 687 | "isnt": 4, 688 | "sunday": 4, 689 | "isn’t": 4, 690 | "episodes": 4, 691 | "ᅠ": 4, 692 | "york": 4, 693 | "whether": 4, 694 | "forgot": 4, 695 | "finished": 4, 696 | "everything": 4, 697 | "matter": 4, 698 | "message": 4, 699 | "kids": 4, 700 | "japan": 4, 701 | "control": 4, 702 | "switch": 4, 703 | "wanted": 4, 704 | "small": 4, 705 | "moment": 4, 706 | "cut": 4, 707 | "plays": 4, 708 | "cold": 4, 709 | "listening": 4, 710 | "hour": 4, 711 | "😂😂": 4, 712 | "wild": 4, 713 | "goal": 4, 714 | "wanting": 4, 715 | "source": 4, 716 | "wicked": 4, 717 | "continue": 4, 718 | "tokens": 4, 719 | "trust": 4, 720 | "drive": 4, 721 | "across": 4, 722 | "joke": 4, 723 | "worst": 4, 724 | "usual": 4, 725 | "shows": 4, 726 | "sleep": 4, 727 | "ask": 4, 728 | "stations": 4, 729 | "empty": 4, 730 | "dating": 4, 731 | "mostly": 4, 732 | "fan": 4, 733 | "uncle": 4, 734 | "happened": 4, 735 | "heavy": 4, 736 | "known": 4, 737 | "wonder": 4, 738 | "wants": 4, 739 | "given": 4, 740 | "excited": 4, 741 | "soft": 4, 742 | "eat": 4, 743 | "missed": 4, 744 | "meant": 4, 745 | "helped": 4, 746 | "california": 4, 747 | "final": 4, 748 | "milk": 4, 749 | "question": 4, 750 | "male": 4, 751 | "starmkszbigcartelcom": 4, 752 | "super": 4, 753 | "cats": 4, 754 | "order": 4, 755 | "enjoyed": 4, 756 | "universe": 4, 757 | "recruiter": 4, 758 | "republican": 4, 759 | "cult": 3, 760 | "shall": 3, 761 | "awesome": 3, 762 | "program": 3, 763 | "x": 3, 764 | "immediately": 3, 765 | "figured": 3, 766 | "yall": 3, 767 | "ducks": 3, 768 | "college": 3, 769 | "football": 3, 770 | "opportunity": 3, 771 | "drop": 3, 772 | "dm": 3, 773 | "forest": 3, 774 | "apps": 3, 775 | "max": 3, 776 | "hed": 3, 777 | "kmh": 3, 778 | "rain": 3, 779 | "cup": 3, 780 | "added": 3, 781 | "torture": 3, 782 | "baby": 3, 783 | "points": 3, 784 | "stuck": 3, 785 | "body": 3, 786 | "rivalry": 3, 787 | "zero": 3, 788 | "reminder": 3, 789 | "regardless": 3, 790 | "misconduct": 3, 791 | "wise": 3, 792 | "bro": 3, 793 | "older": 3, 794 | "bsky": 3, 795 | "behind": 3, 796 | "crying": 3, 797 | "busy": 3, 798 | "holidays": 3, 799 | "sick": 3, 800 | "sound": 3, 801 | "played": 3, 802 | "dragon": 3, 803 | "ladies": 3, 804 | "door": 3, 805 | "arcane": 3, 806 | "freedom": 3, 807 | "room": 3, 808 | "proper": 3, 809 | "roles": 3, 810 | "level": 3, 811 | "comics": 3, 812 | "grade": 3, 813 | "production": 3, 814 | "couple": 3, 815 | "often": 3, 816 | "viking": 3, 817 | "mission": 3, 818 | "experiments": 3, 819 | "strategy": 3, 820 | "bed": 3, 821 | "heard": 3, 822 | "seems": 3, 823 | "environment": 3, 824 | "lose": 3, 825 | "law": 3, 826 | "chega": 3, 827 | "e": 3, 828 | "na": 3, 829 | "detection": 3, 830 | "search": 3, 831 | "jam": 3, 832 | "middle": 3, 833 | "mushroom": 3, 834 | "rip": 3, 835 | "miss": 3, 836 | "understanding": 3, 837 | "wrestlesky": 3, 838 | "🤭": 3, 839 | "perfect": 3, 840 | "father": 3, 841 | "ideal": 3, 842 | "hero": 3, 843 | "ah": 3, 844 | "ya": 3, 845 | "calling": 3, 846 | "fraud": 3, 847 | "however": 3, 848 | "piece": 3, 849 | "absolutely": 3, 850 | "badly": 3, 851 | "facts": 3, 852 | "d": 3, 853 | "omg": 3, 854 | "lore": 3, 855 | "laugh": 3, 856 | "suffering": 3, 857 | "worth": 3, 858 | "wrap": 3, 859 | "towel": 3, 860 | "whos": 3, 861 | "meet": 3, 862 | "box": 3, 863 | "fashion": 3, 864 | "inside": 3, 865 | "feels": 3, 866 | "score": 3, 867 | "collection": 3, 868 | "🤗": 3, 869 | "answer": 3, 870 | "apple": 3, 871 | "countries": 3, 872 | "mortgage": 3, 873 | "upset": 3, 874 | "pressing": 3, 875 | "monochromecreeps": 3, 876 | "focus": 3, 877 | "democrats": 3, 878 | "age": 3, 879 | "surprise": 3, 880 | "stream": 3, 881 | "hold": 3, 882 | "green": 3, 883 | "tech": 3, 884 | "wwe": 3, 885 | "they’re": 3, 886 | "passed": 3, 887 | "term": 3, 888 | "positive": 3, 889 | "loves": 3, 890 | "actual": 3, 891 | "november": 3, 892 | "da": 3, 893 | "background": 3, 894 | "throwing": 3, 895 | "supposed": 3, 896 | "especially": 3, 897 | "speed": 3, 898 | "clear": 3, 899 | "election": 3, 900 | "lives": 3, 901 | "experts": 3, 902 | "process": 3, 903 | "group": 3, 904 | "putin": 3, 905 | "voters": 3, 906 | "watched": 3, 907 | "couldnt": 3, 908 | "pokemon": 3, 909 | "places": 3, 910 | "j1fmtokyo": 3, 911 | "case": 3, 912 | "bet": 3, 913 | "police": 3, 914 | "canadian": 3, 915 | "phone": 3, 916 | "suck": 3, 917 | "operation": 3, 918 | "mention": 3, 919 | "b": 3, 920 | "hip": 3, 921 | "shower": 3, 922 | "issue": 3, 923 | "posts": 3, 924 | "blowing": 3, 925 | "comment": 3, 926 | "letting": 3, 927 | "community": 3, 928 | "🟡": 3, 929 | "🟠": 3, 930 | "beautiful": 3, 931 | "spend": 3, 932 | "fell": 3, 933 | "in?": 3, 934 | "gaming": 3, 935 | "grad": 3, 936 | "hands": 3, 937 | "entire": 3, 938 | "shot": 3, 939 | "brain": 3, 940 | "mbar": 3, 941 | "aware": 3, 942 | "quickly": 3, 943 | "plant": 3, 944 | "drawing": 3, 945 | "liked": 3, 946 | "witch": 3, 947 | "experience": 3, 948 | "due": 3, 949 | "normal": 3, 950 | "conversation": 3, 951 | "set": 3, 952 | "sounders": 3, 953 | "weekend": 3, 954 | "debut": 3, 955 | "child": 3, 956 | "era": 3, 957 | "growth": 3, 958 | "musk": 3, 959 | "desperate": 3, 960 | "telling": 3, 961 | "return": 3, 962 | "hopefully": 3, 963 | "match": 3, 964 | "chat": 3, 965 | "survey": 3, 966 | "results": 3, 967 | "elon": 3, 968 | "based": 3, 969 | "takes": 3, 970 | "league": 3, 971 | "boston": 3, 972 | "truth": 3, 973 | "😈": 3, 974 | "sir": 3, 975 | "ebikes": 3, 976 | "human": 3, 977 | "damage": 3, 978 | "trolls": 3, 979 | "against": 3, 980 | "indeed": 3, 981 | "emotional": 3, 982 | "y’all": 3, 983 | "wake": 3, 984 | "✨": 3, 985 | "used": 3, 986 | "millions": 3, 987 | "magical": 3, 988 | "completely": 3, 989 | "appeared": 3, 990 | "selfie": 3, 991 | "frame": 3, 992 | "arent": 3, 993 | "drunk": 3, 994 | "double": 3, 995 | "none": 3, 996 | "eyes": 3, 997 | "away": 3, 998 | "screen": 3, 999 | "👀": 3, 1000 | "coach": 3, 1001 | "steam": 3, 1002 | "roll": 3, 1003 | "learn": 3, 1004 | "talked": 3 1005 | } 1006 | } -------------------------------------------------------------------------------- /data/sip-data/other/1733098381427.json: -------------------------------------------------------------------------------- 1 | { 2 | "time": "Sun Dec 01 2024 16:13:01 GMT-0800 (Pacific Standard Time)", 3 | "durationSeconds": 30, 4 | "values": { 5 | "0": 6, 6 | "1": 23, 7 | "2": 41, 8 | "3": 21, 9 | "4": 3, 10 | "5": 6, 11 | "6": 7, 12 | "7": 5, 13 | "8": 6, 14 | "9": 3, 15 | "10": 10, 16 | "11": 2, 17 | "12": 6, 18 | "13": 2, 19 | "14": 3, 20 | "15": 2, 21 | "16": 2, 22 | "20": 7, 23 | "23": 4, 24 | "25": 3, 25 | "30": 3, 26 | "33": 2, 27 | "36": 2, 28 | "38": 2, 29 | "48": 2, 30 | "50": 5, 31 | "59": 2, 32 | "99": 2, 33 | "100": 9, 34 | "247": 2, 35 | "300": 3, 36 | "500": 2, 37 | "700": 4, 38 | "1262": 2, 39 | "2004": 2, 40 | "2020": 7, 41 | "2022": 2, 42 | "2024": 5, 43 | "3028": 2, 44 | "112924": 2, 45 | "the": 829, 46 | "to": 604, 47 | "i": 578, 48 | "a": 550, 49 | "and": 448, 50 | "of": 375, 51 | "you": 322, 52 | "is": 294, 53 | "it": 290, 54 | "for": 285, 55 | "that": 253, 56 | "in": 228, 57 | "this": 224, 58 | "my": 223, 59 | "on": 184, 60 | "but": 159, 61 | "so": 146, 62 | "have": 138, 63 | "with": 138, 64 | "me": 136, 65 | "be": 135, 66 | "are": 135, 67 | "like": 126, 68 | "just": 122, 69 | "was": 120, 70 | "not": 119, 71 | "we": 101, 72 | "they": 98, 73 | "as": 97, 74 | "if": 93, 75 | "all": 90, 76 | "at": 90, 77 | "its": 88, 78 | "do": 84, 79 | "from": 80, 80 | "one": 76, 81 | "im": 76, 82 | "more": 75, 83 | "out": 75, 84 | "your": 75, 85 | "can": 75, 86 | "he": 73, 87 | "love": 71, 88 | "get": 70, 89 | "about": 67, 90 | "what": 67, 91 | "will": 65, 92 | "by": 63, 93 | "good": 62, 94 | "when": 60, 95 | "up": 60, 96 | "people": 59, 97 | "would": 58, 98 | "time": 58, 99 | "no": 56, 100 | "or": 56, 101 | "now": 56, 102 | "who": 54, 103 | "know": 54, 104 | "his": 52, 105 | "an": 51, 106 | "there": 51, 107 | "has": 50, 108 | "i’m": 50, 109 | "how": 49, 110 | "us": 48, 111 | "it’s": 47, 112 | "back": 46, 113 | "really": 44, 114 | "their": 43, 115 | "here": 43, 116 | "her": 43, 117 | "did": 42, 118 | "been": 42, 119 | "am": 42, 120 | "going": 41, 121 | "think": 41, 122 | "them": 41, 123 | "some": 41, 124 | "got": 40, 125 | "too": 40, 126 | "only": 40, 127 | "because": 40, 128 | "see": 39, 129 | "also": 38, 130 | "dont": 38, 131 | "even": 38, 132 | "want": 37, 133 | "need": 36, 134 | "were": 35, 135 | "she": 35, 136 | "well": 34, 137 | "go": 32, 138 | "being": 31, 139 | "years": 31, 140 | "new": 31, 141 | "other": 31, 142 | "why": 31, 143 | "had": 30, 144 | "something": 30, 145 | "day": 30, 146 | "our": 29, 147 | "first": 29, 148 | "oh": 29, 149 | "into": 29, 150 | "things": 29, 151 | "yes": 28, 152 | "make": 28, 153 | "don’t": 28, 154 | "these": 27, 155 | "never": 27, 156 | "always": 27, 157 | "thank": 27, 158 | "still": 27, 159 | "say": 26, 160 | "much": 26, 161 | "youre": 26, 162 | "little": 25, 163 | "gonna": 25, 164 | "game": 25, 165 | "lol": 24, 166 | "him": 24, 167 | "many": 24, 168 | "thats": 24, 169 | "long": 24, 170 | "way": 24, 171 | "every": 23, 172 | "could": 23, 173 | "again": 23, 174 | "before": 23, 175 | "bad": 22, 176 | "than": 22, 177 | "off": 22, 178 | "cant": 21, 179 | "over": 21, 180 | "right": 21, 181 | "year": 21, 182 | "after": 21, 183 | "where": 21, 184 | "should": 21, 185 | "lot": 21, 186 | "enough": 21, 187 | "great": 21, 188 | "any": 20, 189 | "thanks": 20, 190 | "work": 20, 191 | "which": 20, 192 | "trump": 20, 193 | "post": 20, 194 | "doing": 20, 195 | "very": 20, 196 | "those": 20, 197 | "around": 20, 198 | "thought": 20, 199 | "find": 19, 200 | "ever": 19, 201 | "next": 19, 202 | "better": 19, 203 | "stop": 19, 204 | "trying": 19, 205 | "through": 18, 206 | "same": 18, 207 | "believe": 18, 208 | "feel": 18, 209 | "look": 18, 210 | "world": 18, 211 | "actually": 18, 212 | "most": 18, 213 | "art": 18, 214 | "keep": 18, 215 | "ive": 17, 216 | "made": 17, 217 | "then": 17, 218 | "link": 17, 219 | "maybe": 17, 220 | "last": 16, 221 | "tell": 16, 222 | "give": 16, 223 | "shit": 16, 224 | "another": 16, 225 | "own": 16, 226 | "probably": 16, 227 | "can’t": 16, 228 | "today": 16, 229 | "christmas": 16, 230 | "such": 16, 231 | "thing": 16, 232 | "take": 16, 233 | "someone": 16, 234 | "sure": 16, 235 | "pretty": 15, 236 | "myself": 15, 237 | "wait": 15, 238 | "two": 15, 239 | "season": 15, 240 | "already": 15, 241 | "may": 15, 242 | "hear": 15, 243 | "black": 15, 244 | "big": 15, 245 | "man": 15, 246 | "cool": 14, 247 | "twitter": 14, 248 | "looks": 14, 249 | "anything": 14, 250 | "happy": 14, 251 | "making": 14, 252 | "book": 14, 253 | "read": 14, 254 | "fun": 14, 255 | "⋆": 14, 256 | "😂": 13, 257 | "saying": 13, 258 | "real": 13, 259 | "anyone": 13, 260 | "come": 13, 261 | "hard": 13, 262 | "life": 13, 263 | "while": 13, 264 | "home": 13, 265 | "down": 13, 266 | "let": 13, 267 | "de": 13, 268 | "team": 13, 269 | "does": 13, 270 | "watch": 13, 271 | "watching": 13, 272 | "winter": 13, 273 | "without": 13, 274 | "food": 13, 275 | "havent": 13, 276 | "both": 13, 277 | "hes": 13, 278 | "follow": 12, 279 | "done": 12, 280 | "point": 12, 281 | "games": 12, 282 | "dog": 12, 283 | "help": 12, 284 | "reason": 12, 285 | "show": 12, 286 | "hope": 12, 287 | "guy": 12, 288 | "please": 12, 289 | "though": 12, 290 | "that’s": 12, 291 | "friend": 11, 292 | "nice": 11, 293 | "old": 11, 294 | "ago": 11, 295 | "okay": 11, 296 | "yet": 11, 297 | "put": 11, 298 | "getting": 11, 299 | "cute": 11, 300 | "understand": 11, 301 | "stuff": 11, 302 | "definitely": 11, 303 | "guess": 11, 304 | "part": 11, 305 | "use": 11, 306 | "check": 11, 307 | "everyone": 11, 308 | "drive": 11, 309 | "bit": 11, 310 | "playing": 11, 311 | "friends": 11, 312 | "nothing": 11, 313 | "few": 11, 314 | "isnt": 11, 315 | "wanna": 11, 316 | "play": 11, 317 | "damn": 11, 318 | "said": 10, 319 | "talk": 10, 320 | "wont": 10, 321 | "ai": 10, 322 | "try": 10, 323 | "fact": 10, 324 | "omg": 10, 325 | "times": 10, 326 | "saw": 10, 327 | "small": 10, 328 | "seen": 10, 329 | "left": 10, 330 | "found": 10, 331 | "place": 10, 332 | "books": 10, 333 | "wrong": 10, 334 | "turn": 10, 335 | "quite": 10, 336 | "doesn’t": 10, 337 | "support": 10, 338 | "beautiful": 10, 339 | "started": 10, 340 | "since": 10, 341 | "move": 10, 342 | "hi": 10, 343 | "soon": 10, 344 | "everything": 10, 345 | "far": 10, 346 | "dec": 10, 347 | "fucking": 10, 348 | "theres": 10, 349 | "end": 10, 350 | "fuck": 10, 351 | "different": 10, 352 | "head": 10, 353 | "id": 10, 354 | "week": 10, 355 | "tonight": 9, 356 | "live": 9, 357 | "least": 9, 358 | "guys": 9, 359 | "piece": 9, 360 | "american": 9, 361 | "came": 9, 362 | "continues": 9, 363 | "used": 9, 364 | "blue": 9, 365 | "hours": 9, 366 | "agree": 9, 367 | "wanted": 9, 368 | "until": 9, 369 | "hey": 9, 370 | "let’s": 9, 371 | "job": 9, 372 | "full": 9, 373 | "remember": 9, 374 | "list": 9, 375 | "shows": 9, 376 | "days": 9, 377 | "yeah": 9, 378 | "weather": 9, 379 | "ass": 9, 380 | "instead": 9, 381 | "mqt": 9, 382 | "[mi]": 9, 383 | "start": 9, 384 | "mean": 9, 385 | "bc": 8, 386 | "told": 8, 387 | "evil": 8, 388 | "buy": 8, 389 | "it?": 8, 390 | "public": 8, 391 | "couple": 8, 392 | "tomorrow": 8, 393 | "once": 8, 394 | "woman": 8, 395 | "joy": 8, 396 | "amazing": 8, 397 | "top": 8, 398 | "set": 8, 399 | "took": 8, 400 | "fight": 8, 401 | "que": 8, 402 | "didn’t": 8, 403 | "gave": 8, 404 | "best": 8, 405 | "family": 8, 406 | "i’ll": 8, 407 | "oc": 8, 408 | "account": 8, 409 | "name": 8, 410 | "free": 8, 411 | "ok": 8, 412 | "second": 8, 413 | "😭": 8, 414 | "later": 8, 415 | "election": 8, 416 | "folks": 8, 417 | "white": 8, 418 | "u": 8, 419 | "might": 8, 420 | "theyre": 8, 421 | "est": 8, 422 | "media": 8, 423 | "yourself": 8, 424 | "won": 8, 425 | "you’re": 7, 426 | "body": 7, 427 | "says": 7, 428 | "article": 7, 429 | "writing": 7, 430 | "win": 7, 431 | "he’s": 7, 432 | "looked": 7, 433 | "worse": 7, 434 | "honestly": 7, 435 | "till": 7, 436 | "away": 7, 437 | "sorry": 7, 438 | "favorite": 7, 439 | "god": 7, 440 | "broken": 7, 441 | "weird": 7, 442 | "common": 7, 443 | "control": 7, 444 | "didnt": 7, 445 | "heres": 7, 446 | "leave": 7, 447 | "nsfw": 7, 448 | "bluesky": 7, 449 | "draw": 7, 450 | "between": 7, 451 | "deal": 7, 452 | "care": 7, 453 | "video": 7, 454 | "needed": 7, 455 | "i’ve": 7, 456 | "whole": 7, 457 | "fine": 7, 458 | "hot": 7, 459 | "actual": 7, 460 | "weeks": 7, 461 | "following": 7, 462 | "dead": 7, 463 | "spent": 7, 464 | "together": 7, 465 | "three": 7, 466 | "stream": 7, 467 | "needs": 7, 468 | "sounds": 7, 469 | "fan": 7, 470 | "having": 7, 471 | "idea": 7, 472 | "send": 7, 473 | "night": 7, 474 | "forget": 7, 475 | "huge": 7, 476 | "means": 7, 477 | "sleep": 7, 478 | "coming": 7, 479 | "month": 7, 480 | "super": 7, 481 | "news": 7, 482 | "learn": 7, 483 | "call": 6, 484 | "yall": 6, 485 | "looking": 6, 486 | "comments": 6, 487 | "story": 6, 488 | "starting": 6, 489 | "government": 6, 490 | "unfortunately": 6, 491 | "whats": 6, 492 | "mom": 6, 493 | "e": 6, 494 | "takes": 6, 495 | "photo": 6, 496 | "insta": 6, 497 | "hate": 6, 498 | "funny": 6, 499 | "strong": 6, 500 | "side": 6, 501 | "state": 6, 502 | "mind": 6, 503 | "building": 6, 504 | "makes": 6, 505 | "gone": 6, 506 | "rest": 6, 507 | "posts": 6, 508 | "blame": 6, 509 | "written": 6, 510 | "talking": 6, 511 | "?": 6, 512 | "goal": 6, 513 | "dude": 6, 514 | "hello": 6, 515 | "é": 6, 516 | "order": 6, 517 | "tho": 6, 518 | "brain": 6, 519 | "welcome": 6, 520 | "happen": 6, 521 | "they’re": 6, 522 | "there’s": 6, 523 | "hold": 6, 524 | "went": 6, 525 | "listen": 6, 526 | "song": 6, 527 | "sometimes": 6, 528 | "content": 6, 529 | "apparently": 6, 530 | "war": 6, 531 | "america": 6, 532 | "open": 6, 533 | "word": 6, 534 | "called": 6, 535 | "feeling": 6, 536 | "able": 6, 537 | "kinda": 6, 538 | "ill": 6, 539 | "figure": 6, 540 | "movies": 6, 541 | "❤️": 6, 542 | "women": 6, 543 | "hit": 6, 544 | "heard": 6, 545 | "democracy": 6, 546 | "doesnt": 6, 547 | "n": 6, 548 | "thinking": 6, 549 | "miss": 6, 550 | "road": 6, 551 | "shes": 6, 552 | "become": 6, 553 | "seems": 6, 554 | "close": 6, 555 | "experience": 6, 556 | "advisory": 6, 557 | "important": 6, 558 | "along": 6, 559 | "americans": 6, 560 | "listening": 6, 561 | "december": 6, 562 | "fire": 6, 563 | "goes": 6, 564 | "friday": 6, 565 | "late": 6, 566 | "including": 6, 567 | "stay": 6, 568 | "sad": 6, 569 | "gets": 6, 570 | "movie": 6, 571 | "sense": 6, 572 | "message": 6, 573 | "speaking": 6, 574 | "mad": 6, 575 | "death": 6, 576 | "break": 6, 577 | "prefer": 6, 578 | "✅": 6, 579 | "true": 5, 580 | "likely": 5, 581 | "change": 5, 582 | "politics": 5, 583 | "kind": 5, 584 | "starter": 5, 585 | "number": 5, 586 | "vote": 5, 587 | "using": 5, 588 | "less": 5, 589 | "ah": 5, 590 | "several": 5, 591 | "tried": 5, 592 | "anymore": 5, 593 | "crazy": 5, 594 | "idk": 5, 595 | "telling": 5, 596 | "fbi": 5, 597 | "collection": 5, 598 | "paper": 5, 599 | "running": 5, 600 | "states": 5, 601 | "tbh": 5, 602 | "played": 5, 603 | "enjoy": 5, 604 | "during": 5, 605 | "final": 5, 606 | "entire": 5, 607 | "vs": 5, 608 | "furry": 5, 609 | "isn’t": 5, 610 | "reading": 5, 611 | "glad": 5, 612 | "chicken": 5, 613 | "rather": 5, 614 | "posted": 5, 615 | "😊": 5, 616 | "eu": 5, 617 | "felt": 5, 618 | "clean": 5, 619 | "works": 5, 620 | "added": 5, 621 | "party": 5, 622 | "wish": 5, 623 | "none": 5, 624 | "ideas": 5, 625 | "else": 5, 626 | "taking": 5, 627 | "cat": 5, 628 | "chance": 5, 629 | "baby": 5, 630 | "high": 5, 631 | "snow": 5, 632 | "appreciate": 5, 633 | "followed": 5, 634 | "birds": 5, 635 | "mainly": 5, 636 | "anime": 5, 637 | "happens": 5, 638 | "likes": 5, 639 | "easy": 5, 640 | "block": 5, 641 | "active": 5, 642 | "special": 5, 643 | "+": 5, 644 | "person": 5, 645 | "comment": 5, 646 | "literally": 5, 647 | "star": 5, 648 | "waiting": 5, 649 | "blocked": 5, 650 | "🤣": 5, 651 | "working": 5, 652 | "face": 5, 653 | "kiss": 5, 654 | "must": 5, 655 | "behind": 5, 656 | "bring": 5, 657 | "info": 5, 658 | "ready": 5, 659 | "finished": 5, 660 | "stand": 5, 661 | "longer": 5, 662 | "turned": 5, 663 | "king": 5, 664 | "room": 5, 665 | "pm": 5, 666 | "president": 5, 667 | "social": 5, 668 | "pay": 5, 669 | "truth": 5, 670 | "kids": 5, 671 | "texas": 5, 672 | "giving": 5, 673 | "morning": 5, 674 | "each": 5, 675 | "matters": 5, 676 | "knew": 5, 677 | "box": 5, 678 | "doubt": 5, 679 | "moving": 5, 680 | "wow": 5, 681 | "comes": 5, 682 | "happening": 5, 683 | "history": 5, 684 | "die": 5, 685 | "pain": 5, 686 | "eat": 5, 687 | "ravens": 5, 688 | "x": 5, 689 | "english": 5, 690 | "against": 5, 691 | "disneyland": 5, 692 | "gay": 5, 693 | "across": 5, 694 | "تھے": 5, 695 | "problem": 5, 696 | "prices": 5, 697 | "mario": 5, 698 | "eventually": 4, 699 | "voted": 4, 700 | "liberal": 4, 701 | "stupid": 4, 702 | "artists": 4, 703 | "pack": 4, 704 | "haven’t": 4, 705 | "comics": 4, 706 | "maga": 4, 707 | "brought": 4, 708 | "wife": 4, 709 | "clearly": 4, 710 | "lost": 4, 711 | "personal": 4, 712 | "private": 4, 713 | "😆": 4, 714 | "worth": 4, 715 | "followers": 4, 716 | "act": 4, 717 | "independent": 4, 718 | "research": 4, 719 | "fat": 4, 720 | "involved": 4, 721 | "text": 4, 722 | "taken": 4, 723 | "brother": 4, 724 | "attention": 4, 725 | "five": 4, 726 | "card": 4, 727 | "🩸": 4, 728 | "recognize": 4, 729 | "thunder": 4, 730 | "successful": 4, 731 | "storm": 4, 732 | "asking": 4, 733 | "republicans": 4, 734 | "political": 4, 735 | "chat": 4, 736 | "what’s": 4, 737 | "four": 4, 738 | "trust": 4, 739 | "seemed": 4, 740 | "closer": 4, 741 | "wonder": 4, 742 | "ones": 4, 743 | "system": 4, 744 | "plan": 4, 745 | "eastern": 4, 746 | "add": 4, 747 | "wasn’t": 4, 748 | "meet": 4, 749 | "sex": 4, 750 | "youve": 4, 751 | "um": 4, 752 | "não": 4, 753 | "near": 4, 754 | "etc": 4, 755 | "previous": 4, 756 | "voting": 4, 757 | "seem": 4, 758 | "biden": 4, 759 | "dems": 4, 760 | "moral": 4, 761 | "fair": 4, 762 | "sunday": 4, 763 | "anyway": 4, 764 | "castle": 4, 765 | "current": 4, 766 | "opinion": 4, 767 | "daughter": 4, 768 | "baltimore": 4, 769 | "incredibly": 4, 770 | "luck": 4, 771 | "material": 4, 772 | "property": 4, 773 | "cats": 4, 774 | "choices": 4, 775 | "clip": 4, 776 | "pick": 4, 777 | "almost": 4, 778 | "version": 4, 779 | "worst": 4, 780 | "youtube": 4, 781 | "mostly": 4, 782 | "catch": 4, 783 | "forgot": 4, 784 | "ball": 4, 785 | "deep": 4, 786 | "character": 4, 787 | "based": 4, 788 | "music": 4, 789 | "months": 4, 790 | "early": 4, 791 | "pass": 4, 792 | "expect": 4, 793 | "reviews": 4, 794 | "obvious": 4, 795 | "🥺": 4, 796 | "girl": 4, 797 | "ten": 4, 798 | "street": 4, 799 | "share": 4, 800 | "char": 4, 801 | "lmao": 4, 802 | "|": 4, 803 | "children": 4, 804 | "warning": 4, 805 | "reread": 4, 806 | "bush": 4, 807 | "future": 4, 808 | "collab": 4, 809 | "soup": 4, 810 | "helps": 4, 811 | "💕": 4, 812 | "air": 4, 813 | "service": 4, 814 | "ice": 4, 815 | "met": 4, 816 | "😉": 4, 817 | "rock": 4, 818 | "often": 4, 819 | "kdrama": 4, 820 | "dick": 4, 821 | "decades": 4, 822 | "totally": 4, 823 | "peace": 4, 824 | "writer": 4, 825 | "reindeer": 4, 826 | "dark": 4, 827 | "ending": 4, 828 | "romance": 4, 829 | "mine": 4, 830 | "lose": 4, 831 | "run": 4, 832 | "holy": 4, 833 | "question": 4, 834 | "nuts": 4, 835 | "billsmafia": 4, 836 | "sucks": 4, 837 | "weapons": 4, 838 | "line": 4, 839 | "course": 4, 840 | "west": 4, 841 | "characters": 4, 842 | "past": 4, 843 | "absolutely": 4, 844 | "series": 4, 845 | "nope": 4, 846 | "saquon": 4, 847 | "build": 4, 848 | "level": 4, 849 | "cuz": 4, 850 | "cover": 4, 851 | "monday": 4, 852 | "begging": 4, 853 | "\\": 4, 854 | "thread": 4, 855 | "tree": 4, 856 | "abusive": 4, 857 | "chaos": 4, 858 | "update": 4, 859 | "journey": 4, 860 | "trip": 4, 861 | "convicted": 4, 862 | "exactly": 4, 863 | "numbers": 4, 864 | "issue": 4, 865 | "poor": 4, 866 | "killing": 4, 867 | "finally": 4, 868 | "porn": 4, 869 | "labor": 4, 870 | "lights": 4, 871 | "avoid": 4, 872 | "energy": 4, 873 | "propaganda": 4, 874 | "gun": 4, 875 | "within": 4, 876 | "queen": 4, 877 | "bought": 4, 878 | "easier": 4, 879 | "seeing": 4, 880 | "college": 4, 881 | "problems": 4, 882 | "church": 4, 883 | "ہے": 4, 884 | "😭😭": 3, 885 | "helped": 3, 886 | "afford": 3, 887 | "thing?": 3, 888 | "excited": 3, 889 | "jets": 3, 890 | "steal": 3, 891 | "single": 3, 892 | "performance": 3, 893 | "bunch": 3, 894 | "non": 3, 895 | "missed": 3, 896 | "under": 3, 897 | "advice": 3, 898 | "difficult": 3, 899 | "neither": 3, 900 | "wouldn’t": 3, 901 | "insanity": 3, 902 | "city": 3, 903 | "frank": 3, 904 | "aw": 3, 905 | "trans": 3, 906 | "dealing": 3, 907 | "delete": 3, 908 | "goofy": 3, 909 | "page": 3, 910 | "billion": 3, 911 | "sold": 3, 912 | "bag": 3, 913 | "snail": 3, 914 | "calling": 3, 915 | "brilliant": 3, 916 | "truly": 3, 917 | "minute": 3, 918 | "trumps": 3, 919 | "round": 3, 920 | "artist": 3, 921 | "bottom": 3, 922 | "effort": 3, 923 | "humor": 3, 924 | "graphics": 3, 925 | "vtuber": 3, 926 | "flu": 3, 927 | "effects": 3, 928 | "5th": 3, 929 | "power": 3, 930 | "grateful": 3, 931 | "claims": 3, 932 | "bestie": 3, 933 | "😂😂😂": 3, 934 | "blacksky": 3, 935 | "tough": 3, 936 | "critical": 3, 937 | "fall": 3, 938 | "moment": 3, 939 | "sitting": 3, 940 | "“": 3, 941 | "remain": 3, 942 | "aren’t": 3, 943 | "gen": 3, 944 | "heat": 3, 945 | "stories": 3, 946 | "noticed": 3, 947 | "angels": 3, 948 | "wild": 3, 949 | "funky": 3, 950 | "adding": 3, 951 | "right?": 3, 952 | "cheese": 3, 953 | "potentially": 3, 954 | "au": 3, 955 | "names": 3, 956 | "ocs": 3, 957 | "sinto": 3, 958 | "quest": 3, 959 | "na": 3, 960 | "ou": 3, 961 | "growing": 3, 962 | "below": 3, 963 | "community": 3, 964 | "musk": 3, 965 | "russian": 3, 966 | "voters": 3, 967 | "finish": 3, 968 | "drawing": 3, 969 | "deck": 3, 970 | "mask": 3, 971 | "extra": 3, 972 | "3rd": 3, 973 | "reporting": 3, 974 | "artworks": 3, 975 | "respect": 3, 976 | "profile": 3, 977 | "gotta": 3, 978 | "wonderful": 3, 979 | "project": 3, 980 | "create": 3, 981 | "youd": 3, 982 | "directory": 3, 983 | "site": 3, 984 | "secret": 3, 985 | "zero": 3, 986 | "wealth": 3, 987 | "themselves": 3, 988 | "result": 3, 989 | "tf": 3, 990 | "forever": 3, 991 | "won’t": 3, 992 | "travel": 3, 993 | "gorgeous": 3, 994 | "hair": 3, 995 | "holiday": 3, 996 | "recipe": 3, 997 | "stick": 3, 998 | "silence": 3, 999 | "attack": 3, 1000 | "nfl": 3, 1001 | "thoughts": 3, 1002 | "00": 3, 1003 | "depth": 3, 1004 | "difference": 3, 1005 | "deciding": 3, 1006 | "loving": 3, 1007 | "local": 3, 1008 | "packs": 3, 1009 | "robert": 3, 1010 | "inspired": 3, 1011 | "wallpaper": 3, 1012 | "write": 3, 1013 | "genre": 3, 1014 | "decent": 3, 1015 | "passed": 3, 1016 | "voice": 3, 1017 | "via": 3, 1018 | "car": 3, 1019 | "grade": 3, 1020 | "liked": 3, 1021 | "😻": 3, 1022 | "rip": 3, 1023 | "democrats": 3, 1024 | "value": 3, 1025 | "therapy": 3, 1026 | "insane": 3, 1027 | "lets": 3, 1028 | "🌐": 3, 1029 | "others": 3, 1030 | "original": 3, 1031 | "money": 3, 1032 | "lil": 3, 1033 | "bro": 3, 1034 | "democratic": 3, 1035 | "swear": 3, 1036 | "rising": 3, 1037 | "we’re": 3, 1038 | "machine": 3, 1039 | "explain": 3, 1040 | "child": 3, 1041 | "greece": 3, 1042 | "wars": 3, 1043 | "men": 3, 1044 | "xd": 3, 1045 | "expires": 3, 1046 | "uh": 3, 1047 | "کے": 3, 1048 | "buddy": 3, 1049 | "consider": 3, 1050 | "space": 3, 1051 | "data": 3, 1052 | "couples": 3, 1053 | "🤣🤣🤣": 3, 1054 | "students": 3, 1055 | "dedication": 3, 1056 | "offense": 3, 1057 | "anti": 3, 1058 | "perfect": 3, 1059 | "roll": 3, 1060 | "reach": 3, 1061 | "properly": 3, 1062 | "fit": 3, 1063 | "gif": 3, 1064 | "arent": 3, 1065 | "currently": 3, 1066 | "safe": 3, 1067 | "decided": 3, 1068 | "suggest": 3, 1069 | "fascism": 3, 1070 | "hour": 3, 1071 | "pizza": 3, 1072 | "interesting": 3, 1073 | "gotten": 3, 1074 | "jump": 3, 1075 | "accounts": 3, 1076 | "bracelets": 3, 1077 | "tempted": 3, 1078 | "ffxiv": 3, 1079 | "water": 3, 1080 | "led": 3, 1081 | "fr": 3, 1082 | "thanksgiving": 3, 1083 | "california": 3, 1084 | "husband": 3, 1085 | "companies": 3, 1086 | "option": 3, 1087 | "valid": 3, 1088 | "coach": 3, 1089 | "conservatives": 3, 1090 | "solid": 3, 1091 | "wouldnt": 3, 1092 | "unless": 3, 1093 | "peanut": 3, 1094 | "band": 3, 1095 | "known": 3, 1096 | "lie": 3, 1097 | "possible": 3, 1098 | "memories": 3, 1099 | "bot": 3, 1100 | "shame": 3, 1101 | "missing": 3, 1102 | "what?": 3, 1103 | "who’s": 3, 1104 | "meant": 3, 1105 | "comfort": 3, 1106 | "sending": 3, 1107 | "dinner": 3, 1108 | "november": 3, 1109 | "abuse": 3, 1110 | "shoot": 3, 1111 | "[…]": 3, 1112 | "ebook": 3, 1113 | "schoolcraft": 3, 1114 | "streaming": 3, 1115 | "putin": 3, 1116 | "figured": 3, 1117 | "challenge": 3, 1118 | "especially": 3, 1119 | "china": 3, 1120 | "itself": 3, 1121 | "football": 3, 1122 | "honey": 3, 1123 | "repeat": 3, 1124 | "enjoyed": 3, 1125 | "relate": 3, 1126 | "indeed": 3, 1127 | "hang": 3, 1128 | "mark": 3, 1129 | "risk": 3, 1130 | "cannon": 3, 1131 | "joe": 3, 1132 | "holding": 3, 1133 | "internet": 3, 1134 | "asked": 3, 1135 | "resources": 3, 1136 | "human": 3, 1137 | "eyes": 3, 1138 | "green": 3, 1139 | "drink": 3, 1140 | "tires": 3, 1141 | "cold": 3, 1142 | "warm": 3, 1143 | "destroy": 3, 1144 | "answer": 3, 1145 | "knowing": 3, 1146 | "group": 3, 1147 | "😅": 3, 1148 | "complete": 3, 1149 | "sick": 3, 1150 | "wearing": 3, 1151 | "information": 3, 1152 | "promosky": 3, 1153 | "valley": 3, 1154 | "rail": 3, 1155 | "ask": 3, 1156 | "1st": 3, 1157 | "view": 3, 1158 | "various": 3, 1159 | "usually": 3, 1160 | "ship": 3, 1161 | "basically": 3, 1162 | "fans": 3, 1163 | "dad": 3, 1164 | "kid": 3, 1165 | "<3": 3, 1166 | "personally": 3, 1167 | "keeps": 3, 1168 | "findom": 3, 1169 | "dumb": 3, 1170 | "expensive": 3, 1171 | "main": 3, 1172 | "distribution": 3, 1173 | "lists": 3, 1174 | "recently": 3, 1175 | "map": 3, 1176 | "privacy": 3, 1177 | "sky": 3, 1178 | "online": 3, 1179 | "proper": 3, 1180 | "choose": 3, 1181 | "marvel": 3, 1182 | "yours": 3, 1183 | "teenage": 3, 1184 | "annoying": 3, 1185 | "translation": 3, 1186 | "statement": 3, 1187 | "pls": 3, 1188 | "teach": 3, 1189 | "slightly": 3, 1190 | "eagles": 3, 1191 | "supposed": 3, 1192 | "hell": 3, 1193 | "barkley": 3, 1194 | "fear": 3, 1195 | "✨": 3, 1196 | "laws": 3, 1197 | "usa": 3, 1198 | "daily": 3, 1199 | "suspended": 3, 1200 | "florida": 3, 1201 | "test": 3, 1202 | "blocking": 3, 1203 | "4th": 3, 1204 | "joke": 3, 1205 | "🤍": 3, 1206 | "bottle": 3, 1207 | "imo": 3, 1208 | "brutal": 3, 1209 | "reasons": 3, 1210 | "general": 3, 1211 | "empty": 3, 1212 | "aint": 3, 1213 | "crowd": 3, 1214 | "mini": 3, 1215 | "d": 3, 1216 | "apple": 3, 1217 | "couldn’t": 3, 1218 | "filmsky": 3, 1219 | "fpl": 3, 1220 | "posting": 3, 1221 | "million": 3, 1222 | "killed": 3, 1223 | "نہیں": 3, 1224 | "type": 3, 1225 | "referring": 3, 1226 | "math": 3, 1227 | "chapter": 3, 1228 | "intelligence": 3, 1229 | "praise": 3, 1230 | "worked": 3, 1231 | "dream": 3, 1232 | "boss": 3, 1233 | "racism": 3, 1234 | "heal": 3, 1235 | "episode": 3, 1236 | "showed": 3, 1237 | "short": 3, 1238 | "🤔": 3, 1239 | "lower": 3, 1240 | "em": 3, 1241 | "train": 3, 1242 | "crisis": 3, 1243 | "house": 3, 1244 | "•": 3, 1245 | "solve": 3, 1246 | "failed": 3, 1247 | "action": 3, 1248 | "void": 3, 1249 | "reality": 3, 1250 | "boi": 3, 1251 | "insurance": 3, 1252 | "gifts": 3, 1253 | "closed": 3, 1254 | "annual": 3, 1255 | "legend": 2, 1256 | "wants": 2, 1257 | "discussion": 2, 1258 | "do?": 2, 1259 | "partner": 2, 1260 | "fixing": 2, 1261 | "involve": 2, 1262 | "colour": 2, 1263 | "👀": 2, 1264 | "poilievre": 2, 1265 | "light": 2, 1266 | "included": 2, 1267 | "btw": 2, 1268 | "checked": 2, 1269 | "shop": 2, 1270 | "kash": 2, 1271 | "cringe": 2, 1272 | "highest": 2, 1273 | "cabinet": 2, 1274 | "lying": 2, 1275 | "nearly": 2, 1276 | "terrible": 2, 1277 | "failure": 2, 1278 | "boy": 2, 1279 | "publicly": 2, 1280 | "bill": 2, 1281 | "prison": 2, 1282 | "govt": 2, 1283 | "letting": 2, 1284 | "offended": 2, 1285 | "mass": 2, 1286 | "search": 2, 1287 | "diary": 2, 1288 | "terms": 2, 1289 | "mitch": 2, 1290 | "laugh": 2, 1291 | "🤣🤣": 2, 1292 | "sound": 2, 1293 | "becoming": 2, 1294 | "autocracy": 2, 1295 | "gain": 2, 1296 | "ignore": 2, 1297 | "warfare": 2, 1298 | "bombs": 2, 1299 | "considering": 2, 1300 | "utter": 2, 1301 | "sexy": 2, 1302 | "sample": 2, 1303 | "st": 2, 1304 | "dc": 2, 1305 | "bless": 2, 1306 | "oo": 2, 1307 | "latina": 2, 1308 | "dang": 2, 1309 | "darling": 2, 1310 | "silly": 2, 1311 | "experiences": 2, 1312 | "newer": 2, 1313 | "feels": 2, 1314 | "she’s": 2, 1315 | "nonsense": 2, 1316 | "sweet": 2, 1317 | "awesome": 2, 1318 | "recent": 2, 1319 | "parents": 2, 1320 | "handful": 2, 1321 | "mail": 2, 1322 | "caillebotte": 2, 1323 | "paris": 2, 1324 | "pet": 2, 1325 | "oz": 2, 1326 | "wicked": 2, 1327 | "spot": 2, 1328 | "loved": 2, 1329 | "❤️❤️❤️": 2, 1330 | "wtf": 2, 1331 | "yummy": 2, 1332 | "credits": 2, 1333 | "middle": 2, 1334 | "dickhead": 2, 1335 | "japan": 2, 1336 | "vibes": 2, 1337 | "product": 2, 1338 | "alley": 2, 1339 | "vet": 2, 1340 | "unaware": 2, 1341 | "shot": 2, 1342 | "degree": 2, 1343 | "blessed": 2, 1344 | "legacy": 2, 1345 | "ways": 2, 1346 | "lmfao": 2, 1347 | "impossible": 2, 1348 | "shake": 2, 1349 | "tired": 2, 1350 | "usc": 2, 1351 | "yards": 2, 1352 | "loss": 2, 1353 | "”": 2, 1354 | "erect": 2, 1355 | "cock": 2, 1356 | "boyfriend": 2, 1357 | "places": 2, 1358 | "twice": 2, 1359 | "climate": 2, 1360 | "lake": 2, 1361 | "trees": 2, 1362 | "s": 2, 1363 | "additional": 2, 1364 | "origins": 2, 1365 | "w": 2, 1366 | "🤡": 2, 1367 | "weapon": 2, 1368 | "girl?": 2, 1369 | "guns": 2, 1370 | "economy": 2, 1371 | "normalize": 2, 1372 | "they’ll": 2, 1373 | "shouldn’t": 2, 1374 | "oooooh": 2, 1375 | "librofmaudiobooks9": 2, 1376 | "pray": 2, 1377 | "born": 2, 1378 | "given": 2, 1379 | "feeding": 2, 1380 | "legal": 2, 1381 | "🇺🇸": 2, 1382 | "killer": 2, 1383 | "policies": 2, 1384 | "picture": 2, 1385 | "pro": 2, 1386 | "com": 2, 1387 | "se": 2, 1388 | "reduce": 2, 1389 | "provide": 2, 1390 | "useful": 2, 1391 | "law": 2, 1392 | "320pm": 2, 1393 | "tattled": 2, 1394 | "bullied": 2, 1395 | "receipts": 2, 1396 | "gym": 2, 1397 | "tampering": 2, 1398 | "up?": 2, 1399 | "horns": 2, 1400 | "offer": 2, 1401 | "bed": 2, 1402 | "mucin": 2, 1403 | "pixelart": 2, 1404 | "oklahoma": 2, 1405 | "hand": 2, 1406 | "physical": 2, 1407 | "ocart": 2, 1408 | "rain": 2, 1409 | "100+": 2, 1410 | "gameplay": 2, 1411 | "✊": 2, 1412 | "proposal": 2, 1413 | "youi’ve": 2, 1414 | "influence": 2, 1415 | "hmu": 2, 1416 | "stack": 2, 1417 | "youtubecomlivet1mgeau": 2, 1418 | "outplayed": 2, 1419 | "tucker": 2, 1420 | "points": 2, 1421 | "👏": 2, 1422 | "submit": 2, 1423 | "meta": 2, 1424 | "llama": 2, 1425 | "impact": 2, 1426 | "chris": 2, 1427 | "interview": 2, 1428 | "explaining": 2, 1429 | "according": 2, 1430 | "🇨🇦": 2, 1431 | "owned": 2, 1432 | "lots": 2, 1433 | "ground": 2, 1434 | "ymm": 2, 1435 | "coat": 2, 1436 | "glory": 2, 1437 | "ultimate": 2, 1438 | "cardiac": 2, 1439 | "😁": 2, 1440 | "kitty": 2, 1441 | "sale": 2, 1442 | "offering": 2, 1443 | "discount": 2, 1444 | "anytime": 2, 1445 | "plate": 2, 1446 | "pile": 2, 1447 | "cup": 2, 1448 | "bros": 2, 1449 | "hackman": 2, 1450 | "weak": 2, 1451 | "generation": 2, 1452 | "anger": 2, 1453 | "occasional": 2, 1454 | "older": 2, 1455 | "gamer": 2, 1456 | "goodness": 2, 1457 | "sharing": 2, 1458 | "harris": 2, 1459 | "genocide": 2, 1460 | "pure": 2, 1461 | "scared": 2, 1462 | "relationship": 2, 1463 | "lgbtq": 2, 1464 | "limited": 2, 1465 | "individual": 2, 1466 | "responsibility": 2, 1467 | "shut": 2, 1468 | "aids": 2, 1469 | "rough": 2, 1470 | "expansion": 2, 1471 | "albums": 2, 1472 | "heart": 2, 1473 | "scene": 2, 1474 | "officially": 2, 1475 | "roblox": 2, 1476 | "whatever": 2, 1477 | "exercise": 2, 1478 | "email": 2, 1479 | "sent": 2, 1480 | "quick": 2, 1481 | "iconic": 2, 1482 | "island": 2, 1483 | "brings": 2, 1484 | "perspective": 2, 1485 | "fail": 2, 1486 | "email?": 2, 1487 | "hopefully": 2, 1488 | "furryart": 2, 1489 | "9th": 2, 1490 | "grad": 2, 1491 | "date": 2, 1492 | "following—i’ve": 2, 1493 | "happily": 2, 1494 | "cards": 2, 1495 | "nerd": 2, 1496 | "slop": 2, 1497 | "direct": 2, 1498 | "learned": 2, 1499 | "cough": 2, 1500 | "half": 2, 1501 | "supports": 2, 1502 | "📢": 2, 1503 | "点击关注": 2, 1504 | "最新机器人:(澳洲sbs中文新闻)": 2, 1505 | "cult": 2, 1506 | "essay": 2, 1507 | "figures": 2, 1508 | "preference": 2, 1509 | "03": 2, 1510 | "ge24": 2, 1511 | "happened?": 2, 1512 | "hilarious": 2, 1513 | "case": 2, 1514 | "selling": 2, 1515 | "specials": 2, 1516 | "magazine": 2, 1517 | "caw": 2, 1518 | "cartoon": 2, 1519 | "neat": 2, 1520 | "docsgooglecomdocumentd1": 2, 1521 | "limit": 2, 1522 | "annoys": 2, 1523 | "mitchell": 2, 1524 | "sides": 2, 1525 | "loves": 2, 1526 | "3m": 2, 1527 | "report": 2, 1528 | "🤭": 2, 1529 | "🔥": 2, 1530 | "dear": 2, 1531 | "want?": 2, 1532 | "mother": 2, 1533 | "career": 2, 1534 | "helping": 2, 1535 | "wide": 2, 1536 | "garage": 2, 1537 | "mate": 2, 1538 | "fanart": 2, 1539 | "areas": 2, 1540 | "powered": 2, 1541 | "beef": 2, 1542 | "healthy": 2, 1543 | "yam": 2, 1544 | "irish": 2, 1545 | "sauce": 2, 1546 | "oil": 2, 1547 | "somebody": 2, 1548 | "haha": 2, 1549 | "reagan": 2, 1550 | "forward": 2, 1551 | "timeline": 2, 1552 | "decorate": 2, 1553 | "blood": 2, 1554 | "moon": 2, 1555 | "fresh": 2, 1556 | "pun": 2, 1557 | "intended": 2, 1558 | "advocating": 2, 1559 | "homophobia": 2, 1560 | "max": 2, 1561 | "🌿": 2, 1562 | "heroes": 2, 1563 | "lone": 2, 1564 | "connection": 2, 1565 | "surrounded": 2, 1566 | "allies": 2, 1567 | "greater": 2, 1568 | "willing": 2, 1569 | "bet": 2, 1570 | "shortterm": 2, 1571 | "skills": 2, 1572 | "agreed": 2, 1573 | "association": 2, 1574 | "company": 2, 1575 | "congrats": 2, 1576 | "elections": 2, 1577 | "canada": 2, 1578 | "🥵": 2, 1579 | "ahhh": 2, 1580 | "sit": 2, 1581 | "aware": 2, 1582 | "discovered": 2, 1583 | "i’d": 2, 1584 | "hong": 2, 1585 | "debut": 2, 1586 | "eydc2024": 2, 1587 | "fighting": 2, 1588 | "woke": 2, 1589 | "credit": 2, 1590 | "contemporary": 2, 1591 | "excellent": 2, 1592 | "drag": 2, 1593 | "zone": 2, 1594 | "alive": 2, 1595 | "setting": 2, 1596 | "sweat": 2, 1597 | "shirt": 2, 1598 | "shorts": 2, 1599 | "aj": 2, 1600 | "slowly": 2, 1601 | "tongue": 2, 1602 | "majority": 2, 1603 | "basketball": 2, 1604 | "explanation": 2, 1605 | "return": 2, 1606 | "alger": 2, 1607 | "traveling": 2, 1608 | "😍": 2, 1609 | "tag": 2, 1610 | "tagging": 2, 1611 | "clear": 2, 1612 | "director": 2, 1613 | "greatest": 2, 1614 | "congratulations": 2, 1615 | "iceland": 2, 1616 | "wind": 2, 1617 | "jack": 2, 1618 | "pokémon": 2, 1619 | "seriously": 2, 1620 | "fruit": 2, 1621 | "events": 2, 1622 | "opening": 2, 1623 | "beginning": 2, 1624 | "calendar": 2, 1625 | "trek": 2, 1626 | "amazon": 2, 1627 | "meme": 2, 1628 | "ate": 2, 1629 | "😔": 2, 1630 | "knee": 2, 1631 | "country": 2, 1632 | "constantly": 2, 1633 | "irl": 2, 1634 | "whoops": 2, 1635 | "massive": 2, 1636 | "whether": 2, 1637 | "dealt": 2, 1638 | "obviously": 2, 1639 | "cooking": 2, 1640 | "👋": 2, 1641 | "drinking": 2, 1642 | "lesbian": 2, 1643 | "spread": 2, 1644 | "shape": 2, 1645 | "fish": 2, 1646 | "argument": 2, 1647 | "unrelated": 2, 1648 | "despite": 2, 1649 | "chill": 2, 1650 | "yay": 2, 1651 | "positive": 2, 1652 | "familiar": 2, 1653 | "bell": 2, 1654 | "worldwide": 2, 1655 | "surprise": 2, 1656 | "overwhelmed": 2, 1657 | "sat": 2, 1658 | "kitchen": 2, 1659 | "publicado": 2, 1660 | "por": 2, 1661 | "en": 2, 1662 | "bonitoredditbot": 2, 1663 | "reddit": 2, 1664 | "nsfwbot": 2, 1665 | "co": 2, 1666 | "westerns": 2, 1667 | "settings": 2, 1668 | "jobs": 2, 1669 | "ttrpg": 2, 1670 | "abandoning": 2, 1671 | "ahead": 2, 1672 | "rabbit": 2, 1673 | "hole": 2, 1674 | "step": 2, 1675 | "reflection": 2, 1676 | "proud": 2, 1677 | "filled": 2, 1678 | "cloned": 2, 1679 | "jimmy": 2, 1680 | "fallon": 2, 1681 | "lies": 2, 1682 | "brotherinlaw": 2, 1683 | "tcmparty": 2, 1684 | "repost": 2, 1685 | "favourite": 2, 1686 | "cdrama": 2, 1687 | "beyond": 2, 1688 | "knight": 2, 1689 | "и": 2, 1690 | "experienced": 2, 1691 | "shoes": 2, 1692 | "store": 2, 1693 | "audio": 2, 1694 | "bookstores": 2, 1695 | "bigger": 2, 1696 | "northern": 2, 1697 | "hill": 2, 1698 | "definition": 2, 1699 | "itll": 2, 1700 | "quiet": 2, 1701 | "force": 2, 1702 | "creative": 2, 1703 | "shitty": 2, 1704 | "💯": 2, 1705 | "fell": 2, 1706 | "bio": 2, 1707 | "gold": 2, 1708 | "immigration": 2, 1709 | "design": 2, 1710 | "acting": 2, 1711 | "mentioned": 2, 1712 | "bleed": 2, 1713 | "apart": 2, 1714 | "wear": 2, 1715 | "latest": 2, 1716 | "path": 2, 1717 | "⠀": 2, 1718 | "puig": 2, 1719 | "torn": 2, 1720 | "acl": 2, 1721 | "golf": 2, 1722 | "vanilla": 2, 1723 | "🪻": 2, 1724 | "proof": 2, 1725 | "cyber": 2, 1726 | "ie": 2, 1727 | "🖤": 2, 1728 | "💀": 2, 1729 | "sales": 2, 1730 | "academic": 2, 1731 | "squirrel": 2, 1732 | "thankful": 2, 1733 | "circle": 2, 1734 | "🥹": 2, 1735 | "seasonal": 2, 1736 | "collapse": 2, 1737 | "hegemony": 2, 1738 | "latter": 2, 1739 | "suffering": 2, 1740 | "theyve": 2, 1741 | "caused": 2, 1742 | "former": 2, 1743 | "resolution": 2, 1744 | "matching": 2, 1745 | "precious": 2, 1746 | "exploitation": 2, 1747 | "capitalism": 2, 1748 | "outside": 2, 1749 | "sources": 2, 1750 | "handle": 2, 1751 | "thrive": 2, 1752 | "charge": 2, 1753 | "gotcha": 2, 1754 | "youll": 2, 1755 | "forcing": 2, 1756 | "sequel": 2, 1757 | "straight": 2, 1758 | "fake": 2, 1759 | "af": 2, 1760 | "cartoons": 2, 1761 | "questions": 2, 1762 | "you?": 2, 1763 | "kick": 2, 1764 | "suspect": 2, 1765 | "larger": 2, 1766 | "plus": 2, 1767 | "yard": 2, 1768 | "losing": 2, 1769 | "imagine": 2, 1770 | "animal": 2, 1771 | "crossing": 2, 1772 | "simple": 2, 1773 | "red": 2, 1774 | "groups": 2, 1775 | "hacked": 2, 1776 | "pokemon": 2, 1777 | "daylight": 2, 1778 | "films": 2, 1779 | "dogs": 2, 1780 | "leaving": 2, 1781 | "cause": 2, 1782 | "nations": 2, 1783 | "certain": 2, 1784 | "nature": 2, 1785 | "photography": 2, 1786 | "couldnt": 2, 1787 | "due": 2, 1788 | "reside": 2, 1789 | "reporter": 2, 1790 | "politicians": 2, 1791 | "educate": 2, 1792 | "topics": 2, 1793 | "people?": 2, 1794 | "require": 2, 1795 | "💪": 2, 1796 | "manage": 2, 1797 | "barely": 2, 1798 | "onto": 2, 1799 | "exact": 2, 1800 | "grew": 2, 1801 | "planning": 2, 1802 | "netflix": 2, 1803 | "tuesday": 2, 1804 | "protest": 2, 1805 | "organized": 2, 1806 | "planned": 2, 1807 | "resistance": 2, 1808 | "defeat": 2, 1809 | "h": 2, 1810 | "tradition": 2, 1811 | "include": 2, 1812 | "donors": 2, 1813 | "tax": 2, 1814 | "yea": 2, 1815 | "rounds": 2, 1816 | "🫣": 2, 1817 | "ipad": 2, 1818 | "pink": 2, 1819 | "lore": 2, 1820 | "gallery": 2, 1821 | "pieces": 2, 1822 | "natural": 2, 1823 | "selection": 2, 1824 | "stupid?": 2, 1825 | "record": 2, 1826 | "o": 2, 1827 | "booksky": 2, 1828 | "ugh": 2, 1829 | "hoping": 2, 1830 | "interested": 2, 1831 | "understanding": 2, 1832 | "sort": 2, 1833 | "meaningful": 2, 1834 | "model": 2, 1835 | "llm": 2, 1836 | "edge": 2, 1837 | "flight": 2, 1838 | "injury": 2, 1839 | "drop": 2, 1840 | "rent": 2, 1841 | "ukraine": 2, 1842 | "sign": 2, 1843 | "rules": 2, 1844 | "merchandise": 2, 1845 | "disingenuous": 2, 1846 | "apply": 2, 1847 | "opportunity": 2, 1848 | "watched": 2, 1849 | "recommended": 2, 1850 | "los": 2, 1851 | "“i": 2, 1852 | "noodle": 2, 1853 | "leftovers": 2, 1854 | "recruiting": 2, 1855 | "rivals": 2, 1856 | "upcoming": 2, 1857 | "windows": 2, 1858 | "alter": 2, 1859 | "whenever": 2, 1860 | "laptop": 2, 1861 | "yep": 2, 1862 | "french": 2, 1863 | "les": 2, 1864 | "catching": 2, 1865 | "india": 2, 1866 | "darn": 2, 1867 | "pointing": 2, 1868 | "fixed": 2, 1869 | "anxiety": 2, 1870 | "pair": 2, 1871 | "chocolate": 2, 1872 | "👍": 2, 1873 | "chatgpt": 2, 1874 | "trouble": 2, 1875 | "yum": 2, 1876 | "board": 2, 1877 | "surpassed": 2, 1878 | "types": 2, 1879 | "leics": 2, 1880 | "term": 2, 1881 | "clown": 2, 1882 | "sonic": 2, 1883 | "broke": 2, 1884 | "whistles": 2, 1885 | "poetry": 2, 1886 | "garbage": 2, 1887 | "msm": 2, 1888 | "sleeping": 2, 1889 | "interests": 2, 1890 | "“you": 2, 1891 | "practice": 2, 1892 | "relationships": 2, 1893 | "desire": 2, 1894 | "realize": 2, 1895 | "divide": 2, 1896 | "this?": 2, 1897 | "bg3": 2, 1898 | "birthday": 2, 1899 | "bernie": 2, 1900 | "paycheck": 2, 1901 | "push": 2, 1902 | "buying": 2, 1903 | "wash": 2, 1904 | "waste": 2, 1905 | "offensive": 2, 1906 | "penalties": 2, 1907 | "field": 2, 1908 | "kathy": 2, 1909 | "mibin": 2, 1910 | "mistake": 2, 1911 | "ian": 2, 1912 | "paid": 2, 1913 | "podcast": 2, 1914 | "cryptosky": 2, 1915 | "nation": 2, 1916 | "matter": 2, 1917 | "plain": 2, 1918 | "laid": 2, 1919 | "allows": 2, 1920 | "battle": 2, 1921 | "shell": 2, 1922 | "yo": 2, 1923 | "friendly": 2, 1924 | "😒": 2, 1925 | "wing": 2, 1926 | "dem": 2, 1927 | "either": 2, 1928 | "swing": 2, 1929 | "estate": 2, 1930 | "mj": 2, 1931 | ">": 2, 1932 | "sharks": 2, 1933 | "carpet": 2, 1934 | "young": 2, 1935 | "correct": 2, 1936 | "standard": 2, 1937 | "episodes": 2, 1938 | "cosmic": 2, 1939 | "outfit": 2, 1940 | "realnsfw": 2, 1941 | "asshole": 2, 1942 | "thick": 2, 1943 | "biggest": 2, 1944 | "mls": 2, 1945 | "la": 2, 1946 | "travis": 2, 1947 | "💙": 2, 1948 | "sunset": 2, 1949 | "insult": 2, 1950 | "process": 2, 1951 | "m": 2, 1952 | "land": 2, 1953 | "infinite": 2, 1954 | "bay": 2, 1955 | "area": 2, 1956 | "speed": 2, 1957 | "workers": 2, 1958 | "selective": 2, 1959 | "includes": 2, 1960 | "usual": 2, 1961 | "monsters": 2, 1962 | "except": 2, 1963 | "meal": 2, 1964 | "bright": 2, 1965 | "turkey": 2, 1966 | "celebrate": 2, 1967 | "fellow": 2, 1968 | "them?": 2, 1969 | "fashion": 2, 1970 | "warrior": 2, 1971 | "😡": 2, 1972 | "note": 2, 1973 | "rare": 2, 1974 | "💖": 2, 1975 | "touch": 2, 1976 | "lesson?": 2, 1977 | "badass": 2, 1978 | "impressive": 2, 1979 | "ventura": 2, 1980 | "throw": 2, 1981 | "east": 2, 1982 | "consultants": 2, 1983 | "threat": 2, 1984 | "rise": 2, 1985 | "confused": 2, 1986 | "above": 2, 1987 | "average": 2, 1988 | "r": 2, 1989 | "square": 2, 1990 | "کی": 2, 1991 | "patel": 2, 1992 | "sites": 2, 1993 | "citizens": 2, 1994 | "drones": 2, 1995 | "israel": 2, 1996 | "gives": 2, 1997 | "indication": 2, 1998 | "alright": 2, 1999 | "drew": 2, 2000 | "millions": 2, 2001 | "⭐": 2, 2002 | "🌟": 2, 2003 | "match": 2, 2004 | "candle": 2 2005 | } 2006 | } -------------------------------------------------------------------------------- /data/sip.ts: -------------------------------------------------------------------------------- 1 | const firehoseSocket = new WebSocket( 2 | "wss://jetstream2.us-west.bsky.network/subscribe" 3 | ); 4 | const english = /^en(-[A-Z0-9]{2,3})?$/; 5 | type WordEntry = { [index: string]: number }; 6 | const wordCount = {} as WordEntry; 7 | 8 | const timeRaw = new Date(); 9 | const timeStamp = timeRaw.toString(); 10 | const filename = 11 | "/home/nate/Documents/repos/hose-race-firehose/sip-data/" + 12 | timeRaw.valueOf().toString() + 13 | ".json"; 14 | 15 | const durationSeconds = 30; 16 | const howMany = 2000; 17 | 18 | firehoseSocket.onmessage = (event) => { 19 | const data = JSON.parse(event.data); 20 | if ( 21 | data.kind === "commit" && 22 | data.commit?.record?.$type === "app.bsky.feed.post" && 23 | data.commit.record.langs?.some((locale: string) => english.test(locale)) 24 | ) { 25 | let text = data.commit.record.text.toLowerCase() as string; 26 | // remove punctuation 27 | text = text.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()'"]/g, ""); 28 | text = text.replace(/\n/g, " "); 29 | const textWords = text.split(" "); 30 | for (const word of textWords) { 31 | const exists = typeof wordCount[word] === "number"; 32 | wordCount[word] = exists ? wordCount[word] + 1 : 1; 33 | } 34 | } 35 | }; 36 | 37 | setTimeout(async () => { 38 | firehoseSocket.close(); 39 | delete wordCount[""]; 40 | const wordArray = Object.entries(wordCount).map(([key, value]) => ({ 41 | [key]: value, 42 | })); 43 | wordArray.sort( 44 | (a: WordEntry, b: WordEntry) => Object.values(b)[0] - Object.values(a)[0] 45 | ); 46 | const wordCountSorted = {} as WordEntry; 47 | const slicedWordArray = wordArray.slice(0, howMany); 48 | slicedWordArray.forEach( 49 | (entry) => 50 | (wordCountSorted[Object.keys(entry)[0]] = Object.values(entry)[0]) 51 | ); 52 | // console.log(wordCountSorted); 53 | const dataEntryObj = { 54 | time: timeStamp, 55 | durationSeconds: durationSeconds, 56 | values: wordCountSorted, 57 | }; 58 | const dataEntry = JSON.stringify(dataEntryObj, null, " "); 59 | await Deno.writeTextFile(filename, dataEntry); 60 | console.log(`Wrote file ${filename}`); 61 | }, 1000 * durationSeconds); 62 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 25 | Hose Race 26 | 27 | 28 |
29 | 30 | 31 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hose-race", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "lucide-react": "^0.479.0", 14 | "react": "^18.3.1", 15 | "react-dom": "^18.3.1", 16 | "use-local-storage": "^3.0.0" 17 | }, 18 | "devDependencies": { 19 | "@eslint/js": "^9.15.0", 20 | "@types/react": "^18.3.12", 21 | "@types/react-dom": "^18.3.1", 22 | "@vitejs/plugin-react": "^4.3.4", 23 | "eslint": "^9.15.0", 24 | "eslint-plugin-react-hooks": "^5.0.0", 25 | "eslint-plugin-react-refresh": "^0.4.14", 26 | "globals": "^15.12.0", 27 | "typescript": "~5.6.2", 28 | "typescript-eslint": "^8.15.0", 29 | "vite": "^6.0.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/audio/chime-better.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/public/audio/chime-better.mp3 -------------------------------------------------------------------------------- /public/audio/click-short.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/public/audio/click-short.mp3 -------------------------------------------------------------------------------- /public/audio/decadent-depraved-mix-20250314.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/public/audio/decadent-depraved-mix-20250314.mp3 -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/public/favicon.ico -------------------------------------------------------------------------------- /public/hose-race-gameplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/public/hose-race-gameplay.png -------------------------------------------------------------------------------- /public/leagues.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "words": [ 4 | "space", 5 | "piece", 6 | "gave", 7 | "ones" 8 | ], 9 | "medianPerSecond": 0.13333333333333333, 10 | "rangePerSecond": 0.03333333333333333 11 | }, 12 | { 13 | "words": [ 14 | "isn’t", 15 | "seems", 16 | "question", 17 | "choose" 18 | ], 19 | "medianPerSecond": 0.13333333333333333, 20 | "rangePerSecond": 0 21 | }, 22 | { 23 | "words": [ 24 | "working", 25 | "movie", 26 | "season", 27 | "during" 28 | ], 29 | "medianPerSecond": 0.16666666666666666, 30 | "rangePerSecond": 0 31 | }, 32 | { 33 | "words": [ 34 | "sounds", 35 | "able", 36 | "taking", 37 | "welcome" 38 | ], 39 | "medianPerSecond": 0.16666666666666666, 40 | "rangePerSecond": 0 41 | }, 42 | { 43 | "words": [ 44 | "finally", 45 | "honestly", 46 | "weird", 47 | "story" 48 | ], 49 | "medianPerSecond": 0.16666666666666666, 50 | "rangePerSecond": 0 51 | }, 52 | { 53 | "words": [ 54 | "exactly", 55 | "link", 56 | "job", 57 | "under" 58 | ], 59 | "medianPerSecond": 0.16666666666666666, 60 | "rangePerSecond": 0 61 | }, 62 | { 63 | "words": [ 64 | "content", 65 | "came", 66 | "behind", 67 | "especially" 68 | ], 69 | "medianPerSecond": 0.16666666666666666, 70 | "rangePerSecond": 0 71 | }, 72 | { 73 | "words": [ 74 | "video", 75 | "he’s", 76 | "talking", 77 | "used" 78 | ], 79 | "medianPerSecond": 0.2, 80 | "rangePerSecond": 0.03333333333333333 81 | }, 82 | { 83 | "words": [ 84 | "literally", 85 | "open", 86 | "must", 87 | "almost" 88 | ], 89 | "medianPerSecond": 0.2, 90 | "rangePerSecond": 0 91 | }, 92 | { 93 | "words": [ 94 | "feeling", 95 | "hate", 96 | "okay", 97 | "lost" 98 | ], 99 | "medianPerSecond": 0.2, 100 | "rangePerSecond": 0 101 | }, 102 | { 103 | "words": [ 104 | "less", 105 | "social", 106 | "person", 107 | "past" 108 | ], 109 | "medianPerSecond": 0.2, 110 | "rangePerSecond": 0 111 | }, 112 | { 113 | "words": [ 114 | "instead", 115 | "gets", 116 | "called", 117 | "ok" 118 | ], 119 | "medianPerSecond": 0.2, 120 | "rangePerSecond": 0 121 | }, 122 | { 123 | "words": [ 124 | "call", 125 | "else", 126 | "house", 127 | "may" 128 | ], 129 | "medianPerSecond": 0.23333333333333334, 130 | "rangePerSecond": 0.03333333333333333 131 | }, 132 | { 133 | "words": [ 134 | "friend", 135 | "music", 136 | "god", 137 | "you’re" 138 | ], 139 | "medianPerSecond": 0.23333333333333334, 140 | "rangePerSecond": 0 141 | }, 142 | { 143 | "words": [ 144 | "free", 145 | "times", 146 | "definitely", 147 | "change" 148 | ], 149 | "medianPerSecond": 0.23333333333333334, 150 | "rangePerSecond": 0 151 | }, 152 | { 153 | "words": [ 154 | "seen", 155 | "check", 156 | "cool", 157 | "care" 158 | ], 159 | "medianPerSecond": 0.23333333333333334, 160 | "rangePerSecond": 0 161 | }, 162 | { 163 | "words": [ 164 | "didn’t", 165 | "went", 166 | "looking", 167 | "twitter" 168 | ], 169 | "medianPerSecond": 0.23333333333333334, 170 | "rangePerSecond": 0 171 | }, 172 | { 173 | "words": [ 174 | "away", 175 | "myself", 176 | "absolutely", 177 | "different" 178 | ], 179 | "medianPerSecond": 0.23333333333333334, 180 | "rangePerSecond": 0.03333333333333333 181 | }, 182 | { 183 | "words": [ 184 | "wait", 185 | "playing", 186 | "true", 187 | "started" 188 | ], 189 | "medianPerSecond": 0.26666666666666666, 190 | "rangePerSecond": 0 191 | }, 192 | { 193 | "words": [ 194 | "ago", 195 | "family", 196 | "games", 197 | "saw" 198 | ], 199 | "medianPerSecond": 0.26666666666666666, 200 | "rangePerSecond": 0 201 | }, 202 | { 203 | "words": [ 204 | "believe", 205 | "support", 206 | "black", 207 | "list" 208 | ], 209 | "medianPerSecond": 0.26666666666666666, 210 | "rangePerSecond": 0 211 | }, 212 | { 213 | "words": [ 214 | "try", 215 | "tell", 216 | "against", 217 | "everything" 218 | ], 219 | "medianPerSecond": 0.26666666666666666, 220 | "rangePerSecond": 0 221 | }, 222 | { 223 | "words": [ 224 | "having", 225 | "week", 226 | "watch", 227 | "mean" 228 | ], 229 | "medianPerSecond": 0.3, 230 | "rangePerSecond": 0 231 | }, 232 | { 233 | "words": [ 234 | "bit", 235 | "already", 236 | "least", 237 | "start" 238 | ], 239 | "medianPerSecond": 0.3, 240 | "rangePerSecond": 0 241 | }, 242 | { 243 | "words": [ 244 | "full", 245 | "home", 246 | "sorry", 247 | "i’ve" 248 | ], 249 | "medianPerSecond": 0.31666666666666665, 250 | "rangePerSecond": 0.03333333333333333 251 | }, 252 | { 253 | "words": [ 254 | "can’t", 255 | "stuff", 256 | "live", 257 | "money" 258 | ], 259 | "medianPerSecond": 0.3333333333333333, 260 | "rangePerSecond": 0 261 | }, 262 | { 263 | "words": [ 264 | "said", 265 | "probably", 266 | "place", 267 | "book" 268 | ], 269 | "medianPerSecond": 0.3333333333333333, 270 | "rangePerSecond": 0 271 | }, 272 | { 273 | "words": [ 274 | "give", 275 | "making", 276 | "nothing", 277 | "media" 278 | ], 279 | "medianPerSecond": 0.3333333333333333, 280 | "rangePerSecond": 0.03333333333333333 281 | }, 282 | { 283 | "words": [ 284 | "through", 285 | "play", 286 | "might", 287 | "part" 288 | ], 289 | "medianPerSecond": 0.36666666666666664, 290 | "rangePerSecond": 0 291 | }, 292 | { 293 | "words": [ 294 | "makes", 295 | "getting", 296 | "without", 297 | "gonna" 298 | ], 299 | "medianPerSecond": 0.36666666666666664, 300 | "rangePerSecond": 0 301 | }, 302 | { 303 | "words": [ 304 | "put", 305 | "pretty", 306 | "looks", 307 | "own" 308 | ], 309 | "medianPerSecond": 0.36666666666666664, 310 | "rangePerSecond": 0 311 | }, 312 | { 313 | "words": [ 314 | "follow", 315 | "both", 316 | "books", 317 | "old" 318 | ], 319 | "medianPerSecond": 0.36666666666666664, 320 | "rangePerSecond": 0 321 | }, 322 | { 323 | "words": [ 324 | "thought", 325 | "bluesky", 326 | "until", 327 | "please" 328 | ], 329 | "medianPerSecond": 0.4, 330 | "rangePerSecond": 0 331 | }, 332 | { 333 | "words": [ 334 | "done", 335 | "though", 336 | "everyone", 337 | "world" 338 | ], 339 | "medianPerSecond": 0.4, 340 | "rangePerSecond": 0 341 | }, 342 | { 343 | "words": [ 344 | "nice", 345 | "come", 346 | "next", 347 | "show" 348 | ], 349 | "medianPerSecond": 0.4, 350 | "rangePerSecond": 0 351 | }, 352 | { 353 | "words": [ 354 | "life", 355 | "fun", 356 | "man", 357 | "anything" 358 | ], 359 | "medianPerSecond": 0.43333333333333335, 360 | "rangePerSecond": 0 361 | }, 362 | { 363 | "words": [ 364 | "real", 365 | "hard", 366 | "does", 367 | "maybe" 368 | ], 369 | "medianPerSecond": 0.43333333333333335, 370 | "rangePerSecond": 0 371 | }, 372 | { 373 | "words": [ 374 | "let", 375 | "happy", 376 | "days", 377 | "help" 378 | ], 379 | "medianPerSecond": 0.43333333333333335, 380 | "rangePerSecond": 0 381 | }, 382 | { 383 | "words": [ 384 | "while", 385 | "find", 386 | "down", 387 | "that’s" 388 | ], 389 | "medianPerSecond": 0.4666666666666667, 390 | "rangePerSecond": 0 391 | }, 392 | { 393 | "words": [ 394 | "read", 395 | "someone", 396 | "another", 397 | "again" 398 | ], 399 | "medianPerSecond": 0.4666666666666667, 400 | "rangePerSecond": 0 401 | }, 402 | { 403 | "words": [ 404 | "use", 405 | "big", 406 | "since", 407 | "yeah" 408 | ], 409 | "medianPerSecond": 0.5, 410 | "rangePerSecond": 0 411 | }, 412 | { 413 | "words": [ 414 | "post", 415 | "lot", 416 | "few", 417 | "best" 418 | ], 419 | "medianPerSecond": 0.5, 420 | "rangePerSecond": 0 421 | }, 422 | { 423 | "words": [ 424 | "doing", 425 | "little", 426 | "bad", 427 | "two" 428 | ], 429 | "medianPerSecond": 0.5166666666666667, 430 | "rangePerSecond": 0.03333333333333333 431 | }, 432 | { 433 | "words": [ 434 | "actually", 435 | "made", 436 | "today", 437 | "hope" 438 | ], 439 | "medianPerSecond": 0.5666666666666667, 440 | "rangePerSecond": 0.03333333333333333 441 | }, 442 | { 443 | "words": [ 444 | "art", 445 | "keep", 446 | "before", 447 | "always" 448 | ], 449 | "medianPerSecond": 0.5666666666666667, 450 | "rangePerSecond": 0 451 | }, 452 | { 453 | "words": [ 454 | "take", 455 | "every", 456 | "those", 457 | "look" 458 | ], 459 | "medianPerSecond": 0.6, 460 | "rangePerSecond": 0.03333333333333333 461 | }, 462 | { 463 | "words": [ 464 | "lol", 465 | "feel", 466 | "thanks", 467 | "same" 468 | ], 469 | "medianPerSecond": 0.6, 470 | "rangePerSecond": 0 471 | }, 472 | { 473 | "words": [ 474 | "yes", 475 | "sure", 476 | "say", 477 | "most" 478 | ], 479 | "medianPerSecond": 0.6333333333333333, 480 | "rangePerSecond": 0.06666666666666667 481 | }, 482 | { 483 | "words": [ 484 | "many", 485 | "year", 486 | "better", 487 | "game" 488 | ], 489 | "medianPerSecond": 0.6666666666666666, 490 | "rangePerSecond": 0 491 | }, 492 | { 493 | "words": [ 494 | "which", 495 | "great", 496 | "something", 497 | "last" 498 | ], 499 | "medianPerSecond": 0.6833333333333333, 500 | "rangePerSecond": 0.03333333333333333 501 | }, 502 | { 503 | "words": [ 504 | "thing", 505 | "never", 506 | "where", 507 | "after" 508 | ], 509 | "medianPerSecond": 0.7, 510 | "rangePerSecond": 0 511 | }, 512 | { 513 | "words": [ 514 | "off", 515 | "work", 516 | "should", 517 | "any" 518 | ], 519 | "medianPerSecond": 0.7333333333333333, 520 | "rangePerSecond": 0 521 | }, 522 | { 523 | "words": [ 524 | "very", 525 | "well", 526 | "she", 527 | "first" 528 | ], 529 | "medianPerSecond": 0.7666666666666667, 530 | "rangePerSecond": 0 531 | }, 532 | { 533 | "words": [ 534 | "than", 535 | "am", 536 | "way", 537 | "thank" 538 | ], 539 | "medianPerSecond": 0.8, 540 | "rangePerSecond": 0.03333333333333333 541 | }, 542 | { 543 | "words": [ 544 | "oh", 545 | "why", 546 | "right", 547 | "these" 548 | ], 549 | "medianPerSecond": 0.8333333333333334, 550 | "rangePerSecond": 0.03333333333333333 551 | }, 552 | { 553 | "words": [ 554 | "other", 555 | "her", 556 | "could", 557 | "into" 558 | ], 559 | "medianPerSecond": 0.8666666666666667, 560 | "rangePerSecond": 0.03333333333333333 561 | }, 562 | { 563 | "words": [ 564 | "don’t", 565 | "even", 566 | "our", 567 | "then" 568 | ], 569 | "medianPerSecond": 0.8833333333333333, 570 | "rangePerSecond": 0.03333333333333333 571 | }, 572 | { 573 | "words": [ 574 | "did", 575 | "us", 576 | "over", 577 | "go" 578 | ], 579 | "medianPerSecond": 0.9333333333333333, 580 | "rangePerSecond": 0.03333333333333333 581 | }, 582 | { 583 | "words": [ 584 | "only", 585 | "back", 586 | "make", 587 | "him" 588 | ], 589 | "medianPerSecond": 1.0166666666666666, 590 | "rangePerSecond": 0.03333333333333333 591 | }, 592 | { 593 | "words": [ 594 | "because", 595 | "new", 596 | "much", 597 | "being" 598 | ], 599 | "medianPerSecond": 1.0333333333333334, 600 | "rangePerSecond": 0 601 | }, 602 | { 603 | "words": [ 604 | "still", 605 | "also", 606 | "want", 607 | "going" 608 | ], 609 | "medianPerSecond": 1.1333333333333333, 610 | "rangePerSecond": 0.1 611 | }, 612 | { 613 | "words": [ 614 | "got", 615 | "i’m", 616 | "need", 617 | "day" 618 | ], 619 | "medianPerSecond": 1.2333333333333334, 620 | "rangePerSecond": 0.06666666666666667 621 | }, 622 | { 623 | "words": [ 624 | "too", 625 | "really", 626 | "see", 627 | "had" 628 | ], 629 | "medianPerSecond": 1.3, 630 | "rangePerSecond": 0.06666666666666667 631 | }, 632 | { 633 | "words": [ 634 | "who", 635 | "it’s", 636 | "been", 637 | "know" 638 | ], 639 | "medianPerSecond": 1.3333333333333333, 640 | "rangePerSecond": 0.03333333333333333 641 | }, 642 | { 643 | "words": [ 644 | "would", 645 | "here", 646 | "think", 647 | "their" 648 | ], 649 | "medianPerSecond": 1.45, 650 | "rangePerSecond": 0.06666666666666667 651 | }, 652 | { 653 | "words": [ 654 | "them", 655 | "love", 656 | "has", 657 | "there" 658 | ], 659 | "medianPerSecond": 1.5333333333333334, 660 | "rangePerSecond": 0.06666666666666667 661 | }, 662 | { 663 | "words": [ 664 | "an", 665 | "his", 666 | "how", 667 | "some" 668 | ], 669 | "medianPerSecond": 1.65, 670 | "rangePerSecond": 0.06666666666666667 671 | }, 672 | { 673 | "words": [ 674 | "now", 675 | "when", 676 | "time", 677 | "by" 678 | ], 679 | "medianPerSecond": 1.8, 680 | "rangePerSecond": 0.1 681 | }, 682 | { 683 | "words": [ 684 | "he", 685 | "more", 686 | "no", 687 | "people" 688 | ], 689 | "medianPerSecond": 1.9333333333333333, 690 | "rangePerSecond": 0.16666666666666666 691 | }, 692 | { 693 | "words": [ 694 | "get", 695 | "from", 696 | "up", 697 | "good" 698 | ], 699 | "medianPerSecond": 2.033333333333333, 700 | "rangePerSecond": 0.06666666666666667 701 | }, 702 | { 703 | "words": [ 704 | "will", 705 | "or", 706 | "can", 707 | "out" 708 | ], 709 | "medianPerSecond": 2.1166666666666667, 710 | "rangePerSecond": 0.1 711 | }, 712 | { 713 | "words": [ 714 | "do", 715 | "your", 716 | "about", 717 | "at" 718 | ], 719 | "medianPerSecond": 2.433333333333333, 720 | "rangePerSecond": 0.3 721 | }, 722 | { 723 | "words": [ 724 | "one", 725 | "we", 726 | "what", 727 | "if" 728 | ], 729 | "medianPerSecond": 2.716666666666667, 730 | "rangePerSecond": 0.3333333333333333 731 | }, 732 | { 733 | "words": [ 734 | "as", 735 | "just", 736 | "all", 737 | "they" 738 | ], 739 | "medianPerSecond": 3.1333333333333333, 740 | "rangePerSecond": 0.5 741 | }, 742 | { 743 | "words": [ 744 | "me", 745 | "was", 746 | "are", 747 | "not" 748 | ], 749 | "medianPerSecond": 3.8666666666666667, 750 | "rangePerSecond": 0.06666666666666667 751 | }, 752 | { 753 | "words": [ 754 | "like", 755 | "so", 756 | "be", 757 | "have" 758 | ], 759 | "medianPerSecond": 4.466666666666667, 760 | "rangePerSecond": 0.6333333333333333 761 | }, 762 | { 763 | "words": [ 764 | "with", 765 | "but", 766 | "my", 767 | "on" 768 | ], 769 | "medianPerSecond": 5.3, 770 | "rangePerSecond": 1.3666666666666667 771 | }, 772 | { 773 | "words": [ 774 | "it", 775 | "that", 776 | "for", 777 | "this" 778 | ], 779 | "medianPerSecond": 8.483333333333333, 780 | "rangePerSecond": 2.066666666666667 781 | }, 782 | { 783 | "words": [ 784 | "of", 785 | "you", 786 | "in", 787 | "is" 788 | ], 789 | "medianPerSecond": 9.6, 790 | "rangePerSecond": 3.066666666666667 791 | }, 792 | { 793 | "words": [ 794 | "i", 795 | "to", 796 | "and", 797 | "a" 798 | ], 799 | "medianPerSecond": 18.45, 800 | "rangePerSecond": 5.233333333333333 801 | } 802 | ] -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | /* div { 2 | border: 1px solid gray; 3 | } */ 4 | 5 | .parent { 6 | height: 100svh; 7 | width: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | justify-content: space-between; 11 | align-items: center; 12 | transition: background-color 0.25s ease-in-out; 13 | } 14 | 15 | .footer { 16 | height: 80px; 17 | border: 1px solid; 18 | width: 100%; 19 | display: flex; 20 | justify-content: center; 21 | } 22 | 23 | .footer-text { 24 | display: flex; 25 | flex-direction: rows; 26 | justify-content: space-between; 27 | align-items: center; 28 | height: 100%; 29 | padding-left: 10px; 30 | padding-right: 10px; 31 | width: 100%; 32 | max-width: var(--maxwidth); 33 | } 34 | 35 | .footer { 36 | margin-top: 15px; 37 | } 38 | 39 | .logo path { 40 | fill: white; 41 | } 42 | 43 | .parent.light .logo path { 44 | fill: black; 45 | } 46 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import useLocalStorage from "use-local-storage"; 3 | 4 | import "./App.css"; 5 | import { AppState, AugmentedLeague, ResultsData } from "./types/types"; 6 | import { getResultsData } from "./utils/getResultsData"; 7 | 8 | import { Game } from "./components/Game/Game"; 9 | import { Onboard } from "./components/Onboard/Onboard"; 10 | import { SelectWord } from "./components/SelectWord/SelectWord"; 11 | import { Result } from "./components/Result/Result"; 12 | import { GithubLogo } from "./components/GithubLogo"; 13 | import { Header } from "./components/Header/Header"; 14 | import { AudioProvider } from "./audio/AudioProvider"; 15 | 16 | function App() { 17 | const [appState, setAppState] = useState("onboard" as AppState); 18 | const [chosenWord, setChosenWord] = useState(""); 19 | const [league, setLeague] = useState({} as AugmentedLeague); 20 | const [finishers, setFinishers] = useState([] as string[]); 21 | const [score, setScore] = useState(0); 22 | const [resultsData, setResultsData] = useState({} as ResultsData); 23 | const darkModePreference = window.matchMedia( 24 | "(prefers-color-scheme: dark)" 25 | ).matches; 26 | const [isDark, setIsDark] = useLocalStorage("isDark", darkModePreference); 27 | 28 | if (finishers.includes(chosenWord) && appState === "play") { 29 | const resultsData = getResultsData(finishers, chosenWord); 30 | setResultsData(resultsData); 31 | setScore(score + resultsData.newPoints); 32 | setAppState("result"); 33 | } 34 | 35 | function handleSubmitWord({ 36 | nextWord, 37 | nextLeague, 38 | }: { 39 | nextWord: string; 40 | nextLeague: AugmentedLeague; 41 | }) { 42 | setChosenWord(nextWord); 43 | setLeague(nextLeague); 44 | setAppState("play"); 45 | } 46 | 47 | function handleAddFinisher(finisher: string) { 48 | if (!finishers.includes(finisher)) setFinishers([...finishers, finisher]); 49 | } 50 | 51 | function handlePlayAgain() { 52 | setChosenWord(""); 53 | setLeague({} as AugmentedLeague); 54 | setFinishers([] as string[]); 55 | setAppState("select"); 56 | } 57 | 58 | let innerComponent; 59 | let ariaText; 60 | switch (appState) { 61 | case "onboard": 62 | innerComponent = ( 63 | setAppState("select")}> 64 | ); 65 | ariaText = "Welcome to Hose Race."; 66 | break; 67 | case "select": 68 | innerComponent = ( 69 | 70 | ); 71 | ariaText = "Pick your word."; 72 | break; 73 | case "play": 74 | innerComponent = ( 75 | 81 | ); 82 | ariaText = "The race has begun!"; 83 | break; 84 | case "result": 85 | innerComponent = ( 86 | 91 | ); 92 | ariaText = `You finished in ${resultsData.placeWord} place. You now have ${score} points.`; 93 | } 94 | 95 | return ( 96 |
97 |
98 | {ariaText} 99 |
100 | 101 |
102 | {innerComponent} 103 | 116 | 117 |
118 | ); 119 | } 120 | 121 | export default App; 122 | -------------------------------------------------------------------------------- /src/assets/astro-turf-feature-small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/astro-turf-feature-small.jpg -------------------------------------------------------------------------------- /src/assets/astro-turf-feature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/astro-turf-feature.jpg -------------------------------------------------------------------------------- /src/assets/hose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/hose.png -------------------------------------------------------------------------------- /src/assets/nozzle-for-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/nozzle-for-favicon.png -------------------------------------------------------------------------------- /src/assets/nozzle1-transp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/nozzle1-transp.png -------------------------------------------------------------------------------- /src/assets/nozzle1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/nozzle1.png -------------------------------------------------------------------------------- /src/assets/nozzle2-transp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/nozzle2-transp.png -------------------------------------------------------------------------------- /src/assets/nozzle2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/nozzle2.png -------------------------------------------------------------------------------- /src/assets/nozzle3-transp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/nozzle3-transp.png -------------------------------------------------------------------------------- /src/assets/nozzle3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/nozzle3.png -------------------------------------------------------------------------------- /src/assets/nozzle4-transp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natepmay/hose-race/fd1aac326b7e857864423372683f0d6fd8729d98/src/assets/nozzle4-transp.png -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/audio/AudioApiContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | interface AudioApi { 4 | playMusic: () => void; 5 | stopMusic: () => void; 6 | playSound: (sound: "incoming" | "roundComplete") => void; 7 | toggleMute: () => void; 8 | getIsMuted: () => boolean; 9 | } 10 | 11 | const defaultAudioApi: AudioApi = { 12 | playMusic: () => {}, 13 | stopMusic: () => {}, 14 | playSound: () => {}, 15 | toggleMute: () => {}, 16 | getIsMuted: () => false, 17 | }; 18 | 19 | export const AudioApiContext: React.Context = 20 | createContext(defaultAudioApi); 21 | -------------------------------------------------------------------------------- /src/audio/AudioProvider.tsx: -------------------------------------------------------------------------------- 1 | import { useAudioTracks } from "../hooks/useAudioTracks"; 2 | import { AudioApiContext } from "./AudioApiContext"; 3 | 4 | export function AudioProvider({ children }: { children: React.ReactNode }) { 5 | const api = useAudioTracks(); 6 | 7 | return ( 8 | {children} 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/AudioTesting.tsx: -------------------------------------------------------------------------------- 1 | import { useAudioTracks } from "../hooks/useAudioTracks"; 2 | 3 | export function AudioTesting() { 4 | const { playMusic, stopMusic, playSound, toggleMute } = useAudioTracks(); 5 | 6 | return ( 7 |
8 | 9 | 10 | 11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Button/Button.css: -------------------------------------------------------------------------------- 1 | .button { 2 | border-radius: 8px; 3 | border: 1px solid transparent; 4 | padding: 0.6em 1.2em; 5 | font-size: 1em; 6 | font-weight: 500; 7 | font-family: inherit; 8 | background-color: #1a1a1a; 9 | cursor: pointer; 10 | transition: border-color 0.25s; 11 | } 12 | .button:hover:not(:disabled) { 13 | border-color: #646cff; 14 | } 15 | .button:focus, 16 | .button:focus-visible { 17 | outline: auto 5px Highlight; /* for non-webkit browsers */ 18 | outline: auto 5px -webkit-focus-ring-color; /* for webkit browsers */ 19 | } 20 | .button:disabled { 21 | cursor: default; 22 | opacity: 0.7; 23 | } 24 | 25 | .parent.light .button { 26 | background-color: #f9f9f9; 27 | color: #213547; 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import "./Button.css"; 2 | 3 | interface Props { 4 | children: React.ReactNode; 5 | onClick?: () => void; 6 | type?: "submit" | "reset" | "button" | undefined; 7 | disabled?: boolean; 8 | } 9 | 10 | export function Button({ children, onClick, type, disabled }: Props) { 11 | return ( 12 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/components/Game/Game.css: -------------------------------------------------------------------------------- 1 | .game { 2 | width: 100%; 3 | height: calc(100svh - 160px); 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | } 9 | 10 | .post-card-slot { 11 | width: 100%; 12 | max-width: var(--maxwidth); 13 | height: 8rem; 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | overflow: hidden; 18 | } 19 | 20 | .tracks { 21 | display: flex; 22 | width: 100%; 23 | } 24 | 25 | .all-racetrack-content { 26 | width: 100%; 27 | height: 100%; 28 | max-width: var(--maxwidth); 29 | position: relative; 30 | display: flex; 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Game/Game.tsx: -------------------------------------------------------------------------------- 1 | import { AugmentedLeague } from "../../types/types"; 2 | import "./Game.css"; 3 | import { Lines } from "../Lines/Lines"; 4 | import { Racetracks } from "../Racetracks/Racetracks"; 5 | import { useFirehose } from "../../hooks/useFirehose"; 6 | import { PostCard } from "../PostCard/PostCard"; 7 | import { useEffect, useContext } from "react"; 8 | import { AudioApiContext } from "../../audio/AudioApiContext"; 9 | 10 | interface Params { 11 | league: AugmentedLeague; 12 | onAddFinisher: (finisher: string) => void; 13 | chosenWord: string; 14 | finishers: string[]; 15 | } 16 | 17 | export function Game({ league, onAddFinisher, chosenWord, finishers }: Params) { 18 | const { postText, wordCount } = useFirehose(league.words); 19 | const { playSound } = useContext(AudioApiContext); 20 | // -- FOR DEBUGGING 21 | // const wordCount = { 22 | // one: 0, 23 | // two: 3, 24 | // three: 5, 25 | // four: 8, 26 | // }; 27 | // const postText = { 28 | // word: "one", 29 | // text: "Hey here's a long-ass sentence with the word in it the word isss...one! ...and now the sentence continues on for a bit of a long-ass time so we can cut it off on both sides but it doesn't have to be equal on both sides for instance it's longer at the end here.", 30 | // url: "https://www.wikipedia.org", 31 | // }; 32 | // -- END FOR DEBUGGING 33 | 34 | for (const word in wordCount) { 35 | if (wordCount[word] >= league.finishLine) onAddFinisher(word); 36 | } 37 | 38 | useEffect(() => { 39 | playSound("incoming"); 40 | }, [playSound, postText]); 41 | 42 | return ( 43 |
44 |
45 | 46 |
47 |
48 | 49 | {/* */} 50 | 51 | {/* Do I need the "tracks" wrapper? */} 52 |
53 | 61 |
62 |
63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/components/GithubLogo.tsx: -------------------------------------------------------------------------------- 1 | export function GithubLogo() { 2 | return ( 3 | 8 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/components/Header/Header.css: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 80px; 3 | border: 1px solid; 4 | width: 100%; 5 | display: flex; 6 | justify-content: center; 7 | } 8 | 9 | .header-text { 10 | display: flex; 11 | flex-direction: rows; 12 | justify-content: space-between; 13 | align-items: center; 14 | height: 100%; 15 | padding-left: 10px; 16 | padding-right: 10px; 17 | width: 100%; 18 | max-width: var(--maxwidth); 19 | } 20 | 21 | .mute-button { 22 | padding-right: 10px; 23 | } 24 | 25 | .logo { 26 | font-family: "Courier New", Courier, monospace; 27 | font-weight: 800; 28 | text-transform: uppercase; 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import "./Header.css"; 2 | import { Moon, Sun } from "lucide-react"; 3 | import { useContext, useState } from "react"; 4 | import { AudioApiContext } from "../../audio/AudioApiContext"; 5 | import { Volume2, VolumeX } from "lucide-react"; 6 | 7 | export function Header({ 8 | score, 9 | isDark, 10 | setIsDark, 11 | }: { 12 | score: number; 13 | isDark: boolean; 14 | setIsDark: React.Dispatch>; 15 | }) { 16 | const { toggleMute, getIsMuted } = useContext(AudioApiContext); 17 | const [muted, setMuted] = useState(false); 18 | 19 | return ( 20 |
21 |
22 |
23 | Hose Race 24 |
25 |
26 | {score} 27 |
28 |
32 |
33 | 43 | 49 |
50 |
51 |
52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/components/Hose/Hose.css: -------------------------------------------------------------------------------- 1 | .track-main { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | overflow: hidden; 6 | height: 100%; 7 | } 8 | 9 | /* Thanks to chatgpt for .hose and .hose::before */ 10 | .hose { 11 | width: 20px; 12 | top: 0; 13 | background-color: green; /* Hose color */ 14 | position: relative; /* To position the pseudo-element */ 15 | transition: all 0.5s ease-in-out; 16 | } 17 | 18 | .hose::before { 19 | content: ""; 20 | position: absolute; 21 | top: 0; 22 | left: 5px; /* Offset inward */ 23 | width: 4px; /* Stripe width */ 24 | height: 100%; /* Full height of the hose */ 25 | background-color: yellow; /* Stripe color */ 26 | } 27 | 28 | .nozzle-container { 29 | /* border: 1px solid white; */ 30 | position: absolute; 31 | top: calc(var(--track-head-height) + 5px); 32 | width: var(--racetrack-width); 33 | height: calc(100% - 5px - var(--track-head-height)); 34 | overflow: hidden; 35 | display: flex; 36 | flex-direction: column; 37 | align-items: center; 38 | } 39 | 40 | @keyframes jiggle { 41 | 0% { 42 | transform: rotate(0deg); 43 | } 44 | 2% { 45 | transform: rotate(8deg); 46 | } 47 | 4% { 48 | transform: rotate(-8deg); 49 | } 50 | 6% { 51 | transform: rotate(0deg); 52 | } 53 | } 54 | 55 | .nozzle { 56 | position: absolute; 57 | animation: jiggle 5s ease-in-out infinite; 58 | transition: all 0.5s ease-in-out; 59 | } 60 | -------------------------------------------------------------------------------- /src/components/Hose/Hose.tsx: -------------------------------------------------------------------------------- 1 | import "./Hose.css"; 2 | import nozzle1 from "../../assets/nozzle1-transp.png"; 3 | import nozzle2 from "../../assets/nozzle2-transp.png"; 4 | import nozzle3 from "../../assets/nozzle3-transp.png"; 5 | import nozzle4 from "../../assets/nozzle4-transp.png"; 6 | 7 | interface Props { 8 | progress: number; 9 | finishLine: number; 10 | finished: boolean; 11 | index: number; 12 | } 13 | 14 | type Dimension = "width" | "height"; 15 | 16 | const nozzleDims: Record[] = [ 17 | { width: 50, height: 73 }, 18 | { width: 30, height: 99 }, 19 | { width: 55, height: 135 }, 20 | { width: 60, height: 150 }, 21 | ]; 22 | 23 | const srcs = [nozzle1, nozzle2, nozzle3, nozzle4]; 24 | 25 | export function Hose({ progress, finishLine, finished, index }: Props) { 26 | const progressPct = finished ? 100 : (progress / finishLine) * 100; 27 | return ( 28 |
29 |
36 |
37 | hose nozzle 48 |
49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Lines/Lines.css: -------------------------------------------------------------------------------- 1 | .full-line { 2 | position: absolute; 3 | height: 1rem; 4 | width: 100%; 5 | display: flex; 6 | align-items: start; 7 | } 8 | 9 | .line-label { 10 | margin-right: 5px; 11 | position: relative; 12 | top: -0.5rem; 13 | height: 1rem; 14 | line-height: 1rem; 15 | width: 20px; 16 | text-align: end; 17 | } 18 | 19 | .line { 20 | background-color: #ccc; 21 | border: none; 22 | height: 2px; 23 | width: 100%; 24 | } 25 | 26 | .lines { 27 | position: absolute; 28 | top: calc(var(--track-head-height) + 5px); 29 | width: 100%; 30 | height: calc(100% - 5px - var(--track-head-height)); 31 | border-color: purple; 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Lines/Lines.tsx: -------------------------------------------------------------------------------- 1 | import "./Lines.css"; 2 | 3 | export function Lines({ finishLine }: { finishLine: number }) { 4 | const lines = []; 5 | for (let i = 0; i < 5; i++) { 6 | const pct = (25 * i).toString() + "%"; 7 | const label = (finishLine / 4) * i; 8 | lines.push( 9 |
10 |
{label}
11 |
12 |
13 | ); 14 | } 15 | 16 | return
{lines}
; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Onboard/Onboard.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "../Button/Button"; 2 | 3 | export function Onboard({ onBegin }: { onBegin: () => void }) { 4 | return ( 5 |
6 |

Hose Race

7 |

8 | Guess which word will show up the most often on English-language 9 | Bluesky. 10 |

11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/PostCard/PostCard.css: -------------------------------------------------------------------------------- 1 | .post-card { 2 | width: 100%; 3 | max-width: var(--maxwidth); 4 | height: 1rem; 5 | display: grid; 6 | grid-template-columns: 1fr auto 1fr; 7 | padding-top: 5px; 8 | padding-left: 10px; 9 | padding-right: 10px; 10 | padding-bottom: 10px; 11 | text-overflow: clip; 12 | font-family: "Courier New", Courier, monospace; 13 | overflow: hidden; 14 | } 15 | 16 | .post-card-item { 17 | overflow: hidden; 18 | height: 1.5rem; 19 | } 20 | 21 | .parent.light .post-card-item.left::before { 22 | background: linear-gradient(to right, white, transparent); 23 | } 24 | 25 | .parent.light .post-card-item.right::after { 26 | background: linear-gradient(to right, transparent, white); 27 | } 28 | 29 | /* https://stackoverflow.com/questions/218065/overflow-to-left-instead-of-right */ 30 | .post-card-item.left { 31 | text-align: right; 32 | position: relative; 33 | width: 100%; 34 | white-space: nowrap; 35 | } 36 | 37 | .post-card-item.inner-left { 38 | float: right; 39 | } 40 | 41 | /* ChatGPT provided these fades */ 42 | .post-card-item.left::before { 43 | content: ""; 44 | position: absolute; 45 | top: 0; 46 | left: 0; 47 | width: 100px; /* Adjust width for fade effect */ 48 | height: 100%; 49 | background: linear-gradient(to right, #242424, transparent); 50 | pointer-events: none; /* Ensures text remains selectable */ 51 | } 52 | 53 | .post-card-item.center { 54 | color: green; 55 | font-weight: 900; 56 | white-space: pre; 57 | } 58 | 59 | .post-card-item.right { 60 | text-align: left; 61 | position: relative; 62 | white-space: pre; 63 | } 64 | 65 | .post-card-item.right::after { 66 | content: ""; 67 | position: absolute; 68 | top: 0; 69 | right: 0; 70 | width: 100px; /* Adjust width for fade effect */ 71 | height: 100%; 72 | background: linear-gradient(to right, transparent, #242424); 73 | pointer-events: none; /* Ensures text remains selectable */ 74 | } 75 | -------------------------------------------------------------------------------- /src/components/PostCard/PostCard.tsx: -------------------------------------------------------------------------------- 1 | import "./PostCard.css"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import { PostText } from "../../types/types"; 5 | 6 | const CHAR_PAD = 100; 7 | 8 | const getIndex = (text: string, word: string) => { 9 | const regex = new RegExp(`\\b${word}\\b`); 10 | const { index } = regex.exec(text.toLowerCase())!; 11 | return index; 12 | }; 13 | 14 | const getBeforeWord = (index: number, text: string) => { 15 | const leftPad = Math.min(CHAR_PAD, index); 16 | return text.slice(index - leftPad, index); 17 | }; 18 | 19 | const getDisplayWord = (index: number, text: string, word: string) => { 20 | return text.slice(index, index + word.length); 21 | }; 22 | 23 | const getAfterWord = (index: number, text: string, word: string) => { 24 | const endIndex = index + word.length; 25 | const remainingChars = text.length - endIndex; 26 | const rightPad = Math.min(CHAR_PAD, remainingChars); 27 | return text.slice(endIndex, endIndex + rightPad); 28 | }; 29 | 30 | export function PostCard({ postText }: { postText: PostText }) { 31 | const [textData, setTextData] = useState({ 32 | word: "", 33 | beforeWord: "", 34 | afterWord: "", 35 | text: "", 36 | url: "", 37 | }); 38 | 39 | useEffect(() => { 40 | if (!postText.text) return; 41 | const { word, text, url } = postText; 42 | const index = getIndex(text, word); 43 | const beforeWord = getBeforeWord(index, text); 44 | const displayWord = getDisplayWord(index, text, word); 45 | const afterWord = getAfterWord(index, text, word); 46 | setTextData({ word: displayWord, beforeWord, afterWord, text, url }); 47 | console.log(url); 48 | }, [postText]); 49 | 50 | return ( 51 | 57 |
58 | 59 | 60 | {textData.beforeWord} 61 | 62 | 63 | {textData.word} 64 | {textData.afterWord} 65 |
66 |
67 | ); 68 | } 69 | -------------------------------------------------------------------------------- /src/components/Racetrack/Racetrack.css: -------------------------------------------------------------------------------- 1 | .racetrack { 2 | width: var(--racetrack-width); 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: space-between; 6 | } 7 | 8 | .track-head { 9 | width: 100%; 10 | height: var(--track-head-height); 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | justify-content: space-evenly; 15 | } 16 | 17 | .trophy-container { 18 | position: absolute; 19 | top: calc(var(--track-head-height) + 5px); 20 | width: var(--racetrack-width); 21 | height: calc(100% - 5px - var(--track-head-height)); 22 | display: flex; 23 | flex-direction: column; 24 | justify-content: center; 25 | align-items: center; 26 | /* border: 1px solid white; */ 27 | } 28 | 29 | .post-light { 30 | width: var(--racetrack-width); 31 | height: 5px; 32 | flex-shrink: 0; 33 | } 34 | 35 | .post-light.on { 36 | background-color: green; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Racetrack/Racetrack.tsx: -------------------------------------------------------------------------------- 1 | import "./Racetrack.css"; 2 | 3 | import { getResultsData } from "../../utils/getResultsData"; 4 | import { Hose } from "../Hose/Hose"; 5 | import { Trophy } from "../Trophy/Trophy"; 6 | import { ResultsData } from "../../types/types"; 7 | 8 | interface Params { 9 | name: string; 10 | progress: number; 11 | postLightState: boolean; 12 | finishLine: number; 13 | chosenWord: string; 14 | finishers: string[]; 15 | index: number; 16 | } 17 | 18 | export function Racetrack({ 19 | name, 20 | progress, 21 | postLightState, 22 | finishLine, 23 | chosenWord, 24 | finishers, 25 | index, 26 | }: Params) { 27 | let finished = false; 28 | let localResultsData = {} as ResultsData; 29 | 30 | if (finishers.includes(name)) { 31 | finished = true; 32 | localResultsData = getResultsData(finishers, name); 33 | } 34 | 35 | const indicator = chosenWord === name ? "*" : ""; 36 | 37 | return ( 38 |
39 |
40 |
41 |
{indicator + name + indicator}
42 |
43 | {finished ? localResultsData.placeWord : progress} 44 |
45 |
46 | 52 | {finished && ( 53 |
54 | 58 |
59 | )} 60 |
61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/components/Racetracks/Racetracks.css: -------------------------------------------------------------------------------- 1 | .racetrack-container { 2 | width: 100%; 3 | height: 100%; 4 | display: flex; 5 | justify-content: space-between; 6 | padding-left: 25px; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/Racetracks/Racetracks.tsx: -------------------------------------------------------------------------------- 1 | import "./Racetracks.css"; 2 | import { Racetrack } from "../Racetrack/Racetrack"; 3 | import { PostText, WordCount } from "../../types/types"; 4 | import { usePostLightStates } from "../../hooks/usePostLightStates"; 5 | 6 | interface Params { 7 | wordCount: WordCount; 8 | postText: PostText; 9 | finishLine: number; 10 | chosenWord: string; 11 | finishers: string[]; 12 | } 13 | 14 | export function Racetracks({ 15 | wordCount, 16 | postText, 17 | finishLine, 18 | chosenWord, 19 | finishers, 20 | }: Params) { 21 | const postLightStates = usePostLightStates(wordCount, postText); 22 | const racetracks = Object.keys(wordCount).map((word, index) => { 23 | return ( 24 | 34 | ); 35 | }); 36 | return
{racetracks}
; 37 | } 38 | -------------------------------------------------------------------------------- /src/components/Result/Result.css: -------------------------------------------------------------------------------- 1 | .result { 2 | padding: 20px; 3 | width: 100%; 4 | max-width: var(--maxwidth); 5 | overflow-x: hidden; 6 | box-sizing: border-box; 7 | } 8 | 9 | .texty { 10 | font-family: "Courier New", Courier, monospace; 11 | display: block; 12 | resize: none; 13 | padding: 10px; 14 | line-height: 1.5em; 15 | width: 100%; 16 | /* max-width: 200px; */ 17 | margin-bottom: 20px; 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Result/Result.tsx: -------------------------------------------------------------------------------- 1 | import "./Result.css"; 2 | import { useEffect, useContext } from "react"; 3 | 4 | import { Trophy } from "../Trophy/Trophy"; 5 | import { Button } from "../Button/Button"; 6 | import { Frown } from "lucide-react"; 7 | import { ResultsData } from "../../types/types"; 8 | import { AudioApiContext } from "../../audio/AudioApiContext"; 9 | 10 | interface Params { 11 | score: number; 12 | resultsData: ResultsData; 13 | onPlayAgain: () => void; 14 | } 15 | 16 | type Placer = "1st" | "2nd" | "3rd"; 17 | 18 | function isPlacer(word: string): word is Placer { 19 | return ["1st", "2nd", "3rd"].includes(word); 20 | } 21 | 22 | type Finisher = Placer | "4th" | "last"; 23 | 24 | const PLACE_WORDS: Record = { 25 | 0: "1st", 26 | 1: "2nd", 27 | 2: "3rd", 28 | 3: "4th", 29 | }; 30 | 31 | export function Result({ score, resultsData, onPlayAgain }: Params) { 32 | const { stopMusic, playSound } = useContext(AudioApiContext); 33 | const placeWord = 34 | resultsData.place in PLACE_WORDS 35 | ? PLACE_WORDS[resultsData.place as 0 | 1 | 2 | 3] 36 | : "last"; 37 | 38 | useEffect(() => { 39 | stopMusic(); 40 | playSound("roundComplete"); 41 | }, [stopMusic, playSound]); 42 | 43 | const shareText = `💧I just earned ${score} meaningless points in Hose Race by racing words on the Bluesky firehose. https://hose-race.natemay.dev 💧`; 44 | 45 | return ( 46 |
47 | {isPlacer(placeWord) ? ( 48 | 49 | ) : ( 50 | 51 | )} 52 |

53 | You finished in {placeWord} place. 54 |

55 |

56 | You've earned {resultsData.newPoints} points. 57 |

58 |

59 | Your new score is {score}. 60 |

61 |

62 | 68 | Share your accomplishment on Bluesky by clicking here. 69 | 70 |

71 |

{shareText}

72 | 73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /src/components/SelectWord/SelectWord.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useContext } from "react"; 3 | 4 | import { Button } from "../Button/Button"; 5 | import { AudioApiContext } from "../../audio/AudioApiContext"; 6 | 7 | import { useLoadLeague } from "../../hooks/useLoadLeague"; 8 | import { AugmentedLeague } from "../../types/types"; 9 | 10 | export function SelectWord({ 11 | onSubmitWord, 12 | }: { 13 | onSubmitWord: ({ 14 | nextWord, 15 | nextLeague, 16 | }: { 17 | nextWord: string; 18 | nextLeague: AugmentedLeague; 19 | }) => void; 20 | }) { 21 | const [chosenWord, setChosenWord] = useState(""); 22 | const league = useLoadLeague(); 23 | const { playMusic } = useContext(AudioApiContext); 24 | 25 | function onOptionChange(e: React.ChangeEvent) { 26 | setChosenWord(e.target.value); 27 | } 28 | 29 | function handleSubmit(e: React.FormEvent) { 30 | e.preventDefault(); 31 | onSubmitWord({ 32 | nextWord: chosenWord, 33 | nextLeague: league, 34 | }); 35 | playMusic(); 36 | } 37 | 38 | const wordItems = 39 | "words" in league && 40 | league.words.map((word) => ( 41 |
42 | 49 | 50 |
51 | )); 52 | 53 | return ( 54 |
59 |
60 |
61 | 62 |

63 | Which word do you think will show up the most in English on 64 | Bluesky? 65 |

66 |
67 |
{wordItems}
68 |
69 | 72 |
73 |
74 |
75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/components/Trophy/Trophy.tsx: -------------------------------------------------------------------------------- 1 | import { Trophy as TrophyIcon } from "lucide-react"; 2 | 3 | export function Trophy({ 4 | place, 5 | size, 6 | }: { 7 | place: "1st" | "2nd" | "3rd"; 8 | size: string; 9 | }) { 10 | const trophyColor = { 11 | "1st": "#e5a50a", 12 | "2nd": "#9a9996", 13 | "3rd": "#c64600", 14 | }; 15 | 16 | return ; 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/useAudio.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useCallback } from "react"; 2 | 3 | export function useAudio(url: string, loop: boolean) { 4 | const audioRef = useRef(new Audio(url)); 5 | audioRef.current.loop = loop; 6 | 7 | const play = useCallback(() => { 8 | audioRef.current.play(); 9 | }, []); 10 | const toggleMute = useCallback( 11 | () => (audioRef.current.muted = !audioRef.current.muted), 12 | [] 13 | ); 14 | const stop = useCallback(() => { 15 | audioRef.current.pause(); 16 | audioRef.current.currentTime = 0; 17 | }, []); 18 | 19 | const getIsMuted = () => audioRef.current.muted; 20 | 21 | return { play, toggleMute, stop, getIsMuted }; 22 | } 23 | -------------------------------------------------------------------------------- /src/hooks/useAudioTracks.tsx: -------------------------------------------------------------------------------- 1 | import { useAudio } from "./useAudio"; 2 | 3 | const MUSIC_URL = "/audio/decadent-depraved-mix-20250314.mp3"; 4 | 5 | export function useAudioTracks() { 6 | const musicPlayer = useAudio(MUSIC_URL, true); 7 | const incomingPlayer = useAudio("/audio/click-short.mp3", false); 8 | const roundCompletePlayer = useAudio("/audio/chime-better.mp3", false); 9 | 10 | const sounds = { 11 | incoming: incomingPlayer, 12 | roundComplete: roundCompletePlayer, 13 | }; 14 | 15 | const playMusic = musicPlayer.play; 16 | 17 | const stopMusic = musicPlayer.stop; 18 | 19 | const playSound = (sound: "incoming" | "roundComplete") => { 20 | sounds[sound].play(); 21 | }; 22 | 23 | const toggleMute = () => { 24 | musicPlayer.toggleMute(); 25 | Object.values(sounds).forEach((player) => player.toggleMute()); 26 | }; 27 | 28 | const getIsMuted = musicPlayer.getIsMuted; 29 | 30 | return { playMusic, stopMusic, playSound, toggleMute, getIsMuted }; 31 | } 32 | -------------------------------------------------------------------------------- /src/hooks/useFirehose.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import type { PostText } from "../types/types"; 3 | 4 | const english = /^en(-[A-Z0-9]{2,3})?$/; 5 | 6 | function matchWord(wordToMatch: string, testString: string) { 7 | const regex = new RegExp(`\\b${wordToMatch}\\b`, "g"); 8 | return regex.test(testString.toLowerCase()); 9 | } 10 | 11 | function setInitialWordCount(wordsToRace: string[]) { 12 | let initialWordCount = {} as { [index: string]: number }; 13 | wordsToRace.forEach((word: string) => { 14 | initialWordCount = { ...initialWordCount, [word]: 0 }; 15 | }); 16 | return initialWordCount; 17 | } 18 | 19 | export function useFirehose(wordsToRace: string[]) { 20 | const [wordCount, setWordCount] = useState(setInitialWordCount(wordsToRace)); 21 | const [postText, setPostText] = useState({} as PostText); 22 | useEffect(() => { 23 | const firehoseSocket = new WebSocket( 24 | "wss://jetstream2.us-west.bsky.network/subscribe" 25 | ); 26 | firehoseSocket.onmessage = (event) => { 27 | const data = JSON.parse(event.data); 28 | if ( 29 | data.kind === "commit" && 30 | data.commit?.record?.$type === "app.bsky.feed.post" && 31 | data.commit.record.langs?.some((locale: string) => english.test(locale)) 32 | ) { 33 | const url = `https://bsky.app/profile/${data.did}/post/${data.commit.rkey}`; 34 | const text = data.commit.record.text as string; 35 | for (const word of wordsToRace) { 36 | if (matchWord(word, text.toLowerCase())) { 37 | setPostText({ word: word, text: text, url: url }); 38 | setWordCount({ 39 | ...wordCount, 40 | [word]: wordCount[word] + 1, 41 | }); 42 | } 43 | } 44 | } 45 | }; 46 | return () => firehoseSocket.close(); 47 | }, [wordCount, wordsToRace]); 48 | return { postText, wordCount }; 49 | } 50 | -------------------------------------------------------------------------------- /src/hooks/useLoadLeague.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { League, AugmentedLeague } from "../types/types"; 3 | 4 | const TARGET_SECONDS = 24; 5 | 6 | function calculateFinishLine(medianPerSecond: number) { 7 | const roughSeconds = medianPerSecond * TARGET_SECONDS; 8 | return Math.round(roughSeconds / 4) * 4; 9 | } 10 | 11 | async function getData(): Promise { 12 | const response = await fetch("/leagues.json"); 13 | if (!response.ok) { 14 | throw new Error(`Response status: ${response.status}`); 15 | } 16 | const json = await response.json(); 17 | return json; 18 | } 19 | 20 | async function getLeagueData(): Promise { 21 | const leagues = (await getData()) as League[]; 22 | const randIndex = Math.floor(Math.random() * leagues.length); 23 | const league = leagues[randIndex] as AugmentedLeague; 24 | league.finishLine = calculateFinishLine(league.medianPerSecond); 25 | return league; 26 | } 27 | 28 | export function useLoadLeague() { 29 | const [league, setLeague] = useState({} as AugmentedLeague); 30 | useEffect(() => { 31 | const getAndSet = async () => { 32 | const leagueResult = await getLeagueData(); 33 | console.log("leagueResult: ", leagueResult); 34 | setLeague(leagueResult); 35 | }; 36 | getAndSet(); 37 | }, []); 38 | return league; 39 | } 40 | -------------------------------------------------------------------------------- /src/hooks/usePostLightStates.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { PostText, PostLightStates, WordCount } from "../types/types"; 3 | 4 | function setInitialPostLightStates(words: string[]) { 5 | const out = {} as PostLightStates; 6 | for (const word of words) { 7 | out[word] = false; 8 | } 9 | return out; 10 | } 11 | 12 | export function usePostLightStates(wordCount: WordCount, postText: PostText) { 13 | const [postLightStates, setPostLightStates] = useState( 14 | setInitialPostLightStates(Object.keys(wordCount)) 15 | ); 16 | 17 | useEffect(() => { 18 | const newStates = {} as PostLightStates; 19 | for (const word in wordCount) { 20 | newStates[word] = word === postText.word; 21 | } 22 | setPostLightStates(newStates); 23 | const timeoutID = setTimeout(() => { 24 | setPostLightStates(setInitialPostLightStates(Object.keys(wordCount))); 25 | }, 1000); 26 | return () => clearTimeout(timeoutID); 27 | }, [wordCount, postText]); 28 | return postLightStates; 29 | } 30 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@100;200;300;400;500;600;700&family=Ponomar&display=swap"); 2 | 3 | :root { 4 | /* font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; */ 5 | font-family: "IBM Plex Sans Arabic", system-ui, Avenir, Helvetica, Arial, 6 | sans-serif; 7 | line-height: 1.5; 8 | font-weight: 400; 9 | color-scheme: light dark; 10 | color: rgba(255, 255, 255, 0.87); 11 | background-color: #242424; 12 | 13 | font-synthesis: none; 14 | text-rendering: optimizeLegibility; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | body { 20 | --maxwidth: 600px; 21 | --racetrack-width: 60px; 22 | --track-head-height: 3rem; 23 | --hose-height: 300px; 24 | padding: 0; 25 | margin: 0; 26 | touch-action: none; 27 | } 28 | 29 | a { 30 | all: unset; 31 | cursor: pointer; 32 | /* font-weight: 500; 33 | color: #646cff; 34 | text-decoration: inherit; */ 35 | } 36 | 37 | a:focus { 38 | outline: auto 5px Highlight; /* for non-webkit browsers */ 39 | outline: auto 5px -webkit-focus-ring-color; /* for webkit browsers */ 40 | } 41 | /* 42 | a:hover { 43 | color: #535bf2; 44 | } */ 45 | 46 | button { 47 | all: unset; 48 | cursor: pointer; 49 | } 50 | button:focus { 51 | outline: auto 5px Highlight; /* for non-webkit browsers */ 52 | outline: auto 5px -webkit-focus-ring-color; /* for webkit browsers */ 53 | } 54 | 55 | h1 { 56 | font-size: 3.2em; 57 | line-height: 1.1; 58 | } 59 | 60 | .parent.light { 61 | color: #213547; 62 | background-color: #ffffff; 63 | } 64 | 65 | .visually-hidden { 66 | position: absolute; 67 | width: 1px; 68 | height: 1px; 69 | padding: 0; 70 | margin: -1px; 71 | overflow: hidden; 72 | clip: rect(0, 0, 0, 0); 73 | white-space: nowrap; /* prevents text wrap */ 74 | border: 0; 75 | } 76 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App.tsx"; 5 | 6 | createRoot(document.getElementById("root")!).render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /src/types/types.ts: -------------------------------------------------------------------------------- 1 | export type PostText = { 2 | word: string; 3 | text: string; 4 | url: string; 5 | }; 6 | 7 | export type PostLightStates = { 8 | [index: string]: boolean; 9 | }; 10 | 11 | export interface WordCount { 12 | [index: string]: number; 13 | } 14 | 15 | export interface League { 16 | words: [string, string, string, string]; 17 | medianPerSecond: number; 18 | rangePerSecond: number; 19 | } 20 | 21 | export interface AugmentedLeague extends League { 22 | finishLine: number; 23 | } 24 | 25 | export type AppState = "onboard" | "countdown" | "select" | "play" | "result"; 26 | 27 | export interface ResultsData { 28 | place: -1 | 0 | 1 | 2 | 3; 29 | placeWord: string; 30 | newPoints: number; 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/getResultsData.ts: -------------------------------------------------------------------------------- 1 | import { ResultsData } from "../types/types"; 2 | 3 | type Place = 0 | 1 | 2 | 3; 4 | 5 | export function getResultsData( 6 | finishers: string[], 7 | chosenWord: string 8 | ): ResultsData { 9 | const POINTS = { 10 | 0: 300, 11 | 1: 200, 12 | 2: 100, 13 | 3: 0, 14 | }; 15 | 16 | const PLACE_WORDS = { 17 | 0: "1st", 18 | 1: "2nd", 19 | 2: "3rd", 20 | 3: "4th", 21 | }; 22 | 23 | const place = finishers.indexOf(chosenWord) as -1 | Place; 24 | const newPoints = place in POINTS ? POINTS[place as Place] : 0; 25 | const placeWord = place in PLACE_WORDS ? PLACE_WORDS[place as Place] : "4th"; 26 | 27 | return { 28 | place: place, 29 | placeWord: placeWord, 30 | newPoints: newPoints, 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------