Commit fcb9a94079110eb467da6f280812998d4efe33d4

Authored by yibo
1 parent 72be2914

Add a click-to-enter splash screen (infiniteslop.ai's unmute trick)

The video autoplays muted underneath a full-screen splash — logo, play icon,
slogan and a credit line, matching infiniteslop.ai's own splash. Clicking it
is the user gesture that lets the browser allow unmuted playback, so desktop
viewers land with sound instead of the silent-until-you-find-the-mute-button
experience from the previous change. Mobile stays muted after entry, same as
infiniteslop.ai, since unmuted autoplay-adjacent playback is unreliable there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0144MiJ2cLjMd9jWaeEpVfUJ
library.json
... ... @@ -79,7 +79,7 @@
79 79 "likes": 0,
80 80 "createdAt": 1788192915845,
81 81 "promptEn": "scholar watches the room warily",
82   - "lastPlayedAt": 1788251736825
  82 + "lastPlayedAt": 1788253865811
83 83 },
84 84 {
85 85 "id": "e8cbb73560ef",
... ... @@ -154,7 +154,7 @@
154 154 "likes": 0,
155 155 "createdAt": 1788192913886,
156 156 "promptEn": "taoist panics, freaked out",
157   - "lastPlayedAt": 1788250312031
  157 + "lastPlayedAt": 1788253751763
158 158 },
159 159 {
160 160 "id": "45c1136d1e05",
... ... @@ -214,7 +214,7 @@
214 214 "likes": 0,
215 215 "createdAt": 1788192912912,
216 216 "promptEn": "taoist charmed, hesitating",
217   - "lastPlayedAt": 1788251760826
  217 + "lastPlayedAt": 1788253947848
218 218 },
219 219 {
220 220 "id": "f8da9934c823",
... ... @@ -319,7 +319,7 @@
319 319 "likes": 0,
320 320 "createdAt": 1788188423583,
321 321 "promptEn": "woman in white scared, dodging",
322   - "lastPlayedAt": 1788251198437
  322 + "lastPlayedAt": 1788253953855
323 323 },
324 324 {
325 325 "id": "1597db4be561",
... ... @@ -348,7 +348,7 @@
348 348 "partition": "ref2va",
349 349 "likes": 0,
350 350 "createdAt": 1788186218619,
351   - "lastPlayedAt": 1788252471153,
  351 + "lastPlayedAt": 1788253885823,
352 352 "promptEn": "woman in white evading, scared"
353 353 },
354 354 {
... ... @@ -499,7 +499,7 @@
499 499 "likes": 0,
500 500 "createdAt": 1788153988474,
501 501 "promptEn": "astronaut at a martian canyon, visor reflecting alien ruins, then aerial view of the city",
502   - "lastPlayedAt": 1788251428589
  502 + "lastPlayedAt": 1788253845801
503 503 },
504 504 {
505 505 "id": "194fc2b56cb5",
... ... @@ -514,7 +514,7 @@
514 514 "likes": 0,
515 515 "createdAt": 1788138492364,
516 516 "promptEn": "astronaut on a Martian canyon, helmet reflecting an alien ruined city",
517   - "lastPlayedAt": 1788251558701
  517 + "lastPlayedAt": 1788253805777
518 518 },
519 519 {
520 520 "id": "f84b8f044ec5",
... ... @@ -679,7 +679,7 @@
679 679 "likes": 0,
680 680 "createdAt": 1788026876886,
681 681 "promptEn": "apartment manager in an eerie corridor, red elevator light flickering, holding out a key",
682   - "lastPlayedAt": 1788250332049
  682 + "lastPlayedAt": 1788253825788
683 683 },
684 684 {
685 685 "id": "cdd03cca8461",
... ... @@ -709,7 +709,7 @@
709 709 "likes": 0,
710 710 "createdAt": 1788026569123,
711 711 "promptEn": "pale dream demon in a suit, reaching out a hand inside a dream train car",
712   - "lastPlayedAt": 1788252102983
  712 + "lastPlayedAt": 1788253611671
713 713 },
714 714 {
715 715 "id": "bd6e2b264243",
... ... @@ -889,7 +889,7 @@
889 889 "likes": 0,
890 890 "createdAt": 1788025620717,
891 891 "promptEn": "alien tyrant holding a glowing energy sphere, sneering on a rocky green battlefield",
892   - "lastPlayedAt": 1788252495168
  892 + "lastPlayedAt": 1788253757767
893 893 },
894 894 {
895 895 "id": "9db41d807330",
... ... @@ -934,7 +934,7 @@
934 934 "likes": 0,
935 935 "createdAt": 1788024900966,
936 936 "promptEn": "player 101 blocking the path on a high glass bridge, threatening you",
937   - "lastPlayedAt": 1788251722799
  937 + "lastPlayedAt": 1788253797782
938 938 },
939 939 {
940 940 "id": "d6aab3fce63d",
... ... @@ -964,7 +964,7 @@
964 964 "likes": 0,
965 965 "createdAt": 1788024491625,
966 966 "promptEn": "prince signs an execution order in the trial hall, points at the guillotine",
967   - "lastPlayedAt": 1788251928912
  967 + "lastPlayedAt": 1788253737754
968 968 },
969 969 {
970 970 "id": "8a933feb52c4",
... ... @@ -994,7 +994,7 @@
994 994 "likes": 0,
995 995 "createdAt": 1788016668421,
996 996 "promptEn": "pirate negotiation, catching Barbossa's flaw and turning the tables",
997   - "lastPlayedAt": 1788250676206
  997 + "lastPlayedAt": 1788253829791
998 998 },
999 999 {
1000 1000 "id": "e3b18b5dfd8e",
... ... @@ -1024,7 +1024,7 @@
1024 1024 "likes": 0,
1025 1025 "createdAt": 1788016203093,
1026 1026 "promptEn": "pirate negotiation, held the goal but paid a price",
1027   - "lastPlayedAt": 1788250906296
  1027 + "lastPlayedAt": 1788253859804
1028 1028 },
1029 1029 {
1030 1030 "id": "18aa882cbd0b",
... ... @@ -1249,7 +1249,7 @@
1249 1249 "likes": 0,
1250 1250 "createdAt": 1788012917294,
1251 1251 "promptEn": "standing on the glass bridge, facing off with player 101",
1252   - "lastPlayedAt": 1788251642756
  1252 + "lastPlayedAt": 1788253839798
1253 1253 },
1254 1254 {
1255 1255 "id": "8f23de61904f",
... ... @@ -1324,7 +1324,7 @@
1324 1324 "likes": 0,
1325 1325 "createdAt": 1788012202708,
1326 1326 "promptEn": "facing Vecna, went in head-on and got seen through",
1327   - "lastPlayedAt": 1788248276792
  1327 + "lastPlayedAt": 1788253713746
1328 1328 },
1329 1329 {
1330 1330 "id": "40ccb04f76e9",
... ... @@ -1369,7 +1369,7 @@
1369 1369 "likes": 0,
1370 1370 "createdAt": 1788011488775,
1371 1371 "promptEn": "graveyard standoff, exploiting a flaw to counter Voldemort",
1372   - "lastPlayedAt": 1788252403130
  1372 + "lastPlayedAt": 1788253875818
1373 1373 },
1374 1374 {
1375 1375 "id": "24845be71ea0",
... ... @@ -1399,7 +1399,7 @@
1399 1399 "likes": 0,
1400 1400 "createdAt": 1788011251158,
1401 1401 "promptEn": "facing Voldemort, held the goal but paid a price",
1402   - "lastPlayedAt": 1788251207183
  1402 + "lastPlayedAt": 1788253835799
1403 1403 },
1404 1404 {
1405 1405 "id": "58f68b111429",
... ... @@ -1504,7 +1504,7 @@
1504 1504 "likes": 0,
1505 1505 "createdAt": 1788009586293,
1506 1506 "promptEn": "facing Thanos, stayed passive and got cornered",
1507   - "lastPlayedAt": 1788249207456
  1507 + "lastPlayedAt": 1788253869815
1508 1508 },
1509 1509 {
1510 1510 "id": "438c98cf4973",
... ... @@ -1639,7 +1639,7 @@
1639 1639 "likes": 0,
1640 1640 "createdAt": 1788007441156,
1641 1641 "promptEn": "battlefield in Jaku city, the city in ruins, heroes in crisis",
1642   - "lastPlayedAt": 1788251770832
  1642 + "lastPlayedAt": 1788253771767
1643 1643 },
1644 1644 {
1645 1645 "id": "68219d6b68c8",
... ... @@ -1759,7 +1759,7 @@
1759 1759 "likes": 0,
1760 1760 "createdAt": 1788005541640,
1761 1761 "promptEn": "facing Frieza, went in head-on and got seen through",
1762   - "lastPlayedAt": 1788246977682
  1762 + "lastPlayedAt": 1788253741753
1763 1763 },
1764 1764 {
1765 1765 "id": "574b47b00316",
... ... @@ -1864,7 +1864,7 @@
1864 1864 "likes": 0,
1865 1865 "createdAt": 1788004115612,
1866 1866 "promptEn": "facing Mahito, stayed passive and got cornered",
1867   - "lastPlayedAt": 1788252553190
  1867 + "lastPlayedAt": 1788253899831
1868 1868 },
1869 1869 {
1870 1870 "id": "f4a2870386c6",
... ... @@ -1969,7 +1969,7 @@
1969 1969 "likes": 0,
1970 1970 "createdAt": 1788002455066,
1971 1971 "promptEn": "facing Reiner, went in head-on and got seen through",
1972   - "lastPlayedAt": 1788251886889
  1972 + "lastPlayedAt": 1788253879816
1973 1973 },
1974 1974 {
1975 1975 "id": "5846d645e870",
... ... @@ -2014,7 +2014,7 @@
2014 2014 "likes": 0,
2015 2015 "createdAt": 1788001508988,
2016 2016 "promptEn": "in the ruins of Konoha, exploiting a flaw to counter Pain",
2017   - "lastPlayedAt": 1788252503166
  2017 + "lastPlayedAt": 1788253801782
2018 2018 },
2019 2019 {
2020 2020 "id": "79304c061606",
... ... @@ -2074,7 +2074,7 @@
2074 2074 "likes": 0,
2075 2075 "createdAt": 1788000812932,
2076 2076 "promptEn": "facing Pain, went in head-on and got seen through",
2077   - "lastPlayedAt": 1788252529179
  2077 + "lastPlayedAt": 1788253905836
2078 2078 },
2079 2079 {
2080 2080 "id": "f691ac021df0",
... ... @@ -2119,7 +2119,7 @@
2119 2119 "likes": 0,
2120 2120 "createdAt": 1788000105889,
2121 2121 "promptEn": "facing Sakazuki, exploiting a flaw to counter him",
2122   - "lastPlayedAt": 1788250850269
  2122 + "lastPlayedAt": 1788253821783
2123 2123 },
2124 2124 {
2125 2125 "id": "c9be7c07b793",
... ... @@ -2164,7 +2164,7 @@
2164 2164 "likes": 0,
2165 2165 "createdAt": 1787999633670,
2166 2166 "promptEn": "facing Sakazuki, went in head-on and got seen through",
2167   - "lastPlayedAt": 1788251129144
  2167 + "lastPlayedAt": 1788253777771
2168 2168 },
2169 2169 {
2170 2170 "id": "a8b8b4430ee3",
... ... @@ -2179,7 +2179,7 @@
2179 2179 "likes": 0,
2180 2180 "createdAt": 1787999397097,
2181 2181 "promptEn": "facing Sakazuki, stayed passive and got cornered",
2182   - "lastPlayedAt": 1788252397128
  2182 + "lastPlayedAt": 1788253761764
2183 2183 },
2184 2184 {
2185 2185 "id": "88923aaf57f3",
... ... @@ -2269,7 +2269,7 @@
2269 2269 "likes": 0,
2270 2270 "createdAt": 1787831362083,
2271 2271 "promptEn": "constable promises to get her out safely if she trusts him",
2272   - "lastPlayedAt": 1788251652757
  2272 + "lastPlayedAt": 1788253719743
2273 2273 },
2274 2274 {
2275 2275 "id": "5ede61c01b22",
... ... @@ -2344,7 +2344,7 @@
2344 2344 "likes": 0,
2345 2345 "createdAt": 1787762224218,
2346 2346 "promptEn": "constable stands under the lamp, holding her gaze through the screen, not backing away",
2347   - "lastPlayedAt": 1788252377122
  2347 + "lastPlayedAt": 1788253631690
2348 2348 },
2349 2349 {
2350 2350 "id": "f2e70205766b",
... ... @@ -2404,7 +2404,7 @@
2404 2404 "likes": 0,
2405 2405 "createdAt": 1787730331663,
2406 2406 "promptEn": "constable steps in from the rainy night, spots her, introduces himself with a cover story",
2407   - "lastPlayedAt": 1788249111414
  2407 + "lastPlayedAt": 1788253781772
2408 2408 },
2409 2409 {
2410 2410 "id": "84e8337de242",
... ... @@ -2554,7 +2554,7 @@
2554 2554 "likes": 0,
2555 2555 "createdAt": 1787708646888,
2556 2556 "promptEn": "constable studies her through the screen growing suspicious, hand drifting to his sword",
2557   - "lastPlayedAt": 1788251602734
  2557 + "lastPlayedAt": 1788253697730
2558 2558 },
2559 2559 {
2560 2560 "id": "1c0c203cfb3b",
... ... @@ -2839,7 +2839,7 @@
2839 2839 "likes": 0,
2840 2840 "createdAt": 1787674609636,
2841 2841 "promptEn": "constable silently watches her through the screen gaps, camera slowly zooming in on her face",
2842   - "lastPlayedAt": 1788249261479
  2842 + "lastPlayedAt": 1788253983858
2843 2843 },
2844 2844 {
2845 2845 "id": "5e53f7f5fe9b",
... ... @@ -2899,7 +2899,7 @@
2899 2899 "likes": 0,
2900 2900 "createdAt": 1787674351536,
2901 2901 "promptEn": "constable searches an old house at night, candle flickering, woman hiding behind the screen",
2902   - "lastPlayedAt": 1788250201967
  2902 + "lastPlayedAt": 1788253615680
2903 2903 },
2904 2904 {
2905 2905 "id": "dc13e4f165dc",
... ... @@ -2914,7 +2914,7 @@
2914 2914 "likes": 0,
2915 2915 "createdAt": 1787672849330,
2916 2916 "promptEn": "constable cautiously follows her silent gesture, hand near his sword, tracking her movement",
2917   - "lastPlayedAt": 1788252175018
  2917 + "lastPlayedAt": 1788253645695
2918 2918 },
2919 2919 {
2920 2920 "id": "feb4df69e1a2",
... ... @@ -3319,7 +3319,7 @@
3319 3319 "likes": 0,
3320 3320 "createdAt": 1787577878370,
3321 3321 "promptEn": "man barges into the old house apologizing, woman in white behind the screen",
3322   - "lastPlayedAt": 1788250294012
  3322 + "lastPlayedAt": 1788253661702
3323 3323 },
3324 3324 {
3325 3325 "id": "0e75ef71c3d0",
... ... @@ -3438,7 +3438,7 @@
3438 3438 "partition": "text",
3439 3439 "likes": 0,
3440 3440 "createdAt": 1787576358982,
3441   - "lastPlayedAt": 1788246088963,
  3441 + "lastPlayedAt": 1788253923842,
3442 3442 "promptEn": "man apologizes, says something outside was chasing him"
3443 3443 },
3444 3444 {
... ... @@ -3469,7 +3469,7 @@
3469 3469 "likes": 0,
3470 3470 "createdAt": 1787575790848,
3471 3471 "promptEn": "man apologizes, says something outside was chasing him",
3472   - "lastPlayedAt": 1788247283936
  3472 + "lastPlayedAt": 1788253927842
3473 3473 },
3474 3474 {
3475 3475 "id": "5e6dcf2c8cfd",
... ... @@ -3589,7 +3589,7 @@
3589 3589 "likes": 0,
3590 3590 "createdAt": 1787497396594,
3591 3591 "promptEn": "otome game male lead showcase PV",
3592   - "lastPlayedAt": 1788250885690
  3592 + "lastPlayedAt": 1788253932562
3593 3593 },
3594 3594 {
3595 3595 "id": "36ac12f2f29e",
... ... @@ -3619,7 +3619,7 @@
3619 3619 "likes": 0,
3620 3620 "createdAt": 1787493303602,
3621 3621 "promptEn": "game menu UI, character swaps out a mechanical arm",
3622   - "lastPlayedAt": 1788252523177
  3622 + "lastPlayedAt": 1788253767768
3623 3623 },
3624 3624 {
3625 3625 "id": "e1fd99e4ab97",
... ... @@ -3889,7 +3889,7 @@
3889 3889 "likes": 0,
3890 3890 "createdAt": 1787444313690,
3891 3891 "promptEn": "protagonist leaps across rooftops fleeing pursuers",
3892   - "lastPlayedAt": 1788250484120
  3892 + "lastPlayedAt": 1788253967852
3893 3893 },
3894 3894 {
3895 3895 "id": "e56962bd770f",
... ... @@ -3904,7 +3904,7 @@
3904 3904 "likes": 0,
3905 3905 "createdAt": 1787442174982,
3906 3906 "promptEn": "protagonist leaps across rooftops fleeing pursuers",
3907   - "lastPlayedAt": 1788249819782
  3907 + "lastPlayedAt": 1788253909828
3908 3908 },
3909 3909 {
3910 3910 "id": "18105bc2900b",
... ... @@ -3934,7 +3934,7 @@
3934 3934 "likes": 0,
3935 3935 "createdAt": 1787436029475,
3936 3936 "promptEn": "protagonist leaps across rooftops fleeing pursuers",
3937   - "lastPlayedAt": 1788243140456
  3937 + "lastPlayedAt": 1788253665707
3938 3938 },
3939 3939 {
3940 3940 "id": "896859e59dae",
... ... @@ -4279,7 +4279,7 @@
4279 4279 "likes": 0,
4280 4280 "createdAt": 1787367454065,
4281 4281 "promptEn": "golden portal opens, man in green jacket throws a glowing spear",
4282   - "lastPlayedAt": 1788250848268
  4282 + "lastPlayedAt": 1788253735749
4283 4283 },
4284 4284 {
4285 4285 "id": "e6c241be377f",
... ... @@ -4324,7 +4324,7 @@
4324 4324 "likes": 0,
4325 4325 "createdAt": 1787332996051,
4326 4326 "promptEn": "golden portal opens, man in green jacket throws a glowing spear",
4327   - "lastPlayedAt": 1788250762247
  4327 + "lastPlayedAt": 1788253717746
4328 4328 },
4329 4329 {
4330 4330 "id": "bf7d973d02a3",
... ... @@ -4474,7 +4474,7 @@
4474 4474 "likes": 0,
4475 4475 "createdAt": 1787290119529,
4476 4476 "promptEn": "misty Jiangnan old town at dawn, woman walking with an oil-paper umbrella",
4477   - "lastPlayedAt": 1788251302490
  4477 + "lastPlayedAt": 1788253747759
4478 4478 },
4479 4479 {
4480 4480 "id": "06bb43196c46",
... ...
live.css
... ... @@ -351,6 +351,57 @@ body {
351 351 .chat-input-row button:disabled { opacity: .5; cursor: not-allowed; }
352 352 .say-hint { padding: 2px 4px 0; font-size: 11.5px; font-weight: bold; color: var(--muted); min-height: 1.4em; }
353 353  
  354 +/* ---- click-to-enter splash: branding + the gesture that unlocks sound ---- */
  355 +.splash {
  356 + position: fixed;
  357 + inset: 0;
  358 + z-index: 50;
  359 + display: flex;
  360 + align-items: center;
  361 + justify-content: center;
  362 + cursor: pointer;
  363 + background: radial-gradient(ellipse at center, rgba(0, 0, 0, .55), rgba(0, 0, 0, .35));
  364 +}
  365 +.splash-inner { text-align: center; padding: 20px; }
  366 +.splash-logo {
  367 + font-family: "Shrikhand", "Cooper Black", serif;
  368 + font-weight: 400;
  369 + font-size: min(64px, 11vw);
  370 + line-height: 1.2;
  371 + color: #fff;
  372 + -webkit-text-stroke: 3px #000;
  373 + paint-order: stroke fill;
  374 + text-shadow: 1px 1px 0 #000, 2px 2px 0 #000, 3px 3px 0 #000, 4px 4px 0 #000, 5px 5px 0 #000;
  375 + margin-bottom: 20px;
  376 +}
  377 +.splash-play {
  378 + width: 76px;
  379 + height: 76px;
  380 + margin: 0 auto 20px;
  381 + display: flex;
  382 + align-items: center;
  383 + justify-content: center;
  384 + border-radius: 50%;
  385 + background: #fff;
  386 + border: 3px solid #000;
  387 + box-shadow: var(--shadow);
  388 + font-size: 26px;
  389 + color: var(--ink);
  390 + padding-left: 5px;
  391 +}
  392 +.splash-sub {
  393 + font: bold 15px Tahoma, sans-serif;
  394 + color: #fff;
  395 + line-height: 1.6;
  396 + text-shadow: 0 1px 4px #000, 0 0 12px rgba(0, 0, 0, .8);
  397 +}
  398 +.splash-credit {
  399 + margin-top: 16px;
  400 + font: bold 13px Tahoma, sans-serif;
  401 + color: var(--accent);
  402 + text-shadow: 0 1px 4px #000;
  403 +}
  404 +
354 405 /* ---- nickname modal ---- */
355 406 .nickname-modal {
356 407 position: fixed;
... ...
live.html
... ... @@ -65,6 +65,15 @@
65 65 </div>
66 66 </div>
67 67  
  68 +<div class="splash" id="splash">
  69 + <div class="splash-inner">
  70 + <div class="splash-logo">AI 短剧频道</div>
  71 + <div class="splash-play">▶</div>
  72 + <div class="splash-sub">一个永不停播的 AI 生成短剧频道<br />弹幕决定下一集演什么</div>
  73 + <div class="splash-credit">🍋 AI 短剧频道 出品</div>
  74 + </div>
  75 +</div>
  76 +
68 77 <div class="nickname-modal" id="nicknameModal" hidden>
69 78 <div class="nickname-box">
70 79 <div class="nickname-title">先起个昵称才能发言</div>
... ... @@ -107,6 +116,7 @@ const nicknameConfirmBtn = document.getElementById(&quot;nicknameConfirmBtn&quot;);
107 116 const chatIdentityRow = document.getElementById("chatIdentityRow");
108 117 const changeNameBtn = document.getElementById("changeNameBtn");
109 118 const currentNickname = document.getElementById("currentNickname");
  119 +const splash = document.getElementById("splash");
110 120 const clientId = Math.random().toString(36).slice(2);
111 121  
112 122 muteBtn.addEventListener("click", () => {
... ... @@ -125,6 +135,23 @@ function tryPlay() {
125 135 });
126 136 }
127 137  
  138 +// Click-to-enter splash (same trick as infiniteslop.ai): the video is already
  139 +// autoplaying muted underneath, and this click is the user gesture that lets
  140 +// us switch it to unmuted playback — the one reliable way around the browser's
  141 +// no-unmuted-autoplay-without-a-gesture policy. Also doubles as a branding
  142 +// moment (logo/slogan/credit) instead of a bare "click to unmute" prompt.
  143 +// Phones stay muted after entry — unmuted autoplay-adjacent playback on
  144 +// mobile Safari/Chrome is unreliable and feels intrusive.
  145 +splash.addEventListener("click", () => {
  146 + splash.remove();
  147 + const desktop = matchMedia("(min-width: 769px)").matches;
  148 + player.muted = !desktop;
  149 + player.play().catch(() => {
  150 + player.muted = true;
  151 + });
  152 + muteBtn.textContent = player.muted ? "🔇 静音" : "🔈 有声";
  153 +});
  154 +
128 155 let chatClosed = false;
129 156 chatToggleBtn.addEventListener("click", () => {
130 157 chatClosed = !chatClosed;
... ...