forge
login
or
join now
maxine.puppykitty.racing
/
kinklist
star
0
fork
atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
this repo has no description
star
0
fork
atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
overview
issues
pulls
pipelines
a
uwx
3 months ago
963b683c
092bbcd5
+413
-238
7 changed files
expand all
collapse all
unified
split
package.json
pnpm-lock.yaml
public
index.html
jsconfig.json
main.tsx
tsconfig.json
rolldown.config.js
+1
package.json
reviewed
···
14
14
"@preact/signals": "^2.8.0",
15
15
"@types/masonry-layout": "^4.2.8",
16
16
"preact-portal": "^1.1.3",
17
17
+
"rolldown": "1.0.0-rc.4",
17
18
"typescript": "6.0.0-dev.20260213"
18
19
},
19
20
"dependencies": {
+204
pnpm-lock.yaml
reviewed
···
39
39
preact-portal:
40
40
specifier: ^1.1.3
41
41
version: 1.1.3(preact@10.28.3)
42
42
+
rolldown:
43
43
+
specifier: 1.0.0-rc.4
44
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
51
+
'@emnapi/core@1.8.1':
52
52
+
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
53
53
+
54
54
+
'@emnapi/runtime@1.8.1':
55
55
+
resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
56
56
+
57
57
+
'@emnapi/wasi-threads@1.1.0':
58
58
+
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
59
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
71
+
72
72
+
'@napi-rs/wasm-runtime@1.1.1':
73
73
+
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
74
74
+
75
75
+
'@oxc-project/types@0.113.0':
76
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
124
+
'@rolldown/binding-android-arm64@1.0.0-rc.4':
125
125
+
resolution: {integrity: sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w==}
126
126
+
engines: {node: ^20.19.0 || >=22.12.0}
127
127
+
cpu: [arm64]
128
128
+
os: [android]
129
129
+
130
130
+
'@rolldown/binding-darwin-arm64@1.0.0-rc.4':
131
131
+
resolution: {integrity: sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q==}
132
132
+
engines: {node: ^20.19.0 || >=22.12.0}
133
133
+
cpu: [arm64]
134
134
+
os: [darwin]
135
135
+
136
136
+
'@rolldown/binding-darwin-x64@1.0.0-rc.4':
137
137
+
resolution: {integrity: sha512-JXmaOJGsL/+rsmMfutcDjxWM2fTaVgCHGoXS7nE8Z3c9NAYjGqHvXrAhMUZvMpHS/k7Mg+X7n/MVKb7NYWKKww==}
138
138
+
engines: {node: ^20.19.0 || >=22.12.0}
139
139
+
cpu: [x64]
140
140
+
os: [darwin]
141
141
+
142
142
+
'@rolldown/binding-freebsd-x64@1.0.0-rc.4':
143
143
+
resolution: {integrity: sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw==}
144
144
+
engines: {node: ^20.19.0 || >=22.12.0}
145
145
+
cpu: [x64]
146
146
+
os: [freebsd]
147
147
+
148
148
+
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4':
149
149
+
resolution: {integrity: sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w==}
150
150
+
engines: {node: ^20.19.0 || >=22.12.0}
151
151
+
cpu: [arm]
152
152
+
os: [linux]
153
153
+
154
154
+
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4':
155
155
+
resolution: {integrity: sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==}
156
156
+
engines: {node: ^20.19.0 || >=22.12.0}
157
157
+
cpu: [arm64]
158
158
+
os: [linux]
159
159
+
160
160
+
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.4':
161
161
+
resolution: {integrity: sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==}
162
162
+
engines: {node: ^20.19.0 || >=22.12.0}
163
163
+
cpu: [arm64]
164
164
+
os: [linux]
165
165
+
166
166
+
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.4':
167
167
+
resolution: {integrity: sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==}
168
168
+
engines: {node: ^20.19.0 || >=22.12.0}
169
169
+
cpu: [x64]
170
170
+
os: [linux]
171
171
+
172
172
+
'@rolldown/binding-linux-x64-musl@1.0.0-rc.4':
173
173
+
resolution: {integrity: sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==}
174
174
+
engines: {node: ^20.19.0 || >=22.12.0}
175
175
+
cpu: [x64]
176
176
+
os: [linux]
177
177
+
178
178
+
'@rolldown/binding-openharmony-arm64@1.0.0-rc.4':
179
179
+
resolution: {integrity: sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ==}
180
180
+
engines: {node: ^20.19.0 || >=22.12.0}
181
181
+
cpu: [arm64]
182
182
+
os: [openharmony]
183
183
+
184
184
+
'@rolldown/binding-wasm32-wasi@1.0.0-rc.4':
185
185
+
resolution: {integrity: sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw==}
186
186
+
engines: {node: '>=14.0.0'}
187
187
+
cpu: [wasm32]
188
188
+
189
189
+
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4':
190
190
+
resolution: {integrity: sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ==}
191
191
+
engines: {node: ^20.19.0 || >=22.12.0}
192
192
+
cpu: [arm64]
193
193
+
os: [win32]
194
194
+
195
195
+
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.4':
196
196
+
resolution: {integrity: sha512-p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09g==}
197
197
+
engines: {node: ^20.19.0 || >=22.12.0}
198
198
+
cpu: [x64]
199
199
+
os: [win32]
200
200
+
201
201
+
'@rolldown/pluginutils@1.0.0-rc.4':
202
202
+
resolution: {integrity: sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==}
203
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
209
+
210
210
+
'@tybys/wasm-util@0.10.1':
211
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
259
+
rolldown@1.0.0-rc.4:
260
260
+
resolution: {integrity: sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA==}
261
261
+
engines: {node: ^20.19.0 || >=22.12.0}
262
262
+
hasBin: true
263
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
272
+
273
273
+
tslib@2.8.1:
274
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
283
+
'@emnapi/core@1.8.1':
284
284
+
dependencies:
285
285
+
'@emnapi/wasi-threads': 1.1.0
286
286
+
tslib: 2.8.1
287
287
+
optional: true
288
288
+
289
289
+
'@emnapi/runtime@1.8.1':
290
290
+
dependencies:
291
291
+
tslib: 2.8.1
292
292
+
optional: true
293
293
+
294
294
+
'@emnapi/wasi-threads@1.1.0':
295
295
+
dependencies:
296
296
+
tslib: 2.8.1
297
297
+
optional: true
298
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
309
+
'@napi-rs/wasm-runtime@1.1.1':
310
310
+
dependencies:
311
311
+
'@emnapi/core': 1.8.1
312
312
+
'@emnapi/runtime': 1.8.1
313
313
+
'@tybys/wasm-util': 0.10.1
314
314
+
optional: true
315
315
+
316
316
+
'@oxc-project/types@0.113.0': {}
317
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
362
+
'@rolldown/binding-android-arm64@1.0.0-rc.4':
363
363
+
optional: true
364
364
+
365
365
+
'@rolldown/binding-darwin-arm64@1.0.0-rc.4':
366
366
+
optional: true
367
367
+
368
368
+
'@rolldown/binding-darwin-x64@1.0.0-rc.4':
369
369
+
optional: true
370
370
+
371
371
+
'@rolldown/binding-freebsd-x64@1.0.0-rc.4':
372
372
+
optional: true
373
373
+
374
374
+
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4':
375
375
+
optional: true
376
376
+
377
377
+
'@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4':
378
378
+
optional: true
379
379
+
380
380
+
'@rolldown/binding-linux-arm64-musl@1.0.0-rc.4':
381
381
+
optional: true
382
382
+
383
383
+
'@rolldown/binding-linux-x64-gnu@1.0.0-rc.4':
384
384
+
optional: true
385
385
+
386
386
+
'@rolldown/binding-linux-x64-musl@1.0.0-rc.4':
387
387
+
optional: true
388
388
+
389
389
+
'@rolldown/binding-openharmony-arm64@1.0.0-rc.4':
390
390
+
optional: true
391
391
+
392
392
+
'@rolldown/binding-wasm32-wasi@1.0.0-rc.4':
393
393
+
dependencies:
394
394
+
'@napi-rs/wasm-runtime': 1.1.1
395
395
+
optional: true
396
396
+
397
397
+
'@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4':
398
398
+
optional: true
399
399
+
400
400
+
'@rolldown/binding-win32-x64-msvc@1.0.0-rc.4':
401
401
+
optional: true
402
402
+
403
403
+
'@rolldown/pluginutils@1.0.0-rc.4': {}
404
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
411
+
'@tybys/wasm-util@0.10.1':
412
412
+
dependencies:
413
413
+
tslib: 2.8.1
414
414
+
optional: true
415
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
464
+
rolldown@1.0.0-rc.4:
465
465
+
dependencies:
466
466
+
'@oxc-project/types': 0.113.0
467
467
+
'@rolldown/pluginutils': 1.0.0-rc.4
468
468
+
optionalDependencies:
469
469
+
'@rolldown/binding-android-arm64': 1.0.0-rc.4
470
470
+
'@rolldown/binding-darwin-arm64': 1.0.0-rc.4
471
471
+
'@rolldown/binding-darwin-x64': 1.0.0-rc.4
472
472
+
'@rolldown/binding-freebsd-x64': 1.0.0-rc.4
473
473
+
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.4
474
474
+
'@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.4
475
475
+
'@rolldown/binding-linux-arm64-musl': 1.0.0-rc.4
476
476
+
'@rolldown/binding-linux-x64-gnu': 1.0.0-rc.4
477
477
+
'@rolldown/binding-linux-x64-musl': 1.0.0-rc.4
478
478
+
'@rolldown/binding-openharmony-arm64': 1.0.0-rc.4
479
479
+
'@rolldown/binding-wasm32-wasi': 1.0.0-rc.4
480
480
+
'@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.4
481
481
+
'@rolldown/binding-win32-x64-msvc': 1.0.0-rc.4
482
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
490
+
491
491
+
tslib@2.8.1:
492
492
+
optional: true
289
493
290
494
typescript@6.0.0-dev.20260213: {}
+1
-148
public/index.html
reviewed
···
448
448
</head>
449
449
450
450
<body>
451
451
-
<nav class="navbar px-5 mt-5 mb-2" role="navigation" aria-label="main navigation">
452
452
-
<div class="navbar-brand">
453
453
-
<div class="navbar-item">
454
454
-
<h1 class="title">Kink list</h1>
455
455
-
</div>
456
456
-
<div class="navbar-item">
457
457
-
<button id="export-image" class="button is-primary">Export</button>
458
458
-
</div>
459
459
-
<div class="navbar-item">
460
460
-
<button id="view-changelog" class="button is-primary">Changelog</button>
461
461
-
</div>
462
462
-
<div class="navbar-item">
463
463
-
<label class="checkbox">
464
464
-
<input id="dark-theme" type="checkbox">
465
465
-
Dark theme
466
466
-
</label>
467
467
-
</div>
468
468
-
</div>
469
469
-
</nav>
470
470
-
471
471
-
<nav class="level px-5">
472
472
-
<div id="legend" class="kinks-legend level-left">
473
473
-
</div>
474
474
-
</nav>
475
475
-
476
476
-
<section class="section kinks-section">
477
477
-
<div id="root" class="masonry">
478
478
-
</div>
479
479
-
</section>
480
480
-
481
481
-
<div id="export-modal-container" class="modal">
482
482
-
<div class="modal-background"></div>
483
483
-
<div class="modal-content">
484
484
-
485
485
-
<div class="box">
486
486
-
<h1 class="title">Exported! Copy the image to your clipboard or save it now.</h1>
487
487
-
<div id="export-modal-content"></div>
488
488
-
</div>
489
489
-
490
490
-
</div>
491
491
-
<button class="modal-close is-large" aria-label="close"></button>
492
492
-
</div>
493
493
-
494
494
-
<div id="changelog-modal-container" class="modal">
495
495
-
<div class="modal-background"></div>
496
496
-
<div class="modal-content">
497
497
-
<div class="box content">
498
498
-
<h1>Changelog</h2>
499
499
-
500
500
-
<h2>Version 14 - February 13th 2026</h2>
501
501
-
<ul>
502
502
-
<li>Use Preact for rendering</li>
503
503
-
<li>Upgrade to Bulma 1.0.4</li>
504
504
-
</ul>
505
505
-
506
506
-
<h2>Version 13 - November 14th 2022</h2>
507
507
-
<ul>
508
508
-
<li>Remove Futanari options in favor of an easier to understand selection of sexual characteristics irrespective of gender</li>
509
509
-
<li>Changed title of "Fantasy / Non-con" to "Fantasy / Con-non-con"</li>
510
510
-
<li>Changed "without breath control" to "w/o breath control" so it fits in the margin</li>
511
511
-
<li>Added "Clone / selfcest"</li>
512
512
-
</ul>
513
513
-
514
514
-
<h2>Version 12 - September 17th 2021</h2>
515
515
-
<ul>
516
516
-
<li>Improve mobile layout</li>
517
517
-
</ul>
518
518
-
519
519
-
<h2>Version 11 - June 17th 2021</h2>
520
520
-
<ul>
521
521
-
<li>Properly handle fail scenario in exporter when clipboard access permission is not given</li>
522
522
-
<li>Rename "Fantasy / Consensual non-con" back to "Fantasy / Non-con" because it didn't fit in the image (oops)</li>
523
523
-
</ul>
524
524
-
525
525
-
<h2>Version 10 - June 15th 2021</h2>
526
526
-
<ul>
527
527
-
<li>Rename "Fantasy / Non-con" category to "Fantasy / Consensual non-con"</li>
528
528
-
<li>Change choice button border color in dark theme to white, thanks to anonymous contributor for this fix</li>
529
529
-
</ul>
530
530
-
531
531
-
<h2>Version 9 - June 15th 2021</h2>
532
532
-
<ul>
533
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
534
-
<li>Added a distinction between Futanari/Transfeminine and Futanari/Hermaphrodite</li>
535
535
-
<li>Removed Hate sex from the list</li>
536
536
-
<li>Added support for descriptions, and added descriptions to most of the kinks (and some categories)</li>
537
537
-
<li>Add light/dark theme detection and toggle</li>
538
538
-
<li>Add Self/Partner to Bodies category</li>
539
539
-
<li>Rename Interactions to Interactions & Groupings</li>
540
540
-
<li>Moved fantasy roleplay scenarios to a new Fantasy / Interactions category</li>
541
541
-
<li>Add website logo and Twitter/Discord embed code</li>
542
542
-
<li>Add this changelog</li>
543
543
-
</ul>
544
544
-
545
545
-
<h2>Version 8 - April 27th 2021</h2>
546
546
-
<ul>
547
547
-
<li>Align legend to the left on mobile devices, thanks to anonymous contributor for this fix</li>
548
548
-
</ul>
549
549
-
550
550
-
<h2>Version 7 - November 9th 2020</h2>
551
551
-
<ul>
552
552
-
<li>Add Scat to the list</li>
553
553
-
</ul>
554
554
-
555
555
-
<h2>Version 6 - October 9th 2020</h2>
556
556
-
<ul>
557
557
-
<li>Rewrite the entire list, using Bulma, in order to completely eliminate scrolling issues</li>
558
558
-
</ul>
559
559
-
560
560
-
<h2>Version 5 - September 21st 2020</h2>
561
561
-
<ul>
562
562
-
<li>Add Clown, Hate sex and Choking to list</li>
563
563
-
<li>Remove "Handholding" meme entry from list</li>
564
564
-
</ul>
565
565
-
566
566
-
<h2>Version 4 - August 17th 2020</h2>
567
567
-
<ul>
568
568
-
<li>Add Zettai ryoiki, Gas masks, Hypnoplay, Droneplay and Kitsunemimi to list</li>
569
569
-
</ul>
570
570
-
571
571
-
<h2>Version 3 - August 9th 2020</h2>
572
572
-
<ul>
573
573
-
<li>Add Beard, Hairy body, Shaven body, Bimbofication, Excessive cum and Gas to list</li>
574
574
-
</ul>
575
575
-
576
576
-
<h2>Version 2 - July 11th 2020</h2>
577
577
-
<ul>
578
578
-
<li>Many internal changes</li>
579
579
-
<li>Fix issues with hotkeys</li>
580
580
-
<li>Add "Autosave" checkbox, as an attempt to stop scrolling issues in Android Chrome webview</li>
581
581
-
</ul>
582
582
-
583
583
-
<h2>Version 1 - July 10th 2020</h2>
584
584
-
<ul>
585
585
-
<li>Initial GitLab release</li>
586
586
-
</ul>
587
587
-
</div>
588
588
-
</div>
589
589
-
<button class="modal-close is-large" aria-label="close"></button>
590
590
-
</div>
451
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
609
-
<script>
610
610
-
// Handle closing modals universally
611
611
-
for (const e of document.querySelectorAll('.modal-close')) {
612
612
-
e.addEventListener('click', () => {
613
613
-
e.parentElement.classList.remove('is-active');
614
614
-
});
615
615
-
}
616
616
-
</script>
617
470
</body>
618
471
619
472
</html>
-11
public/jsconfig.json
reviewed
···
1
1
-
{
2
2
-
"compilerOptions": {
3
3
-
"strict": true,
4
4
-
"strictNullChecks": false,
5
5
-
"module": "commonjs",
6
6
-
"target": "es6",
7
7
-
"lib": ["ESNext", "dom", "dom.iterable"]
8
8
-
},
9
9
-
"exclude": ["node_modules", "**/node_modules/*"],
10
10
-
"include": ["*"]
11
11
-
}
+187
-79
public/kinklist.js
public/main.tsx
reviewed
···
1
1
-
/* eslint-disable unicorn/no-useless-undefined */
2
2
-
/* eslint-disable unicorn/prevent-abbreviations */
3
3
-
/* eslint-disable indent */
4
4
-
5
5
-
// @ts-check
6
6
-
7
7
-
/// <reference path="helpers.d.ts" />
8
8
-
9
9
-
const { h, render, Fragment } = preact;
10
10
-
const { useState, useEffect, useReducer } = preactHooks;
11
11
-
const { signal, computed } = preactSignals;
12
12
-
const Portal = preactPortal;
1
1
+
import { signal, computed } from '@preact/signals';
2
2
+
import { h, Fragment, render } from 'preact';
3
3
+
import { useReducer, useState } from 'preact/hooks';
4
4
+
import Portal from 'preact-portal';
5
5
+
import { Masonry } from 'masonic';
13
6
14
7
const root = document.querySelector('#root');
15
15
-
const legend = document.querySelector('#legend');
16
8
17
17
-
/**
18
18
-
* @typedef {object} Kink
19
19
-
* @property {string} name
20
20
-
* @property {string?} description
21
21
-
*/
9
9
+
interface Kink {
10
10
+
name: string;
11
11
+
description: string | null;
12
12
+
}
22
13
23
23
-
/**
24
24
-
* @typedef {object} KinkCategory
25
25
-
* @property {string} name
26
26
-
* @property {string} description
27
27
-
* @property {Kink[]} kinks
28
28
-
* @property {string[]} participants
29
29
-
*/
14
14
+
interface KinkCategory {
15
15
+
name: string;
16
16
+
description: string;
17
17
+
kinks: Kink[];
18
18
+
participants: string[];
19
19
+
}
30
20
31
21
/**
32
22
* @param {string} kinkStr
33
23
*/
34
34
-
function parseKinks(kinkStr) {
24
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
40
-
const kinkCategories = [];
30
30
+
const kinkCategories: KinkCategory[] = [];
41
31
42
32
/** @type {Kink[]} */
43
43
-
const kinksById = [];
33
33
+
const kinksById: Kink[] = [];
44
34
45
35
/**
46
36
* @type {Partial<KinkCategory>}
47
37
*/
48
48
-
let curKinkCategory;
38
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
86
-
const choiceOptions = [
76
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
102
-
const kinkSelections = new Map();
92
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
109
-
const kinkButtons = new Map();
99
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
115
-
function findKinkCategory(kink) {
105
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
125
-
/**
126
126
-
* @typedef {object} LegendChoiceProps
127
127
-
* @property {string} type
128
128
-
* @property {string} typeDescription
129
129
-
*/
115
115
+
interface LegendChoiceProps {
116
116
+
type: string;
117
117
+
typeDescription: string;
118
118
+
}
130
119
131
120
/**
132
121
* @param {LegendChoiceProps} props
133
122
* @returns
134
123
*/
135
135
-
function LegendChoice({type, typeDescription}) {
124
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
159
-
function sliceOnce(str, char) {
148
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
177
-
function removeSymbols(str, symbolStart, symbolEnd = null) {
166
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
195
-
const toBinary = n => n.toString(2).padStart(8, '0'); // convert num to 8-bit binary string
184
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
202
-
function bytesArrToBase64(arr) {
191
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
225
-
function base64ToBytesArr(str) {
214
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
247
-
function getSelectedKinkOrDefault(kink, participant) {
236
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
256
-
function setKinkSelection(kink, participant, toChoiceId) {
245
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
269
-
function serializeChoices() {
258
258
+
function serializeChoices(): string {
270
259
/**
271
260
* @type {number[]}
272
261
*/
273
273
-
const bytes = [];
262
262
+
const bytes: number[] = [];
274
263
275
264
let i = -1;
276
265
277
266
/**
278
267
* @param {number} num
279
268
*/
280
280
-
function pushNumber(num) {
269
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
309
-
function deserializeChoices(base64) {
298
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
356
-
/**
357
357
-
* @typedef {object} KinkChoiceButtonProps
358
358
-
* @property {Kink} kink
359
359
-
* @property {string} participant
360
360
-
* @property {string} choiceId
361
361
-
* @property {string} choiceDescription
362
362
-
*/
345
345
+
interface KinkChoiceButtonProps {
346
346
+
kink: Kink;
347
347
+
participant: string;
348
348
+
choiceId: string;
349
349
+
choiceDescription: string;
350
350
+
}
363
351
364
352
/**
365
353
* @param {KinkChoiceButtonProps} props
366
354
* @returns
367
355
*/
368
368
-
function KinkChoiceButton({kink, participant, choiceId, choiceDescription}) {
356
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
376
-
function reducer(state, action) {
364
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
428
-
function getKinkButtonStates(kink, participant) {
416
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
435
-
function TheKink({kinkCategory, kink}) {
423
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
482
-
/**
483
483
-
* @typedef {object} TheKinkCategoryProps
484
484
-
* @property {KinkCategory} kinkCategory
485
485
-
*/
470
470
+
interface TheKinkCategoryProps {
471
471
+
kinkCategory: KinkCategory;
472
472
+
}
486
473
487
474
/**
488
475
* @param {TheKinkCategoryProps} kinkCategory
489
476
*/
490
490
-
function TheKinkCategory({kinkCategory}) {
477
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
558
-
/**
559
559
-
* @typedef {object} MasonryItemProps
560
560
-
* @property {number} index
561
561
-
* @property {KinkCategory} data
562
562
-
* @property {number} width
563
563
-
*/
545
545
+
interface MasonryItemProps {
546
546
+
index: number;
547
547
+
data: KinkCategory;
548
548
+
width: number;
549
549
+
}
564
550
565
551
/**
566
552
* @param {MasonryItemProps} props
567
553
*/
568
568
-
function MasonryItem({ index, data: kinkCategory, width }) {
554
554
+
function MasonryItem({ index, data: kinkCategory, width }: MasonryItemProps) {
569
555
return h(TheKinkCategory, { kinkCategory }, null);
570
556
}
571
557
572
558
function Root() {
573
573
-
return h(Masonic.Masonry, {
559
559
+
return h(Masonry, {
574
560
items: kinkData.value.kinkCategories,
575
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
582
-
/**
583
583
-
* @param {{ children: import('preact').ComponentChildren, open: boolean }} props
584
584
-
*/
585
585
-
function Modal({ children, open }) {
567
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
581
+
}
582
582
+
583
583
+
const changelog = [
584
584
+
{ version: '14 - February 13th 2026', changes: [
585
585
+
'Use Preact for rendering',
586
586
+
'Upgrade to Bulma 1.0.4',
587
587
+
]},
588
588
+
{ version: '13 - November 14th 2022', changes: [
589
589
+
'Remove Futanari options in favor of an easier to understand selection of sexual characteristics irrespective of gender',
590
590
+
'Changed title of "Fantasy / Non-con" to "Fantasy / Con-non-con"',
591
591
+
'Changed "without breath control" to "w/o breath control" so it fits in the margin',
592
592
+
'Added "Clone / selfcest"',
593
593
+
]},
594
594
+
{ version: '12 - September 17th 2021', changes: [
595
595
+
'Improve mobile layout',
596
596
+
]},
597
597
+
{ version: '11 - June 17th 2021', changes: [
598
598
+
'Properly handle fail scenario in exporter when clipboard access permission is not given',
599
599
+
'Rename "Fantasy / Consensual non-con" back to "Fantasy / Non-con" because it didn\'t fit in the image (oops)',
600
600
+
]},
601
601
+
{ version: '10 - June 15th 2021', changes: [
602
602
+
'Rename "Fantasy / Non-con" category to "Fantasy / Consensual non-con"',
603
603
+
'Change choice button border color in dark theme to white, thanks to anonymous contributor for this fix',
604
604
+
]},
605
605
+
{ version: '9 - June 15th 2021', changes: [
606
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
607
+
'Added a distinction between Futanari/Transfeminine and Futanari/Hermaphrodite',
608
608
+
'Removed Hate sex from the list',
609
609
+
'Added support for descriptions, and added descriptions to most of the kinks (and some categories)',
610
610
+
'Add light/dark theme detection and toggle',
611
611
+
'Add Self/Partner to Bodies category',
612
612
+
'Rename Interactions to Interactions & Groupings',
613
613
+
'Moved fantasy roleplay scenarios to a new Fantasy / Interactions category',
614
614
+
'Add website logo and Twitter/Discord embed code',
615
615
+
'Add this changelog',
616
616
+
]},
617
617
+
{ version: '8 - April 27th 2021', changes: [
618
618
+
'Align legend to the left on mobile devices, thanks to anonymous contributor for this fix',
619
619
+
]},
620
620
+
{ version: '7 - November 9th 2020', changes: [
621
621
+
'Add Scat to the list',
622
622
+
]},
623
623
+
{ version: '6 - October 9th 2020', changes: [
624
624
+
'Rewrite the entire list, using Bulma, in order to completely eliminate scrolling issues',
625
625
+
]},
626
626
+
{ version: '5 - September 21st 2020', changes: [
627
627
+
'Add Clown, Hate sex and Choking to list',
628
628
+
'Remove "Handholding" meme entry from list',
629
629
+
]},
630
630
+
{ version: '4 - August 17th 2020', changes: [
631
631
+
'Add Zettai ryoiki, Gas masks, Hypnoplay, Droneplay and Kitsunemimi to list',
632
632
+
]},
633
633
+
{ version: '3 - August 9th 2020', changes: [
634
634
+
'Add Beard, Hairy body, Shaven body, Bimbofication, Excessive cum and Gas to list',
635
635
+
]},
636
636
+
{ version: '2 - July 11th 2020', changes: [
637
637
+
'Many internal changes',
638
638
+
'Fix issues with hotkeys',
639
639
+
'Add "Autosave" checkbox, as an attempt to stop scrolling issues in Android Chrome webview',
640
640
+
]},
641
641
+
{ version: '1 - July 10th 2020', changes: [
642
642
+
'Initial GitLab release',
643
643
+
]},
644
644
+
]
645
645
+
646
646
+
function RealRoot() {
647
647
+
const [changelogOpen, setChangelogOpen] = useState(false);
648
648
+
649
649
+
return <div id="root">
650
650
+
<nav class="navbar px-5 mt-5 mb-2" role="navigation" aria-label="main navigation">
651
651
+
<div class="navbar-brand">
652
652
+
<div class="navbar-item">
653
653
+
<h1 class="title">Kink list</h1>
654
654
+
</div>
655
655
+
<div class="navbar-item">
656
656
+
<button id="export-image" class="button is-primary">Export</button>
657
657
+
</div>
658
658
+
<div class="navbar-item">
659
659
+
<button id="view-changelog" class="button is-primary">Changelog</button>
660
660
+
</div>
661
661
+
<div class="navbar-item">
662
662
+
<label class="checkbox">
663
663
+
<input id="dark-theme" type="checkbox" />
664
664
+
Dark theme
665
665
+
</label>
666
666
+
</div>
667
667
+
</div>
668
668
+
</nav>
669
669
+
670
670
+
<nav class="level px-5">
671
671
+
<div id="legend" class="kinks-legend level-left">
672
672
+
</div>
673
673
+
</nav>
674
674
+
675
675
+
<section class="section kinks-section">
676
676
+
<div id="root" class="masonry">
677
677
+
</div>
678
678
+
</section>
679
679
+
680
680
+
<div id="export-modal-container" class="modal">
681
681
+
<div class="modal-background"></div>
682
682
+
<div class="modal-content">
683
683
+
684
684
+
<div class="box">
685
685
+
<h1 class="title">Exported! Copy the image to your clipboard or save it now.</h1>
686
686
+
<div id="export-modal-content"></div>
687
687
+
</div>
688
688
+
689
689
+
</div>
690
690
+
<button class="modal-close is-large" aria-label="close"></button>
691
691
+
</div>
692
692
+
693
693
+
<Modal open={changelogOpen} onClose={() => setChangelogOpen(false)}>
694
694
+
<div class="box content">
695
695
+
<h1>Changelog</h1>
696
696
+
{changelog.map(entry => (
697
697
+
<div>
698
698
+
<h2>{entry.version}</h2>
699
699
+
<ul>
700
700
+
{entry.changes.map(change => <li>{change}</li>)}
701
701
+
</ul>
702
702
+
</div>
703
703
+
))}
704
704
+
</div>
705
705
+
</Modal>
706
706
+
</div>;
599
707
}
600
708
601
709
render(h(Root, null), root);
+12
public/tsconfig.json
reviewed
···
1
1
+
{
2
2
+
"compilerOptions": {
3
3
+
"strict": true,
4
4
+
"strictNullChecks": false,
5
5
+
"module": "commonjs",
6
6
+
"target": "es6",
7
7
+
"lib": ["ESNext", "dom", "dom.iterable"],
8
8
+
"jsx": "preserve"
9
9
+
},
10
10
+
"exclude": ["node_modules", "**/node_modules/*", "kinklist.js"],
11
11
+
"include": ["*"]
12
12
+
}
+8
rolldown.config.js
reviewed
···
1
1
+
import { defineConfig } from 'rolldown';
2
2
+
3
3
+
export default defineConfig({
4
4
+
input: 'public/main.ts',
5
5
+
output: {
6
6
+
file: 'public/kinklist.js',
7
7
+
},
8
8
+
});