this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

a

uwx 963b683c 092bbcd5

+413 -238
+1
package.json
··· 14 14 "@preact/signals": "^2.8.0", 15 15 "@types/masonry-layout": "^4.2.8", 16 16 "preact-portal": "^1.1.3", 17 + "rolldown": "1.0.0-rc.4", 17 18 "typescript": "6.0.0-dev.20260213" 18 19 }, 19 20 "dependencies": {
+204
pnpm-lock.yaml
··· 39 39 preact-portal: 40 40 specifier: ^1.1.3 41 41 version: 1.1.3(preact@10.28.3) 42 + rolldown: 43 + specifier: 1.0.0-rc.4 44 + version: 1.0.0-rc.4 42 45 typescript: 43 46 specifier: 6.0.0-dev.20260213 44 47 version: 6.0.0-dev.20260213 45 48 46 49 packages: 47 50 51 + '@emnapi/core@1.8.1': 52 + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} 53 + 54 + '@emnapi/runtime@1.8.1': 55 + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} 56 + 57 + '@emnapi/wasi-threads@1.1.0': 58 + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} 59 + 48 60 '@essentials/memoize-one@1.1.0': 49 61 resolution: {integrity: sha512-HMkuIkKNe0EWSUpZhlaq9+5Yp47YhrMhxLMnXTRnEyE5N4xKLspAvMGjUFdi794VnEF1EcOZFS8rdROeujrgag==} 50 62 ··· 56 68 57 69 '@essentials/request-timeout@1.3.0': 58 70 resolution: {integrity: sha512-lKZPhKScNFnR1MBnk4+sxshk46fpvdN+Uh1LlKWFO5g1ocuz4EcknNIL7tm/rsCAs/+xMWiBTwbDUvm+pDNlXw==} 71 + 72 + '@napi-rs/wasm-runtime@1.1.1': 73 + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} 74 + 75 + '@oxc-project/types@0.113.0': 76 + resolution: {integrity: sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA==} 59 77 60 78 '@popperjs/core@2.11.8': 61 79 resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} ··· 103 121 peerDependencies: 104 122 react: '>=16.8' 105 123 124 + '@rolldown/binding-android-arm64@1.0.0-rc.4': 125 + resolution: {integrity: sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w==} 126 + engines: {node: ^20.19.0 || >=22.12.0} 127 + cpu: [arm64] 128 + os: [android] 129 + 130 + '@rolldown/binding-darwin-arm64@1.0.0-rc.4': 131 + resolution: {integrity: sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q==} 132 + engines: {node: ^20.19.0 || >=22.12.0} 133 + cpu: [arm64] 134 + os: [darwin] 135 + 136 + '@rolldown/binding-darwin-x64@1.0.0-rc.4': 137 + resolution: {integrity: sha512-JXmaOJGsL/+rsmMfutcDjxWM2fTaVgCHGoXS7nE8Z3c9NAYjGqHvXrAhMUZvMpHS/k7Mg+X7n/MVKb7NYWKKww==} 138 + engines: {node: ^20.19.0 || >=22.12.0} 139 + cpu: [x64] 140 + os: [darwin] 141 + 142 + '@rolldown/binding-freebsd-x64@1.0.0-rc.4': 143 + resolution: {integrity: sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw==} 144 + engines: {node: ^20.19.0 || >=22.12.0} 145 + cpu: [x64] 146 + os: [freebsd] 147 + 148 + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4': 149 + resolution: {integrity: sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w==} 150 + engines: {node: ^20.19.0 || >=22.12.0} 151 + cpu: [arm] 152 + os: [linux] 153 + 154 + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': 155 + resolution: {integrity: sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==} 156 + engines: {node: ^20.19.0 || >=22.12.0} 157 + cpu: [arm64] 158 + os: [linux] 159 + 160 + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': 161 + resolution: {integrity: sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==} 162 + engines: {node: ^20.19.0 || >=22.12.0} 163 + cpu: [arm64] 164 + os: [linux] 165 + 166 + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': 167 + resolution: {integrity: sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==} 168 + engines: {node: ^20.19.0 || >=22.12.0} 169 + cpu: [x64] 170 + os: [linux] 171 + 172 + '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': 173 + resolution: {integrity: sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==} 174 + engines: {node: ^20.19.0 || >=22.12.0} 175 + cpu: [x64] 176 + os: [linux] 177 + 178 + '@rolldown/binding-openharmony-arm64@1.0.0-rc.4': 179 + resolution: {integrity: sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ==} 180 + engines: {node: ^20.19.0 || >=22.12.0} 181 + cpu: [arm64] 182 + os: [openharmony] 183 + 184 + '@rolldown/binding-wasm32-wasi@1.0.0-rc.4': 185 + resolution: {integrity: sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw==} 186 + engines: {node: '>=14.0.0'} 187 + cpu: [wasm32] 188 + 189 + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4': 190 + resolution: {integrity: sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ==} 191 + engines: {node: ^20.19.0 || >=22.12.0} 192 + cpu: [arm64] 193 + os: [win32] 194 + 195 + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.4': 196 + resolution: {integrity: sha512-p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09g==} 197 + engines: {node: ^20.19.0 || >=22.12.0} 198 + cpu: [x64] 199 + os: [win32] 200 + 201 + '@rolldown/pluginutils@1.0.0-rc.4': 202 + resolution: {integrity: sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==} 203 + 106 204 '@tippyjs/react@4.2.6': 107 205 resolution: {integrity: sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==} 108 206 peerDependencies: 109 207 react: '>=16.8' 110 208 react-dom: '>=16.8' 209 + 210 + '@tybys/wasm-util@0.10.1': 211 + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} 111 212 112 213 '@types/jquery@3.5.33': 113 214 resolution: {integrity: sha512-SeyVJXlCZpEki5F0ghuYe+L+PprQta6nRZqhONt9F13dWBtR/ftoaIbdRQ7cis7womE+X2LKhsDdDtkkDhJS6g==} ··· 155 256 resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} 156 257 engines: {node: '>=0.10.0'} 157 258 259 + rolldown@1.0.0-rc.4: 260 + resolution: {integrity: sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA==} 261 + engines: {node: ^20.19.0 || >=22.12.0} 262 + hasBin: true 263 + 158 264 scheduler@0.27.0: 159 265 resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} 160 266 ··· 163 269 164 270 trie-memoize@1.2.0: 165 271 resolution: {integrity: sha512-hEDLVEP1FCgaRtt0oZDJdz2lK9uK7WlB7ASswt9U9cqruSNueVigtRGxI97hevKlViqhAcRgNgzuY/m8FCCMcg==} 272 + 273 + tslib@2.8.1: 274 + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 166 275 167 276 typescript@6.0.0-dev.20260213: 168 277 resolution: {integrity: sha512-zGIJwsX3OEsKIoEvXJzHRpt58fk370/T1N0GsSECAbcTlrsfUe2QQFbdKyKT3HyG2hFFyZg+Q0zYn6VLeahafg==} ··· 171 280 172 281 snapshots: 173 282 283 + '@emnapi/core@1.8.1': 284 + dependencies: 285 + '@emnapi/wasi-threads': 1.1.0 286 + tslib: 2.8.1 287 + optional: true 288 + 289 + '@emnapi/runtime@1.8.1': 290 + dependencies: 291 + tslib: 2.8.1 292 + optional: true 293 + 294 + '@emnapi/wasi-threads@1.1.0': 295 + dependencies: 296 + tslib: 2.8.1 297 + optional: true 298 + 174 299 '@essentials/memoize-one@1.1.0': {} 175 300 176 301 '@essentials/one-key-map@1.2.0': {} ··· 181 306 dependencies: 182 307 '@essentials/raf': 1.2.0 183 308 309 + '@napi-rs/wasm-runtime@1.1.1': 310 + dependencies: 311 + '@emnapi/core': 1.8.1 312 + '@emnapi/runtime': 1.8.1 313 + '@tybys/wasm-util': 0.10.1 314 + optional: true 315 + 316 + '@oxc-project/types@0.113.0': {} 317 + 184 318 '@popperjs/core@2.11.8': {} 185 319 186 320 '@preact/signals-core@1.13.0': {} ··· 225 359 '@react-hook/throttle': 2.2.0(react@19.2.4) 226 360 react: 19.2.4 227 361 362 + '@rolldown/binding-android-arm64@1.0.0-rc.4': 363 + optional: true 364 + 365 + '@rolldown/binding-darwin-arm64@1.0.0-rc.4': 366 + optional: true 367 + 368 + '@rolldown/binding-darwin-x64@1.0.0-rc.4': 369 + optional: true 370 + 371 + '@rolldown/binding-freebsd-x64@1.0.0-rc.4': 372 + optional: true 373 + 374 + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4': 375 + optional: true 376 + 377 + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': 378 + optional: true 379 + 380 + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': 381 + optional: true 382 + 383 + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': 384 + optional: true 385 + 386 + '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': 387 + optional: true 388 + 389 + '@rolldown/binding-openharmony-arm64@1.0.0-rc.4': 390 + optional: true 391 + 392 + '@rolldown/binding-wasm32-wasi@1.0.0-rc.4': 393 + dependencies: 394 + '@napi-rs/wasm-runtime': 1.1.1 395 + optional: true 396 + 397 + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4': 398 + optional: true 399 + 400 + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.4': 401 + optional: true 402 + 403 + '@rolldown/pluginutils@1.0.0-rc.4': {} 404 + 228 405 '@tippyjs/react@4.2.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': 229 406 dependencies: 230 407 react: 19.2.4 231 408 react-dom: 19.2.4(react@19.2.4) 232 409 tippy.js: 6.3.7 233 410 411 + '@tybys/wasm-util@0.10.1': 412 + dependencies: 413 + tslib: 2.8.1 414 + optional: true 415 + 234 416 '@types/jquery@3.5.33': 235 417 dependencies: 236 418 '@types/sizzle': 2.3.10 ··· 279 461 280 462 react@19.2.4: {} 281 463 464 + rolldown@1.0.0-rc.4: 465 + dependencies: 466 + '@oxc-project/types': 0.113.0 467 + '@rolldown/pluginutils': 1.0.0-rc.4 468 + optionalDependencies: 469 + '@rolldown/binding-android-arm64': 1.0.0-rc.4 470 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.4 471 + '@rolldown/binding-darwin-x64': 1.0.0-rc.4 472 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.4 473 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.4 474 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.4 475 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.4 476 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.4 477 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.4 478 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.4 479 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.4 480 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.4 481 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.4 482 + 282 483 scheduler@0.27.0: {} 283 484 284 485 tippy.js@6.3.7: ··· 286 487 '@popperjs/core': 2.11.8 287 488 288 489 trie-memoize@1.2.0: {} 490 + 491 + tslib@2.8.1: 492 + optional: true 289 493 290 494 typescript@6.0.0-dev.20260213: {}
+1 -148
public/index.html
··· 448 448 </head> 449 449 450 450 <body> 451 - <nav class="navbar px-5 mt-5 mb-2" role="navigation" aria-label="main navigation"> 452 - <div class="navbar-brand"> 453 - <div class="navbar-item"> 454 - <h1 class="title">Kink list</h1> 455 - </div> 456 - <div class="navbar-item"> 457 - <button id="export-image" class="button is-primary">Export</button> 458 - </div> 459 - <div class="navbar-item"> 460 - <button id="view-changelog" class="button is-primary">Changelog</button> 461 - </div> 462 - <div class="navbar-item"> 463 - <label class="checkbox"> 464 - <input id="dark-theme" type="checkbox"> 465 - Dark theme 466 - </label> 467 - </div> 468 - </div> 469 - </nav> 470 - 471 - <nav class="level px-5"> 472 - <div id="legend" class="kinks-legend level-left"> 473 - </div> 474 - </nav> 475 - 476 - <section class="section kinks-section"> 477 - <div id="root" class="masonry"> 478 - </div> 479 - </section> 480 - 481 - <div id="export-modal-container" class="modal"> 482 - <div class="modal-background"></div> 483 - <div class="modal-content"> 484 - 485 - <div class="box"> 486 - <h1 class="title">Exported! Copy the image to your clipboard or save it now.</h1> 487 - <div id="export-modal-content"></div> 488 - </div> 489 - 490 - </div> 491 - <button class="modal-close is-large" aria-label="close"></button> 492 - </div> 493 - 494 - <div id="changelog-modal-container" class="modal"> 495 - <div class="modal-background"></div> 496 - <div class="modal-content"> 497 - <div class="box content"> 498 - <h1>Changelog</h2> 499 - 500 - <h2>Version 14 - February 13th 2026</h2> 501 - <ul> 502 - <li>Use Preact for rendering</li> 503 - <li>Upgrade to Bulma 1.0.4</li> 504 - </ul> 505 - 506 - <h2>Version 13 - November 14th 2022</h2> 507 - <ul> 508 - <li>Remove Futanari options in favor of an easier to understand selection of sexual characteristics irrespective of gender</li> 509 - <li>Changed title of "Fantasy / Non-con" to "Fantasy / Con-non-con"</li> 510 - <li>Changed "without breath control" to "w/o breath control" so it fits in the margin</li> 511 - <li>Added "Clone / selfcest"</li> 512 - </ul> 513 - 514 - <h2>Version 12 - September 17th 2021</h2> 515 - <ul> 516 - <li>Improve mobile layout</li> 517 - </ul> 518 - 519 - <h2>Version 11 - June 17th 2021</h2> 520 - <ul> 521 - <li>Properly handle fail scenario in exporter when clipboard access permission is not given</li> 522 - <li>Rename "Fantasy / Consensual non-con" back to "Fantasy / Non-con" because it didn't fit in the image (oops)</li> 523 - </ul> 524 - 525 - <h2>Version 10 - June 15th 2021</h2> 526 - <ul> 527 - <li>Rename "Fantasy / Non-con" category to "Fantasy / Consensual non-con"</li> 528 - <li>Change choice button border color in dark theme to white, thanks to anonymous contributor for this fix</li> 529 - </ul> 530 - 531 - <h2>Version 9 - June 15th 2021</h2> 532 - <ul> 533 - <li>Add Macrophilia, Microphilia, Detachable body parts, Oviposition, Rope bondage, Suspension bondage, Cages, Gaping, Fear play and Water bondage (both types) to the list</li> 534 - <li>Added a distinction between Futanari/Transfeminine and Futanari/Hermaphrodite</li> 535 - <li>Removed Hate sex from the list</li> 536 - <li>Added support for descriptions, and added descriptions to most of the kinks (and some categories)</li> 537 - <li>Add light/dark theme detection and toggle</li> 538 - <li>Add Self/Partner to Bodies category</li> 539 - <li>Rename Interactions to Interactions & Groupings</li> 540 - <li>Moved fantasy roleplay scenarios to a new Fantasy / Interactions category</li> 541 - <li>Add website logo and Twitter/Discord embed code</li> 542 - <li>Add this changelog</li> 543 - </ul> 544 - 545 - <h2>Version 8 - April 27th 2021</h2> 546 - <ul> 547 - <li>Align legend to the left on mobile devices, thanks to anonymous contributor for this fix</li> 548 - </ul> 549 - 550 - <h2>Version 7 - November 9th 2020</h2> 551 - <ul> 552 - <li>Add Scat to the list</li> 553 - </ul> 554 - 555 - <h2>Version 6 - October 9th 2020</h2> 556 - <ul> 557 - <li>Rewrite the entire list, using Bulma, in order to completely eliminate scrolling issues</li> 558 - </ul> 559 - 560 - <h2>Version 5 - September 21st 2020</h2> 561 - <ul> 562 - <li>Add Clown, Hate sex and Choking to list</li> 563 - <li>Remove "Handholding" meme entry from list</li> 564 - </ul> 565 - 566 - <h2>Version 4 - August 17th 2020</h2> 567 - <ul> 568 - <li>Add Zettai ryoiki, Gas masks, Hypnoplay, Droneplay and Kitsunemimi to list</li> 569 - </ul> 570 - 571 - <h2>Version 3 - August 9th 2020</h2> 572 - <ul> 573 - <li>Add Beard, Hairy body, Shaven body, Bimbofication, Excessive cum and Gas to list</li> 574 - </ul> 575 - 576 - <h2>Version 2 - July 11th 2020</h2> 577 - <ul> 578 - <li>Many internal changes</li> 579 - <li>Fix issues with hotkeys</li> 580 - <li>Add "Autosave" checkbox, as an attempt to stop scrolling issues in Android Chrome webview</li> 581 - </ul> 582 - 583 - <h2>Version 1 - July 10th 2020</h2> 584 - <ul> 585 - <li>Initial GitLab release</li> 586 - </ul> 587 - </div> 588 - </div> 589 - <button class="modal-close is-large" aria-label="close"></button> 590 - </div> 451 + <div id="root"></div> 591 452 592 453 <script src="https://unpkg.com/@popperjs/core@2" defer></script> 593 454 <script src="https://unpkg.com/tippy.js@6" defer></script> ··· 606 467 <script src="preact-tippy.js" defer></script> 607 468 <script src="kinklist.js" defer></script> 608 469 <script src="exporter.js" defer></script> 609 - <script> 610 - // Handle closing modals universally 611 - for (const e of document.querySelectorAll('.modal-close')) { 612 - e.addEventListener('click', () => { 613 - e.parentElement.classList.remove('is-active'); 614 - }); 615 - } 616 - </script> 617 470 </body> 618 471 619 472 </html>
-11
public/jsconfig.json
··· 1 - { 2 - "compilerOptions": { 3 - "strict": true, 4 - "strictNullChecks": false, 5 - "module": "commonjs", 6 - "target": "es6", 7 - "lib": ["ESNext", "dom", "dom.iterable"] 8 - }, 9 - "exclude": ["node_modules", "**/node_modules/*"], 10 - "include": ["*"] 11 - }
+187 -79
public/kinklist.js public/main.tsx
··· 1 - /* eslint-disable unicorn/no-useless-undefined */ 2 - /* eslint-disable unicorn/prevent-abbreviations */ 3 - /* eslint-disable indent */ 4 - 5 - // @ts-check 6 - 7 - /// <reference path="helpers.d.ts" /> 8 - 9 - const { h, render, Fragment } = preact; 10 - const { useState, useEffect, useReducer } = preactHooks; 11 - const { signal, computed } = preactSignals; 12 - const Portal = preactPortal; 1 + import { signal, computed } from '@preact/signals'; 2 + import { h, Fragment, render } from 'preact'; 3 + import { useReducer, useState } from 'preact/hooks'; 4 + import Portal from 'preact-portal'; 5 + import { Masonry } from 'masonic'; 13 6 14 7 const root = document.querySelector('#root'); 15 - const legend = document.querySelector('#legend'); 16 8 17 - /** 18 - * @typedef {object} Kink 19 - * @property {string} name 20 - * @property {string?} description 21 - */ 9 + interface Kink { 10 + name: string; 11 + description: string | null; 12 + } 22 13 23 - /** 24 - * @typedef {object} KinkCategory 25 - * @property {string} name 26 - * @property {string} description 27 - * @property {Kink[]} kinks 28 - * @property {string[]} participants 29 - */ 14 + interface KinkCategory { 15 + name: string; 16 + description: string; 17 + kinks: Kink[]; 18 + participants: string[]; 19 + } 30 20 31 21 /** 32 22 * @param {string} kinkStr 33 23 */ 34 - function parseKinks(kinkStr) { 24 + function parseKinks(kinkStr: string) { 35 25 const kinkCode = kinkStr.split('\n') 36 26 .map(e => e.trim()) 37 27 .filter(e => e); 38 28 39 29 /** @type {KinkCategory[]} */ 40 - const kinkCategories = []; 30 + const kinkCategories: KinkCategory[] = []; 41 31 42 32 /** @type {Kink[]} */ 43 - const kinksById = []; 33 + const kinksById: Kink[] = []; 44 34 45 35 /** 46 36 * @type {Partial<KinkCategory>} 47 37 */ 48 - let curKinkCategory; 38 + let curKinkCategory: Partial<KinkCategory>; 49 39 let curKinkId = 0; 50 40 for (const line of kinkCode) { 51 41 if (line.startsWith('#')) { ··· 83 73 const kinkData = computed(() => parseKinks(kinkText.value)); 84 74 85 75 /** @type {[id: string, name: string, color: string][]} */ 86 - const choiceOptions = [ 76 + const choiceOptions: [id: string, name: string, color: string][] = [ 87 77 ['not-entered', 'Not Entered', '#FFFFFF'], 88 78 ['favorite', 'Favorite', '#6DB5FE'], 89 79 ['like', 'Like', '#23FD22'], ··· 99 89 * Entries may be undefined! 100 90 * @type {Map<Kink, Map<string, string>>} 101 91 */ 102 - const kinkSelections = new Map(); 92 + const kinkSelections: Map<Kink, Map<string, string>> = new Map(); 103 93 104 94 /** 105 95 * Maps kink -> participant -> choice option -> button element 106 96 * Entries may be undefined! 107 97 * @type {Map<Kink, Map<string, Map<string, (action: 'select' | 'deselect') => void>>>} 108 98 */ 109 - const kinkButtons = new Map(); 99 + const kinkButtons: Map<Kink, Map<string, Map<string, (action: 'select' | 'deselect') => void>>> = new Map(); 110 100 111 101 /** 112 102 * @param {Kink} kink 113 103 * @returns {KinkCategory | undefined} 114 104 */ 115 - function findKinkCategory(kink) { 105 + function findKinkCategory(kink: Kink): KinkCategory | undefined { 116 106 for (const category of kinkData.value.kinkCategories) { 117 107 const index = category.kinks.indexOf(kink); 118 108 if (index !== -1) { ··· 122 112 return undefined; 123 113 } 124 114 125 - /** 126 - * @typedef {object} LegendChoiceProps 127 - * @property {string} type 128 - * @property {string} typeDescription 129 - */ 115 + interface LegendChoiceProps { 116 + type: string; 117 + typeDescription: string; 118 + } 130 119 131 120 /** 132 121 * @param {LegendChoiceProps} props 133 122 * @returns 134 123 */ 135 - function LegendChoice({type, typeDescription}) { 124 + function LegendChoice({type, typeDescription}: LegendChoiceProps) { 136 125 return h('div', { 137 126 class: 'level-item is-justify-content-flex-start' 138 127 }, [ ··· 156 145 * @param {string} char 157 146 * @returns {[string, string?]} 158 147 */ 159 - function sliceOnce(str, char) { 148 + function sliceOnce(str: string, char: string): [string, string?] { 160 149 const index = str.indexOf(char); 161 150 162 151 if (index !== -1) { ··· 174 163 * @param {string?} symbolEnd 175 164 * @returns {string} 176 165 */ 177 - function removeSymbols(str, symbolStart, symbolEnd = null) { 166 + function removeSymbols(str: string, symbolStart: string, symbolEnd: string | null = null): string { 178 167 if (str.startsWith(symbolStart)) { 179 168 str = str.slice(1); 180 169 } ··· 192 181 * @param {number} n 193 182 * @returns {string} 194 183 */ 195 - const toBinary = n => n.toString(2).padStart(8, '0'); // convert num to 8-bit binary string 184 + const toBinary = (n: number): string => n.toString(2).padStart(8, '0'); // convert num to 8-bit binary string 196 185 197 186 /** 198 187 * https://stackoverflow.com/a/62362724 199 188 * @param {number[] | Uint8Array} arr 200 189 * @returns {string} 201 190 */ 202 - function bytesArrToBase64(arr) { 191 + function bytesArrToBase64(arr: number[] | Uint8Array): string { 203 192 if (Uint8Array.prototype.toBase64) { 204 193 return Uint8Array.from(arr).toBase64(); 205 194 } ··· 222 211 * @param {string} str 223 212 * @returns {Uint8Array} 224 213 */ 225 - function base64ToBytesArr(str) { 214 + function base64ToBytesArr(str: string): Uint8Array { 226 215 if (Uint8Array.fromBase64) { 227 216 const bytes = Uint8Array.fromBase64(str); 228 217 return bytes; ··· 244 233 * @param {string} participant 245 234 * @returns {string} 246 235 */ 247 - function getSelectedKinkOrDefault(kink, participant) { 236 + function getSelectedKinkOrDefault(kink: Kink, participant: string): string { 248 237 return kinkSelections.has(kink) && kinkSelections.get(kink).get(participant) || 'not-entered'; 249 238 } 250 239 ··· 253 242 * @param {string} participant 254 243 * @param {string} toChoiceId 255 244 */ 256 - function setKinkSelection(kink, participant, toChoiceId) { 245 + function setKinkSelection(kink: Kink, participant: string, toChoiceId: string) { 257 246 if (!kinkSelections.has(kink)) kinkSelections.set(kink, new Map()); 258 247 259 248 kinkSelections.get(kink).set(participant, toChoiceId); ··· 266 255 /** 267 256 * @returns {string} 268 257 */ 269 - function serializeChoices() { 258 + function serializeChoices(): string { 270 259 /** 271 260 * @type {number[]} 272 261 */ 273 - const bytes = []; 262 + const bytes: number[] = []; 274 263 275 264 let i = -1; 276 265 277 266 /** 278 267 * @param {number} num 279 268 */ 280 - function pushNumber(num) { 269 + function pushNumber(num: number) { 281 270 if (i !== -1) { 282 271 i |= num << 4; 283 272 bytes.push(i); ··· 306 295 /** 307 296 * @param {string} base64 308 297 */ 309 - function deserializeChoices(base64) { 298 + function deserializeChoices(base64: string) { 310 299 const bytes = base64.startsWith('!') 311 300 ? qfs.decompress(base64ToBytesArr(base64.slice(1))) 312 301 : base64ToBytesArr(base64); ··· 353 342 window.location.hash = serializeChoices(); 354 343 } 355 344 356 - /** 357 - * @typedef {object} KinkChoiceButtonProps 358 - * @property {Kink} kink 359 - * @property {string} participant 360 - * @property {string} choiceId 361 - * @property {string} choiceDescription 362 - */ 345 + interface KinkChoiceButtonProps { 346 + kink: Kink; 347 + participant: string; 348 + choiceId: string; 349 + choiceDescription: string; 350 + } 363 351 364 352 /** 365 353 * @param {KinkChoiceButtonProps} props 366 354 * @returns 367 355 */ 368 - function KinkChoiceButton({kink, participant, choiceId, choiceDescription}) { 356 + function KinkChoiceButton({kink, participant, choiceId, choiceDescription}: KinkChoiceButtonProps) { 369 357 // <div class="column"><button class="choice notEntered" title="Not Entered"></button></div> 370 358 371 359 /** ··· 373 361 * @param {'select' | 'deselect'} action 374 362 * @returns {boolean} 375 363 */ 376 - function reducer(state, action) { 364 + function reducer(state: boolean, action: 'select' | 'deselect'): boolean { 377 365 if (action === 'select') { 378 366 return true; 379 367 } else if (action === 'deselect') { ··· 425 413 * @param {string} participant 426 414 * @returns {Map<string, (action: "select" | "deselect") => void>} 427 415 */ 428 - function getKinkButtonStates(kink, participant) { 416 + function getKinkButtonStates(kink: Kink, participant: string): Map<string, (action: "select" | "deselect") => void> { 429 417 return kinkButtons.get(kink).get(participant); 430 418 } 431 419 432 420 /** 433 421 * @param {{kinkCategory: KinkCategory, kink: Kink}} props 434 422 */ 435 - function TheKink({kinkCategory, kink}) { 423 + function TheKink({kinkCategory, kink}: { kinkCategory: KinkCategory; kink: Kink; }) { 436 424 /* 437 425 <tr class="kinkRow kink-skinny"> 438 426 <td> ··· 479 467 ]); 480 468 } 481 469 482 - /** 483 - * @typedef {object} TheKinkCategoryProps 484 - * @property {KinkCategory} kinkCategory 485 - */ 470 + interface TheKinkCategoryProps { 471 + kinkCategory: KinkCategory; 472 + } 486 473 487 474 /** 488 475 * @param {TheKinkCategoryProps} kinkCategory 489 476 */ 490 - function TheKinkCategory({kinkCategory}) { 477 + function TheKinkCategory({kinkCategory}: TheKinkCategoryProps) { 491 478 /* 492 479 <div class="column is-narrow"> 493 480 <!--<p class="notification is-primary"> ··· 555 542 ); 556 543 } 557 544 558 - /** 559 - * @typedef {object} MasonryItemProps 560 - * @property {number} index 561 - * @property {KinkCategory} data 562 - * @property {number} width 563 - */ 545 + interface MasonryItemProps { 546 + index: number; 547 + data: KinkCategory; 548 + width: number; 549 + } 564 550 565 551 /** 566 552 * @param {MasonryItemProps} props 567 553 */ 568 - function MasonryItem({ index, data: kinkCategory, width }) { 554 + function MasonryItem({ index, data: kinkCategory, width }: MasonryItemProps) { 569 555 return h(TheKinkCategory, { kinkCategory }, null); 570 556 } 571 557 572 558 function Root() { 573 - return h(Masonic.Masonry, { 559 + return h(Masonry, { 574 560 items: kinkData.value.kinkCategories, 575 - // @ts-expect-error - bad typings 576 561 render: MasonryItem, 577 562 columnWidth: 460, 578 563 maxColumnCount: 8, 579 564 }); 580 565 } 581 566 582 - /** 583 - * @param {{ children: import('preact').ComponentChildren, open: boolean }} props 584 - */ 585 - function Modal({ children, open }) { 567 + function Modal({ children, open, onClose }: { children: import('preact').ComponentChildren; open: boolean; onClose: () => void; }) { 586 568 return h( 587 569 Portal, 588 570 { into: 'body' }, ··· 596 578 ] 597 579 ) 598 580 ); 581 + } 582 + 583 + const changelog = [ 584 + { version: '14 - February 13th 2026', changes: [ 585 + 'Use Preact for rendering', 586 + 'Upgrade to Bulma 1.0.4', 587 + ]}, 588 + { version: '13 - November 14th 2022', changes: [ 589 + 'Remove Futanari options in favor of an easier to understand selection of sexual characteristics irrespective of gender', 590 + 'Changed title of "Fantasy / Non-con" to "Fantasy / Con-non-con"', 591 + 'Changed "without breath control" to "w/o breath control" so it fits in the margin', 592 + 'Added "Clone / selfcest"', 593 + ]}, 594 + { version: '12 - September 17th 2021', changes: [ 595 + 'Improve mobile layout', 596 + ]}, 597 + { version: '11 - June 17th 2021', changes: [ 598 + 'Properly handle fail scenario in exporter when clipboard access permission is not given', 599 + 'Rename "Fantasy / Consensual non-con" back to "Fantasy / Non-con" because it didn\'t fit in the image (oops)', 600 + ]}, 601 + { version: '10 - June 15th 2021', changes: [ 602 + 'Rename "Fantasy / Non-con" category to "Fantasy / Consensual non-con"', 603 + 'Change choice button border color in dark theme to white, thanks to anonymous contributor for this fix', 604 + ]}, 605 + { version: '9 - June 15th 2021', changes: [ 606 + 'Add Macrophilia, Microphilia, Detachable body parts, Oviposition, Rope bondage, Suspension bondage, Cages, Gaping, Fear play and Water bondage (both types) to the list', 607 + 'Added a distinction between Futanari/Transfeminine and Futanari/Hermaphrodite', 608 + 'Removed Hate sex from the list', 609 + 'Added support for descriptions, and added descriptions to most of the kinks (and some categories)', 610 + 'Add light/dark theme detection and toggle', 611 + 'Add Self/Partner to Bodies category', 612 + 'Rename Interactions to Interactions & Groupings', 613 + 'Moved fantasy roleplay scenarios to a new Fantasy / Interactions category', 614 + 'Add website logo and Twitter/Discord embed code', 615 + 'Add this changelog', 616 + ]}, 617 + { version: '8 - April 27th 2021', changes: [ 618 + 'Align legend to the left on mobile devices, thanks to anonymous contributor for this fix', 619 + ]}, 620 + { version: '7 - November 9th 2020', changes: [ 621 + 'Add Scat to the list', 622 + ]}, 623 + { version: '6 - October 9th 2020', changes: [ 624 + 'Rewrite the entire list, using Bulma, in order to completely eliminate scrolling issues', 625 + ]}, 626 + { version: '5 - September 21st 2020', changes: [ 627 + 'Add Clown, Hate sex and Choking to list', 628 + 'Remove "Handholding" meme entry from list', 629 + ]}, 630 + { version: '4 - August 17th 2020', changes: [ 631 + 'Add Zettai ryoiki, Gas masks, Hypnoplay, Droneplay and Kitsunemimi to list', 632 + ]}, 633 + { version: '3 - August 9th 2020', changes: [ 634 + 'Add Beard, Hairy body, Shaven body, Bimbofication, Excessive cum and Gas to list', 635 + ]}, 636 + { version: '2 - July 11th 2020', changes: [ 637 + 'Many internal changes', 638 + 'Fix issues with hotkeys', 639 + 'Add "Autosave" checkbox, as an attempt to stop scrolling issues in Android Chrome webview', 640 + ]}, 641 + { version: '1 - July 10th 2020', changes: [ 642 + 'Initial GitLab release', 643 + ]}, 644 + ] 645 + 646 + function RealRoot() { 647 + const [changelogOpen, setChangelogOpen] = useState(false); 648 + 649 + return <div id="root"> 650 + <nav class="navbar px-5 mt-5 mb-2" role="navigation" aria-label="main navigation"> 651 + <div class="navbar-brand"> 652 + <div class="navbar-item"> 653 + <h1 class="title">Kink list</h1> 654 + </div> 655 + <div class="navbar-item"> 656 + <button id="export-image" class="button is-primary">Export</button> 657 + </div> 658 + <div class="navbar-item"> 659 + <button id="view-changelog" class="button is-primary">Changelog</button> 660 + </div> 661 + <div class="navbar-item"> 662 + <label class="checkbox"> 663 + <input id="dark-theme" type="checkbox" /> 664 + Dark theme 665 + </label> 666 + </div> 667 + </div> 668 + </nav> 669 + 670 + <nav class="level px-5"> 671 + <div id="legend" class="kinks-legend level-left"> 672 + </div> 673 + </nav> 674 + 675 + <section class="section kinks-section"> 676 + <div id="root" class="masonry"> 677 + </div> 678 + </section> 679 + 680 + <div id="export-modal-container" class="modal"> 681 + <div class="modal-background"></div> 682 + <div class="modal-content"> 683 + 684 + <div class="box"> 685 + <h1 class="title">Exported! Copy the image to your clipboard or save it now.</h1> 686 + <div id="export-modal-content"></div> 687 + </div> 688 + 689 + </div> 690 + <button class="modal-close is-large" aria-label="close"></button> 691 + </div> 692 + 693 + <Modal open={changelogOpen} onClose={() => setChangelogOpen(false)}> 694 + <div class="box content"> 695 + <h1>Changelog</h1> 696 + {changelog.map(entry => ( 697 + <div> 698 + <h2>{entry.version}</h2> 699 + <ul> 700 + {entry.changes.map(change => <li>{change}</li>)} 701 + </ul> 702 + </div> 703 + ))} 704 + </div> 705 + </Modal> 706 + </div>; 599 707 } 600 708 601 709 render(h(Root, null), root);
+12
public/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "strict": true, 4 + "strictNullChecks": false, 5 + "module": "commonjs", 6 + "target": "es6", 7 + "lib": ["ESNext", "dom", "dom.iterable"], 8 + "jsx": "preserve" 9 + }, 10 + "exclude": ["node_modules", "**/node_modules/*", "kinklist.js"], 11 + "include": ["*"] 12 + }
+8
rolldown.config.js
··· 1 + import { defineConfig } from 'rolldown'; 2 + 3 + export default defineConfig({ 4 + input: 'public/main.ts', 5 + output: { 6 + file: 'public/kinklist.js', 7 + }, 8 + });