-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprototype.html
More file actions
661 lines (613 loc) · 47.7 KB
/
Copy pathprototype.html
File metadata and controls
661 lines (613 loc) · 47.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LaughterBox · 高保真原型 v6.0.0</title>
<meta name="description" content="LaughterBox 高保真可交互原型" />
<!-- Design Tokens (CSS variables) -->
<style>
:root {
--bg: #fafafa;
--fg: #171717;
--card: #ffffff;
--border: rgba(229, 229, 229, 0.55);
--muted: #737373;
--muted-fg: #f5f5f5;
--shadow-card: 0 8px 30px rgb(0 0 0 / 0.04);
--shadow-card-hover: 0 12px 40px rgb(0 0 0 / 0.08);
--shadow-btn: 0 4px 14px rgb(0 0 0 / 0.08);
--shadow-toast-success: 0 8px 24px rgb(34 197 94 / 0.4);
--shadow-toast-error: 0 8px 24px rgb(239 68 68 / 0.4);
--radius-lg: 1.5rem;
--radius-xl: 2rem;
--radius-full: 9999px;
--ease: cubic-bezier(0.34, 1.56, 0.64, 1);
--accent-red: #ef4444;
--accent-blue: #3b82f6;
--accent-green: #22c55e;
--font-serif: 'Noto Serif SC', ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif;
--font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
html.dark {
--bg: #0a0a0a;
--fg: #fafafa;
--card: #171717;
--border: rgba(38 38 38 / 0.55);
--muted: #a3a3a3;
--muted-fg: #1a1a1a;
--shadow-card: 0 8px 30px rgb(0 0 0 / 0.20);
--shadow-card-hover: 0 12px 40px rgb(0 0 0 / 0.40);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: var(--font-sans);
background: var(--bg);
color: var(--fg);
min-height: 100vh;
transition: background 0.3s ease, color 0.3s ease;
-webkit-font-smoothing: antialiased;
}
/* ============ Header ============ */
header {
position: sticky; top: 0; z-index: 10;
border-bottom: 1px solid var(--border);
background: color-mix(in srgb, var(--bg) 90%, transparent);
backdrop-filter: blur(12px);
}
.header-inner {
max-width: 72rem; margin: 0 auto;
display: flex; align-items: center; justify-content: space-between;
padding: 0.75rem 1rem;
}
.brand { display: flex; align-items: center; gap: 0.75rem; }
.brand-badge {
width: 2rem; height: 2rem; border-radius: 0.75rem;
background: var(--fg); color: var(--bg);
display: flex; align-items: center; justify-content: center;
}
.brand-title { font-size: 0.875rem; font-weight: 600; letter-spacing: -0.01em; }
.brand-sub { font-size: 0.6875rem; color: var(--muted); }
/* ============ Main ============ */
main {
flex: 1; display: flex; flex-direction: column;
align-items: center; justify-content: center;
padding: 2rem 1rem;
}
.viewer { position: relative; width: 100%; max-width: 48rem; }
@media (min-width: 1024px) { .viewer { max-width: 64rem; } }
/* ============ Joke Card ============ */
.joke-card {
position: relative; display: flex; flex-direction: column;
align-items: center; justify-content: center;
min-height: 360px;
border-radius: var(--radius-lg);
background: var(--card);
box-shadow: var(--shadow-card);
padding: 2rem;
cursor: grab;
transition: box-shadow 0.3s ease, transform 0.3s ease;
animation: fadeIn 0.4s ease;
overflow: hidden;
}
@media (min-width: 768px) {
.joke-card { min-height: 480px; padding: 3rem; }
}
.joke-card:hover { box-shadow: var(--shadow-card-hover); }
.joke-card:active { cursor: grabbing; }
.joke-card.dragging { transition: transform 0.2s ease; }
.quote-deco { position: absolute; opacity: 0.10; }
html.dark .quote-deco { opacity: 0.20; }
.quote-deco.tl { top: 2rem; left: 2rem; transform: rotate(180deg); }
.quote-deco.br { bottom: 2rem; right: 2rem; }
.joke-text {
max-width: 90%; text-align: center;
font-family: var(--font-serif);
font-size: 1.5rem; font-weight: 300; line-height: 1.65;
letter-spacing: -0.025em;
position: relative; z-index: 1;
}
@media (min-width: 768px) {
.joke-text { font-size: 1.875rem; line-height: 1.7; }
}
@media (min-width: 1024px) {
.joke-text { font-size: 2.25rem; line-height: 1.8; }
}
.index-container {
display: flex; align-items: center; gap: 1rem;
margin-top: 2.5rem; opacity: 0.3;
}
.index-line { width: 2rem; height: 1px; background: currentColor; }
.index-label { font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.1em; text-transform: uppercase; }
/* ============ Nav Controls ============ */
.nav-controls { margin-top: 2.5rem; display: flex; flex-direction: column; align-items: center; gap: 1.25rem; }
.btn-row { display: flex; align-items: center; gap: 1rem; }
.nav-btn {
border: none; background: var(--card); border-radius: var(--radius-full);
display: flex; align-items: center; justify-content: center;
cursor: pointer; box-shadow: var(--shadow-btn);
transition: all 0.2s ease;
width: 3.5rem; height: 3.5rem;
color: var(--fg);
}
.nav-btn.lg { width: 4rem; height: 4rem; }
@media (min-width: 768px) {
.nav-btn { width: 4rem; height: 4rem; }
.nav-btn.lg { width: 5rem; height: 5rem; }
}
.nav-btn:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgb(0 0 0 / 0.1); }
.nav-btn:active { transform: scale(0.95) translateY(0); }
.nav-btn svg { width: 1.5rem; height: 1.5rem; stroke-width: 2.25; }
@media (min-width: 768px) { .nav-btn svg { width: 1.75rem; height: 1.75rem; } }
/* ============ Secondary Actions ============ */
.actions { display: flex; align-items: center; gap: 0.75rem; }
.action-btn {
width: 2.75rem; height: 2.75rem;
border: none; background: transparent; border-radius: var(--radius-full);
display: flex; align-items: center; justify-content: center;
cursor: pointer; color: var(--muted);
transition: color 0.2s ease, transform 0.2s ease, background 0.2s ease;
}
.action-btn:hover { transform: scale(1.1); color: var(--fg); background: color-mix(in srgb, var(--fg) 8%, transparent); }
.action-btn:active { transform: scale(0.9); }
.action-btn svg { width: 1.35rem; height: 1.35rem; transition: fill 0.3s ease; stroke-width: 2; }
.action-btn.active.favorite.active { color: var(--accent-red); }
.action-btn.active.favorite.active svg { fill: currentColor; animation: heartBeat 0.5s ease; }
.action-btn.autoplay.active { color: var(--accent-blue); }
/* ============ Theme toggle ============ */
.theme-toggle {
width: 2.25rem; height: 2.25rem; border: 1px solid var(--border);
border-radius: var(--radius-full); background: var(--card); cursor: pointer;
display: flex; align-items: center; justify-content: center;
transition: all 0.2s ease; position: relative;
}
.theme-toggle:hover { transform: translateY(-1px); box-shadow: 0 4px 14px rgb(0 0 0 / 0.08); }
.theme-toggle svg { width: 1rem; height: 1rem; transition: transform 0.3s ease, opacity 0.3s ease; }
.theme-toggle .sun { transform: rotate(0); opacity: 1; }
.theme-toggle .moon { position: absolute; transform: rotate(90deg); opacity: 0; }
html.dark .theme-toggle .sun { transform: rotate(-90deg); opacity: 0; }
html.dark .theme-toggle .moon { transform: rotate(0); opacity: 1; }
/* ============ Toast ============ */
.toast {
position: fixed; top: 2rem; left: 50%;
transform: translate(-50%, -20px) scale(0.95);
background: var(--accent-green); color: white;
padding: 0.75rem 1.5rem; border-radius: var(--radius-full);
font-size: 0.875rem; font-weight: 500;
box-shadow: var(--shadow-toast-success);
opacity: 0; transition: transform 0.3s var(--ease), opacity 0.3s ease;
z-index: 1000; pointer-events: none; display: flex; align-items: center; gap: 0.5rem;
}
.toast.show { opacity: 1; transform: translate(-50%, 0) scale(1); }
.toast.error { background: var(--accent-red); box-shadow: var(--shadow-toast-error); }
/* ============ Footer ============ */
footer {
border-top: 1px solid var(--border);
padding: 1rem 0; text-align: center;
font-size: 0.75rem; color: var(--muted);
}
/* ============ Animations ============ */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes heartBeat {
0%, 100% { transform: scale(1); }
25% { transform: scale(1.3); }
50% { transform: scale(0.95); }
75% { transform: scale(1.15); }
}
@keyframes slideInLeft { from { opacity: 0; transform: translateX(50px); } to { opacity: 1; transform: translateX(0); } }
@keyframes slideInRight { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }
@keyframes scaleIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
.slide-in-left { animation: slideInLeft 0.4s var(--ease); }
.slide-in-right { animation: slideInRight 0.4s var(--ease); }
.scale-in { animation: scaleIn 0.4s var(--ease); }
/* Accessibility helpers */
.sr-only {
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
</style>
</head>
<body>
<div style="min-height: 100vh; display: flex; flex-direction: column;">
<header>
<div class="header-inner">
<div class="brand">
<div class="brand-badge" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 2l2.39 5.37L20 8l-4.2 3.73L17.5 18l-5.5-3.27L6.5 18 17.18 8 12 2z"/>
</svg>
</div>
<div>
<div class="brand-title">LaughterBox</div>
<div class="brand-sub">极简笑话收藏 · <span id="total-count">—</span> 则</div>
</div>
</div>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<button id="theme-toggle" class="theme-toggle" aria-label="切换主题">
<svg class="sun" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
<svg class="moon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
</button>
</div>
</div>
</header>
<main>
<div class="viewer">
<div id="joke-card" class="joke-card" tabindex="0" role="region" aria-label="笑话卡片">
<div class="quote-deco tl" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="currentColor"><path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z"/></svg>
</div>
<div style="position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 2rem;">
<p id="joke-text" class="joke-text">加载中...</p>
<div class="index-container" aria-hidden="true">
<div class="index-line"></div>
<span class="index-label" id="joke-index">001 / 001</span>
<div class="index-line"></div>
</div>
</div>
<div class="quote-deco br" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="currentColor"><path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z"/></svg>
</div>
</div>
<div class="nav-controls" role="toolbar" aria-label="导航控件">
<div class="btn-row">
<button id="btn-prev" class="nav-btn" aria-label="上一个笑话">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
</button>
<button id="btn-random" class="nav-btn lg" aria-label="随机笑话">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 3h5v5"/><path d="M4 20l7 7"/><path d="M21 16V8a2 2 0 0 0-2-2h-1"/><path d="M3 8v8a2 2 0 0 0 2 2h1"/><path d="m21 3-6 6"/><path d="M4 4l7 7"/></svg>
</button>
<button id="btn-next" class="nav-btn" aria-label="下一个笑话">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
</button>
</div>
<div class="actions">
<button id="btn-autoplay" class="action-btn autoplay" aria-label="开始自动播放">
<svg class="play-icon" xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><polygon points="6 3 20 12 6 21 6 3"/></svg>
<svg class="pause-icon" style="display:none;position:absolute;" xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="4" width="4" height="16" rx="1"/><rect x="14" y="4" width="4" height="16" rx="1"/></svg>
</button>
<button id="btn-favorite" class="action-btn favorite" aria-label="收藏">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>
</button>
<button id="btn-copy" class="action-btn" aria-label="复制">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
</button>
<button id="btn-share" class="action-btn" aria-label="分享">
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
</button>
</div>
<div style="font-size: 0.75rem; letter-spacing: 0.15em; color: var(--muted); opacity: 0.6;">v6.0.0</div>
</div>
</div>
</main>
<footer>
左右滑动或使用方向键切换 · 按空格随机
</footer>
</div>
<div id="toast" class="toast" role="status" aria-live="polite">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
<span id="toast-text">已复制到剪贴板</span>
</div>
<script>
// =============== 真实笑话数据(从 JOKES_DATA_DEDUPED 提取)===============
const JOKES = [
"青年问禅师:大师,我很爱我的女朋友,她也有很多优点,但是总有几个缺点让我非常讨厌,有什么方法能让她改变?禅师浅笑,答:方法很简单,不过若想我教你,你需先下山为我找一张只有正面没有背面的纸回来。青年略一沉吟,默默掏出一张人民币。",
"父亲问我人生有什么追求?我回答金钱和美女,父亲凶狠地打了我的脸;我回答事业与爱情,父亲赞赏地摸了我的头。",
"记者采访一位百岁老人:您长寿的秘诀是什么?老人说:保持呼吸,不要断气。",
"老师问小明:如果你有5个苹果,我拿走3个,还剩几个?小明说:还剩一个烂苹果。老师:为什么?小明:因为那5个苹果里本来就有一个是烂的。",
"一个人去买西瓜,问老板:这西瓜甜吗?老板说:甜得像蜜一样!那人买了一个,回家切开一尝,一点都不甜,就回去找老板。老板说:你看,这西瓜长得多甜美的样子,怎么会不甜呢?肯定是你吃的方式不对。",
"老婆让我做饭,我问:做什么菜?老婆说:随便。我:那我做个西红柿炒鸡蛋?老婆:不想吃。我:那做个红烧肉?老婆:太油腻。我:那你说做什么?老婆:随便。",
"考试前,小明对同桌说:等会儿考试的时候,你踢我一下,我就知道选A;踢两下,选B;踢三下,选C;踢四下,选D。考试开始了,小明感觉同桌一直在踢他,踢了几十下,小明急了,小声问:你到底踢够了没有?同桌说:我也不知道选哪个,就都踢了一遍。",
"一个人问路:请问,去医院怎么走?路人:你沿着这条路一直走,走到第一个十字路口右转,然后再走一会儿,看到一个垃圾桶,你就把垃圾扔了,然后继续走,看到一个红绿灯,你就等绿灯,然后过马路,再走一会儿,就到了。那人说:谢谢,不过我只是想问路,不是让你给我讲人生哲理。",
"老婆说:我要减肥,从今天开始,晚上只吃水果。我说:好啊,我支持你。第一天晚上,老婆吃了一个西瓜、两个苹果、三个香蕉、四个橙子……第二天,老婆说:我觉得减肥还是应该从明天开始。",
"什么动物最容易摔倒?答案:狐狸,因为它很狡猾(脚滑)。",
"什么东西越洗越脏?答案:水。",
"一个人种了一棵树,他每天都给树浇水、施肥,希望树能快点长大。过了几年,树终于长大了,他却把树砍了,因为他觉得树挡住了他的阳光。",
"儿子问爸爸:爸爸,为什么你的头发都白了?爸爸说:因为你不听话,我操心操的。儿子说:那爷爷的头发怎么全白了?爸爸:……",
"面试官问求职者:你有什么缺点?求职者说:我的缺点就是诚实。面试官说:诚实不是缺点啊。求职者说:真的吗?谢谢你的夸奖!",
"爱因斯坦的一位朋友问他:你为什么总是穿得这么随便?爱因斯坦说:反正这里的人都认识我。后来爱因斯坦成了名人,他还是穿得这么随便。他的朋友又问他:你现在是名人了,为什么还穿得这么随便?爱因斯坦说:反正这里的人都不认识我。",
"老师问学生:为什么我们要学历史?学生说:因为历史会重演。老师说:那我们更要好好学习历史,避免重演错误。学生说:可是我想看看历史能不能重演得更好玩一点。",
"爸爸问儿子:你长大了想做什么?儿子说:我长大了想当一个宇航员。爸爸说:当宇航员很好,不过你要好好学习。儿子说:我知道,我要好好学习数学,因为太空里有很多星星,我要数清楚。",
"妻子问丈夫:你记得我们第一次见面是什么时候吗?丈夫说:当然记得,那是一个阳光明媚的下午,你穿着白色的连衣裙,看起来就像天使一样。妻子说:可是我们第一次见面是在晚上,而且我穿的是黑色的衣服。丈夫说:哦,是吗?那可能是我记错了,不过你在我心里永远是天使。",
"老师问学生:什么是乐观主义?学生说:乐观主义就是认为明天会比今天更好。老师说:那什么是悲观主义?学生说:悲观主义就是认为明天会比今天更糟,但还是会起床。",
"妈妈问女儿:你为什么这么喜欢吃零食?女儿说:因为零食好吃。妈妈说:可是零食不健康。女儿说:可是吃零食的时候很快乐,快乐比健康更重要。妈妈说:那你要是生病了怎么办?女儿说:生病了我会吃药,药很苦,这样就能对比出零食的甜了。",
"一个人在办公室里工作,突然觉得很累,就趴在桌子上睡着了。他的同事把他叫醒,说:你怎么在上班时间睡觉?那个人说:我没有睡觉,我只是在思考人生。同事说:那你思考出什么了?那个人说:我思考出,还是睡觉舒服。",
"一个人去餐厅吃饭,点了一份牛排。服务员端上来,他尝了一口,说:这牛排怎么这么硬?服务员说:因为我们的牛排是给有牙齿的人吃的。那个人说:可是我有牙齿啊。服务员说:那你可能需要更坚硬的牙齿。",
"一个人去咖啡店喝咖啡,问服务员:你们这里的咖啡多少钱一杯?服务员说:30块钱一杯。那个人说:这么贵!服务员说:不过我们这里的咖啡可以续杯。那个人说:那好,给我来一杯,然后我要续到明天。",
"两个人在聊天,一个人说:我最近在学画画。另一个人说:那你画得怎么样?那个人说:我画的画就像我的人生一样,虽然不完美,但很有个性。",
"猫追老鼠,老鼠躲进了洞里。猫在洞门口等了很久,老鼠都不出来。猫想了一个办法,它在洞门口学狗叫:汪汪汪!老鼠想:狗来了,猫肯定会走的。于是老鼠就出来了。猫一把抓住老鼠,说:你看,学会一门外语多重要!",
"什么动物能贴在墙上?答案:海豹,因为它是海报(海豹)。",
"一个人拿着一个杯子去装水,装了一半就不装了。他的朋友说:你怎么不装满?他说:装满了就倒不出来了。朋友说:可是你装这么少,也不够用啊。他说:没关系,我可以再去装。",
"一个人在公园里放风筝,风筝飞得很高。他的朋友问他:你为什么要把风筝放得这么高?那个人说:因为我想让它看看这个世界有多大。朋友说:可是风筝线断了怎么办?那个人说:那它就能真正地看看这个世界了。",
"老师问小明:小明,你为什么迟到?小明说:因为我在路上遇到了一个强盗。老师说:强盗抢了你什么?小明说:他抢了我的作业。",
"一个人在河边钓鱼,钓了很久都没钓到一条鱼。他的朋友问他:你钓了这么久,怎么一条鱼都没钓到?他说:因为这里没有鱼。朋友说:那你为什么还在这里钓?他说:因为这里的风景好。",
"老板问员工:你为什么每天都迟到?员工说:因为我家的闹钟坏了。老板说:那你为什么不换一个闹钟?员工说:因为我每天都迟到,没时间去买。",
"什么东西有头没有脚?答案:蒜,因为它有蒜头。",
"一个人去书店买书,问老板:有没有《如何快速赚钱》这本书?老板说:有啊,不过这本书在最里面的架子上。那人说:为什么放在那么里面?老板说:因为买这本书的人都想快速赚钱,不想走太远。",
"一个人在看电视,看了很久都没换台。他的朋友问他:你看了这么久,为什么不换台?他说:因为这个节目太好看了。朋友说:可是这个节目已经结束了。他说:那我在看广告。",
"老师问学生:什么是友谊?学生说:友谊就是在你没钱的时候,朋友会借钱给你。老师说:那什么是真正的友谊?学生说:真正的友谊就是在你没钱的时候,朋友会借钱给你,而且不用你还。",
"爸爸问儿子:你为什么总是玩手机?儿子说:因为手机里有很多有趣的东西。爸爸说:那你为什么不出去和朋友玩?儿子说:因为我的朋友都在玩手机。",
"两个人在聊天,一个人说:今天的太阳真大。另一个人说:是啊,就像一个大火球。那个人说:不过这个大火球离我们太远了,不然我们就能烤火了。另一个人说:要是真离得那么近,我们早就被烤成肉干了。",
"什么动物最节俭?答案:蚕,因为它会结茧(节俭)。",
"一个人去超市买东西,结账的时候发现钱不够。他对收银员说:我能不能先欠着,明天再来还?收银员说:不行,我们这里不赊账。那个人说:那我能不能把东西放回去一部分?收银员说:可以,不过你已经打开的东西不能放回去。那个人说:可是我还没打开任何东西。收银员说:那你为什么要问能不能欠着?",
"老师问学生:为什么要节约用水?学生说:因为水是生命之源。老师说:还有呢?学生说:因为水费很贵。",
"妈妈问儿子:你为什么把房间弄得这么乱?儿子说:因为我在寻找灵感。妈妈说:那你找到灵感了吗?儿子说:找到了,我找到灵感就是:乱一点没关系,反正妈妈会收拾。",
"一个人去医院看病,医生问:你哪里不舒服?那个人说:我头疼。医生说:那你先去做个检查。那个人说:可是我没有钱。医生说:那你可以先去外面吹吹风,头疼可能就好了。",
"一个人在图书馆看书,看了很久都没看进去。他的朋友问他:你看了这么久,看了什么?他说:我在看书的封面。朋友说:那你为什么不翻开看?他说:因为封面太好看了。",
"两个人在聊天,一个人说:我最近在学瑜伽。另一个人说:瑜伽有什么好处?那个人说:瑜伽能让我变得更柔软。另一个人说:那你现在能摸到自己的脚趾吗?那个人说:不能,不过我现在能接受自己摸不到脚趾这个事实了。",
"老师问学生:如果你有100万,你会怎么花?学生说:我会先买一个大房子,然后买一辆好车,然后去旅游。老师说:那你剩下的钱呢?学生说:剩下的钱?剩下的钱我会先欠着,等我有了更多的钱再还。",
"什么动物最懂礼貌?答案:长颈鹿,因为它总是(颈)点头。",
"什么东西越热越爱出来?答案:汗。",
"什么东西没吃的时候是绿的,吃的时候是红的,吐出来是黑的?答案:西瓜。",
"什么东西怕水不怕火?答案:冰块。",
"妈妈问女儿:你为什么喜欢和小明玩?女儿说:因为小明很聪明。妈妈说:那你要向小明学习。女儿说:我已经在向他学习了,他不做作业,我也不做作业。",
"老师问学生:什么是成功?学生说:成功就是把复杂的事情变简单。老师说:那什么是失败?学生说:失败就是把简单的事情变复杂。",
"一个人去买衣服,试了很多件都不满意。服务员问:您想要什么样的衣服?那个人说:我想要一件既便宜又好看,质量又好的衣服。服务员说:那您可能需要去梦里买。",
"两个人在聊天,一个人说:我最近在学做饭。另一个人说:那你做的菜好吃吗?那个人说:我做的菜就像我的人生一样,虽然有很多不足,但至少能吃。",
"老师问学生:为什么要学习数学?学生说:因为数学能让我在买东西的时候知道自己有没有被坑。老师说:还有呢?学生说:因为数学能让我知道,我为什么被坑了。",
"爸爸问儿子:你为什么考试考得这么差?儿子说:因为老师问的问题都是我还没学的。爸爸说:那你不会的问题为什么不问我?儿子说:因为你也不会。",
"一个人在公园里散步,看到一个老人在钓鱼。他走过去问:大爷,您钓了多久了?老人说:我钓了三十年了。那个人说:那您一定很会钓鱼。老人说:是啊,我现在看到鱼就知道它会不会上钩。那个人说:那今天的鱼会上钩吗?老人说:今天的鱼都休息了。",
"妻子问丈夫:你觉得我胖吗?丈夫说:不胖,你很健康。妻子说:可是我最近穿裤子的时候,扣子扣不上了。丈夫说:那是因为裤子缩水了,不是你胖了。",
"老师问学生:为什么要保护环境?学生说:因为地球是我们的家。老师说:还有呢?学生说:因为如果环境不好,我们就没地方玩了。",
"一个人去超市买东西,看到一个商品上面写着买一送一。他很高兴,就买了一份。回到家,他打开包装,发现里面只有一个商品。他很生气,就回到超市找老板。老板说:买一送一,买一个商品,送一个包装袋,这不是送了吗?",
"一个人在办公室里工作,突然觉得很饿,就偷偷地吃了一包零食。他的同事问他:你在吃什么?那个人说:我在吃工作的动力。同事说:那你能不能给我一点动力?那个人说:不行,我的动力只够我自己用。",
"老师问小明:小明,你的作业为什么没做?小明说:因为我家的狗把我的作业吃了。老师说:那你为什么不把狗带来?小明说:因为它今天消化不良。",
"一个人在打电话,说了很久都没挂。他的朋友问他:你说了这么久,为什么不挂?他说:因为对方还没挂。朋友说:那对方为什么没挂?他说:因为我还没挂。",
"爸爸对儿子说:你要好好学习,将来才能有出息。儿子说:爸爸,你小时候好好学习了吗?爸爸说:当然了,爸爸小时候学习可好了。儿子说:那你现在怎么没出息?",
"一个人去买狗,问老板:这只狗聪明吗?老板说:当然聪明,它昨天还给我买报纸呢!那人说:真的吗?那我要了!第二天,那人回来找老板:你这狗根本不会买报纸!老板说:可是昨天它真的买了啊,你再问问它今天想买什么报纸?",
"两个人在钓鱼,一个人突然说:这里的鱼真傻,都不上钩。另一个人说:不是鱼傻,是你放的鱼饵不对。那人说:我放的是巧克力啊,我自己都喜欢吃。",
"快递员打电话:您好,您的快递到了,请问在家吗?我说:在,你放门口吧。快递员:不行,这个必须本人签收。我:那你等我一下,我正在开门。过了五分钟,快递员:您怎么还没出来?我:我家的门有点问题,正在修呢。快递员:那我明天再来吧。我:别,你等我,我已经从窗户爬出来了。",
"老师问小明:小明,你为什么上课睡觉?小明说:因为我昨晚没睡好。老师说:你昨晚为什么没睡好?小明说:因为我担心今天上课会睡着。",
"同事问小明:小明,你为什么每天都加班?小明说:因为我工作效率低。同事说:那你为什么不提高工作效率?小明说:因为我每天都加班,没时间提高。",
"老板在会上说:我们公司的理念就是客户至上,员工至上。小明问:那客户和员工哪个更重要?老板说:当然是客户更重要。小明说:那为什么还说员工至上?老板说:因为员工是为客户服务的。",
"小明问老板:老板,我什么时候能加薪?老板说:等你工作满一年。小明说:可是我已经工作满一年了。老板说:那等你工作满两年。",
"牛顿坐在苹果树下,一个苹果掉了下来,砸到了他的头上。牛顿想:为什么苹果会掉下来,而不是飞上去呢?后来他发现了万有引力定律。其实,苹果掉下来是因为它熟了。",
"贝多芬失聪后,还能继续创作音乐。有人问他:你失聪了,怎么还能创作音乐?贝多芬说:因为我能用心去听。",
"一个人在河边看到一块漂亮的石头,他把石头捡起来,带回家。过了几天,他觉得这块石头也没那么漂亮了,就把它扔了。又过了几天,他在河边又看到一块漂亮的石头,他又捡起来,带回家,然后又扔了。后来他才明白,原来最漂亮的石头是他第一次捡到的那块。",
"什么动物最没有方向感?答案:麋鹿,因为它迷路(麋鹿)了。",
"一个人买了一面镜子,他每天都照镜子,觉得自己越来越丑。他的朋友说:你换一面镜子吧。他说:不用,我觉得这面镜子很真实。后来他才发现,原来这面镜子是哈哈镜。",
"一个人在跑步,跑了很久都没停下来。他的朋友问他:你跑了这么久,为什么不停下来?他说:因为我还没跑到终点。朋友说:那终点在哪里?他说:我也不知道。",
"一个人去咖啡店喝咖啡,点了一杯咖啡。他喝了一口,说:这咖啡怎么这么苦?服务员说:因为咖啡本来就是苦的。那个人说:那能不能给我加点糖?服务员说:可以,不过糖要另外收费。那个人说:那还是算了,我就当体验生活的苦吧。",
"青年问禅师:如何才能让别人喜欢我?禅师拿出一颗糖,剥了皮吃下去,然后把糖纸扔在地上。青年不解,问:这是何意?禅师说:你要像糖一样,给人甜蜜,而不是像糖纸一样,只剩包装。",
"妻子问丈夫:你看我最近是不是胖了?丈夫说:没有啊,我觉得你很苗条。妻子说:真的吗?可是我刚才称体重,比以前重了两斤。丈夫说:那是因为你在我心里的分量重了。",
"医生对病人说:你要少吃盐,多运动。病人说:我已经很注意了,每天都吃很少的盐,而且每天都运动。医生说:那你为什么还这么胖?病人说:因为我吃的盐都在零食里,我的运动就是从沙发走到冰箱。",
"妈妈问儿子:你为什么把玩具拆了?儿子说:我想看看它是怎么工作的。妈妈说:那你装回去了吗?儿子说:装回去了,不过多了几个零件。妈妈说:那怎么办?儿子说:没关系,下次拆别的玩具的时候再用。",
"女儿问妈妈:妈妈,为什么你每天都要化妆?妈妈说:因为妈妈想变得漂亮一点。女儿说:可是你化了妆也没漂亮多少啊。",
"爷爷对孙子说:你要珍惜现在的幸福生活,爷爷小时候可苦了。孙子说:爷爷,你小时候有电视看吗?爷爷说:没有,那时候连电都没有。孙子说:那你小时候有手机玩吗?爷爷说:没有,那时候连电话都没有。孙子说:那你小时候真可怜。",
"同学问小红:小红,你为什么这次考试考得这么好?小红说:因为我复习了。同学说:那你复习了什么?小红说:我复习了考试题目。",
"考试的时候,小明偷看同桌的答案,被老师发现了。老师说:小明,你为什么偷看同桌的答案?小明说:因为我想知道他是不是也不会做。",
"老板问员工:你觉得我们公司的产品怎么样?员工说:我觉得我们公司的产品就像我的女朋友一样。老板说:为什么这么说?员工说:因为虽然有很多缺点,但我还是很喜欢。",
"什么东西有头没有脚?答案:蒜头。",
"一个人有一个木桶,木桶有一个漏洞,每次装水都会漏出去一部分。他的朋友说:你把漏洞补一下吧。他说:不用,我每次多装一点水就行了。结果他每次装水都比别人多花很多时间,而且最后装的水还是和别人一样多。",
"一个人去餐厅吃饭,问服务员:你们这里有什么特色菜?服务员说:我们这里的特色菜是红烧狮子头。那个人说:那我要一个。服务员说:不过我们这里的狮子头不是真的狮子头,是猪肉做的。那个人说:没关系,我也不吃真的狮子头。",
"两个人在聊天,一个人说:我觉得现在的人都太依赖手机了。另一个人说:是啊,没有手机简直活不下去。那个人说:那你现在在干什么?另一个人说:我在玩手机。",
"郑国人买鞋,宁可相信尺码也不相信自己的脚。后来他去买内裤,老板说:你怎么不拿个尺子来量?他说:不用,我有之前的内裤尺码。老板说:那你之前的内裤呢?他说:刚才买鞋的时候当尺子用了。",
"儿子问:爸爸,为什么汽车的速度表上最高能到200公里,但是我们从来没开到过那么快?爸爸说:就像你数学书后面的答案,虽然都有,但你从来没全做对过。",
"医生问病人:你失眠多久了?病人说:三个晚上了。医生说:那你白天精神怎么样?病人说:白天睡得特别香。",
"老虎抓住了一只兔子,准备吃了它。兔子说:老虎大哥,你不能吃我,我是一只聪明的兔子,我可以帮你做很多事情。老虎说:你能帮我做什么?兔子说:我可以帮你找食物,帮你放哨,还可以给你讲笑话。老虎说:那你先讲个笑话听听,如果好笑,我就不吃你。兔子说:从前有一只老虎,它抓住了一只兔子,然后让兔子讲笑话,兔子讲了一个笑话,老虎笑了,然后老虎就把兔子放了。老虎说:这不好笑。兔子说:那你等一下,我还有更好笑的。老虎说:不用了,我已经饱了。",
"大象和蚂蚁结婚了,可是没过多久,大象就死了。蚂蚁非常伤心,它一边哭一边说:亲爱的,你怎么这么早就走了,我下半辈子不用干别的了,就忙着埋你了。",
"乌龟和兔子比赛跑步,兔子很快就跑到了前面,它回头一看,乌龟还在后面慢慢爬。兔子想:反正乌龟爬得慢,我先睡一会儿。兔子醒来一看,乌龟已经爬到终点了。兔子非常生气,它对乌龟说:你作弊!乌龟说:我没有作弊,是你自己睡着了。兔子说:那我们再比一次。乌龟说:好啊,不过这次得我选路线。乌龟选了一条有很多河的路线,兔子不会游泳,只能看着乌龟慢慢爬过去。",
"猪对牛说:你每天都要干活,真辛苦。牛说:你每天都不用干活,真舒服。猪说:可是我很快就要被宰了。牛说:可是我能活很久。猪说:那我们换一下吧。牛说:好啊。于是猪开始每天干活,牛开始每天休息。过了一段时间,猪被宰了,牛也因为不干活被宰了。",
"两个人在聊天,一个人说:我最近在减肥,每天都吃很少。另一个人说:那你瘦了吗?那个人说:瘦了,瘦了一斤。另一个人说:才瘦一斤啊,我瘦了十斤呢。那个人说:那你是怎么瘦的?另一个人说:我把手机里的美颜相机卸载了。"
];
// =============== 状态管理 ===============
const state = {
index: Math.floor(Math.random() * JOKES.length),
favorites: new Set(),
autoPlay: false,
autoTimer: null,
direction: 0, // -1 left, 0 random, 1 right
};
// =============== DOM 元素 ===============
const $ = (id) => document.getElementById(id);
const jokeTextEl = $('joke-text');
const jokeIndexEl = $('joke-index');
const jokeCardEl = $('joke-card');
const totalCountEl = $('total-count');
const toastEl = $('toast');
const toastTextEl = $('toast-text');
totalCountEl.textContent = JOKES.length;
// =============== 渲染函数 ===============
function render(direction = 0) {
const idx = state.index;
const total = JOKES.length;
jokeTextEl.textContent = JOKES[idx];
jokeIndexEl.textContent = `${String(idx + 1).padStart(3, '0')} / ${String(total).padStart(3, '0')}`;
// 动画
jokeCardEl.classList.remove('slide-in-left', 'slide-in-right', 'scale-in');
// 触发重排以重启动画
void jokeCardEl.offsetWidth;
if (direction === -1) jokeCardEl.classList.add('slide-in-right');
else if (direction === 1) jokeCardEl.classList.add('slide-in-left');
else jokeCardEl.classList.add('scale-in');
// 更新收藏按钮状态
const favBtn = $('btn-favorite');
if (state.favorites.has(idx)) {
favBtn.classList.add('active');
} else {
favBtn.classList.remove('active');
}
}
// =============== 导航 ===============
function nextJoke() {
if (JOKES.length <= 1) return;
state.direction = 1;
state.index = (state.index + 1) % JOKES.length;
render(1);
}
function prevJoke() {
if (JOKES.length <= 1) return;
state.direction = -1;
state.index = (state.index - 1 + JOKES.length) % JOKES.length;
render(-1);
}
function randomJoke() {
if (JOKES.length <= 1) return;
let nextIdx = state.index;
while (nextIdx === state.index && JOKES.length > 1) {
nextIdx = Math.floor(Math.random() * JOKES.length);
}
state.index = nextIdx;
render(0);
}
// =============== 收藏 ===============
function toggleFavorite() {
const idx = state.index;
const favBtn = $('btn-favorite');
if (state.favorites.has(idx)) {
state.favorites.delete(idx);
favBtn.classList.remove('active');
showToast('已取消收藏');
} else {
state.favorites.add(idx);
favBtn.classList.add('active');
// 心跳动画
const svg = favBtn.querySelector('svg');
svg.style.animation = 'heartBeat 0.5s ease';
setTimeout(() => { svg.style.animation = ''; }, 500);
showToast('已加入收藏');
}
}
// =============== 复制 ===============
function copyJoke() {
const text = JOKES[state.index];
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(() => showToast('已复制到剪贴板')).catch(() => fallbackCopy(text))
} else {
fallbackCopy(text);
}
} catch (e) {
fallbackCopy(text);
}
}
function fallbackCopy(text) {
try {
const ta = document.createElement('textarea');
ta.value = text; ta.setAttribute('readonly', '');
ta.style.position = 'absolute';
ta.style.left = '-9999px';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
showToast('已复制到剪贴板');
} catch (e) {
showToast('复制失败,请重试', true);
}
}
// =============== 分享 ===============
function shareJoke() {
const text = JOKES[state.index];
if (navigator.share) {
navigator.share({ title: 'LaughterBox', text: text, url: window.location.href }).catch(() => {});
} else {
copyJoke();
}
}
// =============== 自动播放 ===============
function toggleAutoPlay() {
state.autoPlay = !state.autoPlay;
const btn = $('btn-autoplay');
const playIcon = btn.querySelector('.play-icon');
const pauseIcon = btn.querySelector('.pause-icon');
if (state.autoPlay) {
btn.classList.add('active');
playIcon.style.display = 'none';
pauseIcon.style.display = 'inline-block';
state.autoTimer = setInterval(nextJoke, 30000);
showToast('已开启自动播放');
} else {
btn.classList.remove('active');
playIcon.style.display = 'inline-block';
pauseIcon.style.display = 'none';
if (state.autoTimer) clearInterval(state.autoTimer);
state.autoTimer = null;
showToast('已停止自动播放');
}
}
// =============== Toast 提示 ===============
let toastTimer = null;
function showToast(message, isError = false) {
toastTextEl.textContent = message;
toastEl.classList.toggle('error', isError);
toastEl.classList.add('show');
if (toastTimer) clearTimeout(toastTimer);
toastTimer = setTimeout(() => {
toastEl.classList.remove('show');
}, 2000);
}
// =============== 主题切换 ===============
function toggleTheme() {
const html = document.documentElement;
if (html.classList.contains('dark')) {
html.classList.remove('dark');
localStorage.setItem('laughterbox-theme', 'light');
} else {
html.classList.add('dark');
localStorage.setItem('laughterbox-theme', 'dark');
}
}
// 初始化主题
(function initTheme() {
const saved = localStorage.getItem('laughterbox-theme');
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
if (saved === 'dark' || (!saved && prefersDark)) {
document.documentElement.classList.add('dark');
}
})();
// =============== 拖拽 ===============
let dragStartX = 0;
let isDragging = false;
jokeCardEl.addEventListener('pointerdown', (e) => {
dragStartX = e.clientX;
isDragging = true;
jokeCardEl.classList.add('dragging');
jokeCardEl.style.cursor = 'grabbing';
jokeCardEl.setPointerCapture(e.pointerId);
});
jokeCardEl.addEventListener('pointermove', (e) => {
if (!isDragging) return;
const dx = e.clientX - dragStartX;
jokeCardEl.style.transform = `translateX(${dx * 0.3}px)`;
});
jokeCardEl.addEventListener('pointerup', (e) => {
if (!isDragging) return;
const dx = e.clientX - dragStartX;
isDragging = false;
jokeCardEl.classList.remove('dragging');
jokeCardEl.style.cursor = 'grab';
jokeCardEl.style.transform = '';
if (dx < -60) nextJoke();
else if (dx > 60) prevJoke();
});
// =============== 事件绑定 ===============
$('btn-prev').addEventListener('click', prevJoke);
$('btn-next').addEventListener('click', nextJoke);
$('btn-random').addEventListener('click', randomJoke);
$('btn-favorite').addEventListener('click', toggleFavorite);
$('btn-copy').addEventListener('click', copyJoke);
$('btn-share').addEventListener('click', shareJoke);
$('btn-autoplay').addEventListener('click', toggleAutoPlay);
$('theme-toggle').addEventListener('click', toggleTheme);
// =============== 键盘快捷键 ===============
window.addEventListener('keydown', (e) => {
const tag = (e.target && e.target.tagName) || '';
if (tag === 'INPUT' || tag === 'TEXTAREA') return;
if (e.key === 'ArrowRight') { e.preventDefault(); nextJoke(); }
else if (e.key === 'ArrowLeft') { e.preventDefault(); prevJoke(); }
else if (e.key === ' ' || e.key.toLowerCase() === 'r') { e.preventDefault(); randomJoke(); }
else if (e.key.toLowerCase() === 'f') { e.preventDefault(); toggleFavorite(); }
else if (e.key.toLowerCase() === 'c') { e.preventDefault(); copyJoke(); }
});
// =============== 初始化 ===============
render();
// 初次加载动画延迟
setTimeout(() => {
document.querySelectorAll('[aria-hidden="true"]').forEach(() => {});
}, 50);
</script>
</body>
</html>